diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 42627744..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "src/volesti"] - path = src/volesti - url = https://github.com/GeomScale/volesti.git diff --git a/inst/AUTHORS b/inst/AUTHORS index 956eb325..213c3a8f 100644 --- a/inst/AUTHORS +++ b/inst/AUTHORS @@ -1,4 +1,5 @@ -1. Bojan Nikolic . We have modified the implementations of Khachiyan's Algorithm by B. Nikolic from bnmin1-1.11 package for the Computation of Minimum Volume Enclosing Ellipsoids in /src/external/minimum_ellipsoid. - -2. Kjell Konis , Stefan I. Larimore and Timothy A. Davis , Kjell Eikland, Michel Berkelaar, Richard Stallman, Authors of lpsolve package , in /src/external/lpsolve. - +1. Kjell Konis , Stefan I. Larimore and Timothy A. Davis , Kjell Eikland, Michel Berkelaar, Richard Stallman, Authors of lpsolve package , in /src/external/lpSolve. +2. Bojan Nikolic . We have modified the implementations of Khachiyan's Algorithm by B. Nikolic from bnmin1-1.11 package for the Computation of Minimum Volume Enclosing Ellipsoids in /src/external/minimum_ellipsoid. +3. Authors of Spectra are described in detail here: https://github.com/yixuan/spectra/blob/master/AUTHORS.md +4. Author of PackedCSparse is Yin Tat Lee as described in https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/tree/master/code/solver/PackedCSparse +5. David H. Bailey author of qd library \ No newline at end of file diff --git a/inst/COPYRIGHTS b/inst/COPYRIGHTS index 5f83b029..a0c8033f 100644 --- a/inst/COPYRIGHTS +++ b/inst/COPYRIGHTS @@ -1,6 +1,9 @@ All files in src/external are taken from -(i) lpsolve (https://cran.r-project.org/package=lpSolve) version 5.6.20, -(ii) minimum_ellipsoid (or bnmin1) (https://www.mrao.cam.ac.uk/~bn204/oof/bnmin1.html) version 1.11 +1. lpsolve (https://cran.r-project.org/package=lpSolve) version 5.6.20, +2. minimum_ellipsoid (or bnmin1) (https://www.mrao.cam.ac.uk/~bn204/oof/bnmin1.html) version 1.11 +3. Spectra (https://github.com/yixuan/spectra/tree/master) version 0.8.1 +4. PackedCSparse (https://github.com/ConstrainedSampler/PolytopeSamplerMatlab) +5. qd (https://www.davidhbailey.com/dhbsoftware/) version 2.3.22 Copyrights and modification details are explicitly described at the beginning of each of those files. diff --git a/src/Makevars b/src/Makevars index e0ef376b..eb27e4d5 100644 --- a/src/Makevars +++ b/src/Makevars @@ -1,16 +1,16 @@ -PKG_CPPFLAGS=-Ivolesti/external -Iexternal/lpSolve/src -Ivolesti/external/minimum_ellipsoid -Ivolesti/include -Ivolesti/include/convex_bodies/spectrahedra +PKG_CPPFLAGS=-Iexternal -Iexternal/lpSolve/src -Iexternal/minimum_ellipsoid -Ivolesti/include -Ivolesti/include/convex_bodies/spectrahedra PKG_CXXFLAGS= -DBOOST_NO_AUTO_PTR -DDISABLE_NLP_ORACLES -PKG_LIBS=-Lexternal/lpSolve/src -llp_solve -Lvolesti/external/PackedCSparse/qd -lqd $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) +PKG_LIBS=-Lexternal/lpSolve/src -llp_solve -Lexternal/PackedCSparse/qd -lqd $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) -$(SHLIB): external/lpSolve/src/liblp_solve.a volesti/external/PackedCSparse/qd/libqd.a +$(SHLIB): external/lpSolve/src/liblp_solve.a external/PackedCSparse/qd/libqd.a external/lpSolve/src/liblp_solve.a: @(cd external/lpSolve/src && $(MAKE) liblp_solve.a \ CC="$(CC)" CPPFLAGS="$(CPPFLAGS)" CFLAGS="$(CFLAGS)" \ CPICFLAGS="$(CPICFLAGS)" AR="$(AR)" RANLIB="$(RANLIB)") -volesti/external/PackedCSparse/qd/libqd.a: - @(cd volesti/external/PackedCSparse/qd && $(MAKE) libqd.a \ +external/PackedCSparse/qd/libqd.a: + @(cd external/PackedCSparse/qd && $(MAKE) libqd.a \ CC="$(CC)" CPPFLAGS="$(CPPFLAGS)" CFLAGS="$(CFLAGS)" \ CPICFLAGS="$(CPICFLAGS)" AR="$(AR)") diff --git a/src/Makevars.win b/src/Makevars.win index 554a2bce..2a06a4d5 100644 --- a/src/Makevars.win +++ b/src/Makevars.win @@ -1,9 +1,9 @@ -PKG_CPPFLAGS=-Ivolesti/external -Iexternal/lpSolve/src -Ivolesti/external/minimum_ellipsoid -Ivolesti/include -Ivolesti/include/convex_bodies/spectrahedra +PKG_CPPFLAGS=-Iexternal -Iexternal/lpSolve/src -Iexternal/minimum_ellipsoid -Ivolesti/include -Ivolesti/include/convex_bodies/spectrahedra PKG_CXXFLAGS= -lm -ldl -DBOOST_NO_AUTO_PTR -DDISABLE_NLP_ORACLES -PKG_LIBS=-Lexternal/lpSolve/src -llp_solve -Lvolesti/external/PackedCSparse/qd -lqd $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) +PKG_LIBS=-Lexternal/lpSolve/src -llp_solve -Lexternal/PackedCSparse/qd -lqd $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) -$(SHLIB): external/lpSolve/src/liblp_solve.a volesti/external/PackedCSparse/qd/libqd.a +$(SHLIB): external/lpSolve/src/liblp_solve.a external/PackedCSparse/qd/libqd.a external/lpSolve/src/liblp_solve.a: @(cd external/lpSolve/src && $(MAKE) liblp_solve.a \ @@ -11,7 +11,7 @@ external/lpSolve/src/liblp_solve.a: CFLAGS="$(CFLAGS)" CPICFLAGS="$(CPICFLAGS)" AR="$(AR)" \ RANLIB="$(RANLIB)") -volesti/external/PackedCSparse/qd/libqd.a: - @(cd volesti/external/PackedCSparse/qd && $(MAKE) libqd.a \ +external/PackedCSparse/qd/libqd.a: + @(cd external/PackedCSparse/qd && $(MAKE) libqd.a \ CC="$(CC)" CPPFLAGS="$(CPPFLAGS)" CFLAGS="$(CFLAGS)" \ CPICFLAGS="$(CPICFLAGS)" AR="$(AR)") \ No newline at end of file diff --git a/src/external/PackedCSparse/FloatArray.h b/src/external/PackedCSparse/FloatArray.h new file mode 100644 index 00000000..28d1c5da --- /dev/null +++ b/src/external/PackedCSparse/FloatArray.h @@ -0,0 +1,307 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis + +#pragma once +#include +#include +#include +namespace PackedCSparse { + template + struct BaseImpl + { + T x[k]; + + BaseImpl() {}; + + BaseImpl(const T& rhs) + { + for (size_t i = 0; i < k; i++) + x[i] = rhs; + } + + BaseImpl operator+(const BaseImpl& rhs) const + { + BaseImpl lhs; + for (size_t i = 0; i < k; i++) + lhs.x[i] = x[i] + rhs.x[i]; + return lhs; + } + + BaseImpl operator-(const BaseImpl& rhs) const + { + BaseImpl lhs; + for (size_t i = 0; i < k; i++) + lhs.x[i] = x[i] - rhs.x[i]; + return lhs; + } + + BaseImpl operator*(const BaseImpl& rhs) const + { + BaseImpl lhs; + for (size_t i = 0; i < k; i++) + lhs.x[i] = x[i] * rhs.x[i]; + return lhs; + } + + BaseImpl operator/(const BaseImpl& rhs) const + { + BaseImpl lhs; + for (size_t i = 0; i < k; i++) + lhs.x[i] = x[i] / rhs.x[i]; + return lhs; + } + + BaseImpl& operator+=(const BaseImpl& rhs) + { + for (size_t i = 0; i < k; i++) + x[i] += rhs.x[i]; + return *this; + } + + BaseImpl& operator-=(const BaseImpl& rhs) + { + for (size_t i = 0; i < k; i++) + x[i] -= rhs.x[i]; + return *this; + } + + BaseImpl& operator*=(const BaseImpl& rhs) + { + for (size_t i = 0; i < k; i++) + x[i] *= rhs.x[i]; + return *this; + } + + BaseImpl& operator/=(const BaseImpl& rhs) + { + for (size_t i = 0; i < k; i++) + x[i] /= rhs.x[i]; + return *this; + } + + explicit operator bool() const + { + bool ret = false; + for (size_t i = 0; i < k; i++) + ret = ret || bool(x[i]); + return ret; + } + + static T get(const BaseImpl& a, size_t index) + { + return a.x[index]; + } + + static void set(BaseImpl& a, size_t index, const T& value) + { + a.x[index] = value; + } + + static BaseImpl abs(const BaseImpl& a) + { + BaseImpl out; + for (size_t i = 0; i < k; i++) + out.x[i] = std::abs(a.x[i]); + return out; + } + + static BaseImpl log(const BaseImpl& a) + { + BaseImpl out; + for (size_t i = 0; i < k; i++) + out.x[i] = std::log(a.x[i]); + return out; + } + + static void fmadd(BaseImpl& a, const BaseImpl& b, const BaseImpl& c) + { + for (size_t i = 0; i < k; i++) + a.x[i] += b.x[i] * c.x[i]; + } + + static void fnmadd(BaseImpl& a, const BaseImpl& b, const BaseImpl& c) + { + for (size_t i = 0; i < k; i++) + a.x[i] -= b.x[i] * c.x[i]; + } + + static void fmadd(BaseImpl& a, const BaseImpl& b, const T& c) + { + for (size_t i = 0; i < k; i++) + a.x[i] += b.x[i] * c; + } + + static void fnmadd(BaseImpl& a, const BaseImpl& b, const T& c) + { + for (size_t i = 0; i < k; i++) + a.x[i] -= b.x[i] * c; + } + + static BaseImpl clipped_sqrt(const BaseImpl& a, const T nonpos_output) + { + BaseImpl out; + for (size_t i = 0; i < k; i++) + { + T r = a.x[i]; + if (r > 0) + out.x[i] = sqrt(r); + else + out.x[i] = nonpos_output; + } + return out; + } + + static BaseImpl sign(std::mt19937_64& gen) + { + BaseImpl out; + unsigned long long seed = gen(); + for (size_t i = 0; i < k; i++) + { + out.x[i] = T((2 * ((seed >> i) & 1)) - 1.0); + if ((i & 63) == 63) seed = gen(); + } + return out; + } + + }; + + template + struct BaseScalarImpl + { + static T get(const T& x, size_t index) + { + return x; + } + + static void set(T& x, size_t index, T& value) + { + x = value; + } + + static T abs(const T &x) + { + return ::abs(x); + } + + static T log(const T &x) + { + return ::log(x); + } + + static void fmadd(T& a, const T& b, const T& c) + { + a += b * c; + } + + static void fnmadd(T& a, const T& b, const T& c) + { + a -= b * c; + } + + static T clipped_sqrt(const T& x, const T& nonpos_output) + { + if (x > 0.0) + return sqrt(x); + else + return nonpos_output; + } + + static T sign(std::mt19937_64& gen) + { + unsigned long long seed = gen(); + return T((2 * (seed & 1)) - 1.0); + } + }; + + template + struct FloatTypeSelector + { + using type = typename std::conditional>::type; + using funcImpl = typename std::conditional, BaseImpl>::type; + }; + + #ifdef __AVX2__ + #include "FloatArrayAVX2.h" + #else + template + struct FloatTypeSelector + { + using type = typename std::conditional< k == 1, double, BaseImpl>::type; + using funcImpl = typename std::conditional< k == 1, BaseScalarImpl, BaseImpl>::type; + }; + + template + struct FloatTypeSelector, l> + { + using type = BaseImpl; + using funcImpl = BaseImpl; + }; + #endif + + template + struct FloatTypeSelector, l> + { + using type = BaseImpl; + using funcImpl = BaseImpl; + }; + + template + using FloatArray = typename FloatTypeSelector::type; + + template + using FloatArrayFunc = typename FloatTypeSelector::funcImpl; + + template + auto get(const T& a, size_t index) -> decltype(FloatArrayFunc::get(a, index)) + { + return FloatArrayFunc::get(a, index); + } + + template + void set(T1& a, size_t index, T2 value) + { + FloatArrayFunc::set(a, index, value); + } + + template + void fmadd(T1& a, const T2& b, const T3& c) + { + FloatArrayFunc::fmadd(a, b, c); + } + + template + void fnmadd(T1& a, const T2& b, const T3& c) + { + FloatArrayFunc::fnmadd(a, b, c); + } + + template + T1 clipped_sqrt(const T1& a, const T2 b) + { + return FloatArrayFunc::clipped_sqrt(a, b); + } + + template + T abs(const T& a) + { + return FloatArrayFunc::abs(a); + } + + template + T log(const T& a) + { + return FloatArrayFunc::log(a); + } + + template + T sign(std::mt19937_64& gen) + { + return FloatArrayFunc::sign(gen); + } +} diff --git a/src/external/PackedCSparse/FloatArrayAVX2.h b/src/external/PackedCSparse/FloatArrayAVX2.h new file mode 100644 index 00000000..3ae7592a --- /dev/null +++ b/src/external/PackedCSparse/FloatArrayAVX2.h @@ -0,0 +1,222 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis + +template + struct m256dArray +{ + __m256d x[k]; + + m256dArray() {}; + + m256dArray(const double rhs) + { + for (size_t i = 0; i < k; i++) + x[i] = _mm256_set1_pd(rhs); + } + + template + m256dArray(const m256dArray& rhs) + { + for (size_t i = 0; i < k; i++) + x[i] = rhs.x[i % k2]; + } + + m256dArray operator+(const m256dArray& rhs) const + { + m256dArray out; + for (size_t i = 0; i < k; i++) + out.x[i] = _mm256_add_pd(x[i], rhs.x[i]); + return out; + } + + m256dArray operator-(const m256dArray& rhs) const + { + m256dArray out; + for (size_t i = 0; i < k; i++) + out.x[i] = _mm256_sub_pd(x[i], rhs.x[i]); + return out; + } + + m256dArray operator*(const m256dArray& rhs) const + { + m256dArray out; + for (size_t i = 0; i < k; i++) + out.x[i] = _mm256_mul_pd(x[i], rhs.x[i]); + return out; + } + + m256dArray operator/(const m256dArray& rhs) const + { + m256dArray out; + for (size_t i = 0; i < k; i++) + out.x[i] = _mm256_div_pd(x[i], rhs.x[i]); + return out; + } + + m256dArray& operator+=(const m256dArray& rhs) + { + for (size_t i = 0; i < k; i++) + x[i] = _mm256_add_pd(x[i], rhs.x[i]); + return *this; + } + + m256dArray& operator-=(const m256dArray& rhs) + { + for (size_t i = 0; i < k; i++) + x[i] = _mm256_sub_pd(x[i], rhs.x[i]); + return *this; + } + + m256dArray& operator*=(const m256dArray& rhs) + { + for (size_t i = 0; i < k; i++) + x[i] = _mm256_mul_pd(x[i], rhs.x[i]); + return *this; + } + + m256dArray& operator/=(const m256dArray& rhs) + { + for (size_t i = 0; i < k; i++) + x[i] = _mm256_div_pd(x[i], rhs.x[i]); + return *this; + } + + explicit operator bool() const + { + bool ret = false; + __m256d z = _mm256_set1_pd(0.0); + for (size_t i = 0; i < k; i++) + { + __m256d c = _mm256_cmp_pd(x[i], z, _CMP_EQ_OQ); + ret = ret || (_mm256_movemask_pd(c) != 0xf); + } + return ret; + } + + static double get(const m256dArray& x, size_t index) + { + double y[4]; + _mm256_store_pd(y, x.x[index / 4]); + return y[index & 3]; + } + + static void set(m256dArray& x, size_t index, double value) + { + __m256d v = _mm256_broadcast_sd(&value); + switch (index & 3) + { + case 0: x.x[index / 4] = _mm256_blend_pd(x.x[index / 4], v, 1); break; + case 1: x.x[index / 4] = _mm256_blend_pd(x.x[index / 4], v, 2); break; + case 2: x.x[index / 4] = _mm256_blend_pd(x.x[index / 4], v, 4); break; + default: x.x[index / 4] = _mm256_blend_pd(x.x[index / 4], v, 8); break; + } + } + + static m256dArray abs(const m256dArray& x) + { + const __m256d mask = _mm256_castsi256_pd(_mm256_set1_epi64x(0x7FFFFFFFFFFFFFFF)); + + m256dArray out; + for (size_t i = 0; i < k; i++) + out.x[i] = _mm256_and_pd(x.x[i], mask); + return out; + } + + static m256dArray log(const m256dArray& x) + { + // gcc does not support _mm256_log_pd + // Do it sequentially instead + + //m256dArray out; + //for (size_t i = 0; i < k; i++) + // out.x[i] = _mm256_log_pd(x.x[i]); + + m256dArray out; + for (size_t i = 0; i < 4*k; i++) + set(out, i, std::log(get(x,i))); + return out; + } + + static void fmadd(m256dArray& a, const m256dArray& b, const double& c) + { + auto cx = _mm256_set1_pd(c); + for (size_t i = 0; i < k; i++) + a.x[i] = _mm256_fmadd_pd(b.x[i], cx, a.x[i]); + } + + static void fnmadd(m256dArray& a, const m256dArray& b, const double& c) + { + auto cx = _mm256_set1_pd(c); + for (size_t i = 0; i < k; i++) + a.x[i] = _mm256_fnmadd_pd(b.x[i], cx, a.x[i]); + } + + static void fmadd(m256dArray& a, const m256dArray& b, const m256dArray& c) + { + for (size_t i = 0; i < k; i++) + a.x[i] = _mm256_fmadd_pd(b.x[i], c.x[i], a.x[i]); + } + + static void fnmadd(m256dArray& a, const m256dArray& b, const m256dArray& c) + { + for (size_t i = 0; i < k; i++) + a.x[i] = _mm256_fnmadd_pd(b.x[i], c.x[i], a.x[i]); + } + + static m256dArray clipped_sqrt(const m256dArray& x, const double nonpos_output) + { + m256dArray out; + + const __m256d large = { nonpos_output, nonpos_output, nonpos_output, nonpos_output }; + const __m256d zero = _mm256_setzero_pd(); + for (size_t i = 0; i < k; i++) + { + __m256d xi = x.x[i]; + __m256d mask = _mm256_cmp_pd(xi, zero, _CMP_LE_OS); // mask = (rhs.x[i]<= 0) ? -1 : 0 + out.x[i] = _mm256_blendv_pd(_mm256_sqrt_pd(xi), large, mask); + } + return out; + } + + static m256dArray sign(std::mt19937_64& gen) + { + m256dArray out; + const __m256i bits = _mm256_set_epi64x(1, 2, 4, 8); + const __m256d zero = _mm256_setzero_pd(); + const __m256d pos = _mm256_set_pd(1.0, 1.0, 1.0, 1.0); + const __m256d neg = _mm256_set_pd(-1.0, -1.0, -1.0, -1.0); + + unsigned long long seed = gen(); + for (size_t i = 0; i < k; i++) + { + __m256i s = _mm256_set1_epi64x((seed >> (4 * i)) & 15); + __m256i xi = _mm256_and_si256(s, bits); + __m256d x = _mm256_castsi256_pd(xi); + __m256d mask = _mm256_cmp_pd(x, zero, _CMP_EQ_OQ); // mask = (rhs.x[i] == 0) ? -1 : 0 + out.x[i] = _mm256_blendv_pd(pos, neg, mask); + if ((i & 63) == 63) seed = gen(); + } + return out; + } +}; + +template + struct FloatTypeSelector +{ + static_assert(k == 1 || k % 4 == 0, "Array assumes k = 1 or a multiple of 4"); + using type = typename std::conditional< k == 1, double, m256dArray>::type; + using funcImpl = typename std::conditional< k == 1, BaseScalarImpl, m256dArray>::type; +}; + +template + struct FloatTypeSelector, l> +{ + using type = m256dArray; + using funcImpl = m256dArray; +}; diff --git a/src/external/PackedCSparse/PackedChol.h b/src/external/PackedCSparse/PackedChol.h new file mode 100644 index 00000000..97d10220 --- /dev/null +++ b/src/external/PackedCSparse/PackedChol.h @@ -0,0 +1,308 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis + +#pragma once +#include "Eigen/Eigen" +#include "SparseMatrix.h" +#include "chol.h" +#include "leverage.h" +#include "leverageJL.h" +#include "multiply.h" +#include "qd/dd_real.h" +#include +#include +using namespace PackedCSparse; + +template +void get_slice(Tout *out, Tin *in, size_t n, size_t idx) { + for (size_t j = 0; j < n; j++) + out[j] = to_double(get(in[j], idx)); +} + +template +void set_slice(Tout *out, Tin *in, size_t n, size_t idx) { + for (size_t j = 0; j < n; j++) + set(out[j], idx, to_double(in[j])); +} + +template struct PackedChol { + using Tx = double; + using Tx2 = FloatArray; + using Te = dd_real; + + // parameters + SparseMatrix A; + SparseMatrix At; + UniqueAlignedPtr w; + Tx accuracyThreshold = 1e-6; + std::vector + exactIdx; // k size array. Indices we perform high precision calculation + std::vector + numExact; // number of times we perform high precision decompose (length + // k+1, the last one records how many times we do decompose) + bool decomposed = false; + + // preprocess info for different CSparse operations (PackedDouble) + MultiplyOutput H; // cache for H = A W A' + CholOutput L; // cache for L = chol(H) + LeverageOutput diagP; // cache for L = chol(H) + LeverageJLOutput diagPJL; // cache for L = chol(H) + + // preprocess info for different CSparse operations (dd_real) + MultiplyOutput H_exact; // cache for H = A W A' + CholOutput L_exact; // cache for L = chol(H) + LeverageOutput diagP_exact; // cache for L = chol(H) + LeverageJLOutput diagPJL_exact; // cache for L = chol(H) + SparseMatrix Le[k]; // store output of L_exact + + PackedChol(const SparseMatrix &A_) { + A = std::move(A_.clone()); + At = transpose(A); + w.reset(pcs_aligned_new(A.n)); + numExact.resize(k + 1); + } + + void setSeed(unsigned long long seed) { + diagPJL.gen.seed(seed); + diagPJL_exact.gen.seed(seed); + } + + bool allExact() { return exactIdx.size() == k; } + + bool hasExact() { return exactIdx.size() > 0; } + + template Tx2 decompose(const Tv2_ *w_in) { + + Tx2 acc = Tx2(0.0); + + // record w + Ti n = A.n; + + for (Ti j = 0; j < n; j++) { + w[j] = w_in[j]; + } + // compute chol + ++numExact[k]; + if (accuracyThreshold > 0.0 || + !decomposed) // the first time we call, always run the double chol. + { + multiply(H, A, w.get(), At); + chol(L, H); + decomposed = true; + + exactIdx.clear(); + acc = estimateAccuracy(); + for (size_t i = 0; i < k; i++) { + if (get(acc, i) >= + accuracyThreshold) // >= is important for the case accuracyThreshold + // = 0.0, we need to compute everything exactly + exactIdx.push_back(i); + } + } else if (!allExact()) { + exactIdx.clear(); + for (size_t i = 0; i < k; i++) + exactIdx.push_back(i); + } + + if (hasExact()) { + Te *w_exact = new Te[n]; + + for (size_t i : exactIdx) { + ++numExact[i]; + get_slice(w_exact, w.get(), n, i); + multiply(H_exact, A, w_exact, At); + chol(L_exact, H_exact); + + // copy result to Le[i] + if (!Le[i].initialized()) + Le[i] = std::move(L_exact.template clone()); + else { + Ti nz = L_exact.nnz(); + for (Ti s = 0; s < nz; ++s) + Le[i].x[s] = (L_exact.x[s]); + } + } + + delete[] w_exact; + } + return acc; + } + + Tx2 logdet() { + pcs_assert(decomposed, "logdet: Need to call decompose first."); + + Ti m = A.m; + Tx2 ret = Tx2(0); + + if (!allExact()) { + Ti *Lp = L.p.get(); + Tx2 *Lx = L.x.get(); + for (Ti j = 0; j < m; j++) + ret += log(Lx[Lp[j]]); + } + + if (hasExact()) { + for (size_t i : exactIdx) { + Te ret_e = 0.0; + Ti *Lp = Le[i].p.get(); + Te *Lx = Le[i].x.get(); + + for (Ti j = 0; j < m; j++) + ret_e += log(Lx[Lp[j]]); + + set(ret, i, to_double(ret_e)); + } + } + + return ret * Tx2(2.0); + } + + void diagL(Tx2 *out) { + pcs_assert(decomposed, "diagL: Need to call decompose first."); + + Ti m = A.m; + + if (!allExact()) { + Ti *Li = L.i.get(), *Lp = L.p.get(); + Tx2 *Lx = L.x.get(); + for (Ti j = 0; j < m; j++) + out[j] = Lx[Lp[j]]; + } + + if (hasExact()) { + for (size_t i : exactIdx) { + Ti *Lp = Le[i].p.get(); + Te *Lx = Le[i].x.get(); + + for (Ti j = 0; j < m; j++) + set(out[j], i, to_double(Lx[Lp[j]])); + } + } + } + + SparseMatrix getL(Ti i) { + pcs_assert(decomposed, "getL: Need to call decompose first."); + + Ti m = L.m, n = L.n, nz = L.nnz(); + SparseMatrix out(m, n, nz); + + Ti *outp = out.p.get(), *Lp = L.p.get(); + Ti *outi = out.i.get(), *Li = L.i.get(); + + for (Ti s = 0; s <= n; s++) + outp[s] = Lp[s]; + + for (Ti s = 0; s < nz; s++) + outi[s] = Li[s]; + + bool isExact = false; + for (size_t i_ : exactIdx) { + if (i_ == i) + isExact = true; + } + + double *outx = out.x.get(); + if (isExact) { + Te *Lx = Le[i].x.get(); + for (Ti s = 0; s < nz; s++) + outx[s] = to_double(Lx[s]); + } else { + Tx2 *Lx = L.x.get(); + for (Ti s = 0; s < nz; s++) + outx[s] = get(Lx[s], i); + } + + return std::move(out); + } + + void solve(Tx2 *b, Tx2 *out) { + pcs_assert(decomposed, "solve: Need to call decompose first."); + + if (!allExact()) { + lsolve(L, b, out); + ltsolve(L, out, out); + } + + if (hasExact()) { + Ti m = A.m; + Te *b_exact = new Te[m]; + Te *out_exact = new Te[m]; + + for (size_t i : exactIdx) { + get_slice(b_exact, b, m, i); + lsolve(Le[i], b_exact, out_exact); + ltsolve(Le[i], out_exact, out_exact); + set_slice(out, out_exact, m, i); + } + + delete[] b_exact; + delete[] out_exact; + } + }; + + void leverageScoreComplement(Tx2 *out) { + pcs_assert(decomposed, + "leverageScoreComplement: Need to call decompose first."); + + Ti n = A.n, m = A.m; + + if (!allExact()) { + Tx2 T1 = Tx2(1.0), T2 = Tx2(2.0); + leverage(diagP, L, A, At); + + Tx2 *tau = diagP.x.get(); + for (Ti j = 0; j < n; j++) + out[j] = T1 - tau[j] * w[j]; + } + + if (hasExact()) { + Te T1 = Te(1.0), T2 = Te(2.0); + for (size_t i : exactIdx) { + leverage(diagP_exact, Le[i], A, At); + + Te *tau = diagP_exact.x.get(); + for (Ti j = 0; j < n; j++) + set(out[j], i, to_double(T1 - tau[j] * get(w[j], i))); + } + } + } + + void leverageScoreComplementJL(Tx2 *out, size_t JL_k) { + pcs_assert(decomposed, + "leverageScoreComplementJL: Need to call decompose first."); + + Ti m = A.m, n = A.n; + + if (!allExact()) { + Tx2 T1 = Tx2(1.0), T2 = Tx2(2.0); + leverageJL(diagPJL, L, A, At, JL_k); + + Tx2 *tau = diagPJL.x.get(); + for (Ti j = 0; j < n; j++) + out[j] = T1 - tau[j] * w[j]; + } + + if (hasExact()) { + Te T1 = Te(1.0), T2 = Te(2.0); + for (size_t i : exactIdx) { + leverageJL(diagPJL_exact, Le[i], A, At, JL_k); + + Te *tau = diagPJL_exact.x.get(); + for (Ti j = 0; j < n; j++) + set(out[j], i, to_double(T1 - tau[j] * get(w[j], i))); + } + } + } + + Tx2 estimateAccuracy() { + pcs_assert(decomposed, "estimateAccuracy: Need to call decompose first."); + + return cholAccuracy(diagPJL, L, A, At, w.get()); + } +}; diff --git a/src/external/PackedCSparse/SparseMatrix.h b/src/external/PackedCSparse/SparseMatrix.h new file mode 100644 index 00000000..5dca4286 --- /dev/null +++ b/src/external/PackedCSparse/SparseMatrix.h @@ -0,0 +1,230 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis +#pragma once +#include +#include +#include "FloatArray.h" + +namespace PackedCSparse { + static void pcs_assert(bool value, const char* message) + { + if (value == false) + throw std::logic_error(message); + } + + template + T* pcs_aligned_new(size_t size) + { + int alignment = 64; // size of memory cache line + int offset = alignment - 1 + sizeof(void*); + void* p1 = (void*)new char[size * sizeof(T) + offset]; + void** p2 = (void**)(((size_t)(p1)+offset) & ~(alignment - 1)); + p2[-1] = p1; + return (T*)p2; + } + + template + struct AlignedDeleter + { + void operator()(T* p) const + { + delete[](char*)(((void**)p)[-1]); + } + }; + + template + using UniqueAlignedPtr = std::unique_ptr>; + + template + using UniquePtr = std::unique_ptr; + + // Tx = Type for entries, Ti = Type for indices. + // if Tx == bool, the matrix stores only sparsity information + template + struct SparseMatrix + { + Ti m = 0; /* number of rows */ + Ti n = 0; /* number of columns */ + UniquePtr p; /* column pointers (size n+1) */ + UniquePtr i; /* row indices, size nnz */ + UniqueAlignedPtr x; /* numerical values, size nnz */ + + SparseMatrix() = default; + + SparseMatrix(Ti m_, Ti n_, Ti nzmax_) + { + initialize(m_, n_, nzmax_); + } + + bool initialized() const + { + return p && i; + } + + void initialize(Ti m_, Ti n_, Ti nzmax) + { + if (nzmax < 1) nzmax = 1; + m = m_; n = n_; + p.reset(new Ti[n + 1]); + i.reset(new Ti[nzmax]); + if (!std::is_same::value) + x.reset(pcs_aligned_new(nzmax)); + } + + Ti nnz() const + { + return p[n]; + } + + template + SparseMatrix clone() const + { + SparseMatrix C(m, n, nnz()); + Ti* Ap = p.get(), * Ai = i.get(); Tx* Ax = x.get(); + Ti2* Cp = C.p.get(), * Ci = C.i.get(); Tx2* Cx = C.x.get(); + + for (Ti s = 0; s <= n; s++) + Cp[s] = Ti2(Ap[s]); + + Ti nz = nnz(); + for (Ti s = 0; s < nz; s++) + Ci[s] = Ti2(Ai[s]); + + if (Cx) + { + for (Ti s = 0; s < nz; s++) + Cx[s] = Ax? Tx2(Ax[s]): Tx2(1.0); + } + + return C; + } + }; + + template + struct DenseVector + { + Ti n = 0; /* number of columns */ + UniqueAlignedPtr x; /* numerical values, size nnz */ + + DenseVector() = default; + + DenseVector(Ti n_) + { + initialize(n_); + } + + bool initialized() const + { + return bool(x); + } + + void initialize(Ti n_) + { + n = n_; + x.reset(pcs_aligned_new(n_)); + } + }; + + + // basic functions + template + SparseMatrix speye(Ti n, Tx* d = nullptr) + { + SparseMatrix D(n, n, n); + + for (Ti k = 0; k < n; k++) + { + D.i[k] = k; + D.p[k] = k; + } + D.p[n] = n; + + Tx Tx1 = Tx(1.0); + for (Ti k = 0; k < n; k++) + D.x[k] = (d ? d[k] : Tx1); + return D; + } + + // Solve L out = x + // Input: L in Tx^{n by n}, x in Tx2^{n} + // Output: out in Tx2^{n}. + // If out is provided, we will output to out. Else, output to x. + template + void lsolve(const SparseMatrix& L, Tx2* x, Tx2* out = nullptr) + { + pcs_assert(L.initialized(), "lsolve: bad inputs."); + pcs_assert(L.n == L.m, "lsolve: dimensions mismatch."); + + Ti n = L.n, * Lp = L.p.get(), * Li = L.i.get(); Tx* Lx = L.x.get(); + + if (!out) out = x; + if (x != out) std::copy(x, x + n, out); + + for (Ti j = 0; j < n; j++) + { + Tx2 out_j = out[j] / Lx[Lp[j]]; + out[j] = out_j; + + Ti p_start = Lp[j] + 1, p_end = Lp[j + 1]; + for (Ti p = p_start; p < p_end; p++) + { //out[Li[p]] -= Lx[p] * out[j]; + fnmadd(out[Li[p]], out_j, Lx[p]); + } + } + } + + // Solve L' out = x + // Input: L in Tx^{n by n}, x in Tx2^{n} + // Output: out in Tx2^{n}. + // If out is provided, we will output to out. Else, output to x. + template + void ltsolve(const SparseMatrix& L, Tx2* x, Tx2* out = nullptr) + { + pcs_assert(L.initialized(), "ltsolve: bad inputs."); + pcs_assert(L.n == L.m, "ltsolve: dimensions mismatch."); + + Ti n = L.n, * Lp = L.p.get(), * Li = L.i.get(); Tx* Lx = L.x.get(); + + if (!out) out = x; + if (x != out) std::copy(x, x + n, out); + + for (Ti j = n - 1; j != -1; j--) + { + Tx2 out_j = out[j]; + + Ti p_start = Lp[j] + 1, p_end = Lp[j + 1]; + for (Ti p = p_start; p < p_end; p++) + { //out[j] -= Lx[p] * out[Li[p]]; + fnmadd(out_j, out[Li[p]], Lx[p]); + } + + out[j] = out_j / Tx2(Lx[Lp[j]]); + } + } + + // Update y <-- y + A x + // Input: A in Tx^{n by n}, x, y in Tx2^{n} + template + void gaxpy(const SparseMatrix& A, const Tx2* x, Tx2* y) + { + pcs_assert(A.initialized(), "gaxpy: bad inputs."); + Ti m = A.m, n = A.n, * Ap = A.p.get(), * Ai = A.i.get(); Tx* Ax = A.x.get(); + + for (Ti j = 0; j < n; j++) + { + Tx2 x_j = x[j]; + + Ti p_start = Ap[j], p_end = Ap[j + 1]; + for (Ti p = p_start; p < p_end; p++) + { //y[Ai[p]] += Ax[p] * x[j]; + fmadd(y[Ai[p]], x_j, Ax[p]); + } + } + } +}; diff --git a/src/external/PackedCSparse/add.h b/src/external/PackedCSparse/add.h new file mode 100644 index 00000000..16195722 --- /dev/null +++ b/src/external/PackedCSparse/add.h @@ -0,0 +1,102 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis + +#pragma once +#include +#include "SparseMatrix.h" + +// Problem: +// Compute M = A + B + +// Algorithm: +// M = 0 +// M(A != 0) += A(A != 0) +// M(B != 0) += B(A != 0) + +namespace PackedCSparse { + template + struct AddOutput : SparseMatrix + { + UniquePtr forwardA; + UniquePtr forwardB; + + template + void initialize(const SparseMatrix& A, const SparseMatrix& B) + { + pcs_assert(A.initialized() && B.initialized(), "add: bad inputs."); + pcs_assert(A.n == B.n && A.m == B.m, "add: dimensions mismatch."); + + Ti m = A.m, n = A.n; + Ti Anz = A.nnz(); Ti* Ap = A.p.get(), * Ai = A.i.get(); + Ti Bnz = B.nnz(); Ti* Bp = B.p.get(), * Bi = B.i.get(); + this->m = A.m; this->n = A.n; + + std::vector Ci; + Ti* Cp = new Ti[n + 1]; + forwardA.reset(new Ti[Anz]); + forwardB.reset(new Ti[Bnz]); + + Cp[0] = 0; + for (Ti i = 0; i < n; i++) + { + Ti s1 = Ap[i], s2 = Bp[i], end1 = Ap[i + 1], end2 = Bp[i + 1]; + while ((s1 < end1) || (s2 < end2)) + { + Ti q = Ti(Ci.size()); + Ti i1 = (s1 < end1) ? Ai[s1] : m; + Ti i2 = (s2 < end2) ? Bi[s2] : m; + Ti min_i = std::min(i1, i2); + Ci.push_back(min_i); + + if (i1 == min_i) + forwardA[s1++] = q; + + if (i2 == min_i) + forwardB[s2++] = q; + } + Cp[i + 1] = Ti(Ci.size()); + } + + this->p.reset(Cp); + this->i.reset(new Ti[Ci.size()]); + this->x.reset(pcs_aligned_new(Ci.size())); + std::copy(Ci.begin(), Ci.end(), this->i.get()); + } + }; + + template + void add(AddOutput& o, const SparseMatrix& A, const SparseMatrix& B) + { + if (!o.initialized()) + o.initialize(A, B); + + Ti m = o.m, n = o.n; + Ti Anz = A.nnz(); Ti* Ap = A.p.get(), * Ai = A.i.get(); Tx* Ax = A.x.get(); + Ti Bnz = B.nnz(); Ti* Bp = B.p.get(), * Bi = B.i.get(); Tx* Bx = B.x.get(); + Ti Cnz = o.nnz(); Ti* Cp = o.p.get(), * Ci = o.i.get(); Tx2* Cx = o.x.get(); + Ti* forwardA = o.forwardA.get(), *forwardB = o.forwardB.get(); + + for (Ti s = 0; s < Cnz; s++) + Cx[s] = 0; + + for (Ti s = 0; s < Anz; s++) + Cx[forwardA[s]] = Ax[s]; + + for (Ti s = 0; s < Bnz; s++) + Cx[forwardB[s]] += Bx[s]; + } + + template + AddOutput add(const SparseMatrix& A, const SparseMatrix& B) + { + AddOutput o; + add(o, A, B); + return o; + } +} diff --git a/src/external/PackedCSparse/chol.h b/src/external/PackedCSparse/chol.h new file mode 100644 index 00000000..d6001ae2 --- /dev/null +++ b/src/external/PackedCSparse/chol.h @@ -0,0 +1,247 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis + +#pragma once +#include +#include +#include "SparseMatrix.h" +#include "transpose.h" + +// Problem: +// Compute chol(A) + +// Algorithm: +// We need to study this later as this is the bottleneck. +// Document it as a lyx. +// chol_up_looking: +// Compute L row by row +// This is faster when it is compute bound. +// +// chol_left_looking: +// Compute L col by col +// This is faster when it is memory bound. + + +namespace PackedCSparse { + template + struct CholOutput : SparseMatrix + { + TransposeOutput Lt; // sparsity pattern of the Lt + UniquePtr diag; // the index for diagonal element. Ax[diag[k]] is A_kk + UniquePtr c; // c[i] = index the last nonzero on column i in the current L + UniqueAlignedPtr w; // the row of L we are computing + + // The cost of this is roughly 3 times larger than chol + // One can optimize it by using other data structure + void initialize(const SparseMatrix& A) + { + pcs_assert(A.initialized(), "chol: bad inputs."); + pcs_assert(A.n == A.m, "chol: dimensions mismatch."); + + Ti n = A.n, * Ap = A.p.get(), * Ai = A.i.get(); + + // initialize + this->diag.reset(new Ti[n]); + this->c.reset(new Ti[n]); + this->w.reset(pcs_aligned_new(n)); + + // compute the sparsity pattern of L and diag + using queue = std::priority_queue, std::greater>; + queue q; // sol'n of the current row of L + Ti* mark = new Ti[n]; // used to prevent same indices push to q twice + std::vector* cols = new std::vector[n]; // stores the non-zeros of each col of L + Ti nz = 0, Anz = Ap[n]; + + for (Ti i = 0; i < n; i++) + mark[i] = -1; + + // for each row of A + for (Ti i = 0; i < n; i++) + { // push i-th row of A, called a_12, into mark + Ti s; + for (s = Ap[i]; s < Ap[i + 1]; s++) + { + Ti j = Ai[s]; + if (j >= i) break; + + q.push(j); + mark[j] = i; + } + if (s >= Anz) // this case happens only if the diagonal is 0. No cholesky in this case. + this->diag[i] = 0; + else + this->diag[i] = s; + + // Solve L_11 l_12 = a_12 + while (!q.empty()) + { + Ti j = q.top(); + + for (Ti k : cols[j]) + { + if (mark[k] != i) + { + q.push(k); + mark[k] = i; + } + } + q.pop(); + + // update j col + cols[j].push_back(i); + ++nz; + } + + // diag + cols[i].push_back(i); + ++nz; + } + delete[] mark; + + // write it as the compress form + SparseMatrix::initialize(n, n, nz); + + Ti s_start = 0; Ti s = 0; + for (Ti i = 0; i < n; i++) + { + this->p[i] = s_start; + for (Ti k : cols[i]) + this->i[s++] = k; + s_start += Ti(cols[i].size()); + } + this->p[n] = s_start; + delete[] cols; + + this->Lt = transpose(*this); + + // initialize w to 0 + Tx Tv0 = Tx(0); + for (Ti k = 0; k < n; k++) + w[k] = Tv0; + } + }; + + template + void chol(CholOutput& o, const SparseMatrix& A) + { + if (!o.initialized()) + o.initialize(A); + + //chol_up_looking(o, A); + chol_left_looking(o, A); + } + + template + void chol_up_looking(CholOutput& o, const SparseMatrix& A) + { + Ti *Ap = A.p.get(), * Ai = A.i.get(); Tx* Ax = A.x.get(); + Ti nzmax = o.nzmax; Ti n = A.n; + Ti *Lp = o.p.get(); Ti* Li = o.i.get(); + Ti *Ltp = o.Lt.p.get(); Ti* Lti = o.Lt.i.get(); + + Tx T0 = Tx(0); + Tx* Lx = o.x.get(); Tx* w = o.w.get(); Ti* c = o.c.get(); + Ti* diag = o.diag.get(); + + Ti* Lti_ptr = Lti; + for (Ti k = 0; k < n; ++k) + { + c[k] = Lp[k]; + + Ti s_end = diag[k]; + for (Ti s = Ap[k]; s < s_end; ++s) + w[Ai[s]] = Ax[s]; + + // Solve L_11 l_12 = a_12 + Tx d = Ax[s_end]; Ti i; + for (; (i = *(Lti_ptr++)) < k;) + { + Ti dLi = Lp[i], ci = c[i]++; + Tx Lki = w[i] / Lx[dLi]; + w[i] = T0; // maintain x = 0 for the (k+1) iteration + + for (Ti q = dLi + 1; q < ci; ++q) + fnmadd(w[Li[q]], Lx[q], Lki); + + d -= Lki * Lki; + Lx[ci] = Lki; + } + + // l_22 = sqrt(a22 - ) + Lx[c[k]++] = clipped_sqrt(d); + } + } + + template + void chol_left_looking(CholOutput& o, const SparseMatrix& A) + { + Ti* Ap = A.p.get(), * Ai = A.i.get(); Tx* Ax = A.x.get(); + Ti nzmax = o.nnz(); Ti n = A.n; + Ti* Lp = o.p.get(); Ti* Li = o.i.get(); + Ti* Ltp = o.Lt.p.get(); Ti* Lti = o.Lt.i.get(); + + Tx T0 = Tx(0), T1 = Tx(1); + Tx* Lx = o.x.get(); + Tx* w = o.w.get(); Ti* c = o.c.get(); + Ti* diag = o.diag.get(); + + for (Ti j = 0; j < n; ++j) + { + c[j] = Lp[j]; + + // x = A_{j:n, j} + { + Ti is_start = diag[j], is_end = Ap[j + 1]; + for (Ti is = is_start; is < is_end; ++is) + w[Ai[is]] = Ax[is]; + } + + // for each p in L_{j, 1:j-1} + Ti ps_start = Ltp[j], ps_end = Ltp[j + 1] - 1; + for (Ti ps = ps_start; ps < ps_end; ++ps) + { + Ti p = Lti[ps]; + Ti cp = c[p]++; + Tx Ljp = Lx[cp]; + + // for each i in L_{j:n,p} + Ti is_start = cp, is_end = Lp[p + 1]; + for (Ti is = is_start; is < is_end; ++is) + { + Ti i = Li[is]; + fnmadd(w[i], Lx[is], Ljp); + } + } + + Tx Ljj = clipped_sqrt(w[j], 1e128); + Lx[c[j]++] = Ljj; + Tx inv_Ljj = T1 / Ljj; + w[j] = T0; + + // for each i in L_{:,j} + { + Ti is_start = Lp[j] + 1, is_end = Lp[j + 1]; + for (Ti is = is_start; is < is_end; ++is) + { + Ti i = Li[is]; + Lx[is] = w[i] * inv_Ljj; + w[i] = T0; + } + } + } + } + + template + CholOutput chol(const SparseMatrix& A) + { + CholOutput o; + chol(o, A); + return o; + } +} diff --git a/src/external/PackedCSparse/leverage.h b/src/external/PackedCSparse/leverage.h new file mode 100644 index 00000000..69549308 --- /dev/null +++ b/src/external/PackedCSparse/leverage.h @@ -0,0 +1,65 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis + +#pragma once +#include "SparseMatrix.h" +#include "projinv.h" +#include "outerprod.h" + +// Problem: +// Compute M = diag(A' inv(LL') A) + +namespace PackedCSparse { + template + struct LeverageOutput : DenseVector + { + ProjinvOutput Hinv; // Hinv = inv(H)|_L + OuterprodOutput tau; // tau = diag(A' Hinv A) + + template + void initialize(const SparseMatrix& L, const SparseMatrix& A, const SparseMatrix& At) + { + pcs_assert(L.initialized() && A.initialized() && At.initialized(), "leverage: bad inputs."); + pcs_assert(L.m == L.n && L.n == A.m && L.n == At.n && A.n == At.m, "leverage: dimensions mismatch."); + DenseVector::initialize(A.n); + Hinv.initialize(L); + tau.initialize(A, Hinv, At); + } + }; + + template + void leverage(LeverageOutput& o, const SparseMatrix& L, const SparseMatrix& A, const SparseMatrix& At) + { + if (!o.initialized()) + o.initialize(L, A, At); + + Tx T1 = Tx(1.0), T2 = Tx(2.0); + projinv(o.Hinv, L); + + Ti m = A.m, n = A.n; + Ti* Sp = o.Hinv.p.get(); Tx* Sv = o.Hinv.x.get(); + for (Ti k = 0; k < m; ++k) + Sv[Sp[k]] /= T2; + + outerprod(o.tau, A, o.Hinv, At); + + Tx* x = o.x.get(), * tau = o.tau.x.get(); + for (Ti j = 0; j < n; j++) + x[j] = T2 * tau[j]; + } + + + template + LeverageOutput leverage(const SparseMatrix& L, const SparseMatrix& A, const SparseMatrix& At) + { + LeverageOutput o; + leverage(o, L, A, At); + return o; + } +} diff --git a/src/external/PackedCSparse/leverageJL.h b/src/external/PackedCSparse/leverageJL.h new file mode 100644 index 00000000..a5001bcd --- /dev/null +++ b/src/external/PackedCSparse/leverageJL.h @@ -0,0 +1,148 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis + +#pragma once +#include +#include "SparseMatrix.h" + +// Problem: +// Approximate M = diag(A' inv(LL') A) +namespace PackedCSparse { + const size_t JLPackedSize = 4; + + template + struct LeverageJLOutput : DenseVector + { + UniqueAlignedPtr d; // random direction d + UniqueAlignedPtr L_d; // random direction d + UniqueAlignedPtr AtL_d; // A' L^{-1} d + Ti m = 0; + std::mt19937_64 gen; + + template + void initialize(const SparseMatrix& L, const SparseMatrix& A, const SparseMatrix& At) + { + pcs_assert(L.initialized() && A.initialized() && At.initialized(), "leverageJL: bad inputs."); + pcs_assert(L.m == L.n && L.n == A.m && L.n == At.n && A.n == At.m, "leverageJL: dimensions mismatch."); + this->n = A.n; this->m = A.m; + this->x.reset(pcs_aligned_new(this->n)); + this->d.reset(pcs_aligned_new(this->m * 2 * JLPackedSize)); + this->L_d.reset(pcs_aligned_new(this->m * 2 * JLPackedSize)); + this->AtL_d.reset(pcs_aligned_new(this->n * 2 * JLPackedSize)); + } + }; + + // compute sum_{j=1}^{k} (A' L^{-T} u_j) .* (A' L^{-T} u_j) + template + void projectionJL(LeverageJLOutput& o, const SparseMatrix& L, const SparseMatrix& A, const SparseMatrix& At) + { + Ti m = A.m, n = A.n; + Tx T0 = Tx(0.0), T1 = Tx(1.0); + Tx* d = o.d.get(), * L_d = o.L_d.get(), * AtL_d = o.AtL_d.get(), * x = o.x.get(); + + for (Ti i = 0; i < m * k; i++) + d[i] = sign(o.gen); + + for (Ti i = 0; i < n * k; i++) + AtL_d[i] = T0; + + ltsolve(L, (BaseImpl*)d, (BaseImpl*)L_d); + gaxpy(At, (BaseImpl*)L_d, (BaseImpl*)AtL_d); + + for (Ti i = 0; i < n; i++) + { + Tx ret_i = T0; + for (Ti j = 0; j < k; j++) + ret_i += AtL_d[i * k + j] * AtL_d[i * k + j]; + + x[i] += ret_i; + } + } + + template + void leverageJL(LeverageJLOutput& o, const SparseMatrix& L, const SparseMatrix& A, const SparseMatrix& At, size_t k) + { + if (!o.initialized()) + o.initialize(L, A, At); + + Ti n = A.n; Tx* x = o.x.get(); + for (Ti i = 0; i < n; i++) + x[i] = Tx(0.0); + + constexpr size_t k_step = JLPackedSize; + for(size_t i = 1; i <= k / k_step; i++) + projectionJL(o, L, A, At); + + for (size_t i = 1; i <= k % k_step; i++) + projectionJL<1>(o, L, A, At); + + Tx ratio = Tx(1 / double(k)); + for (Ti i = 0; i < n; i++) + x[i] *= ratio; + } + + template + LeverageJLOutput leverageJL(const SparseMatrix& L, const SparseMatrix& A, const SparseMatrix& At, size_t k) + { + LeverageJLOutput o; + leverageJL(o, L, A, At, k); + return o; + } + + + // compute (A' L^{-T} u_j) .* (A' L^{-T} v_j) for j = 1, 2, ... k + template + Tx cholAccuracy(LeverageJLOutput& o, const SparseMatrix& L, const SparseMatrix& A, const SparseMatrix& At, const Tx* w) + { + if (!o.initialized()) + o.initialize(L, A, At); + + constexpr Ti k = JLPackedSize; + constexpr Ti k_ = 2 * k; + + + Ti m = A.m, n = A.n; + Tx T0 = Tx(0.0), T1 = Tx(1.0); + Tx* d = o.d.get(), * L_d = o.L_d.get(), * AtL_d = o.AtL_d.get(), * x = o.x.get(); + + std::uniform_real_distribution distribution(-sqrt(3.0),sqrt(3.0)); + for (Ti i = 0; i < m * k_; i++) + d[i] = Tx(distribution(o.gen)); // roughly uniform distribution with variance 1 + + for (Ti i = 0; i < n * k_; i++) + AtL_d[i] = T0; + + ltsolve(L, (BaseImpl*)d, (BaseImpl*)L_d); + gaxpy(At, (BaseImpl*)L_d, (BaseImpl*)AtL_d); + + Tx result[k]; + for (Ti j = 0; j < k; j++) + result[j] = Tx(0.0); + + for (Ti i = 0; i < m; i++) + { + Tx* d = o.d.get() + i * (2 * k); + for (Ti j = 0; j < k; j++) + result[j] -= d[j] * d[j + k]; + } + + for (Ti i = 0; i < n; i++) + { + Tx w_i = w[i]; + for (Ti j = 0; j < k; j++) + result[j] += AtL_d[i * k_ + j] * AtL_d[i * k_ + j + k] * w_i; + } + + Tx est = Tx(0.0); + for (Ti j = 0; j < k; j++) + est += result[j] * result[j]; + + return clipped_sqrt(est/Tx(double(k)), 0.0); + } +} diff --git a/src/external/PackedCSparse/multiply.h b/src/external/PackedCSparse/multiply.h new file mode 100644 index 00000000..cfd53375 --- /dev/null +++ b/src/external/PackedCSparse/multiply.h @@ -0,0 +1,142 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis + +#pragma once +#include +#include +#include "SparseMatrix.h" + +// Problem: +// Compute M = A diag(w) B + +// Algorithm: +// Compute M col by col + +namespace PackedCSparse { + template + struct MultiplyOutput : SparseMatrix + { + UniqueAlignedPtr c; + + template + void initialize(const SparseMatrix& A, const SparseMatrix& B) + { + pcs_assert(A.initialized() && B.initialized(), "multiply: bad inputs."); + pcs_assert(A.n == B.m, "multiply: dimensions mismatch."); + + Ti m = A.m, n = B.n; + Ti* Ap = A.p.get(), * Ai = A.i.get(); + Ti* Bp = B.p.get(), * Bi = B.i.get(); + + this->c.reset(pcs_aligned_new(m)); + + Ti* last_j = new Ti[m]; + for (Ti i = 0; i < m; i++) + { + last_j[i] = -1; + this->c[i] = Tx(0.0); + } + + Ti* Cp = new Ti[size_t(n)+1]; + std::vector Ci; + + Cp[0] = 0; + for (Ti j1 = 0; j1 < n; j1++) + { + for (Ti p1 = Bp[j1]; p1 < Bp[j1 + 1]; p1++) + { + Ti j2 = Bi[p1]; + for (Ti p2 = Ap[j2]; p2 < Ap[j2 + 1]; p2++) + { + Ti i = Ai[p2]; + if (last_j[i] != j1) + { + last_j[i] = j1; + Ci.push_back(i); + } + } + } + Cp[j1 + 1] = Ti(Ci.size()); + } + delete[] last_j; + + for (Ti j = 0; j < n; j++) + std::sort(Ci.begin() + Cp[j], Ci.begin() + Cp[j + 1]); + + this->m = m; this->n = n; + this->x.reset(pcs_aligned_new(Ci.size())); + this->p.reset(Cp); + this->i.reset(new Ti[Ci.size()]); + std::copy(Ci.begin(), Ci.end(), this->i.get()); + } + }; + + template + void multiply_general(MultiplyOutput& o, const SparseMatrix& A, const Tx2* w, const SparseMatrix& B) + { + if (!o.initialized()) + o.initialize(A, B); + + Ti m = o.m, n = o.n; + Ti* Ap = A.p.get(), * Ai = A.i.get(); Tx* Ax = A.x.get(); + Ti* Bp = B.p.get(), * Bi = B.i.get(); Tx* Bx = B.x.get(); + Ti* Cp = o.p.get(), * Ci = o.i.get(); Tx2* Cx = o.x.get(); + Tx2* c = o.c.get(); // initialized to 0 + + const Tx2 T0 = Tx2(0); + for (Ti j1 = 0; j1 < n; j1++) + { + for (Ti p1 = Bp[j1]; p1 < Bp[j1 + 1]; p1++) + { + Ti j2 = Bi[p1]; + Tx2 beta = has_weight? (Tx2(Bx[p1]) * w[j2]) : Tx2(Bx[p1]); + + for (Ti p2 = Ap[j2]; p2 < Ap[j2 + 1]; p2++) + { + //x[Ai[p2]] += beta * Ax[p2]; + fmadd(c[Ai[p2]], beta, Ax[p2]); + } + } + + for (Ti p1 = Cp[j1]; p1 < Cp[j1 + 1]; p1++) + { + Cx[p1] = c[Ci[p1]]; + c[Ci[p1]] = T0; // ensure c is 0 after the call + } + } + } + + template + void multiply(MultiplyOutput& o, const SparseMatrix& A, const SparseMatrix& B) + { + multiply_general(o, A, nullptr, B); + } + + template + void multiply(MultiplyOutput& o, const SparseMatrix& A, const Tx2* w, const SparseMatrix& B) + { + multiply_general(o, A, w, B); + } + + template + MultiplyOutput multiply(const SparseMatrix& A, const Tx2* w, const SparseMatrix& B) + { + MultiplyOutput o; + multiply(o, A, w, B); + return o; + } + + template + MultiplyOutput multiply(const SparseMatrix& A, const SparseMatrix& B) + { + MultiplyOutput o; + multiply(o, A, B); + return o; + } +} diff --git a/src/external/PackedCSparse/outerprod.h b/src/external/PackedCSparse/outerprod.h new file mode 100644 index 00000000..6f0e98e3 --- /dev/null +++ b/src/external/PackedCSparse/outerprod.h @@ -0,0 +1,86 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis +#pragma once +#include "SparseMatrix.h" + +// Problem: +// Compute x = diag(At S Bt) + +// Algorithm: +// Note that x = diag(B St A) = grad_H Tr(St A H B) +// We run autodiff on the function Tr(St A H B). +// Hence, the algorithm is essentially same as multiply(A, B) with the same runtime. + +namespace PackedCSparse { + template + struct OuterprodOutput : DenseVector + { + UniqueAlignedPtr s_col; + UniquePtr s_mark; + + template + void initialize(const SparseMatrix& A, const SparseMatrix& S, const SparseMatrix& B) + { + pcs_assert(A.initialized() && B.initialized() && S.initialized(), "outerprod: bad inputs."); + pcs_assert(A.m == S.m && S.n == B.n, "outerprod: dimensions mismatch."); + + DenseVector::initialize(A.n); + s_col.reset(pcs_aligned_new(S.m)); + s_mark.reset(new Ti[S.m]); + } + }; + + template + void outerprod(OuterprodOutput& o, const SparseMatrix& A, const SparseMatrix& S, const SparseMatrix& B) + { + if (!o.initialized()) + o.initialize(A, S, B); + + Ti Sn = S.n, Sm = S.m, An = A.n; + Ti* Ap = A.p.get(), * Ai = A.i.get(); Tx2* Ax = A.x.get(); + Ti* Bp = B.p.get(), * Bi = B.i.get(); Tx2* Bx = B.x.get(); + Ti* Sp = S.p.get(), * Si = S.i.get(); Tx* Sx = S.x.get(); + Tx* s_col = o.s_col.get(); + Ti* s_mark = o.s_mark.get(); + Tx* x = o.x.get(); + + std::fill(s_mark, s_mark + Sm, Ti(-1)); + std::fill(x, x + An, Tx(0.0)); + + for (Ti j = 0; j < Sn; j++) + { + for (Ti p = Sp[j]; p < Sp[j + 1]; p++) + { + s_col[Si[p]] = Sx[p]; + s_mark[Si[p]] = j; + } + + for (Ti p = Bp[j]; p < Bp[j + 1]; p++) + { + Ti i = Bi[p]; Tx b = Bx[p]; + for (Ti q = Ap[i]; q < Ap[i + 1]; q++) + { + Tx a = Ax[q]; Ti a_i = Ai[q]; + if (s_mark[a_i] == j) + { //x[i] += s_col[a_i] * a * b; + fmadd(x[i], s_col[a_i], a * b); + } + } + } + } + } + + template + OuterprodOutput outerprod(const SparseMatrix& A, const SparseMatrix& S, const SparseMatrix& B) + { + OuterprodOutput o; + outerprod(o, A, S, B); + return o; + } +} diff --git a/src/external/PackedCSparse/projinv.h b/src/external/PackedCSparse/projinv.h new file mode 100644 index 00000000..a319a47e --- /dev/null +++ b/src/external/PackedCSparse/projinv.h @@ -0,0 +1,90 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis + +#pragma once +#include "SparseMatrix.h" + +// Problem: +// Compute inv(L L') restricted on L + +// Algorithm: +// We need to study this later as this is the bottleneck. +// Document it as a lyx. + +namespace PackedCSparse { + template + struct ProjinvOutput : SparseMatrix + { + TransposeOutput Lt; // sparsity pattern of the Lt + UniqueAlignedPtr w; // the row of L we are computing + UniquePtr c; // c[i] = index the last nonzero on column i in the current L + + void initialize(const SparseMatrix& L) + { + pcs_assert(L.initialized(), "chol: bad inputs."); + pcs_assert(L.n == L.m, "chol: dimensions mismatch."); + + // Copy the sparsity of L + SparseMatrix::operator=(std::move(L.clone())); + + // allocate workspaces + Ti n = L.n; + w.reset(pcs_aligned_new(n)); + c.reset(new Ti[n]); + Lt = transpose(L); + } + }; + + template + void projinv(ProjinvOutput& o, const SparseMatrix& L) + { + if (!o.initialized()) + o.initialize(L); + + Tx* Sx = o.x.get(); Ti n = o.n; + Ti* Li = L.i.get(), * Lp = L.p.get(); Tx* Lv = L.x.get(); + Ti* Lti = o.Lt.i.get(), * Ltp = o.Lt.p.get(); + Tx* w = o.w.get(); + Ti* c = o.c.get(); + Tx T0 = Tx(0), T1 = Tx(1); + + for (Ti k = 0; k < n; k++) + c[k] = Lp[k + 1] - 1; + + for (Ti k = n - 1; k != -1; k--) + { + for (Ti p = Lp[k] + 1; p < Lp[k + 1]; p++) + w[Li[p]] = Sx[p]; + + Tx sum = T1 / Lv[Lp[k]]; + for (Ti p = Ltp[k + 1] - 1; p != Ltp[k] - 1; p--) + { + Ti i = Lti[p], Lpi = Lp[i]; + + for (Ti q = Lp[i + 1] - 1; q != Lpi; q--) + fnmadd(sum, Lv[q], w[Li[q]]); + //sum -= Lv[q] * w[Li[q]]; + + sum = sum / Lv[Lpi]; + w[i] = sum; + Sx[c[i]] = sum; + c[i]--; + sum = T0; + } + } + } + + template + ProjinvOutput projinv(const SparseMatrix& L) + { + ProjinvOutput o; + projinv(o, L); + return o; + } +} diff --git a/src/external/PackedCSparse/qd/COPYING b/src/external/PackedCSparse/qd/COPYING new file mode 100644 index 00000000..a20ad70e --- /dev/null +++ b/src/external/PackedCSparse/qd/COPYING @@ -0,0 +1,16 @@ +This work was supported by the Director, Office of Science, Division +of Mathematical, Information, and Computational Sciences of the +U.S. Department of Energy under contract numbers DE-AC03-76SF00098 and +DE-AC02-05CH11231. + +Copyright (c) 2003-2009, The Regents of the University of California, +through Lawrence Berkeley National Laboratory (subject to receipt of +any required approvals from U.S. Dept. of Energy) All rights reserved. + +By downloading or using this software you are agreeing to the modified +BSD license that is in file "BSD-LBNL-License.doc" in the main ARPREC +directory. If you wish to use the software for commercial purposes +please contact the Technology Transfer Department at TTD@lbl.gov or +call 510-286-6457." + + diff --git a/src/external/PackedCSparse/qd/Makefile b/src/external/PackedCSparse/qd/Makefile new file mode 100644 index 00000000..791e92b9 --- /dev/null +++ b/src/external/PackedCSparse/qd/Makefile @@ -0,0 +1,15 @@ +QD_CPPFLAGS=$(CPPFLAGS) -I$(R_INCLUDE_DIR) -march=native + +QD_SOURCES= bits.cc c_dd.cc c_qd.cc dd_const.cc dd_real.cc fpu.cc \ + qd_const.cc qd_real.cc util.cc + +QD_OBJECTS=$(QD_SOURCES:.cc=.o) + +libqd.a: $(QD_OBJECTS) + $(AR) rc libqd.a $(QD_OBJECTS) + +.cc.o: + $(CC) $(CFLAGS) $(CPICFLAGS) $(QD_CPPFLAGS) -c $< -o $@ + +clean: + rm -rf $(QD_OBJECTS) libqd.a diff --git a/src/external/PackedCSparse/qd/NEWS b/src/external/PackedCSparse/qd/NEWS new file mode 100644 index 00000000..f32a7575 --- /dev/null +++ b/src/external/PackedCSparse/qd/NEWS @@ -0,0 +1,181 @@ +Changes for 2.3.22 + Made changes suggested by Vasiliy Sotnikov + +Changes for 2.3.21 + Changed renorm in include/qd/qd_inline.h + +Changes for 2.3.20 + added #include to quadt_test.cpp + changed references to 2.3.20 from 2.3.18 + +Changes for 2.3.19 + - Updated qd_real.cpp and dd_real.cpp to fix a buffer overflow problem. + +Changes for 2.3.18 + - Updated qd_real.cpp and dd_real.cpp to fix a problem in output. + +Changes for 2.3.17 + - updated qd_real.cpp, to fix a problem with improper treatment of + negative arguments in nroot. + +Changes for 2.3.16 + - Updated dd_real.cpp, to fix a problem with inaccurate values of + tanh for small arguments. + +Changes for 2.3.15 + - Updated qd_real.cpp, to fix a problem with static definitions. + +Changes for 2.3.14 + - Updated autoconfig (replaced config.sub and config.guess) + +Changes for 2.3.7 + - Fixed bug in to_digits where digits larger than 10 + where output occasionally. + +Changes for 2.3.6 + - Added fmod (C++) and mod (Fortran) functions. + +Changes for 2.3.5 + - Fixed bug in division of qd_real by dd_real. + - Fixed bug in ddoutc (Fortran ddmod.f). + - Now compiles with g++ 4.3. + - Distribute tests/coeff.dat. + +Changes for 2.3.4 + - Fixed bug in Makefile for cygwin / mingw systems. + +Changes for 2.3.3 + - Fixed bug in atan2. + +Changes for 2.3.2 + - Fixed bug in sin / cos / sincos where too much accuracy was + lost for (moderately) large angles. + - Use fused-multiply add intrinsics on IA-64 platforms if + compiled by Intel compiler. + - Fixed bug in c_dd_write and c_qd_write. + - Fixed bug were qdext.mod was not being installed. + +Changes for 2.3.1 + - Fixed bug in sincos and cos_taylor. This affected the result + of trigonometric functions in some cases. + +Changes for 2.3.0 + This is a fairly significant change, breaking API compatibility. + - Moved C++ main entry in libqdmod.a to libqd_f_main.a. + This allows to link Fortran code using QD with custom + C++ main function. Pure Fortran code will need to be linked + with qd_f_main library in addition to qdmod and qd library. + - Constructors accepting pointers made explicit. + - Fortran routines labeled as elemental or pure, where appropriate. + - Write() is now to_string(), and now takes a single fmtflag. + - dd_real addition and multiplication made commutative. + - dd_real now represented as array of two doubles, instead of + two discrete scalars. + - New Fortran generic routines to read / write, operations with + complex and integers. + - Improved exp, sin, and cos functions. + - Removed unused constants and obscure constants only used internally + from public interface. + +Changes for 2.2.6 + - Fixed bug in mixed precision multiplication: qd_real * dd_real. + +Changes for 2.2.5 + - Bug fix in qd_real addition when --enable-ieee-add is specified. + - Debugging routines dump and dump_bits updated; + dump_components removed (just use dump). + - Fortran support for Fortran strings. Use character arrays instead. + - Return NaN under error conditions. + - Added _inf constant; exp now returns Inf when argument is too large. + - Output formatting fixes for Inf and NaNs. + - Added more real-complex mixed arithmetic routines in Fortran + interface. + +Changes for 2.2.4 + - Added random_number interface for Fortran modules. + - Use slightly more conservative values for eps. + - Avoid unnecessary overflow near overflow threshold. + - Added radix, digits, min/maxexponent, range, and precision + intrinsics to Fortran interface. + - Added safe_max (C++) and safe_huge (Fortran). + +Changes for 2.2.3 + - Fix sign function bug in Fortran modules. + +Changes for 2.2.2 + - Do not bother setting uninitialized dd_real and qd_reals to zero. + - Use clock_gettime if available for timing. + - Fortran I/O should be more consistent with C++ version. + - fpu.h is now included with dd_real.h. + +Changes for 2.2.1 + - Minor fixes when printing in scientific format. + - Change search order of C++ compilers in Apple systems to avoid + case insensitive filesystems. + +Changes for 2.2.0 + - Added F95 interface for complex types. + - Renamed dd.h and qd.h to dd_real.h and qd_real.h, respectively. + This will break older C++ code using 2.1.x library, but it was + conflicting with QuickDraw libraries on Macs. (Hence the version + bump to 2.2). + - Removed overloaded typecast operators for int and double. These + permitted *automatic* conversion of dd_real/qd_real to double or + int, which is somewhat dangerous. Instead to_int and to_double + routines are added. + +Changes for 2.1.214 + - Updated pslq_test. + - Implmented numeric_limits<>. + - Better polyroot. + - Added isnan, isfinite, isinf functions. + - Fix / improve input output functions. + - Drop Microsoft Visual C++ 6.0 support. + - More efficient dd_real::sin. + +Changes for 2.1.213 + - Support for x86_64 platforms. + - Drop libtool support for now. + +Changes for 2.1.212 + - Support for pathCC compiler. + - Added accurate and sloppy versions of add / sub / mul / div avaialble. + - Added autodetection of fma functions. + +Changes for 2.1 (2003-12-30) + - added automake scripts. + - use libtool to compile / link and build libraries. + - supports standard installation targets (make install). + - support for Intel C++ compilers (icc / ecc). + - Fortran programs are now linked by C++ compiler. + - support for building shared library. + - minor bug fixes. + +Changes for 2.0 (2003-12-08) + - all header files are in "include/qd" directory. + - added autoconf scripts. + - added config.h and qd_config.h to store configuration information. + - renamed x86_* routines to fpu_* routines. + - added separate Fortran interface (f_* routines). + - options for sloppy multiply and sloppy divison separated. + - fixed C interface to be actually in C syntax. + - updated / added README, AUTHORS, NEWS, and LICENSE files. + - minor bug fixes. + +Changes for 1.2 (2003-12-04) + - added "dist-clean" target in Makefile + - initialize dd and qd variables to zero + - increases tolerance for qd / dd tests + - changed .cc extension to .cpp + - updated README, COPYING, and NEWS files + - added ChangeLog file + - fixed bug in '-all' flag in qd_test + - minor bug fixes + +Changes for 1.1 (2002-10-22) + - added "Changes" file (this file) + - fixed to + - fixed constant (3/4) * pi + - fixed exp(x) to return zero if x is a large negative number + - removed "docs" target in Makefile + diff --git a/src/external/PackedCSparse/qd/README b/src/external/PackedCSparse/qd/README new file mode 100644 index 00000000..8a085d72 --- /dev/null +++ b/src/external/PackedCSparse/qd/README @@ -0,0 +1,437 @@ +Quad Double computation package +Copyright (C) 2003-2019 +================================================ + +Revision date: 26 February 2019 + +Authors: +Yozo Hida U.C. Berkeley yozo@cs.berkeley.edu +Xiaoye S. Li Lawrence Berkeley Natl Lab xiaoye@nersc.gov +David H. Bailey Lawrence Berkeley Natl Lab dhbailey@lbl.gov + +C++ usage guide: +Alex Kaiser Lawrence Berkeley Natl Lab adkaiser@lbl.gov + +This work was supported by the Director, Office of Science, Division of Mathematical, +Information, and Computational Sciences of the U.S. Department of Energy under contract +number DE-AC02-05CH11231. + +This work was supported by the Director, Office of Science, Division of Mathematical, +Information, and Computational Sciences of the U.S. Department of Energy under contract +numbers DE-AC03-76SF00098 and DE-AC02-05CH11231. + +*** IMPORTANT NOTES: + +See the file COPYING for modified BSD license information. +See the file INSTALL for installation instructions. +See the file NEWS for recent revisions. +See the file docs/qd.pdf for additional information. + +Outline: + +I. Introduction +II. Installation of package, and linking and executing user files +III. C++ Usage +IV. Fortran Usage +V. Note on x86-Based Processors (MOST systems in use today) + + +I. Introduction + +This package provides numeric types of twice the precision of IEEE double (106 mantissa +bits, or approximately 32 decimal digits) and four times the precision of IEEE double (212 +mantissa bits, or approximately 64 decimal digits). Due to features such as operator and +function overloading, these facilities can be utilized with only minor modifications to +conventional C++ and Fortran-90 programs. + +In addition to the basic arithmetic operations (add, subtract, multiply, divide, square root), +common transcendental functions such as the exponential, logarithm, trigonometric and +hyperbolic functions are also included. A detailed description of the algorithms used is +available in the docs subdirectory (see docs/qd.pdf). An abridged version of this paper, +which was presented at the ARITH-15 conference, is also available at: + +Yozo Hida, Xiaoye S. Li and David H. Bailey, "Algorithms for quad-double precision + floating point arithmetic," 15th IEEE Symposium on Computer Arithmetic, IEEE Computer + Society, 2001, pg. 155-162, available at + https://www.davidhbailey.com/dhbpapers/arith15.pdf. + + +II. Installation of package, and linking and executing user files + +A. Directories + +There are six directories and several files in the main directory of this distribution, +described below + +src This contains the source code of the quad-double and double-double + library. This source code does not include inline functions, + which are found in the header files in the include directory. + +include This directory contains the header files. + +fortran This directory contains Fortran-90 files. + +tests This directory contains some simple (not comprehensive) tests. + +docs This directory contains a technical paper describing the algorithms. + +config This directory contains various scripts used by the configure + script and the Makefile. + +Please note that all commands refer to a Unix-type environment such as Mac OSX or Ubuntu +Linux using the bash shell. + +B. Installing and building + +To build the library, first run the included configure script by typing + + ./configure + +This script automatically generates makefiles for building the library and selects compilers +and necessary flags and libraries to include. If the user wishes to specify compilers or flags +they may use the following options. + + CXX C++ compiler to use + CXXFLAGS C++ compiler flags to use + CC C compiler to use (for C demo program) + CFLAGS C compiler flags to use (for C demo program) + FC Fortran 90 compiler + FCFLAGS Fortran 90 compiler flags to use + FCLIBS Fortran 90 libraries needed to link with C++ code. + +For example, if one is using GNU compilers, configure with: + + ./configure CXX=g++ FC=gfortran + +The Fortran and C++ compilers must produce compatible binaries. On some systems +additional flags must be included to ensure that portions of the +library are not built with 32 and 64 bit object files. For example, on +64-Bit Mac OSX 10.6 (Snow Leopard) and 10.7 (Lion) the correct +configure line using GNU compilers is: + + ./configure CXX=g++ FC=gfortran FCFLAGS=-m64 + +To build the library, simply type + + make + +and the automatically generated makefiles will build the library including archive files. + +To allow for easy linking to the library, the user may also wish to +install the archive files to a standard place. To do this type: + + make install + +This will also build the library if it has not already been built. Many systems, including Mac +and Ubuntu Linux systems, require administrator privileges to install the library at such +standard places. On such systems, one may type: + + sudo make install + +instead if one has sufficient access. + +The directory "tests" contains programs for high precision quadrature and integer-relation +detection. To build such programs, type: + + make demo + +in the "tests" directory. + +C. Linking and executing user programs + +C++ source files: + +The simplest way to link to the library is to install it to a standard place as described above, and use the -l option. For example + + g++ compileExample.cpp -o compileExample -l qd + +One can also use this method to build with make. A file called "compileExample.cpp" and the +associated makefile "makeCompileExample" illustrate the process. + +A third alternative is to use a link script. If one types "make demo" in the test +directory, the output produced gives guidance as to how to build the files. By +following the structure of the compiling commands one may copy the appropriate portions, +perhaps replacing the filename with an argument that the user can include at link time. +An example of such a script is as follows: + +g++ -DHAVE_CONFIG_H -I.. -I../include -I../include -O2 -MT $1.o -MD -MP -MF +.deps/qd_test.Tpo -c -o $1.o $1.cpp +mv -f .deps/$1.Tpo .deps/$1.Po +g++ -O2 -o $1 $1.o ../src/libqd.a -lm + +To use the link script, make it executable (by typing "chmod +x link.scr) and then type: + +./link.scr compileExample + +Note that the file extension is not included because the script handles all extensions, +expecting the source file to have the extension ".cpp". + +Fortran-90 source files: + +Similarly, a script for compiling fortran programs may be constructed as follows. +In the fortran directory, type "make quadtsq". This compiles the Fortran program +tquadts.f, links with all necessary library files, and produces the executable +"quadts". As this is being done, all flags and linked libraries are displayed. +For instance, on a 2019-era Apple Macintosh system, where the library was installed +as above with g++ for C++ and gfortran for Fortran-90, the following is output: + +gfortran -m64 -ffree-form -c -o tquadtsq.o tquadtsq.f +/bin/sh ../libtool --tag=CXX --mode=link g++ -O2 -o quadtsq tquadtsq.o second.o +libqdmod.la libqd_f_main.la ../src/libqd.la +-L/usr/local/gfortran/lib/gcc/x86_64-apple-darwin16/6.3.0 +-L/usr/local/gfortran/lib/gcc/x86_64-apple-darwin16/6.3.0/../../.. +-lgfortran -lquadmath -lm -lm + +Thus a general compile-link script is the following: + +gfortran -m64 -ffree-form -c -o $1.o $1.f90 +/bin/sh ../libtool --tag=CXX --mode=link g++ -O2 -o $1 $1.o second.o \ + libqdmod.la libqd_f_main.la ../src/libqd.la \ + -L/usr/local/gfortran/lib/gcc/x86_64-apple-darwin16/6.3.0 \ + -L/usr/local/gfortran/lib/gcc/x86_64-apple-darwin16/6.3.0/../../.. \ + -lgfortran -lquadmath -lm -lm + +Note that if the .f90 suffix is used for Fortran-90 source files, the +-ffree-form flag may be omitted, but the first line above should end with +"$1.f90" (as shown above). After forming the script, name file, "complink.scr", +and then type "chmod +x complink.scr". To use this script compile and link a +program named "prog.f90", type "./complink.scr prog". + + +III. C++ usage + +As much as possible, operator overloading is included to make basic programming as much +like using standard typed floating-point arithmetic. Changing many codes should be as +simple as changing type statements and a few other lines. + +i. Constructors + +To create dd_real and qd_real variables calculated to the proper precision, one must use +care to use the included constructors properly. Many computations in which variables are +not explicitly typed to multiple-precision may be evaluated with double-precision +arithmetic. The user must take care to ensure that this does not cause errors. In particular, +an expression such as 1.0/3.0 will be evaluated to double precision before assignment or +further arithmetic. Upon assignment to a multi-precision variable, the value will be zero +padded. This problem is serious and potentially difficult to debug. To avoid this, use the +included constructors to force arithmetic to be performed in the full precision requested. + +For a table with descriptions, please see the documentation file qd.pdf in the docs directory. + +ii. Included functions and Constants + +Supported functions include assignment operators, comparisons, arithmetic and +assignment operators, and increments for integer types. Standard C math functions such as +exponentiation, trigonometric, logarithmic, hyperbolic, exponential and rounding functions +are included. As in assignment statements, one must be careful with implied typing of +constants when using these functions. Many codes need particular conversion for the power +function, which is frequently used with constants that must be explicitly typed for multi- +precision codes. + +Many constants are included, which are global and calculated upon initialization. The +following list of constants is calculated for both the dd_real and qd_real classes separately. +Use care to select the correct value. + +For a table with descriptions, please see the included file README.pdf + +ii. Conversion of types + +Static casts may be used to convert constants between types. One may also use constructors +to return temporary multi-precision types within expressions, but should be careful, as this +will waste memory if done repeatedly. For example: + + qd_real y ; + y = sin( qd_real(4.0) / 3.0 ) ; + +C-style casts may be used, but are not recommended. Dynamic and reinterpret casts are +not supported and should be considered unreliable. Casting between multi-precision and +standard precision types can be dangerous, and care must be taken to ensure that programs +are working properly and accuracy has not degraded by use of a misplaced type-conversion. + +D. Available precision, Control of Precision Levels, + +The library provides greatly extended accuracy when compared to standard double +precision. The type dd_real provides for 106 mantissa bits, or about 32 decimal digits. The +type qd_real provides for 212 mantissa bits, or about 64 decimal digits. + +Both the dd_real and qd_real values use the exponent from the highest double-precision +word for arithmetic, and as such do not extend the total range of values available. That +means that the maximum absolute value for either data type is the same as that of double- +precision, or approximately 10^308. The precision near this range, however, is greatly +increased. + +E. I/O + +The standard I/O stream routines have been overloaded to be fully compatible with all +included data types. One may need to manually reset the precision of the stream to obtain +full output. For example, if 60 digits are desired, use: + +cout.precision(60) ; + +When reading values using cin, each input numerical value must start on a separate +line. Two formats are acceptable: + + 1. Write the full constant + 3. Mantissa e exponent + +Here are three valid examples: + + 1.1 + 3.14159 26535 89793 + 123.123123e50 + +When read using cin, these constants will be converted using full multi-precision accuracy. + + +IV. Fortran-90 Usage + +NEW (2007-01-10): The Fortran translation modules now support the complex datatypes +"dd_complex" and "qd_complex". + +Since the quad-double library is written in C++, it must be linked in with a C++ compiler (so +that C++ specific things such as static initializations are correctly handled). Thus the main +program must be written in C/C++ and call the Fortran 90 subroutine. The Fortran 90 +subroutine should be called f_main. + +Here is a sample Fortran-90 program, equivalent to the above C++ program: + + subroutine f_main + use qdmodule + implicit none + type (qd_real) a, b + a = 1.d0 + b = cos(a)**2 + sin(a)**2 - 1.d0 + call qdwrite(6, b) + stop + end subroutine + +This verifies that cos^2(1) + sin^2(1) = 1 to 64 digit accuracy. + +Most operators and generic function references, including many mixed-mode type +combinations with double-precision (ie real*8), have been overloaded (extended) to work +with double-double and quad-double data. It is important, however, that users keep in +mind the fact that expressions are evaluated strictly according to conventional Fortran +operator precedence rules. Thus some subexpressions may be evaluated only to 15-digit +accuracy. For example, with the code + + real*8 d1 + type (dd_real) t1, t2 + ... + t1 = cos (t2) + d1/3.d0 + +the expression d1/3.d0 is computed to real*8 accuracy only (about 15 digits), since both d1 +and 3.d0 have type real*8. This result is then converted to dd_real by zero extension before +being added to cos(t2). So, for example, if d1 held the value 1.d0, then the quotient d1/3.d0 +would only be accurate to 15 digits. If a fully accurate double-double quotient is required, +this should be written: + + real*8 d1 + type (dd_real) t1, t2 + ... + t1 = cos (t2) + ddreal (d1) / 3.d0 + +which forces all operations to be performed with double-double arithmetic. + +Along this line, a constant such as 1.1 appearing in an expression is evaluated only to real*4 +accuracy, and a constant such as 1.1d0 is evaluated only to real*8 accuracy (this is +according to standard Fortran conventions). If full quad-double accuracy is required, for +instance, one should write + + type (qd_real) t1 + ... + t1 = '1.1' + +The quotes enclosing 1.1 specify to the compiler that the constant is to be converted to +binary using quad-double arithmetic, before assignment to t1. Quoted constants may only +appear in assignment statements such as this. + +To link a Fortran-90 program with the C++ qd library, it is recommended to link with the +C++ compiler used to generate the library. The Fortran 90 interface (along with a C-style +main function calling f_main) is found in qdmod library. The qd-config script installed +during "make install" can be used to determine which flags to pass to compile and link your +programs: + + "qd-config --fcflags" displays compiler flags needed to compile your Fortran files. + "qd-config --fclibs" displays linker flags needed by the C++ linker to link in all the +necessary libraries. + +A sample Makefile that can be used as a template for compiling Fortran programs using +quad-double library is found in fortran/Makefile.sample. + +F90 functions defined with dd_real arguments: + Arithmetic: + - * / ** + Comparison tests: == < > <= >= /= + Others: abs, acos, aint, anint, asin, atan, atan2, cos, cosh, dble, erf, + erfc, exp, int, log, log10, max, min, mod, ddcsshf (cosh and sinh), + ddcssnf (cos and sin), ddranf (random number generator in (0,1)), + ddnrtf (n-th root), sign, sin, sinh, sqr, sqrt, tan, tanh + +Similar functions are provided for qd_real arguments with function names qdcsshf, +qdcssnf, qdranf and qdnrtf instead of the names in the list above. + +Input and output of double-double and quad-double data is done using the special +subroutines ddread, ddwrite, qdread and qdwrite. The first argument of these subroutines +is the Fortran I/O unit number, while additional arguments (as many as needed, up to 9 +arguments) are scalar variables or array elements of the appropriate type. Example: + + integer n + type (qd_real) qda, qdb, qdc(n) + ... + call qdwrite (6, qda, qdb) + do j = 1, n + call qdwrite (6, qdc(j)) + enddo + +Each input values must be on a separate line, and may include D or E exponents. Double- +double and quad-double constants may also be specified in assignment statements by +enclosing them in quotes, as in + + ... + type (qd_real) pi + ... + pi = +"3.14159265358979323846264338327950288419716939937510582097494459230" + ... + +Sample Fortran-90 programs illustrating some of these features are provided in the f90 +subdirectory. + + +V. Note on x86-Based Processors (MOST systems in use today) + +The algorithms in this library assume IEEE double precision floating point arithmetic. Since +Intel x86 processors have extended (80-bit) floating point registers, some compilers, +albeit a declining number, may generate commands for the 80-bit instructions. The QD +library does NOT work correctly with 80-bit instructions, so if one's code does not operate +correctly, this may be the reason. To avoid such problems, the round-to-double flag must be +enabled in the control word of the FPU for this library to function properly. The following +functions contains appropriate code to facilitate manipulation of this flag. For non-x86 +systems these functions do nothing (but still exist). + +fpu_fix_start This turns on the round-to-double bit in the control word. +fpu_fix_end This restores the control flag. + +These functions must be called by the main program, as follows: + + int main() { + unsigned int old_cw; + fpu_fix_start(&old_cw); + + ... user code using quad-double library ... + + fpu_fix_end(&old_cw); + } + +A Fortran-90 example is the following: + + subroutine f_main + use qdmodule + implicit none + integer*4 old_cw + + call f_fpu_fix_start(old_cw) + + ... user code using quad-double library ... + + call f_fpu_fix_end(old_cw) + end subroutine + diff --git a/src/external/PackedCSparse/qd/bits.cc b/src/external/PackedCSparse/qd/bits.cc new file mode 100644 index 00000000..4eaf9a26 --- /dev/null +++ b/src/external/PackedCSparse/qd/bits.cc @@ -0,0 +1,85 @@ +/* + * src/bits.cc + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2001 + * + * Defines various routines to get / set bits of a IEEE floating point + * number. This used by the library for debugging purposes. + */ + +#include +#include +#include +#include + +#include "qd_config.h" +#include "inline.h" +#include "bits.h" + +#ifdef HAVE_IEEEFP_H +#include +#endif + +using std::setw; + +int get_double_expn(double x) { + if (x == 0.0) + return INT_MIN; + if (QD_ISINF(x) || QD_ISNAN(x)) + return INT_MAX; + + double y = std::abs(x); + int i = 0; + if (y < 1.0) { + while (y < 1.0) { + y *= 2.0; + i++; + } + return -i; + } else if (y >= 2.0) { + while (y >= 2.0) { + y *= 0.5; + i++; + } + return i; + } + return 0; +} + +void print_double_info(std::ostream &os, double x) { + std::streamsize old_prec = os.precision(19); + std::ios_base::fmtflags old_flags = os.flags(); + os << std::scientific; + + os << setw(27) << x << ' '; + if (QD_ISNAN(x) || QD_ISINF(x) || (x == 0.0)) { + os << " "; + } else { + + x = std::abs(x); + int expn = get_double_expn(x); + double d = std::ldexp(1.0, expn); + os << setw(5) << expn << " "; + for (int i = 0; i < 53; i++) { + if (x >= d) { + x -= d; + os << '1'; + } else + os << '0'; + d *= 0.5; + } + + if (x != 0.0) { + // should not happen + os << " +trailing stuff"; + } + } + + os.precision(old_prec); + os.flags(old_flags); +} + diff --git a/src/external/PackedCSparse/qd/bits.h b/src/external/PackedCSparse/qd/bits.h new file mode 100644 index 00000000..58570aac --- /dev/null +++ b/src/external/PackedCSparse/qd/bits.h @@ -0,0 +1,32 @@ +/* + * include/bits.h + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2001 + * + * This file defines various routines to get / set bits of a IEEE floating + * point number. This is used by the library for debugging purposes. + */ + +#ifndef _QD_BITS_H +#define _QD_BITS_H + +#include +#include "qd_config.h" + +/* Returns the exponent of the double precision number. + Returns INT_MIN is x is zero, and INT_MAX if x is INF or NaN. */ +int get_double_expn(double x); + +/* Prints + SIGN EXPN MANTISSA + of the given double. If x is NaN, INF, or Zero, this + prints out the strings NaN, +/- INF, and 0. */ +void print_double_info(std::ostream &os, double x); + + +#endif /* _QD_BITS_H */ + diff --git a/src/external/PackedCSparse/qd/c_dd.cc b/src/external/PackedCSparse/qd/c_dd.cc new file mode 100644 index 00000000..0a7c12ac --- /dev/null +++ b/src/external/PackedCSparse/qd/c_dd.cc @@ -0,0 +1,314 @@ +/* + * src/c_dd.cc + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2001 + * + * Contains the C wrapper functions for double-double precision arithmetic. + * This can be used from Fortran code. + */ +#include + +#include "qd_config.h" +#include "dd_real.h" +#include "c_dd.h" + +#define TO_DOUBLE_PTR(a, ptr) ptr[0] = a.x[0]; ptr[1] = a.x[1]; + +extern "C" { + +/* add */ +void c_dd_add(const double *a, const double *b, double *c) { + dd_real cc; + cc = dd_real(a) + dd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_dd_add_dd_d(const double *a, double b, double *c) { + dd_real cc; + cc = dd_real(a) + b; + TO_DOUBLE_PTR(cc, c); +} +void c_dd_add_d_dd(double a, const double *b, double *c) { + dd_real cc; + cc = a + dd_real(b); + TO_DOUBLE_PTR(cc, c); +} + + +/* sub */ +void c_dd_sub(const double *a, const double *b, double *c) { + dd_real cc; + cc = dd_real(a) - dd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_dd_sub_dd_d(const double *a, double b, double *c) { + dd_real cc; + cc = dd_real(a) - b; + TO_DOUBLE_PTR(cc, c); +} +void c_dd_sub_d_dd(double a, const double *b, double *c) { + dd_real cc; + cc = a - dd_real(b); + TO_DOUBLE_PTR(cc, c); +} + + +/* mul */ +void c_dd_mul(const double *a, const double *b, double *c) { + dd_real cc; + cc = dd_real(a) * dd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_dd_mul_dd_d(const double *a, double b, double *c) { + dd_real cc; + cc = dd_real(a) * b; + TO_DOUBLE_PTR(cc, c); +} +void c_dd_mul_d_dd(double a, const double *b, double *c) { + dd_real cc; + cc = a * dd_real(b); + TO_DOUBLE_PTR(cc, c); +} + + +/* div */ +void c_dd_div(const double *a, const double *b, double *c) { + dd_real cc; + cc = dd_real(a) / dd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_dd_div_dd_d(const double *a, double b, double *c) { + dd_real cc; + cc = dd_real(a) / b; + TO_DOUBLE_PTR(cc, c); +} +void c_dd_div_d_dd(double a, const double *b, double *c) { + dd_real cc; + cc = a / dd_real(b); + TO_DOUBLE_PTR(cc, c); +} + + +/* copy */ +void c_dd_copy(const double *a, double *b) { + b[0] = a[0]; + b[1] = a[1]; +} +void c_dd_copy_d(double a, double *b) { + b[0] = a; + b[1] = 0.0; +} + + +void c_dd_sqrt(const double *a, double *b) { + dd_real bb; + bb = sqrt(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_sqr(const double *a, double *b) { + dd_real bb; + bb = sqr(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_dd_abs(const double *a, double *b) { + dd_real bb; + bb = abs(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_dd_npwr(const double *a, int n, double *b) { + dd_real bb; + bb = npwr(dd_real(a), n); + TO_DOUBLE_PTR(bb, b); +} + +void c_dd_nroot(const double *a, int n, double *b) { + dd_real bb; + bb = nroot(dd_real(a), n); + TO_DOUBLE_PTR(bb, b); +} + +void c_dd_nint(const double *a, double *b) { + dd_real bb; + bb = nint(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_aint(const double *a, double *b) { + dd_real bb; + bb = aint(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_floor(const double *a, double *b) { + dd_real bb; + bb = floor(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_ceil(const double *a, double *b) { + dd_real bb; + bb = ceil(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_dd_log(const double *a, double *b) { + dd_real bb; + bb = log(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_log10(const double *a, double *b) { + dd_real bb; + bb = log10(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_exp(const double *a, double *b) { + dd_real bb; + bb = exp(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_dd_sin(const double *a, double *b) { + dd_real bb; + bb = sin(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_cos(const double *a, double *b) { + dd_real bb; + bb = cos(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_tan(const double *a, double *b) { + dd_real bb; + bb = tan(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_dd_asin(const double *a, double *b) { + dd_real bb; + bb = asin(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_acos(const double *a, double *b) { + dd_real bb; + bb = acos(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_atan(const double *a, double *b) { + dd_real bb; + bb = atan(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_dd_atan2(const double *a, const double *b, double *c) { + dd_real cc; + cc = atan2(dd_real(a), dd_real(b)); + TO_DOUBLE_PTR(cc, c); +} + +void c_dd_sinh(const double *a, double *b) { + dd_real bb; + bb = sinh(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_cosh(const double *a, double *b) { + dd_real bb; + bb = cosh(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_tanh(const double *a, double *b) { + dd_real bb; + bb = tanh(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_dd_asinh(const double *a, double *b) { + dd_real bb; + bb = asinh(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_acosh(const double *a, double *b) { + dd_real bb; + bb = acosh(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_dd_atanh(const double *a, double *b) { + dd_real bb; + bb = atanh(dd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_dd_sincos(const double *a, double *s, double *c) { + dd_real ss, cc; + sincos(dd_real(a), ss, cc); + TO_DOUBLE_PTR(ss, s); + TO_DOUBLE_PTR(cc, c); +} + +void c_dd_sincosh(const double *a, double *s, double *c) { + dd_real ss, cc; + sincosh(dd_real(a), ss, cc); + TO_DOUBLE_PTR(ss, s); + TO_DOUBLE_PTR(cc, c); +} + +void c_dd_read(const char *s, double *a) { + dd_real aa(s); + TO_DOUBLE_PTR(aa, a); +} + +void c_dd_swrite(const double *a, int precision, char *s, int len) { + dd_real(a).write(s, len, precision); +} + +void c_dd_write(const double *a) { + std::cout << dd_real(a).to_string(dd_real::_ndigits) << std::endl; +} + +void c_dd_neg(const double *a, double *b) { + b[0] = -a[0]; + b[1] = -a[1]; +} + +void c_dd_rand(double *a) { + dd_real aa; + aa = ddrand(); + TO_DOUBLE_PTR(aa, a); +} + +void c_dd_comp(const double *a, const double *b, int *result) { + dd_real aa(a), bb(b); + if (aa < bb) + *result = -1; + else if (aa > bb) + *result = 1; + else + *result = 0; +} + +void c_dd_comp_dd_d(const double *a, double b, int *result) { + dd_real aa(a), bb(b); + if (aa < bb) + *result = -1; + else if (aa > bb) + *result = 1; + else + *result = 0; +} + +void c_dd_comp_d_dd(double a, const double *b, int *result) { + dd_real aa(a), bb(b); + if (aa < bb) + *result = -1; + else if (aa > bb) + *result = 1; + else + *result = 0; +} + +void c_dd_pi(double *a) { + TO_DOUBLE_PTR(dd_real::_pi, a); +} + +} diff --git a/src/external/PackedCSparse/qd/c_dd.h b/src/external/PackedCSparse/qd/c_dd.h new file mode 100644 index 00000000..310162ed --- /dev/null +++ b/src/external/PackedCSparse/qd/c_dd.h @@ -0,0 +1,98 @@ +/* + * include/c_dd.h + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2001 + * + * Contains C wrapper function prototypes for double-double precision + * arithmetic. This can also be used from fortran code. + */ +#ifndef _QD_C_DD_H +#define _QD_C_DD_H + +#include "qd_config.h" +#include "fpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* add */ +void c_dd_add(const double *a, const double *b, double *c); +void c_dd_add_d_dd(double a, const double *b, double *c); +void c_dd_add_dd_d(const double *a, double b, double *c); + +/* sub */ +void c_dd_sub(const double *a, const double *b, double *c); +void c_dd_sub_d_dd(double a, const double *b, double *c); +void c_dd_sub_dd_d(const double *a, double b, double *c); + +/* mul */ +void c_dd_mul(const double *a, const double *b, double *c); +void c_dd_mul_d_dd(double a, const double *b, double *c); +void c_dd_mul_dd_d(const double *a, double b, double *c); + +/* div */ +void c_dd_div(const double *a, const double *b, double *c); +void c_dd_div_d_dd(double a, const double *b, double *c); +void c_dd_div_dd_d(const double *a, double b, double *c); + +/* copy */ +void c_dd_copy(const double *a, double *b); +void c_dd_copy_d(double a, double *b); + +void c_dd_sqrt(const double *a, double *b); +void c_dd_sqr(const double *a, double *b); + +void c_dd_abs(const double *a, double *b); + +void c_dd_npwr(const double *a, int b, double *c); +void c_dd_nroot(const double *a, int b, double *c); + +void c_dd_nint(const double *a, double *b); +void c_dd_aint(const double *a, double *b); +void c_dd_floor(const double *a, double *b); +void c_dd_ceil(const double *a, double *b); + +void c_dd_exp(const double *a, double *b); +void c_dd_log(const double *a, double *b); +void c_dd_log10(const double *a, double *b); + +void c_dd_sin(const double *a, double *b); +void c_dd_cos(const double *a, double *b); +void c_dd_tan(const double *a, double *b); + +void c_dd_asin(const double *a, double *b); +void c_dd_acos(const double *a, double *b); +void c_dd_atan(const double *a, double *b); +void c_dd_atan2(const double *a, const double *b, double *c); + +void c_dd_sinh(const double *a, double *b); +void c_dd_cosh(const double *a, double *b); +void c_dd_tanh(const double *a, double *b); + +void c_dd_asinh(const double *a, double *b); +void c_dd_acosh(const double *a, double *b); +void c_dd_atanh(const double *a, double *b); + +void c_dd_sincos(const double *a, double *s, double *c); +void c_dd_sincosh(const double *a, double *s, double *c); + +void c_dd_read(const char *s, double *a); +void c_dd_swrite(const double *a, int precision, char *s, int len); +void c_dd_write(const double *a); +void c_dd_neg(const double *a, double *b); +void c_dd_rand(double *a); +void c_dd_comp(const double *a, const double *b, int *result); +void c_dd_comp_dd_d(const double *a, double b, int *result); +void c_dd_comp_d_dd(double a, const double *b, int *result); +void c_dd_pi(double *a); + +#ifdef __cplusplus +} +#endif + +#endif /* _QD_C_DD_H */ diff --git a/src/external/PackedCSparse/qd/c_qd.cc b/src/external/PackedCSparse/qd/c_qd.cc new file mode 100644 index 00000000..fd50e922 --- /dev/null +++ b/src/external/PackedCSparse/qd/c_qd.cc @@ -0,0 +1,450 @@ +/* + * src/c_qd.cc + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2001 + * + * Contains C wrapper function for quad-double precision arithmetic. + * This can be used from fortran code. + */ +#include + +#include "qd_config.h" +#include "qd_real.h" +#include "c_qd.h" + +#define TO_DOUBLE_PTR(a, ptr) ptr[0] = a.x[0]; ptr[1] = a.x[1]; \ + ptr[2] = a.x[2]; ptr[3] = a.x[3]; + +extern "C" { + + + +/* add */ +void c_qd_add(const double *a, const double *b, double *c) { + qd_real cc; + cc = qd_real(a) + qd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_add_qd_dd(const double *a, const double *b, double *c) { + qd_real cc; + cc = qd_real(a) + dd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_add_dd_qd(const double *a, const double *b, double *c) { + qd_real cc; + cc = dd_real(a) + qd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_add_qd_d(const double *a, double b, double *c) { + qd_real cc; + cc = qd_real(a) + b; + TO_DOUBLE_PTR(cc, c); +} +void c_qd_add_d_qd(double a, const double *b, double *c) { + qd_real cc; + cc = a + qd_real(b); + TO_DOUBLE_PTR(cc, c); +} + + + +/* sub */ +void c_qd_sub(const double *a, const double *b, double *c) { + qd_real cc; + cc = qd_real(a) - qd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_sub_qd_dd(const double *a, const double *b, double *c) { + qd_real cc; + cc = qd_real(a) - dd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_sub_dd_qd(const double *a, const double *b, double *c) { + qd_real cc; + cc = dd_real(a) - qd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_sub_qd_d(const double *a, double b, double *c) { + qd_real cc; + cc = qd_real(a) - b; + TO_DOUBLE_PTR(cc, c); +} +void c_qd_sub_d_qd(double a, const double *b, double *c) { + qd_real cc; + cc = a - qd_real(b); + TO_DOUBLE_PTR(cc, c); +} + + + +/* mul */ +void c_qd_mul(const double *a, const double *b, double *c) { + qd_real cc; + cc = qd_real(a) * qd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_mul_qd_dd(const double *a, const double *b, double *c) { + qd_real cc; + cc = qd_real(a) * dd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_mul_dd_qd(const double *a, const double *b, double *c) { + qd_real cc; + cc = dd_real(a) * qd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_mul_qd_d(const double *a, double b, double *c) { + qd_real cc; + cc = qd_real(a) * b; + TO_DOUBLE_PTR(cc, c); +} +void c_qd_mul_d_qd(double a, const double *b, double *c) { + qd_real cc; + cc = a * qd_real(b); + TO_DOUBLE_PTR(cc, c); +} + + + +/* div */ +void c_qd_div(const double *a, const double *b, double *c) { + qd_real cc; + cc = qd_real(a) / qd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_div_qd_dd(const double *a, const double *b, double *c) { + qd_real cc; + cc = qd_real(a) / dd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_div_dd_qd(const double *a, const double *b, double *c) { + qd_real cc; + cc = dd_real(a) / qd_real(b); + TO_DOUBLE_PTR(cc, c); +} +void c_qd_div_qd_d(const double *a, double b, double *c) { + qd_real cc; + cc = qd_real(a) / b; + TO_DOUBLE_PTR(cc, c); +} +void c_qd_div_d_qd(double a, const double *b, double *c) { + qd_real cc; + cc = a / qd_real(b); + TO_DOUBLE_PTR(cc, c); +} + + + + +/* selfadd */ +void c_qd_selfadd(const double *a, double *b) { + qd_real bb(b); + bb += qd_real(a); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_selfadd_dd(const double *a, double *b) { + qd_real bb(b); + bb += dd_real(a); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_selfadd_d(double a, double *b) { + qd_real bb(b); + bb += a; + TO_DOUBLE_PTR(bb, b); +} + + + +/* selfsub */ +void c_qd_selfsub(const double *a, double *b) { + qd_real bb(b); + bb -= qd_real(a); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_selfsub_dd(const double *a, double *b) { + qd_real bb(b); + bb -= dd_real(a); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_selfsub_d(double a, double *b) { + qd_real bb(b); + bb -= a; + TO_DOUBLE_PTR(bb, b); +} + + + +/* selfmul */ +void c_qd_selfmul(const double *a, double *b) { + qd_real bb(b); + bb *= qd_real(a); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_selfmul_dd(const double *a, double *b) { + qd_real bb(b); + bb *= dd_real(a); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_selfmul_d(double a, double *b) { + qd_real bb(b); + bb *= a; + TO_DOUBLE_PTR(bb, b); +} + + + +/* selfdiv */ +void c_qd_selfdiv(const double *a, double *b) { + qd_real bb(b); + bb /= qd_real(a); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_selfdiv_dd(const double *a, double *b) { + qd_real bb(b); + bb /= dd_real(a); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_selfdiv_d(double a, double *b) { + qd_real bb(b); + bb /= a; + TO_DOUBLE_PTR(bb, b); +} + + + +/* copy */ +void c_qd_copy(const double *a, double *b) { + b[0] = a[0]; + b[1] = a[1]; + b[2] = a[2]; + b[3] = a[3]; +} +void c_qd_copy_dd(const double *a, double *b) { + b[0] = a[0]; + b[1] = a[1]; + b[2] = 0.0; + b[3] = 0.0; +} +void c_qd_copy_d(double a, double *b) { + b[0] = a; + b[1] = 0.0; + b[2] = 0.0; + b[3] = 0.0; +} + + +void c_qd_sqrt(const double *a, double *b) { + qd_real bb; + bb = sqrt(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_sqr(const double *a, double *b) { + qd_real bb; + bb = sqr(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_qd_abs(const double *a, double *b) { + qd_real bb; + bb = abs(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_qd_npwr(const double *a, int n, double *b) { + qd_real bb; + bb = npwr(qd_real(a), n); + TO_DOUBLE_PTR(bb, b); +} + +void c_qd_nroot(const double *a, int n, double *b) { + qd_real bb; + bb = nroot(qd_real(a), n); + TO_DOUBLE_PTR(bb, b); +} + +void c_qd_nint(const double *a, double *b) { + qd_real bb; + bb = nint(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_aint(const double *a, double *b) { + qd_real bb; + bb = aint(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_floor(const double *a, double *b) { + qd_real bb; + bb = floor(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_ceil(const double *a, double *b) { + qd_real bb; + bb = ceil(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_qd_log(const double *a, double *b) { + qd_real bb; + bb = log(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_log10(const double *a, double *b) { + qd_real bb; + bb = log10(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_exp(const double *a, double *b) { + qd_real bb; + bb = exp(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_qd_sin(const double *a, double *b) { + qd_real bb; + bb = sin(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_cos(const double *a, double *b) { + qd_real bb; + bb = cos(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_tan(const double *a, double *b) { + qd_real bb; + bb = tan(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_qd_asin(const double *a, double *b) { + qd_real bb; + bb = asin(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_acos(const double *a, double *b) { + qd_real bb; + bb = acos(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_atan(const double *a, double *b) { + qd_real bb; + bb = atan(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_qd_atan2(const double *a, const double *b, double *c) { + qd_real cc; + cc = atan2(qd_real(a), qd_real(b)); + TO_DOUBLE_PTR(cc, c); +} + +void c_qd_sinh(const double *a, double *b) { + qd_real bb; + bb = sinh(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_cosh(const double *a, double *b) { + qd_real bb; + bb = cosh(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_tanh(const double *a, double *b) { + qd_real bb; + bb = tanh(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_qd_asinh(const double *a, double *b) { + qd_real bb; + bb = asinh(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_acosh(const double *a, double *b) { + qd_real bb; + bb = acosh(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} +void c_qd_atanh(const double *a, double *b) { + qd_real bb; + bb = atanh(qd_real(a)); + TO_DOUBLE_PTR(bb, b); +} + +void c_qd_sincos(const double *a, double *s, double *c) { + qd_real ss, cc; + sincos(qd_real(a), ss, cc); + TO_DOUBLE_PTR(cc, c); + TO_DOUBLE_PTR(ss, s); +} + +void c_qd_sincosh(const double *a, double *s, double *c) { + qd_real ss, cc; + sincosh(qd_real(a), ss, cc); + TO_DOUBLE_PTR(cc, c); + TO_DOUBLE_PTR(ss, s); +} + +void c_qd_read(const char *s, double *a) { + qd_real aa(s); + TO_DOUBLE_PTR(aa, a); +} + +void c_qd_swrite(const double *a, int precision, char *s, int len) { + qd_real(a).write(s, len, precision); +} + +void c_qd_write(const double *a) { + std::cout << qd_real(a).to_string(qd_real::_ndigits) << std::endl; +} + +void c_qd_neg(const double *a, double *b) { + b[0] = -a[0]; + b[1] = -a[1]; + b[2] = -a[2]; + b[3] = -a[3]; +} + +void c_qd_rand(double *a) { + qd_real aa; + aa = qdrand(); + TO_DOUBLE_PTR(aa, a); +} + +void c_qd_comp(const double *a, const double *b, int *result) { + qd_real aa(a), bb(b); + if (aa < bb) + *result = -1; + else if (aa > bb) + *result = 1; + else + *result = 0; +} + +void c_qd_comp_qd_d(const double *a, double b, int *result) { + qd_real aa(a); + if (aa < b) + *result = -1; + else if (aa > b) + *result = 1; + else + *result = 0; +} + +void c_qd_comp_d_qd(double a, const double *b, int *result) { + qd_real bb(b); + if (a < bb) + *result = -1; + else if (a > bb) + *result = 1; + else + *result = 0; +} + +void c_qd_pi(double *a) { + TO_DOUBLE_PTR(qd_real::_pi, a); +} + +} diff --git a/src/external/PackedCSparse/qd/c_qd.h b/src/external/PackedCSparse/qd/c_qd.h new file mode 100644 index 00000000..d11a7ff1 --- /dev/null +++ b/src/external/PackedCSparse/qd/c_qd.h @@ -0,0 +1,119 @@ +/* + * include/c_qd.h + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2001 + * + * Contains C wrapper function prototypes for quad-double precision + * arithmetic. This can also be used from fortran code. + */ +#ifndef _QD_C_QD_H +#define _QD_C_QD_H + +#include "c_dd.h" +#include "qd_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* add */ +void c_qd_add(const double *a, const double *b, double *c); +void c_qd_add_dd_qd(const double *a, const double *b, double *c); +void c_qd_add_qd_dd(const double *a, const double *b, double *c); +void c_qd_add_d_qd(double a, const double *b, double *c); +void c_qd_add_qd_d(const double *a, double b, double *c); +void c_qd_selfadd(const double *a, double *b); +void c_qd_selfadd_dd(const double *a, double *b); +void c_qd_selfadd_d(double a, double *b); + +/* sub */ +void c_qd_sub(const double *a, const double *b, double *c); +void c_qd_sub_dd_qd(const double *a, const double *b, double *c); +void c_qd_sub_qd_dd(const double *a, const double *b, double *c); +void c_qd_sub_d_qd(double a, const double *b, double *c); +void c_qd_sub_qd_d(const double *a, double b, double *c); +void c_qd_selfsub(const double *a, double *b); +void c_qd_selfsub_dd(const double *a, double *b); +void c_qd_selfsub_d(double a, double *b); + +/* mul */ +void c_qd_mul(const double *a, const double *b, double *c); +void c_qd_mul_dd_qd(const double *a, const double *b, double *c); +void c_qd_mul_qd_dd(const double *a, const double *b, double *c); +void c_qd_mul_d_qd(double a, const double *b, double *c); +void c_qd_mul_qd_d(const double *a, double b, double *c); +void c_qd_selfmul(const double *a, double *b); +void c_qd_selfmul_dd(const double *a, double *b); +void c_qd_selfmul_d(double a, double *b); + +/* div */ +void c_qd_div(const double *a, const double *b, double *c); +void c_qd_div_dd_qd(const double *a, const double *b, double *c); +void c_qd_div_qd_dd(const double *a, const double *b, double *c); +void c_qd_div_d_qd(double a, const double *b, double *c); +void c_qd_div_qd_d(const double *a, double b, double *c); +void c_qd_selfdiv(const double *a, double *b); +void c_qd_selfdiv_dd(const double *a, double *b); +void c_qd_selfdiv_d(double a, double *b); + +/* copy */ +void c_qd_copy(const double *a, double *b); +void c_qd_copy_dd(const double *a, double *b); +void c_qd_copy_d(double a, double *b); + +void c_qd_sqrt(const double *a, double *b); +void c_qd_sqr(const double *a, double *b); + +void c_qd_abs(const double *a, double *b); + +void c_qd_npwr(const double *a, int b, double *c); +void c_qd_nroot(const double *a, int b, double *c); + +void c_qd_nint(const double *a, double *b); +void c_qd_aint(const double *a, double *b); +void c_qd_floor(const double *a, double *b); +void c_qd_ceil(const double *a, double *b); + +void c_qd_exp(const double *a, double *b); +void c_qd_log(const double *a, double *b); +void c_qd_log10(const double *a, double *b); + +void c_qd_sin(const double *a, double *b); +void c_qd_cos(const double *a, double *b); +void c_qd_tan(const double *a, double *b); + +void c_qd_asin(const double *a, double *b); +void c_qd_acos(const double *a, double *b); +void c_qd_atan(const double *a, double *b); +void c_qd_atan2(const double *a, const double *b, double *c); + +void c_qd_sinh(const double *a, double *b); +void c_qd_cosh(const double *a, double *b); +void c_qd_tanh(const double *a, double *b); + +void c_qd_asinh(const double *a, double *b); +void c_qd_acosh(const double *a, double *b); +void c_qd_atanh(const double *a, double *b); + +void c_qd_sincos(const double *a, double *s, double *c); +void c_qd_sincosh(const double *a, double *s, double *c); + +void c_qd_read(const char *s, double *a); +void c_qd_swrite(const double *a, int precision, char *s, int len); +void c_qd_write(const double *a); +void c_qd_neg(const double *a, double *b); +void c_qd_rand(double *a); +void c_qd_comp(const double *a, const double *b, int *result); +void c_qd_comp_qd_d(const double *a, double b, int *result); +void c_qd_comp_d_qd(double a, const double *b, int *result); +void c_qd_pi(double *a); + +#ifdef __cplusplus +} +#endif + +#endif /* _QD_C_QD_H */ diff --git a/src/external/PackedCSparse/qd/dd_const.cc b/src/external/PackedCSparse/qd/dd_const.cc new file mode 100644 index 00000000..38b7b5ae --- /dev/null +++ b/src/external/PackedCSparse/qd/dd_const.cc @@ -0,0 +1,40 @@ +/* + * src/dd_const.cc + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2007 + */ +#include "qd_config.h" +#include "dd_real.h" + +const dd_real dd_real::_2pi = dd_real(6.283185307179586232e+00, + 2.449293598294706414e-16); +const dd_real dd_real::_pi = dd_real(3.141592653589793116e+00, + 1.224646799147353207e-16); +const dd_real dd_real::_pi2 = dd_real(1.570796326794896558e+00, + 6.123233995736766036e-17); +const dd_real dd_real::_pi4 = dd_real(7.853981633974482790e-01, + 3.061616997868383018e-17); +const dd_real dd_real::_3pi4 = dd_real(2.356194490192344837e+00, + 9.1848509936051484375e-17); +const dd_real dd_real::_e = dd_real(2.718281828459045091e+00, + 1.445646891729250158e-16); +const dd_real dd_real::_log2 = dd_real(6.931471805599452862e-01, + 2.319046813846299558e-17); +const dd_real dd_real::_log10 = dd_real(2.302585092994045901e+00, + -2.170756223382249351e-16); +const dd_real dd_real::_nan = dd_real(qd::_d_nan, qd::_d_nan); +const dd_real dd_real::_inf = dd_real(qd::_d_inf, qd::_d_inf); + +const double dd_real::_eps = 4.93038065763132e-32; // 2^-104 +const double dd_real::_min_normalized = 2.0041683600089728e-292; // = 2^(-1022 + 53) +const dd_real dd_real::_max = + dd_real(1.79769313486231570815e+308, 9.97920154767359795037e+291); +const dd_real dd_real::_safe_max = + dd_real(1.7976931080746007281e+308, 9.97920154767359795037e+291); +const int dd_real::_ndigits = 31; + + diff --git a/src/external/PackedCSparse/qd/dd_inline.h b/src/external/PackedCSparse/qd/dd_inline.h new file mode 100644 index 00000000..89bc24f2 --- /dev/null +++ b/src/external/PackedCSparse/qd/dd_inline.h @@ -0,0 +1,621 @@ +/* + * include/dd_inline.h + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2001 + * + * Contains small functions (suitable for inlining) in the double-double + * arithmetic package. + */ +#ifndef _QD_DD_INLINE_H +#define _QD_DD_INLINE_H + +#include +#include "inline.h" + +#ifndef QD_INLINE +#define inline +#endif + + +/*********** Additions ************/ +/* double-double = double + double */ +inline dd_real dd_real::add(double a, double b) { + double s, e; + s = qd::two_sum(a, b, e); + return dd_real(s, e); +} + +/* double-double + double */ +inline dd_real operator+(const dd_real &a, double b) { + double s1, s2; + s1 = qd::two_sum(a.x[0], b, s2); + s2 += a.x[1]; + s1 = qd::quick_two_sum(s1, s2, s2); + return dd_real(s1, s2); +} + +/* double-double + double-double */ +inline dd_real dd_real::ieee_add(const dd_real &a, const dd_real &b) { + /* This one satisfies IEEE style error bound, + due to K. Briggs and W. Kahan. */ + double s1, s2, t1, t2; + + s1 = qd::two_sum(a.x[0], b.x[0], s2); + t1 = qd::two_sum(a.x[1], b.x[1], t2); + s2 += t1; + s1 = qd::quick_two_sum(s1, s2, s2); + s2 += t2; + s1 = qd::quick_two_sum(s1, s2, s2); + return dd_real(s1, s2); +} + +inline dd_real dd_real::sloppy_add(const dd_real &a, const dd_real &b) { + /* This is the less accurate version ... obeys Cray-style + error bound. */ + double s, e; + + s = qd::two_sum(a.x[0], b.x[0], e); + e += (a.x[1] + b.x[1]); + s = qd::quick_two_sum(s, e, e); + return dd_real(s, e); +} + +inline dd_real operator+(const dd_real &a, const dd_real &b) { +#ifndef QD_IEEE_ADD + return dd_real::sloppy_add(a, b); +#else + return dd_real::ieee_add(a, b); +#endif +} + +/* double + double-double */ +inline dd_real operator+(double a, const dd_real &b) { + return (b + a); +} + + +/*********** Self-Additions ************/ +/* double-double += double */ +inline dd_real &dd_real::operator+=(double a) { + double s1, s2; + s1 = qd::two_sum(x[0], a, s2); + s2 += x[1]; + x[0] = qd::quick_two_sum(s1, s2, x[1]); + return *this; +} + +/* double-double += double-double */ +inline dd_real &dd_real::operator+=(const dd_real &a) { +#ifndef QD_IEEE_ADD + double s, e; + s = qd::two_sum(x[0], a.x[0], e); + e += x[1]; + e += a.x[1]; + x[0] = qd::quick_two_sum(s, e, x[1]); + return *this; +#else + double s1, s2, t1, t2; + s1 = qd::two_sum(x[0], a.x[0], s2); + t1 = qd::two_sum(x[1], a.x[1], t2); + s2 += t1; + s1 = qd::quick_two_sum(s1, s2, s2); + s2 += t2; + x[0] = qd::quick_two_sum(s1, s2, x[1]); + return *this; +#endif +} + +/*********** Subtractions ************/ +/* double-double = double - double */ +inline dd_real dd_real::sub(double a, double b) { + double s, e; + s = qd::two_diff(a, b, e); + return dd_real(s, e); +} + +/* double-double - double */ +inline dd_real operator-(const dd_real &a, double b) { + double s1, s2; + s1 = qd::two_diff(a.x[0], b, s2); + s2 += a.x[1]; + s1 = qd::quick_two_sum(s1, s2, s2); + return dd_real(s1, s2); +} + +/* double-double - double-double */ +inline dd_real operator-(const dd_real &a, const dd_real &b) { +#ifndef QD_IEEE_ADD + double s, e; + s = qd::two_diff(a.x[0], b.x[0], e); + e += a.x[1]; + e -= b.x[1]; + s = qd::quick_two_sum(s, e, e); + return dd_real(s, e); +#else + double s1, s2, t1, t2; + s1 = qd::two_diff(a.x[0], b.x[0], s2); + t1 = qd::two_diff(a.x[1], b.x[1], t2); + s2 += t1; + s1 = qd::quick_two_sum(s1, s2, s2); + s2 += t2; + s1 = qd::quick_two_sum(s1, s2, s2); + return dd_real(s1, s2); +#endif +} + +/* double - double-double */ +inline dd_real operator-(double a, const dd_real &b) { + double s1, s2; + s1 = qd::two_diff(a, b.x[0], s2); + s2 -= b.x[1]; + s1 = qd::quick_two_sum(s1, s2, s2); + return dd_real(s1, s2); +} + +/*********** Self-Subtractions ************/ +/* double-double -= double */ +inline dd_real &dd_real::operator-=(double a) { + double s1, s2; + s1 = qd::two_diff(x[0], a, s2); + s2 += x[1]; + x[0] = qd::quick_two_sum(s1, s2, x[1]); + return *this; +} + +/* double-double -= double-double */ +inline dd_real &dd_real::operator-=(const dd_real &a) { +#ifndef QD_IEEE_ADD + double s, e; + s = qd::two_diff(x[0], a.x[0], e); + e += x[1]; + e -= a.x[1]; + x[0] = qd::quick_two_sum(s, e, x[1]); + return *this; +#else + double s1, s2, t1, t2; + s1 = qd::two_diff(x[0], a.x[0], s2); + t1 = qd::two_diff(x[1], a.x[1], t2); + s2 += t1; + s1 = qd::quick_two_sum(s1, s2, s2); + s2 += t2; + x[0] = qd::quick_two_sum(s1, s2, x[1]); + return *this; +#endif +} + +/*********** Unary Minus ***********/ +inline dd_real dd_real::operator-() const { + return dd_real(-x[0], -x[1]); +} + +/*********** Multiplications ************/ +/* double-double = double * double */ +inline dd_real dd_real::mul(double a, double b) { + double p, e; + p = qd::two_prod(a, b, e); + return dd_real(p, e); +} + +/* double-double * (2.0 ^ exp) */ +inline dd_real ldexp(const dd_real &a, int exp) { + return dd_real(std::ldexp(a.x[0], exp), std::ldexp(a.x[1], exp)); +} + +/* double-double * double, where double is a power of 2. */ +inline dd_real mul_pwr2(const dd_real &a, double b) { + return dd_real(a.x[0] * b, a.x[1] * b); +} + +/* double-double * double */ +inline dd_real operator*(const dd_real &a, double b) { + double p1, p2; + + p1 = qd::two_prod(a.x[0], b, p2); + p2 += (a.x[1] * b); + p1 = qd::quick_two_sum(p1, p2, p2); + return dd_real(p1, p2); +} + +/* double-double * double-double */ +inline dd_real operator*(const dd_real &a, const dd_real &b) { + double p1, p2; + + p1 = qd::two_prod(a.x[0], b.x[0], p2); + p2 += (a.x[0] * b.x[1] + a.x[1] * b.x[0]); + p1 = qd::quick_two_sum(p1, p2, p2); + return dd_real(p1, p2); +} + +/* double * double-double */ +inline dd_real operator*(double a, const dd_real &b) { + return (b * a); +} + +/*********** Self-Multiplications ************/ +/* double-double *= double */ +inline dd_real &dd_real::operator*=(double a) { + double p1, p2; + p1 = qd::two_prod(x[0], a, p2); + p2 += x[1] * a; + x[0] = qd::quick_two_sum(p1, p2, x[1]); + return *this; +} + +/* double-double *= double-double */ +inline dd_real &dd_real::operator*=(const dd_real &a) { + double p1, p2; + p1 = qd::two_prod(x[0], a.x[0], p2); + p2 += a.x[1] * x[0]; + p2 += a.x[0] * x[1]; + x[0] = qd::quick_two_sum(p1, p2, x[1]); + return *this; +} + +/*********** Divisions ************/ +inline dd_real dd_real::div(double a, double b) { + double q1, q2; + double p1, p2; + double s, e; + + q1 = a / b; + + /* Compute a - q1 * b */ + p1 = qd::two_prod(q1, b, p2); + s = qd::two_diff(a, p1, e); + e -= p2; + + /* get next approximation */ + q2 = (s + e) / b; + + s = qd::quick_two_sum(q1, q2, e); + + return dd_real(s, e); +} + +/* double-double / double */ +inline dd_real operator/(const dd_real &a, double b) { + + double q1, q2; + double p1, p2; + double s, e; + dd_real r; + + q1 = a.x[0] / b; /* approximate quotient. */ + + /* Compute this - q1 * d */ + p1 = qd::two_prod(q1, b, p2); + s = qd::two_diff(a.x[0], p1, e); + e += a.x[1]; + e -= p2; + + /* get next approximation. */ + q2 = (s + e) / b; + + /* renormalize */ + r.x[0] = qd::quick_two_sum(q1, q2, r.x[1]); + + return r; +} + +inline dd_real dd_real::sloppy_div(const dd_real &a, const dd_real &b) { + double s1, s2; + double q1, q2; + dd_real r; + + q1 = a.x[0] / b.x[0]; /* approximate quotient */ + + /* compute this - q1 * dd */ + r = b * q1; + s1 = qd::two_diff(a.x[0], r.x[0], s2); + s2 -= r.x[1]; + s2 += a.x[1]; + + /* get next approximation */ + q2 = (s1 + s2) / b.x[0]; + + /* renormalize */ + r.x[0] = qd::quick_two_sum(q1, q2, r.x[1]); + return r; +} + +inline dd_real dd_real::accurate_div(const dd_real &a, const dd_real &b) { + double q1, q2, q3; + dd_real r; + + q1 = a.x[0] / b.x[0]; /* approximate quotient */ + + r = a - q1 * b; + + q2 = r.x[0] / b.x[0]; + r -= (q2 * b); + + q3 = r.x[0] / b.x[0]; + + q1 = qd::quick_two_sum(q1, q2, q2); + r = dd_real(q1, q2) + q3; + return r; +} + +/* double-double / double-double */ +inline dd_real operator/(const dd_real &a, const dd_real &b) { +#ifdef QD_SLOPPY_DIV + return dd_real::sloppy_div(a, b); +#else + return dd_real::accurate_div(a, b); +#endif +} + +/* double / double-double */ +inline dd_real operator/(double a, const dd_real &b) { + return dd_real(a) / b; +} + +inline dd_real inv(const dd_real &a) { + return 1.0 / a; +} + +/*********** Self-Divisions ************/ +/* double-double /= double */ +inline dd_real &dd_real::operator/=(double a) { + *this = *this / a; + return *this; +} + +/* double-double /= double-double */ +inline dd_real &dd_real::operator/=(const dd_real &a) { + *this = *this / a; + return *this; +} + +/********** Remainder **********/ +inline dd_real drem(const dd_real &a, const dd_real &b) { + dd_real n = nint(a / b); + return (a - n * b); +} + +inline dd_real divrem(const dd_real &a, const dd_real &b, dd_real &r) { + dd_real n = nint(a / b); + r = a - n * b; + return n; +} + +/*********** Squaring **********/ +inline dd_real sqr(const dd_real &a) { + double p1, p2; + double s1, s2; + p1 = qd::two_sqr(a.x[0], p2); + p2 += 2.0 * a.x[0] * a.x[1]; + p2 += a.x[1] * a.x[1]; + s1 = qd::quick_two_sum(p1, p2, s2); + return dd_real(s1, s2); +} + +inline dd_real dd_real::sqr(double a) { + double p1, p2; + p1 = qd::two_sqr(a, p2); + return dd_real(p1, p2); +} + + +/********** Exponentiation **********/ +inline dd_real dd_real::operator^(int n) { + return npwr(*this, n); +} + + +/*********** Assignments ************/ +/* double-double = double */ +inline dd_real &dd_real::operator=(double a) { + x[0] = a; + x[1] = 0.0; + return *this; +} + +/*********** Equality Comparisons ************/ +/* double-double == double */ +inline bool operator==(const dd_real &a, double b) { + return (a.x[0] == b && a.x[1] == 0.0); +} + +/* double-double == double-double */ +inline bool operator==(const dd_real &a, const dd_real &b) { + return (a.x[0] == b.x[0] && a.x[1] == b.x[1]); +} + +/* double == double-double */ +inline bool operator==(double a, const dd_real &b) { + return (a == b.x[0] && b.x[1] == 0.0); +} + +/*********** Greater-Than Comparisons ************/ +/* double-double > double */ +inline bool operator>(const dd_real &a, double b) { + return (a.x[0] > b || (a.x[0] == b && a.x[1] > 0.0)); +} + +/* double-double > double-double */ +inline bool operator>(const dd_real &a, const dd_real &b) { + return (a.x[0] > b.x[0] || (a.x[0] == b.x[0] && a.x[1] > b.x[1])); +} + +/* double > double-double */ +inline bool operator>(double a, const dd_real &b) { + return (a > b.x[0] || (a == b.x[0] && b.x[1] < 0.0)); +} + +/*********** Less-Than Comparisons ************/ +/* double-double < double */ +inline bool operator<(const dd_real &a, double b) { + return (a.x[0] < b || (a.x[0] == b && a.x[1] < 0.0)); +} + +/* double-double < double-double */ +inline bool operator<(const dd_real &a, const dd_real &b) { + return (a.x[0] < b.x[0] || (a.x[0] == b.x[0] && a.x[1] < b.x[1])); +} + +/* double < double-double */ +inline bool operator<(double a, const dd_real &b) { + return (a < b.x[0] || (a == b.x[0] && b.x[1] > 0.0)); +} + +/*********** Greater-Than-Or-Equal-To Comparisons ************/ +/* double-double >= double */ +inline bool operator>=(const dd_real &a, double b) { + return (a.x[0] > b || (a.x[0] == b && a.x[1] >= 0.0)); +} + +/* double-double >= double-double */ +inline bool operator>=(const dd_real &a, const dd_real &b) { + return (a.x[0] > b.x[0] || (a.x[0] == b.x[0] && a.x[1] >= b.x[1])); +} + +/* double >= double-double */ +inline bool operator>=(double a, const dd_real &b) { + return (b <= a); +} + +/*********** Less-Than-Or-Equal-To Comparisons ************/ +/* double-double <= double */ +inline bool operator<=(const dd_real &a, double b) { + return (a.x[0] < b || (a.x[0] == b && a.x[1] <= 0.0)); +} + +/* double-double <= double-double */ +inline bool operator<=(const dd_real &a, const dd_real &b) { + return (a.x[0] < b.x[0] || (a.x[0] == b.x[0] && a.x[1] <= b.x[1])); +} + +/* double <= double-double */ +inline bool operator<=(double a, const dd_real &b) { + return (b >= a); +} + +/*********** Not-Equal-To Comparisons ************/ +/* double-double != double */ +inline bool operator!=(const dd_real &a, double b) { + return (a.x[0] != b || a.x[1] != 0.0); +} + +/* double-double != double-double */ +inline bool operator!=(const dd_real &a, const dd_real &b) { + return (a.x[0] != b.x[0] || a.x[1] != b.x[1]); +} + +/* double != double-double */ +inline bool operator!=(double a, const dd_real &b) { + return (a != b.x[0] || b.x[1] != 0.0); +} + +/*********** Micellaneous ************/ +/* this == 0 */ +inline bool dd_real::is_zero() const { + return (x[0] == 0.0); +} + +/* this == 1 */ +inline bool dd_real::is_one() const { + return (x[0] == 1.0 && x[1] == 0.0); +} + +/* this > 0 */ +inline bool dd_real::is_positive() const { + return (x[0] > 0.0); +} + +/* this < 0 */ +inline bool dd_real::is_negative() const { + return (x[0] < 0.0); +} + +inline dd_real::operator bool() const { + return (x[0] != 0.0); +} + +inline dd_real::operator double() const { + return to_double(*this); +} + +/* Absolute value */ +inline dd_real abs(const dd_real &a) { + return (a.x[0] < 0.0) ? -a : a; +} + +inline dd_real fabs(const dd_real &a) { + return abs(a); +} + +/* Round to Nearest integer */ +inline dd_real nint(const dd_real &a) { + double hi = qd::nint(a.x[0]); + double lo; + + if (hi == a.x[0]) { + /* High word is an integer already. Round the low word.*/ + lo = qd::nint(a.x[1]); + + /* Renormalize. This is needed if x[0] = some integer, x[1] = 1/2.*/ + hi = qd::quick_two_sum(hi, lo, lo); + } else { + /* High word is not an integer. */ + lo = 0.0; + if (std::abs(hi-a.x[0]) == 0.5 && a.x[1] < 0.0) { + /* There is a tie in the high word, consult the low word + to break the tie. */ + hi -= 1.0; /* NOTE: This does not cause INEXACT. */ + } + } + + return dd_real(hi, lo); +} + +inline dd_real floor(const dd_real &a) { + double hi = std::floor(a.x[0]); + double lo = 0.0; + + if (hi == a.x[0]) { + /* High word is integer already. Round the low word. */ + lo = std::floor(a.x[1]); + hi = qd::quick_two_sum(hi, lo, lo); + } + + return dd_real(hi, lo); +} + +inline dd_real ceil(const dd_real &a) { + double hi = std::ceil(a.x[0]); + double lo = 0.0; + + if (hi == a.x[0]) { + /* High word is integer already. Round the low word. */ + lo = std::ceil(a.x[1]); + hi = qd::quick_two_sum(hi, lo, lo); + } + + return dd_real(hi, lo); +} + +inline dd_real aint(const dd_real &a) { + return (a.x[0] >= 0.0) ? floor(a) : ceil(a); +} + +/* Cast to double. */ +inline double to_double(const dd_real &a) { + return a.x[0]; +} + +/* Cast to int. */ +inline int to_int(const dd_real &a) { + return static_cast(a.x[0]); +} + +/* Random number generator */ +inline dd_real dd_real::rand() { + return ddrand(); +} + +#endif /* _QD_DD_INLINE_H */ diff --git a/src/external/PackedCSparse/qd/dd_real.cc b/src/external/PackedCSparse/qd/dd_real.cc new file mode 100644 index 00000000..ff4d5223 --- /dev/null +++ b/src/external/PackedCSparse/qd/dd_real.cc @@ -0,0 +1,1303 @@ +/* + * src/dd_real.cc + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2007 + * + * Contains implementation of non-inlined functions of double-double + * package. Inlined functions are found in dd_inline.h (in include directory). + */ +#include +#include +#include +#include +#include +#include +#include + +#include "qd_config.h" +#include "dd_real.h" +#include "util.h" + +#include "bits.h" + +#ifndef QD_INLINE +#include "dd_inline.h" +#endif + +using std::cout; +using std::cerr; +using std::endl; +using std::ostream; +using std::istream; +using std::ios_base; +using std::string; +using std::setw; + +/* This routine is called whenever a fatal error occurs. */ +void dd_real::error(const char *msg) { + //if (msg) { cerr << "ERROR " << msg << endl; } +} + +/* Computes the square root of the double-double number dd. + NOTE: dd must be a non-negative number. */ +QD_API dd_real sqrt(const dd_real &a) { + /* Strategy: Use Karp's trick: if x is an approximation + to sqrt(a), then + + sqrt(a) = a*x + [a - (a*x)^2] * x / 2 (approx) + + The approximation is accurate to twice the accuracy of x. + Also, the multiplication (a*x) and [-]*x can be done with + only half the precision. + */ + + if (a.is_zero()) + return 0.0; + + if (a.is_negative()) { + dd_real::error("(dd_real::sqrt): Negative argument."); + return dd_real::_nan; + } + + double x = 1.0 / std::sqrt(a.x[0]); + double ax = a.x[0] * x; + return dd_real::add(ax, (a - dd_real::sqr(ax)).x[0] * (x * 0.5)); +} + +/* Computes the square root of a double in double-double precision. + NOTE: d must not be negative. */ +dd_real dd_real::sqrt(double d) { + return ::sqrt(dd_real(d)); +} + +/* Computes the n-th root of the double-double number a. + NOTE: n must be a positive integer. + NOTE: If n is even, then a must not be negative. */ +dd_real nroot(const dd_real &a, int n) { + /* Strategy: Use Newton iteration for the function + + f(x) = x^(-n) - a + + to find its root a^{-1/n}. The iteration is thus + + x' = x + x * (1 - a * x^n) / n + + which converges quadratically. We can then find + a^{1/n} by taking the reciprocal. + */ + + if (n <= 0) { + dd_real::error("(dd_real::nroot): N must be positive."); + return dd_real::_nan; + } + + if (n%2 == 0 && a.is_negative()) { + dd_real::error("(dd_real::nroot): Negative argument."); + return dd_real::_nan; + } + + if (n == 1) { + return a; + } + if (n == 2) { + return sqrt(a); + } + + if (a.is_zero()) + return 0.0; + + /* Note a^{-1/n} = exp(-log(a)/n) */ + dd_real r = abs(a); + dd_real x = std::exp(-std::log(r.x[0]) / n); + + /* Perform Newton's iteration. */ + x += x * (1.0 - r * npwr(x, n)) / static_cast(n); + if (a.x[0] < 0.0) + x = -x; + return 1.0/x; +} + +/* Computes the n-th power of a double-double number. + NOTE: 0^0 causes an error. */ +dd_real npwr(const dd_real &a, int n) { + + if (n == 0) { + if (a.is_zero()) { + dd_real::error("(dd_real::npwr): Invalid argument."); + return dd_real::_nan; + } + return 1.0; + } + + dd_real r = a; + dd_real s = 1.0; + int N = std::abs(n); + + if (N > 1) { + /* Use binary exponentiation */ + while (N > 0) { + if (N % 2 == 1) { + s *= r; + } + N /= 2; + if (N > 0) + r = sqr(r); + } + } else { + s = r; + } + + /* Compute the reciprocal if n is negative. */ + if (n < 0) + return (1.0 / s); + + return s; +} + +dd_real pow(const dd_real &a, int n) { + return npwr(a, n); +} + +dd_real pow(const dd_real &a, const dd_real &b) { + return exp(b * log(a)); +} + +static const int n_inv_fact = 15; +static const double inv_fact[n_inv_fact][2] = { + { 1.66666666666666657e-01, 9.25185853854297066e-18}, + { 4.16666666666666644e-02, 2.31296463463574266e-18}, + { 8.33333333333333322e-03, 1.15648231731787138e-19}, + { 1.38888888888888894e-03, -5.30054395437357706e-20}, + { 1.98412698412698413e-04, 1.72095582934207053e-22}, + { 2.48015873015873016e-05, 2.15119478667758816e-23}, + { 2.75573192239858925e-06, -1.85839327404647208e-22}, + { 2.75573192239858883e-07, 2.37677146222502973e-23}, + { 2.50521083854417202e-08, -1.44881407093591197e-24}, + { 2.08767569878681002e-09, -1.20734505911325997e-25}, + { 1.60590438368216133e-10, 1.25852945887520981e-26}, + { 1.14707455977297245e-11, 2.06555127528307454e-28}, + { 7.64716373181981641e-13, 7.03872877733453001e-30}, + { 4.77947733238738525e-14, 4.39920548583408126e-31}, + { 2.81145725434552060e-15, 1.65088427308614326e-31} +}; + +/* Exponential. Computes exp(x) in double-double precision. */ +dd_real exp(const dd_real &a) { + /* Strategy: We first reduce the size of x by noting that + + exp(kr + m * log(2)) = 2^m * exp(r)^k + + where m and k are integers. By choosing m appropriately + we can make |kr| <= log(2) / 2 = 0.347. Then exp(r) is + evaluated using the familiar Taylor series. Reducing the + argument substantially speeds up the convergence. */ + + const double k = 512.0; + const double inv_k = 1.0 / k; + + if (a.x[0] <= -709.0) + return 0.0; + + if (a.x[0] >= 709.0) + return dd_real::_inf; + + if (a.is_zero()) + return 1.0; + + if (a.is_one()) + return dd_real::_e; + + double m = std::floor(a.x[0] / dd_real::_log2.x[0] + 0.5); + dd_real r = mul_pwr2(a - dd_real::_log2 * m, inv_k); + dd_real s, t, p; + + p = sqr(r); + s = r + mul_pwr2(p, 0.5); + p *= r; + t = p * dd_real(inv_fact[0][0], inv_fact[0][1]); + int i = 0; + do { + s += t; + p *= r; + ++i; + t = p * dd_real(inv_fact[i][0], inv_fact[i][1]); + } while (std::abs(to_double(t)) > inv_k * dd_real::_eps && i < 5); + + s += t; + + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s += 1.0; + + return ldexp(s, static_cast(m)); +} + +/* Logarithm. Computes log(x) in double-double precision. + This is a natural logarithm (i.e., base e). */ +dd_real log(const dd_real &a) { + /* Strategy. The Taylor series for log converges much more + slowly than that of exp, due to the lack of the factorial + term in the denominator. Hence this routine instead tries + to determine the root of the function + + f(x) = exp(x) - a + + using Newton iteration. The iteration is given by + + x' = x - f(x)/f'(x) + = x - (1 - a * exp(-x)) + = x + a * exp(-x) - 1. + + Only one iteration is needed, since Newton's iteration + approximately doubles the number of digits per iteration. */ + + if (a.is_one()) { + return 0.0; + } + + if (a.x[0] <= 0.0) { + dd_real::error("(dd_real::log): Non-positive argument."); + return dd_real::_nan; + } + + dd_real x = std::log(a.x[0]); /* Initial approximation */ + + x = x + a * exp(-x) - 1.0; + return x; +} + +dd_real log10(const dd_real &a) { + return log(a) / dd_real::_log10; +} + +static const dd_real _pi16 = dd_real(1.963495408493620697e-01, + 7.654042494670957545e-18); + +/* Table of sin(k * pi/16) and cos(k * pi/16). */ +static const double sin_table [4][2] = { + {1.950903220161282758e-01, -7.991079068461731263e-18}, + {3.826834323650897818e-01, -1.005077269646158761e-17}, + {5.555702330196021776e-01, 4.709410940561676821e-17}, + {7.071067811865475727e-01, -4.833646656726456726e-17} +}; + +static const double cos_table [4][2] = { + {9.807852804032304306e-01, 1.854693999782500573e-17}, + {9.238795325112867385e-01, 1.764504708433667706e-17}, + {8.314696123025452357e-01, 1.407385698472802389e-18}, + {7.071067811865475727e-01, -4.833646656726456726e-17} +}; + +/* Computes sin(a) using Taylor series. + Assumes |a| <= pi/32. */ +static dd_real sin_taylor(const dd_real &a) { + const double thresh = 0.5 * std::abs(to_double(a)) * dd_real::_eps; + dd_real r, s, t, x; + + if (a.is_zero()) { + return 0.0; + } + + int i = 0; + x = -sqr(a); + s = a; + r = a; + do { + r *= x; + t = r * dd_real(inv_fact[i][0], inv_fact[i][1]); + s += t; + i += 2; + } while (i < n_inv_fact && std::abs(to_double(t)) > thresh); + + return s; +} + +static dd_real cos_taylor(const dd_real &a) { + const double thresh = 0.5 * dd_real::_eps; + dd_real r, s, t, x; + + if (a.is_zero()) { + return 1.0; + } + + x = -sqr(a); + r = x; + s = 1.0 + mul_pwr2(r, 0.5); + int i = 1; + do { + r *= x; + t = r * dd_real(inv_fact[i][0], inv_fact[i][1]); + s += t; + i += 2; + } while (i < n_inv_fact && std::abs(to_double(t)) > thresh); + + return s; +} + +static void sincos_taylor(const dd_real &a, + dd_real &sin_a, dd_real &cos_a) { + if (a.is_zero()) { + sin_a = 0.0; + cos_a = 1.0; + return; + } + + sin_a = sin_taylor(a); + cos_a = sqrt(1.0 - sqr(sin_a)); +} + + +dd_real sin(const dd_real &a) { + + /* Strategy. To compute sin(x), we choose integers a, b so that + + x = s + a * (pi/2) + b * (pi/16) + + and |s| <= pi/32. Using the fact that + + sin(pi/16) = 0.5 * sqrt(2 - sqrt(2 + sqrt(2))) + + we can compute sin(x) from sin(s), cos(s). This greatly + increases the convergence of the sine Taylor series. */ + + if (a.is_zero()) { + return 0.0; + } + + // approximately reduce modulo 2*pi + dd_real z = nint(a / dd_real::_2pi); + dd_real r = a - dd_real::_2pi * z; + + // approximately reduce modulo pi/2 and then modulo pi/16. + dd_real t; + double q = std::floor(r.x[0] / dd_real::_pi2.x[0] + 0.5); + t = r - dd_real::_pi2 * q; + int j = static_cast(q); + q = std::floor(t.x[0] / _pi16.x[0] + 0.5); + t -= _pi16 * q; + int k = static_cast(q); + int abs_k = std::abs(k); + + if (j < -2 || j > 2) { + dd_real::error("(dd_real::sin): Cannot reduce modulo pi/2."); + return dd_real::_nan; + } + + if (abs_k > 4) { + dd_real::error("(dd_real::sin): Cannot reduce modulo pi/16."); + return dd_real::_nan; + } + + if (k == 0) { + switch (j) { + case 0: + return sin_taylor(t); + case 1: + return cos_taylor(t); + case -1: + return -cos_taylor(t); + default: + return -sin_taylor(t); + } + } + + dd_real u(cos_table[abs_k-1][0], cos_table[abs_k-1][1]); + dd_real v(sin_table[abs_k-1][0], sin_table[abs_k-1][1]); + dd_real sin_t, cos_t; + sincos_taylor(t, sin_t, cos_t); + if (j == 0) { + if (k > 0) { + r = u * sin_t + v * cos_t; + } else { + r = u * sin_t - v * cos_t; + } + } else if (j == 1) { + if (k > 0) { + r = u * cos_t - v * sin_t; + } else { + r = u * cos_t + v * sin_t; + } + } else if (j == -1) { + if (k > 0) { + r = v * sin_t - u * cos_t; + } else if (k < 0) { + r = -u * cos_t - v * sin_t; + } + } else { + if (k > 0) { + r = -u * sin_t - v * cos_t; + } else { + r = v * cos_t - u * sin_t; + } + } + + return r; +} + +dd_real cos(const dd_real &a) { + + if (a.is_zero()) { + return 1.0; + } + + // approximately reduce modulo 2*pi + dd_real z = nint(a / dd_real::_2pi); + dd_real r = a - z * dd_real::_2pi; + + // approximately reduce modulo pi/2 and then modulo pi/16 + dd_real t; + double q = std::floor(r.x[0] / dd_real::_pi2.x[0] + 0.5); + t = r - dd_real::_pi2 * q; + int j = static_cast(q); + q = std::floor(t.x[0] / _pi16.x[0] + 0.5); + t -= _pi16 * q; + int k = static_cast(q); + int abs_k = std::abs(k); + + if (j < -2 || j > 2) { + dd_real::error("(dd_real::cos): Cannot reduce modulo pi/2."); + return dd_real::_nan; + } + + if (abs_k > 4) { + dd_real::error("(dd_real::cos): Cannot reduce modulo pi/16."); + return dd_real::_nan; + } + + if (k == 0) { + switch (j) { + case 0: + return cos_taylor(t); + case 1: + return -sin_taylor(t); + case -1: + return sin_taylor(t); + default: + return -cos_taylor(t); + } + } + + dd_real sin_t, cos_t; + sincos_taylor(t, sin_t, cos_t); + dd_real u(cos_table[abs_k-1][0], cos_table[abs_k-1][1]); + dd_real v(sin_table[abs_k-1][0], sin_table[abs_k-1][1]); + + if (j == 0) { + if (k > 0) { + r = u * cos_t - v * sin_t; + } else { + r = u * cos_t + v * sin_t; + } + } else if (j == 1) { + if (k > 0) { + r = - u * sin_t - v * cos_t; + } else { + r = v * cos_t - u * sin_t; + } + } else if (j == -1) { + if (k > 0) { + r = u * sin_t + v * cos_t; + } else { + r = u * sin_t - v * cos_t; + } + } else { + if (k > 0) { + r = v * sin_t - u * cos_t; + } else { + r = - u * cos_t - v * sin_t; + } + } + + return r; +} + +void sincos(const dd_real &a, dd_real &sin_a, dd_real &cos_a) { + + if (a.is_zero()) { + sin_a = 0.0; + cos_a = 1.0; + return; + } + + // approximately reduce modulo 2*pi + dd_real z = nint(a / dd_real::_2pi); + dd_real r = a - dd_real::_2pi * z; + + // approximately reduce module pi/2 and pi/16 + dd_real t; + double q = std::floor(r.x[0] / dd_real::_pi2.x[0] + 0.5); + t = r - dd_real::_pi2 * q; + int j = static_cast(q); + int abs_j = std::abs(j); + q = std::floor(t.x[0] / _pi16.x[0] + 0.5); + t -= _pi16 * q; + int k = static_cast(q); + int abs_k = std::abs(k); + + if (abs_j > 2) { + dd_real::error("(dd_real::sincos): Cannot reduce modulo pi/2."); + cos_a = sin_a = dd_real::_nan; + return; + } + + if (abs_k > 4) { + dd_real::error("(dd_real::sincos): Cannot reduce modulo pi/16."); + cos_a = sin_a = dd_real::_nan; + return; + } + + dd_real sin_t, cos_t; + dd_real s, c; + + sincos_taylor(t, sin_t, cos_t); + + if (abs_k == 0) { + s = sin_t; + c = cos_t; + } else { + dd_real u(cos_table[abs_k-1][0], cos_table[abs_k-1][1]); + dd_real v(sin_table[abs_k-1][0], sin_table[abs_k-1][1]); + + if (k > 0) { + s = u * sin_t + v * cos_t; + c = u * cos_t - v * sin_t; + } else { + s = u * sin_t - v * cos_t; + c = u * cos_t + v * sin_t; + } + } + + if (abs_j == 0) { + sin_a = s; + cos_a = c; + } else if (j == 1) { + sin_a = c; + cos_a = -s; + } else if (j == -1) { + sin_a = -c; + cos_a = s; + } else { + sin_a = -s; + cos_a = -c; + } + +} + +dd_real atan(const dd_real &a) { + return atan2(a, dd_real(1.0)); +} + +dd_real atan2(const dd_real &y, const dd_real &x) { + /* Strategy: Instead of using Taylor series to compute + arctan, we instead use Newton's iteration to solve + the equation + + sin(z) = y/r or cos(z) = x/r + + where r = sqrt(x^2 + y^2). + The iteration is given by + + z' = z + (y - sin(z)) / cos(z) (for equation 1) + z' = z - (x - cos(z)) / sin(z) (for equation 2) + + Here, x and y are normalized so that x^2 + y^2 = 1. + If |x| > |y|, then first iteration is used since the + denominator is larger. Otherwise, the second is used. + */ + + if (x.is_zero()) { + + if (y.is_zero()) { + /* Both x and y is zero. */ + dd_real::error("(dd_real::atan2): Both arguments zero."); + return dd_real::_nan; + } + + return (y.is_positive()) ? dd_real::_pi2 : -dd_real::_pi2; + } else if (y.is_zero()) { + return (x.is_positive()) ? dd_real(0.0) : dd_real::_pi; + } + + if (x == y) { + return (y.is_positive()) ? dd_real::_pi4 : -dd_real::_3pi4; + } + + if (x == -y) { + return (y.is_positive()) ? dd_real::_3pi4 : -dd_real::_pi4; + } + + dd_real r = sqrt(sqr(x) + sqr(y)); + dd_real xx = x / r; + dd_real yy = y / r; + + /* Compute double precision approximation to atan. */ + dd_real z = std::atan2(to_double(y), to_double(x)); + dd_real sin_z, cos_z; + + if (std::abs(xx.x[0]) > std::abs(yy.x[0])) { + /* Use Newton iteration 1. z' = z + (y - sin(z)) / cos(z) */ + sincos(z, sin_z, cos_z); + z += (yy - sin_z) / cos_z; + } else { + /* Use Newton iteration 2. z' = z - (x - cos(z)) / sin(z) */ + sincos(z, sin_z, cos_z); + z -= (xx - cos_z) / sin_z; + } + + return z; +} + +dd_real tan(const dd_real &a) { + dd_real s, c; + sincos(a, s, c); + return s/c; +} + +dd_real asin(const dd_real &a) { + dd_real abs_a = abs(a); + + if (abs_a > 1.0) { + dd_real::error("(dd_real::asin): Argument out of domain."); + return dd_real::_nan; + } + + if (abs_a.is_one()) { + return (a.is_positive()) ? dd_real::_pi2 : -dd_real::_pi2; + } + + return atan2(a, sqrt(1.0 - sqr(a))); +} + +dd_real acos(const dd_real &a) { + dd_real abs_a = abs(a); + + if (abs_a > 1.0) { + dd_real::error("(dd_real::acos): Argument out of domain."); + return dd_real::_nan; + } + + if (abs_a.is_one()) { + return (a.is_positive()) ? dd_real(0.0) : dd_real::_pi; + } + + return atan2(sqrt(1.0 - sqr(a)), a); +} + +dd_real sinh(const dd_real &a) { + if (a.is_zero()) { + return 0.0; + } + + if (abs(a) > 0.05) { + dd_real ea = exp(a); + return mul_pwr2(ea - inv(ea), 0.5); + } + + /* since a is small, using the above formula gives + a lot of cancellation. So use Taylor series. */ + dd_real s = a; + dd_real t = a; + dd_real r = sqr(t); + double m = 1.0; + double thresh = std::abs((to_double(a)) * dd_real::_eps); + + do { + m += 2.0; + t *= r; + t /= (m-1) * m; + + s += t; + } while (abs(t) > thresh); + + return s; + +} + +dd_real cosh(const dd_real &a) { + if (a.is_zero()) { + return 1.0; + } + + dd_real ea = exp(a); + return mul_pwr2(ea + inv(ea), 0.5); +} + +dd_real tanh(const dd_real &a) { + if (a.is_zero()) { + return 0.0; + } + + if (std::abs(to_double(a)) > 0.05) { + dd_real ea = exp(a); + dd_real inv_ea = inv(ea); + return (ea - inv_ea) / (ea + inv_ea); + } else { + dd_real s, c; + s = sinh(a); + c = sqrt(1.0 + sqr(s)); + return s / c; + } +} + +void sincosh(const dd_real &a, dd_real &s, dd_real &c) { + if (std::abs(to_double(a)) <= 0.05) { + s = sinh(a); + c = sqrt(1.0 + sqr(s)); + } else { + dd_real ea = exp(a); + dd_real inv_ea = inv(ea); + s = mul_pwr2(ea - inv_ea, 0.5); + c = mul_pwr2(ea + inv_ea, 0.5); + } +} + +dd_real asinh(const dd_real &a) { + return log(a + sqrt(sqr(a) + 1.0)); +} + +dd_real acosh(const dd_real &a) { + if (a < 1.0) { + dd_real::error("(dd_real::acosh): Argument out of domain."); + return dd_real::_nan; + } + + return log(a + sqrt(sqr(a) - 1.0)); +} + +dd_real atanh(const dd_real &a) { + if (abs(a) >= 1.0) { + dd_real::error("(dd_real::atanh): Argument out of domain."); + return dd_real::_nan; + } + + return mul_pwr2(log((1.0 + a) / (1.0 - a)), 0.5); +} + +QD_API dd_real fmod(const dd_real &a, const dd_real &b) { + dd_real n = aint(a / b); + return (a - b * n); +} + +QD_API dd_real ddrand() { + static const double m_const = 4.6566128730773926e-10; /* = 2^{-31} */ + double m = m_const; + dd_real r = 0.0; + double d; + + /* Strategy: Generate 31 bits at a time, using lrand48 + random number generator. Shift the bits, and reapeat + 4 times. */ + + for (int i = 0; i < 4; i++, m *= m_const) { +// d = lrand48() * m; + d = std::rand() * m; + r += d; + } + + return r; +} + +/* polyeval(c, n, x) + Evaluates the given n-th degree polynomial at x. + The polynomial is given by the array of (n+1) coefficients. */ +dd_real polyeval(const dd_real *c, int n, const dd_real &x) { + /* Just use Horner's method of polynomial evaluation. */ + dd_real r = c[n]; + + for (int i = n-1; i >= 0; i--) { + r *= x; + r += c[i]; + } + + return r; +} + +/* polyroot(c, n, x0) + Given an n-th degree polynomial, finds a root close to + the given guess x0. Note that this uses simple Newton + iteration scheme, and does not work for multiple roots. */ +QD_API dd_real polyroot(const dd_real *c, int n, + const dd_real &x0, int max_iter, double thresh) { + dd_real x = x0; + dd_real f; + dd_real *d = new dd_real[n]; + bool conv = false; + int i; + double max_c = std::abs(to_double(c[0])); + double v; + + if (thresh == 0.0) thresh = dd_real::_eps; + + /* Compute the coefficients of the derivatives. */ + for (i = 1; i <= n; i++) { + v = std::abs(to_double(c[i])); + if (v > max_c) max_c = v; + d[i-1] = c[i] * static_cast(i); + } + thresh *= max_c; + + /* Newton iteration. */ + for (i = 0; i < max_iter; i++) { + f = polyeval(c, n, x); + + if (abs(f) < thresh) { + conv = true; + break; + } + x -= (f / polyeval(d, n-1, x)); + } + delete [] d; + + if (!conv) { + dd_real::error("(dd_real::polyroot): Failed to converge."); + return dd_real::_nan; + } + + return x; +} + + +/* Constructor. Reads a double-double number from the string s + and constructs a double-double number. */ +dd_real::dd_real(const char *s) { + if (dd_real::read(s, *this)) { + dd_real::error("(dd_real::dd_real): INPUT ERROR."); + *this = dd_real::_nan; + } +} + +dd_real &dd_real::operator=(const char *s) { + if (dd_real::read(s, *this)) { + dd_real::error("(dd_real::operator=): INPUT ERROR."); + *this = dd_real::_nan; + } + return *this; +} + +/* Outputs the double-double number dd. */ +ostream &operator<<(ostream &os, const dd_real &dd) { + bool showpos = (os.flags() & ios_base::showpos) != 0; + bool uppercase = (os.flags() & ios_base::uppercase) != 0; + return os << dd.to_string((int)os.precision(), (int)os.width(), os.flags(), + showpos, uppercase, os.fill()); +} + +/* Reads in the double-double number a. */ +istream &operator>>(istream &s, dd_real &a) { + char str[255]; + s >> str; + a = dd_real(str); + return s; +} + +void dd_real::to_digits(char *s, int &expn, int precision) const { + int D = precision + 1; /* number of digits to compute */ + + dd_real r = abs(*this); + int e; /* exponent */ + int i, d; + + if (x[0] == 0.0) { + /* this == 0.0 */ + expn = 0; + for (i = 0; i < precision; i++) s[i] = '0'; + return; + } + + /* First determine the (approximate) exponent. */ + e = to_int(std::floor(std::log10(std::abs(x[0])))); + + if (e < -300) { + r *= dd_real(10.0) ^ 300; + r /= dd_real(10.0) ^ (e + 300); + } else if (e > 300) { + r = ldexp(r, -53); + r /= dd_real(10.0) ^ e; + r = ldexp(r, 53); + } else { + r /= dd_real(10.0) ^ e; + } + + /* Fix exponent if we are off by one */ + if (r >= 10.0) { + r /= 10.0; + e++; + } else if (r < 1.0) { + r *= 10.0; + e--; + } + + if (r >= 10.0 || r < 1.0) { + dd_real::error("(dd_real::to_digits): can't compute exponent."); + return; + } + + /* Extract the digits */ + for (i = 0; i < D; i++) { + d = static_cast(r.x[0]); + r -= d; + r *= 10.0; + + s[i] = static_cast(d + '0'); + } + + /* Fix out of range digits. */ + for (i = D-1; i > 0; i--) { + if (s[i] < '0') { + s[i-1]--; + s[i] += 10; + } else if (s[i] > '9') { + s[i-1]++; + s[i] -= 10; + } + } + + if (s[0] <= '0') { + dd_real::error("(dd_real::to_digits): non-positive leading digit."); + return; + } + + /* Round, handle carry */ + if (s[D-1] >= '5') { + s[D-2]++; + + i = D-2; + while (i > 0 && s[i] > '9') { + s[i] -= 10; + s[--i]++; + } + } + + /* If first digit is 10, shift everything. */ + if (s[0] > '9') { + e++; + for (i = precision; i >= 2; i--) s[i] = s[i-1]; + s[0] = '1'; + s[1] = '0'; + } + + s[precision] = 0; + expn = e; +} + +/* Writes the double-double number into the character array s of length len. + The integer d specifies how many significant digits to write. + The string s must be able to hold at least (d+8) characters. + showpos indicates whether to use the + sign, and uppercase indicates + whether the E or e is to be used for the exponent. */ +void dd_real::write(char *s, int len, int precision, + bool showpos, bool uppercase) const { + string str = to_string(precision, 0, ios_base::scientific, showpos, uppercase); + std::strncpy(s, str.c_str(), len-1); + s[len-1] = 0; +} + + +void round_string(char *s, int precision, int *offset){ + /* + Input string must be all digits or errors will occur. + */ + + int i; + int D = precision ; + + /* Round, handle carry */ + if (D>0 && s[D] >= '5') { + s[D-1]++; + + i = D-1; + while (i > 0 && s[i] > '9') { + s[i] -= 10; + s[--i]++; + } + } + + /* If first digit is 10, shift everything. */ + if (s[0] > '9') { + // e++; // don't modify exponent here + for (i = precision; i >= 1; i--) s[i+1] = s[i]; + s[0] = '1'; + s[1] = '0'; + + (*offset)++ ; // now offset needs to be increased by one + precision++ ; + } + + s[precision] = 0; // add terminator for array +} + +string dd_real::to_string(int precision, int width, ios_base::fmtflags fmt, + bool showpos, bool uppercase, char fill) const { + string s; + bool fixed = (fmt & ios_base::fixed) != 0; + bool sgn = true; + int i, e = 0; + + if (isnan()) { + s = uppercase ? "NAN" : "nan"; + sgn = false; + } else { + if (*this < 0.0) + s += '-'; + else if (showpos) + s += '+'; + else + sgn = false; + + if (isinf()) { + s += uppercase ? "INF" : "inf"; + } else if (*this == 0.0) { + /* Zero case */ + s += '0'; + if (precision > 0) { + s += '.'; + s.append(precision, '0'); + } + } else { + /* Non-zero case */ + int off = (fixed ? (1 + to_int(floor(log10(abs(*this))))) : 1); + int d = precision + off; + + int d_with_extra = d; + if(fixed) + d_with_extra = std::max(60, d); // longer than the max accuracy for DD + + // highly special case - fixed mode, precision is zero, abs(*this) < 1.0 + // without this trap a number like 0.9 printed fixed with 0 precision prints as 0 + // should be rounded to 1. + if(fixed && (precision == 0) && (abs(*this) < 1.0)){ + if(abs(*this) >= 0.5) + s += '1'; + else + s += '0'; + + return s; + } + + // handle near zero to working precision (but not exactly zero) + if (fixed && d <= 0) { + s += '0'; + if (precision > 0) { + s += '.'; + s.append(precision, '0'); + } + } else { // default + + char *t; // = new char[d+1]; + int j; + + if(fixed){ + t = new char[d_with_extra+1]; + to_digits(t, e, d_with_extra); + } + else{ + t = new char[d+1]; + to_digits(t, e, d); + } + + off = e + 1; + + if (fixed) { + // fix the string if it's been computed incorrectly + // round here in the decimal string if required + round_string(t, d, &off); + + if (off > 0) { + for (i = 0; i < off; i++) s += t[i]; + if (precision > 0) { + s += '.'; + for (j = 0; j < precision; j++, i++) s += t[i]; + } + } else { + s += "0."; + if (off < 0) s.append(-off, '0'); + for (i = 0; i < d; i++) s += t[i]; + } + } else { + s += t[0]; + if (precision > 0) s += '.'; + + for (i = 1; i <= precision; i++) + s += t[i]; + + } + delete [] t; + } + } + + // trap for improper offset with large values + // without this trap, output of values of the for 10^j - 1 fail for j > 28 + // and are output with the point in the wrong place, leading to a dramatically off value + if(fixed && (precision > 0)){ + // make sure that the value isn't dramatically larger + double from_string = atof(s.c_str()); + + // if this ratio is large, then we've got problems + if( fabs( from_string / this->x[0] ) > 3.0 ){ + + // loop on the string, find the point, move it up one + // don't act on the first character + for(i=1; i < (int)s.length(); i++){ + if(s[i] == '.'){ + s[i] = s[i-1] ; + s[i-1] = '.' ; + break; + } + } + + from_string = atof(s.c_str()); + // if this ratio is large, then the string has not been fixed + if( fabs( from_string / this->x[0] ) > 3.0 ){ + dd_real::error("Re-rounding unsuccessful in large number fixed point trap.") ; + } + } + } + + + if (!fixed && !isinf()) { + /* Fill in exponent part */ + s += uppercase ? 'E' : 'e'; + append_expn(s, e); + } + } + + /* Fill in the blanks */ + int len = s.length(); + if (len < width) { + int delta = width - len; + if (fmt & ios_base::internal) { + if (sgn) + s.insert(static_cast(1), delta, fill); + else + s.insert(static_cast(0), delta, fill); + } else if (fmt & ios_base::left) { + s.append(delta, fill); + } else { + s.insert(static_cast(0), delta, fill); + } + } + + return s; +} + +/* Reads in a double-double number from the string s. */ +int dd_real::read(const char *s, dd_real &a) { + const char *p = s; + char ch; + int sign = 0; + int point = -1; + int nd = 0; + int e = 0; + bool done = false; + dd_real r = 0.0; + int nread; + + /* Skip any leading spaces */ + while (*p == ' ') + p++; + + while (!done && (ch = *p) != '\0') { + if (ch >= '0' && ch <= '9') { + int d = ch - '0'; + r *= 10.0; + r += static_cast(d); + nd++; + } else { + + switch (ch) { + + case '.': + if (point >= 0) + return -1; + point = nd; + break; + + case '-': + case '+': + if (sign != 0 || nd > 0) + return -1; + sign = (ch == '-') ? -1 : 1; + break; + + case 'E': + case 'e': + nread = std::sscanf(p+1, "%d", &e); + done = true; + if (nread != 1) + return -1; + break; + + default: + return -1; + } + } + + p++; + } + + if (point >= 0) { + e -= (nd - point); + } + + if (e != 0) { + r *= (dd_real(10.0) ^ e); + } + + a = (sign == -1) ? -r : r; + return 0; +} + +/* Debugging routines */ +void dd_real::dump(const string &name, std::ostream &os) const { + std::ios_base::fmtflags old_flags = os.flags(); + std::streamsize old_prec = os.precision(19); + os << std::scientific; + + if (name.length() > 0) os << name << " = "; + os << "[ " << setw(27) << x[0] << ", " << setw(27) << x[1] << " ]" << endl; + + os.precision(old_prec); + os.flags(old_flags); +} + +void dd_real::dump_bits(const string &name, std::ostream &os) const { + string::size_type len = name.length(); + if (len > 0) { + os << name << " = "; + len +=3; + } + os << "[ "; + len += 2; + print_double_info(os, x[0]); + os << endl; + for (string::size_type i = 0; i < len; i++) os << ' '; + print_double_info(os, x[1]); + os << " ]" << endl; +} + +dd_real dd_real::debug_rand() { + + if (std::rand() % 2 == 0) + return ddrand(); + + int expn = 0; + dd_real a = 0.0; + double d; + for (int i = 0; i < 2; i++) { + d = std::ldexp(static_cast(std::rand()) / RAND_MAX, -expn); + a += d; + expn = expn + 54 + std::rand() % 200; + } + return a; +} diff --git a/src/external/PackedCSparse/qd/dd_real.h b/src/external/PackedCSparse/qd/dd_real.h new file mode 100644 index 00000000..e16438aa --- /dev/null +++ b/src/external/PackedCSparse/qd/dd_real.h @@ -0,0 +1,293 @@ +/* + * include/dd_real.h + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2007 + * + * Double-double precision (>= 106-bit significand) floating point + * arithmetic package based on David Bailey's Fortran-90 double-double + * package, with some changes. See + * + * http://www.nersc.gov/~dhbailey/mpdist/mpdist.html + * + * for the original Fortran-90 version. + * + * Overall structure is similar to that of Keith Brigg's C++ double-double + * package. See + * + * http://www-epidem.plansci.cam.ac.uk/~kbriggs/doubledouble.html + * + * for more details. In particular, the fix for x86 computers is borrowed + * from his code. + * + * Yozo Hida + */ + +#ifndef _QD_DD_REAL_H +#define _QD_DD_REAL_H + +#include +#include +#include +#include +#include "qd_config.h" +#include "fpu.h" + +// Some compilers define isnan, isfinite, and isinf as macros, even for +// C++ codes, which cause havoc when overloading these functions. We undef +// them here. +#ifdef isnan +#undef isnan +#endif + +#ifdef isfinite +#undef isfinite +#endif + +#ifdef isinf +#undef isinf +#endif + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +struct QD_API dd_real { + double x[2]; + + dd_real(double hi, double lo) { x[0] = hi; x[1] = lo; } + dd_real() {x[0] = 0.0; x[1] = 0.0; } + dd_real(double h) { x[0] = h; x[1] = 0.0; } + dd_real(int h) { + x[0] = (static_cast(h)); + x[1] = 0.0; + } + + dd_real (const char *s); + explicit dd_real (const double *d) { + x[0] = d[0]; x[1] = d[1]; + } + + static void error(const char *msg); + + double _hi() const { return x[0]; } + double _lo() const { return x[1]; } + + static const dd_real _2pi; + static const dd_real _pi; + static const dd_real _3pi4; + static const dd_real _pi2; + static const dd_real _pi4; + static const dd_real _e; + static const dd_real _log2; + static const dd_real _log10; + static const dd_real _nan; + static const dd_real _inf; + + static const double _eps; + static const double _min_normalized; + static const dd_real _max; + static const dd_real _safe_max; + static const int _ndigits; + + bool isnan() const { return QD_ISNAN(x[0]) || QD_ISNAN(x[1]); } + bool isfinite() const { return QD_ISFINITE(x[0]); } + bool isinf() const { return QD_ISINF(x[0]); } + + static dd_real add(double a, double b); + static dd_real ieee_add(const dd_real &a, const dd_real &b); + static dd_real sloppy_add(const dd_real &a, const dd_real &b); + + dd_real &operator+=(double a); + dd_real &operator+=(const dd_real &a); + + static dd_real sub(double a, double b); + + dd_real &operator-=(double a); + dd_real &operator-=(const dd_real &a); + + dd_real operator-() const; + + static dd_real mul(double a, double b); + + dd_real &operator*=(double a); + dd_real &operator*=(const dd_real &a); + + static dd_real div(double a, double b); + static dd_real sloppy_div(const dd_real &a, const dd_real &b); + static dd_real accurate_div(const dd_real &a, const dd_real &b); + + dd_real &operator/=(double a); + dd_real &operator/=(const dd_real &a); + + dd_real &operator=(double a); + dd_real &operator=(const char *s); + + dd_real operator^(int n); + static dd_real sqr(double d); + + static dd_real sqrt(double a); + + bool is_zero() const; + bool is_one() const; + bool is_positive() const; + bool is_negative() const; + + explicit operator bool() const; // new + explicit operator double() const; // new + + static dd_real rand(void); + + void to_digits(char *s, int &expn, int precision = _ndigits) const; + void write(char *s, int len, int precision = _ndigits, + bool showpos = false, bool uppercase = false) const; + std::string to_string(int precision = _ndigits, int width = 0, + std::ios_base::fmtflags fmt = static_cast(0), + bool showpos = false, bool uppercase = false, char fill = ' ') const; + int read(const char *s, dd_real &a); + + /* Debugging Methods */ + void dump(const std::string &name, std::ostream &os = std::cerr) const; + void dump_bits(const std::string &name, + std::ostream &os = std::cerr) const; + + static dd_real debug_rand(); +}; + + +namespace std { + template <> + class numeric_limits : public numeric_limits { + public: + inline static double epsilon() { return dd_real::_eps; } + inline static dd_real max() { return dd_real::_max; } + inline static dd_real safe_max() { return dd_real::_safe_max; } + inline static double min() { return dd_real::_min_normalized; } + static const int digits = 104; + static const int digits10 = 31; + }; +} + +QD_API dd_real ddrand(void); +QD_API dd_real sqrt(const dd_real &a); + +QD_API dd_real polyeval(const dd_real *c, int n, const dd_real &x); +QD_API dd_real polyroot(const dd_real *c, int n, + const dd_real &x0, int max_iter = 32, double thresh = 0.0); + +QD_API inline bool isnan(const dd_real &a) { return a.isnan(); } +QD_API inline bool isfinite(const dd_real &a) { return a.isfinite(); } +QD_API inline bool isinf(const dd_real &a) { return a.isinf(); } + +/* Computes dd * d where d is known to be a power of 2. */ +QD_API dd_real mul_pwr2(const dd_real &dd, double d); + +QD_API dd_real operator+(const dd_real &a, double b); +QD_API dd_real operator+(double a, const dd_real &b); +QD_API dd_real operator+(const dd_real &a, const dd_real &b); + +QD_API dd_real operator-(const dd_real &a, double b); +QD_API dd_real operator-(double a, const dd_real &b); +QD_API dd_real operator-(const dd_real &a, const dd_real &b); + +QD_API dd_real operator*(const dd_real &a, double b); +QD_API dd_real operator*(double a, const dd_real &b); +QD_API dd_real operator*(const dd_real &a, const dd_real &b); + +QD_API dd_real operator/(const dd_real &a, double b); +QD_API dd_real operator/(double a, const dd_real &b); +QD_API dd_real operator/(const dd_real &a, const dd_real &b); + +QD_API dd_real inv(const dd_real &a); + +QD_API dd_real rem(const dd_real &a, const dd_real &b); +QD_API dd_real drem(const dd_real &a, const dd_real &b); +QD_API dd_real divrem(const dd_real &a, const dd_real &b, dd_real &r); + +QD_API dd_real pow(const dd_real &a, int n); +QD_API dd_real pow(const dd_real &a, const dd_real &b); +QD_API dd_real npwr(const dd_real &a, int n); +QD_API dd_real sqr(const dd_real &a); + +QD_API dd_real sqrt(const dd_real &a); +QD_API dd_real nroot(const dd_real &a, int n); + +QD_API bool operator==(const dd_real &a, double b); +QD_API bool operator==(double a, const dd_real &b); +QD_API bool operator==(const dd_real &a, const dd_real &b); + +QD_API bool operator<=(const dd_real &a, double b); +QD_API bool operator<=(double a, const dd_real &b); +QD_API bool operator<=(const dd_real &a, const dd_real &b); + +QD_API bool operator>=(const dd_real &a, double b); +QD_API bool operator>=(double a, const dd_real &b); +QD_API bool operator>=(const dd_real &a, const dd_real &b); + +QD_API bool operator<(const dd_real &a, double b); +QD_API bool operator<(double a, const dd_real &b); +QD_API bool operator<(const dd_real &a, const dd_real &b); + +QD_API bool operator>(const dd_real &a, double b); +QD_API bool operator>(double a, const dd_real &b); +QD_API bool operator>(const dd_real &a, const dd_real &b); + +QD_API bool operator!=(const dd_real &a, double b); +QD_API bool operator!=(double a, const dd_real &b); +QD_API bool operator!=(const dd_real &a, const dd_real &b); + +QD_API dd_real nint(const dd_real &a); +QD_API dd_real floor(const dd_real &a); +QD_API dd_real ceil(const dd_real &a); +QD_API dd_real aint(const dd_real &a); + +QD_API dd_real ddrand(void); + +double to_double(const dd_real &a); +int to_int(const dd_real &a); + +QD_API dd_real exp(const dd_real &a); +QD_API dd_real ldexp(const dd_real &a, int exp); +QD_API dd_real log(const dd_real &a); +QD_API dd_real log10(const dd_real &a); + +QD_API dd_real sin(const dd_real &a); +QD_API dd_real cos(const dd_real &a); +QD_API dd_real tan(const dd_real &a); +QD_API void sincos(const dd_real &a, dd_real &sin_a, dd_real &cos_a); + +QD_API dd_real asin(const dd_real &a); +QD_API dd_real acos(const dd_real &a); +QD_API dd_real atan(const dd_real &a); +QD_API dd_real atan2(const dd_real &y, const dd_real &x); + +QD_API dd_real sinh(const dd_real &a); +QD_API dd_real cosh(const dd_real &a); +QD_API dd_real tanh(const dd_real &a); +QD_API void sincosh(const dd_real &a, + dd_real &sinh_a, dd_real &cosh_a); + +QD_API dd_real asinh(const dd_real &a); +QD_API dd_real acosh(const dd_real &a); +QD_API dd_real atanh(const dd_real &a); + +QD_API dd_real fabs(const dd_real &a); +QD_API dd_real abs(const dd_real &a); /* same as fabs */ + +QD_API dd_real fmod(const dd_real &a, const dd_real &b); + +QD_API std::ostream& operator<<(std::ostream &s, const dd_real &a); +QD_API std::istream& operator>>(std::istream &s, dd_real &a); +#ifdef QD_INLINE +#include "dd_inline.h" +#endif + +#endif /* _QD_DD_REAL_H */ + diff --git a/src/external/PackedCSparse/qd/fpu.cc b/src/external/PackedCSparse/qd/fpu.cc new file mode 100644 index 00000000..96ddc488 --- /dev/null +++ b/src/external/PackedCSparse/qd/fpu.cc @@ -0,0 +1,124 @@ +/* + * src/fpu.cc + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2001 + * + * Contains functions to set and restore the round-to-double flag in the + * control word of a x86 FPU. + */ + +#include "qd_config.h" +#include "fpu.h" + +#ifdef X86 +#ifdef _WIN32 +#include +#else + +#ifdef HAVE_FPU_CONTROL_H +#include +#endif + +#ifndef _FPU_GETCW +#define _FPU_GETCW(x) asm volatile ("fnstcw %0":"=m" (x)); +#endif + +#ifndef _FPU_SETCW +#define _FPU_SETCW(x) asm volatile ("fldcw %0": :"m" (x)); +#endif + +#ifndef _FPU_EXTENDED +#define _FPU_EXTENDED 0x0300 +#endif + +#ifndef _FPU_DOUBLE +#define _FPU_DOUBLE 0x0200 +#endif + +#endif +#endif /* X86 */ + +extern "C" { + +void fpu_fix_start(unsigned int *old_cw) { +#ifdef X86 +#ifdef _WIN32 +#ifdef __BORLANDC__ + /* Win 32 Borland C */ + unsigned short cw = _control87(0, 0); + _control87(0x0200, 0x0300); + if (old_cw) { + *old_cw = cw; + } +#else + /* Win 32 MSVC */ + unsigned int cw = _control87(0, 0); + _control87(0x00010000, 0x00030000); + if (old_cw) { + *old_cw = cw; + } +#endif +#else + /* Linux */ + volatile unsigned short cw, new_cw; + _FPU_GETCW(cw); + + new_cw = (cw & ~_FPU_EXTENDED) | _FPU_DOUBLE; + _FPU_SETCW(new_cw); + + if (old_cw) { + *old_cw = cw; + } +#endif +#endif +} + +void fpu_fix_end(unsigned int *old_cw) { +#ifdef X86 +#ifdef _WIN32 + +#ifdef __BORLANDC__ + /* Win 32 Borland C */ + if (old_cw) { + unsigned short cw = (unsigned short) *old_cw; + _control87(cw, 0xFFFF); + } +#else + /* Win 32 MSVC */ + if (old_cw) { + _control87(*old_cw, 0xFFFFFFFF); + } +#endif + +#else + /* Linux */ + if (old_cw) { + int cw; + cw = *old_cw; + _FPU_SETCW(cw); + } +#endif +#endif +} + +#ifdef HAVE_FORTRAN + +#define f_fpu_fix_start FC_FUNC_(f_fpu_fix_start, F_FPU_FIX_START) +#define f_fpu_fix_end FC_FUNC_(f_fpu_fix_end, F_FPU_FIX_END) + +void f_fpu_fix_start(unsigned int *old_cw) { + fpu_fix_start(old_cw); +} + +void f_fpu_fix_end(unsigned int *old_cw) { + fpu_fix_end(old_cw); +} + +#endif + +} + diff --git a/src/external/PackedCSparse/qd/fpu.h b/src/external/PackedCSparse/qd/fpu.h new file mode 100644 index 00000000..35eab18c --- /dev/null +++ b/src/external/PackedCSparse/qd/fpu.h @@ -0,0 +1,39 @@ +/* + * include/fpu.h + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2001 + * + * Contains functions to set and restore the round-to-double flag in the + * control word of a x86 FPU. The algorithms in the double-double and + * quad-double package does not function with the extended mode found in + * these FPU. + */ +#ifndef _QD_FPU_H +#define _QD_FPU_H + +#include "qd_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Set the round-to-double flag, and save the old control word in old_cw. + * If old_cw is NULL, the old control word is not saved. + */ +QD_API void fpu_fix_start(unsigned int *old_cw); + +/* + * Restore the control word. + */ +QD_API void fpu_fix_end(unsigned int *old_cw); + +#ifdef __cplusplus +} +#endif + +#endif /* _QD_FPU_H */ diff --git a/src/external/PackedCSparse/qd/inline.h b/src/external/PackedCSparse/qd/inline.h new file mode 100644 index 00000000..52425545 --- /dev/null +++ b/src/external/PackedCSparse/qd/inline.h @@ -0,0 +1,143 @@ +/* + * include/inline.h + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2001 + * + * This file contains the basic functions used both by double-double + * and quad-double package. These are declared as inline functions as + * they are the smallest building blocks of the double-double and + * quad-double arithmetic. + */ +#ifndef _QD_INLINE_H +#define _QD_INLINE_H + +#define _QD_SPLITTER 134217729.0 // = 2^27 + 1 +#define _QD_SPLIT_THRESH 6.69692879491417e+299 // = 2^996 + +#ifdef QD_VACPP_BUILTINS_H +/* For VisualAge C++ __fmadd */ +#include +#endif + +#include +#include + +namespace qd { + +static const double _d_nan = std::numeric_limits::quiet_NaN(); +static const double _d_inf = std::numeric_limits::infinity(); + +/*********** Basic Functions ************/ +/* Computes fl(a+b) and err(a+b). Assumes |a| >= |b|. */ +inline double quick_two_sum(double a, double b, double &err) { + double s = a + b; + err = b - (s - a); + return s; +} + +/* Computes fl(a-b) and err(a-b). Assumes |a| >= |b| */ +inline double quick_two_diff(double a, double b, double &err) { + double s = a - b; + err = (a - s) - b; + return s; +} + +/* Computes fl(a+b) and err(a+b). */ +inline double two_sum(double a, double b, double &err) { + double s = a + b; + double bb = s - a; + err = (a - (s - bb)) + (b - bb); + return s; +} + +/* Computes fl(a-b) and err(a-b). */ +inline double two_diff(double a, double b, double &err) { + double s = a - b; + double bb = s - a; + err = (a - (s - bb)) - (b + bb); + return s; +} + +#ifndef QD_FMS +/* Computes high word and lo word of a */ +inline void split(double a, double &hi, double &lo) { + double temp; + if (a > _QD_SPLIT_THRESH || a < -_QD_SPLIT_THRESH) { + a *= 3.7252902984619140625e-09; // 2^-28 + temp = _QD_SPLITTER * a; + hi = temp - (temp - a); + lo = a - hi; + hi *= 268435456.0; // 2^28 + lo *= 268435456.0; // 2^28 + } else { + temp = _QD_SPLITTER * a; + hi = temp - (temp - a); + lo = a - hi; + } +} +#endif + +/* Computes fl(a*b) and err(a*b). */ +inline double two_prod(double a, double b, double &err) { +#ifdef QD_FMS + double p = a * b; + err = QD_FMS(a, b, p); + return p; +#else + double a_hi, a_lo, b_hi, b_lo; + double p = a * b; + split(a, a_hi, a_lo); + split(b, b_hi, b_lo); + err = ((a_hi * b_hi - p) + a_hi * b_lo + a_lo * b_hi) + a_lo * b_lo; + return p; +#endif +} + +/* Computes fl(a*a) and err(a*a). Faster than the above method. */ +inline double two_sqr(double a, double &err) { +#ifdef QD_FMS + double p = a * a; + err = QD_FMS(a, a, p); + return p; +#else + double hi, lo; + double q = a * a; + split(a, hi, lo); + err = ((hi * hi - q) + 2.0 * hi * lo) + lo * lo; + return q; +#endif +} + +/* Computes the nearest integer to d. */ +inline double nint(double d) { + if (d == std::floor(d)) + return d; + return std::floor(d + 0.5); +} + +/* Computes the truncated integer. */ +inline double aint(double d) { + return (d >= 0.0) ? std::floor(d) : std::ceil(d); +} + +/* These are provided to give consistent + interface for double with double-double and quad-double. */ +inline void sincosh(double t, double &sinh_t, double &cosh_t) { + sinh_t = std::sinh(t); + cosh_t = std::cosh(t); +} + +inline double sqr(double t) { + return t * t; +} + +inline double to_double(double a) { return a; } +inline int to_int(double a) { return static_cast(a); } + +} + +#endif /* _QD_INLINE_H */ diff --git a/src/external/PackedCSparse/qd/qd.pdf b/src/external/PackedCSparse/qd/qd.pdf new file mode 100644 index 00000000..9525b8ca Binary files /dev/null and b/src/external/PackedCSparse/qd/qd.pdf differ diff --git a/src/external/PackedCSparse/qd/qd_config.h b/src/external/PackedCSparse/qd/qd_config.h new file mode 100644 index 00000000..d23e33a6 --- /dev/null +++ b/src/external/PackedCSparse/qd/qd_config.h @@ -0,0 +1,92 @@ +#include +#include + +/* include/qd/qd_config.h. Generated from qd_config.h.in by configure. */ +#ifndef _QD_QD_CONFIG_H +#define _QD_QD_CONFIG_H 1 + +#ifndef QD_API +#define QD_API /**/ +#endif + +/* Set to 1 if using VisualAge C++ compiler for __fmadd builtin. */ +#ifndef QD_VACPP_BUILTINS_H +/* #undef QD_VACPP_BUILTINS_H */ +#endif + +/* If fused multiply-add is available, define to correct macro for + using it. It is invoked as QD_FMA(a, b, c) to compute fl(a * b + c). + If correctly rounded multiply-add is not available (or if unsure), + keep it undefined.*/ +#ifndef QD_FMA +/* #undef QD_FMA */ +#endif + +/* If fused multiply-subtract is available, define to correct macro for + using it. It is invoked as QD_FMS(a, b, c) to compute fl(a * b - c). + If correctly rounded multiply-add is not available (or if unsure), + keep it undefined.*/ +#ifndef QD_FMS +#define QD_FMS(a, b, c) std::fma(a,b,-c) +/* #undef QD_FMS */ +#endif + +/* Set the following to 1 to define commonly used function + to be inlined. This should be set to 1 unless the compiler + does not support the "inline" keyword, or if building for + debugging purposes. */ +#ifndef QD_INLINE +#define QD_INLINE 1 +#endif + +/* Set the following to 1 to use ANSI C++ standard header files + such as cmath, iostream, etc. If set to zero, it will try to + include math.h, iostream.h, etc, instead. */ +#ifndef QD_HAVE_STD +#define QD_HAVE_STD 1 +#endif + +/* Set the following to 1 to make the addition and subtraction + to satisfy the IEEE-style error bound + + fl(a + b) = (1 + d) * (a + b) + + where |d| <= eps. If set to 0, the addition and subtraction + will satisfy the weaker Cray-style error bound + + fl(a + b) = (1 + d1) * a + (1 + d2) * b + + where |d1| <= eps and |d2| eps. */ +#ifndef QD_IEEE_ADD +/* #undef QD_IEEE_ADD */ +#endif + +/* Set the following to 1 to use slightly inaccurate but faster + version of multiplication. */ +#ifndef QD_SLOPPY_MUL +#define QD_SLOPPY_MUL 1 +#endif + +/* Set the following to 1 to use slightly inaccurate but faster + version of division. */ +#ifndef QD_SLOPPY_DIV +#define QD_SLOPPY_DIV 1 +#endif + +/* Define this macro to be the isfinite(x) function. */ +#ifndef QD_ISFINITE +#define QD_ISFINITE(x) std::isfinite(x) +#endif + +/* Define this macro to be the isinf(x) function. */ +#ifndef QD_ISINF +#define QD_ISINF(x) std::isinf(x) +#endif + +/* Define this macro to be the isnan(x) function. */ +#ifndef QD_ISNAN +#define QD_ISNAN(x) std::isnan(x) +#endif + + +#endif /* _QD_QD_CONFIG_H */ diff --git a/src/external/PackedCSparse/qd/qd_const.cc b/src/external/PackedCSparse/qd/qd_const.cc new file mode 100644 index 00000000..6f4e01d2 --- /dev/null +++ b/src/external/PackedCSparse/qd/qd_const.cc @@ -0,0 +1,62 @@ +/* + * src/qd_const.cc + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2001 + * + * Defines constants used in quad-double package. + */ +#include "qd_config.h" +#include "qd_real.h" + +/* Some useful constants. */ +const qd_real qd_real::_2pi = qd_real(6.283185307179586232e+00, + 2.449293598294706414e-16, + -5.989539619436679332e-33, + 2.224908441726730563e-49); +const qd_real qd_real::_pi = qd_real(3.141592653589793116e+00, + 1.224646799147353207e-16, + -2.994769809718339666e-33, + 1.112454220863365282e-49); +const qd_real qd_real::_pi2 = qd_real(1.570796326794896558e+00, + 6.123233995736766036e-17, + -1.497384904859169833e-33, + 5.562271104316826408e-50); +const qd_real qd_real::_pi4 = qd_real(7.853981633974482790e-01, + 3.061616997868383018e-17, + -7.486924524295849165e-34, + 2.781135552158413204e-50); +const qd_real qd_real::_3pi4 = qd_real(2.356194490192344837e+00, + 9.1848509936051484375e-17, + 3.9168984647504003225e-33, + -2.5867981632704860386e-49); +const qd_real qd_real::_e = qd_real(2.718281828459045091e+00, + 1.445646891729250158e-16, + -2.127717108038176765e-33, + 1.515630159841218954e-49); +const qd_real qd_real::_log2 = qd_real(6.931471805599452862e-01, + 2.319046813846299558e-17, + 5.707708438416212066e-34, + -3.582432210601811423e-50); +const qd_real qd_real::_log10 = qd_real(2.302585092994045901e+00, + -2.170756223382249351e-16, + -9.984262454465776570e-33, + -4.023357454450206379e-49); +const qd_real qd_real::_nan = qd_real(qd::_d_nan, qd::_d_nan, + qd::_d_nan, qd::_d_nan); +const qd_real qd_real::_inf = qd_real(qd::_d_inf, qd::_d_inf, + qd::_d_inf, qd::_d_inf); + +const double qd_real::_eps = 1.21543267145725e-63; // = 2^-209 +const double qd_real::_min_normalized = 1.6259745436952323e-260; // = 2^(-1022 + 3*53) +const qd_real qd_real::_max = qd_real( + 1.79769313486231570815e+308, 9.97920154767359795037e+291, + 5.53956966280111259858e+275, 3.07507889307840487279e+259); +const qd_real qd_real::_safe_max = qd_real( + 1.7976931080746007281e+308, 9.97920154767359795037e+291, + 5.53956966280111259858e+275, 3.07507889307840487279e+259); +const int qd_real::_ndigits = 62; + diff --git a/src/external/PackedCSparse/qd/qd_inline.h b/src/external/PackedCSparse/qd/qd_inline.h new file mode 100644 index 00000000..89ba275b --- /dev/null +++ b/src/external/PackedCSparse/qd/qd_inline.h @@ -0,0 +1,1047 @@ +/* + * include/qd_inline.h + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2001 + * + * Contains small functions (suitable for inlining) in the quad-double + * arithmetic package. + */ +#ifndef _QD_QD_INLINE_H +#define _QD_QD_INLINE_H + +#include +#include "inline.h" + +#ifndef QD_INLINE +#define inline +#endif + +/********** Constructors **********/ +inline qd_real::qd_real(double x0, double x1, double x2, double x3) { + x[0] = x0; + x[1] = x1; + x[2] = x2; + x[3] = x3; +} + +inline qd_real::qd_real(const double *xx) { + x[0] = xx[0]; + x[1] = xx[1]; + x[2] = xx[2]; + x[3] = xx[3]; +} + +inline qd_real::qd_real(double x0) { + x[0] = x0; + x[1] = x[2] = x[3] = 0.0; +} + +inline qd_real::qd_real() { + x[0] = 0.0; + x[1] = 0.0; + x[2] = 0.0; + x[3] = 0.0; +} + +inline qd_real::qd_real(const dd_real &a) { + x[0] = a._hi(); + x[1] = a._lo(); + x[2] = x[3] = 0.0; +} + +inline qd_real::qd_real(int i) { + x[0] = static_cast(i); + x[1] = x[2] = x[3] = 0.0; +} + +/********** Accessors **********/ +inline double qd_real::operator[](int i) const { + return x[i]; +} + +inline double &qd_real::operator[](int i) { + return x[i]; +} + +inline bool qd_real::isnan() const { + return QD_ISNAN(x[0]) || QD_ISNAN(x[1]) || QD_ISNAN(x[2]) || QD_ISNAN(x[3]); +} + +/********** Renormalization **********/ +namespace qd { +inline void quick_renorm(double &c0, double &c1, + double &c2, double &c3, double &c4) { + double t0, t1, t2, t3; + double s; + s = qd::quick_two_sum(c3, c4, t3); + s = qd::quick_two_sum(c2, s , t2); + s = qd::quick_two_sum(c1, s , t1); + c0 = qd::quick_two_sum(c0, s , t0); + + s = qd::quick_two_sum(t2, t3, t2); + s = qd::quick_two_sum(t1, s , t1); + c1 = qd::quick_two_sum(t0, s , t0); + + s = qd::quick_two_sum(t1, t2, t1); + c2 = qd::quick_two_sum(t0, s , t0); + + c3 = t0 + t1; +} + +inline void renorm(double &c0, double &c1, + double &c2, double &c3) { + double s0, s1, s2 = 0.0, s3 = 0.0; + + if (QD_ISINF(c0)) return; + + s0 = qd::quick_two_sum(c2, c3, c3); + s0 = qd::quick_two_sum(c1, s0, c2); + c0 = qd::quick_two_sum(c0, s0, c1); + + s0 = c0; + s1 = c1; + if (s1 != 0.0) { + s1 = qd::quick_two_sum(s1, c2, s2); + if (s2 != 0.0) + s2 = qd::quick_two_sum(s2, c3, s3); + else + s1 = qd::quick_two_sum(s1, c3, s2); + } else { + s0 = qd::quick_two_sum(s0, c2, s1); + if (s1 != 0.0) + s1 = qd::quick_two_sum(s1, c3, s2); + else + s0 = qd::quick_two_sum(s0, c3, s1); + } + + c0 = s0; + c1 = s1; + c2 = s2; + c3 = s3; +} + +inline void renorm(double &c0, double &c1, + double &c2, double &c3, double &c4) { + double s0, s1, s2 = 0.0, s3 = 0.0; + + if (QD_ISINF(c0)) return; + + s0 = qd::quick_two_sum(c3, c4, c4); + s0 = qd::quick_two_sum(c2, s0, c3); + s0 = qd::quick_two_sum(c1, s0, c2); + c0 = qd::quick_two_sum(c0, s0, c1); + + s0 = c0; + s1 = c1; + + if (s1 != 0.0) { + s1 = qd::quick_two_sum(s1, c2, s2); + if (s2 != 0.0) { + s2 = qd::quick_two_sum(s2, c3, s3); + if (s3 != 0.0) + s3 += c4; + else + s2 = qd::quick_two_sum(s2, c4, s3); + } else { + s1 = qd::quick_two_sum(s1, c3, s2); + if (s2 != 0.0) + s2 = qd::quick_two_sum(s2, c4, s3); + else + s1 = qd::quick_two_sum(s1, c4, s2); + } + } else { + s0 = qd::quick_two_sum(s0, c2, s1); + if (s1 != 0.0) { + s1 = qd::quick_two_sum(s1, c3, s2); + if (s2 != 0.0) + s2 = qd::quick_two_sum(s2, c4, s3); + else + s1 = qd::quick_two_sum(s1, c4, s2); + } else { + s0 = qd::quick_two_sum(s0, c3, s1); + if (s1 != 0.0) + s1 = qd::quick_two_sum(s1, c4, s2); + else + s0 = qd::quick_two_sum(s0, c4, s1); + } + } + + c0 = s0; + c1 = s1; + c2 = s2; + c3 = s3; +} +} + +inline void qd_real::renorm() { + qd::renorm(x[0], x[1], x[2], x[3]); +} + +inline void qd_real::renorm(double &e) { + qd::renorm(x[0], x[1], x[2], x[3], e); +} + + +/********** Additions ************/ +namespace qd { + +inline void three_sum(double &a, double &b, double &c) { + double t1, t2, t3; + t1 = qd::two_sum(a, b, t2); + a = qd::two_sum(c, t1, t3); + b = qd::two_sum(t2, t3, c); +} + +inline void three_sum2(double &a, double &b, double &c) { + double t1, t2, t3; + t1 = qd::two_sum(a, b, t2); + a = qd::two_sum(c, t1, t3); + b = t2 + t3; +} + +} + +/* quad-double + double */ +inline qd_real operator+(const qd_real &a, double b) { + double c0, c1, c2, c3; + double e; + + c0 = qd::two_sum(a[0], b, e); + c1 = qd::two_sum(a[1], e, e); + c2 = qd::two_sum(a[2], e, e); + c3 = qd::two_sum(a[3], e, e); + + qd::renorm(c0, c1, c2, c3, e); + + return qd_real(c0, c1, c2, c3); +} + +/* quad-double + double-double */ +inline qd_real operator+(const qd_real &a, const dd_real &b) { + + double s0, s1, s2, s3; + double t0, t1; + + s0 = qd::two_sum(a[0], b._hi(), t0); + s1 = qd::two_sum(a[1], b._lo(), t1); + + s1 = qd::two_sum(s1, t0, t0); + + s2 = a[2]; + qd::three_sum(s2, t0, t1); + + s3 = qd::two_sum(t0, a[3], t0); + t0 += t1; + + qd::renorm(s0, s1, s2, s3, t0); + return qd_real(s0, s1, s2, s3); +} + + +/* double + quad-double */ +inline qd_real operator+(double a, const qd_real &b) { + return (b + a); +} + +/* double-double + quad-double */ +inline qd_real operator+(const dd_real &a, const qd_real &b) { + return (b + a); +} + +namespace qd { + +/* s = quick_three_accum(a, b, c) adds c to the dd-pair (a, b). + * If the result does not fit in two doubles, then the sum is + * output into s and (a,b) contains the remainder. Otherwise + * s is zero and (a,b) contains the sum. */ +inline double quick_three_accum(double &a, double &b, double c) { + double s; + bool za, zb; + + s = qd::two_sum(b, c, b); + s = qd::two_sum(a, s, a); + + za = (a != 0.0); + zb = (b != 0.0); + + if (za && zb) + return s; + + if (!zb) { + b = a; + a = s; + } else { + a = s; + } + + return 0.0; +} + +} + +inline qd_real qd_real::ieee_add(const qd_real &a, const qd_real &b) { + int i, j, k; + double s, t; + double u, v; /* double-length accumulator */ + double x[4] = {0.0, 0.0, 0.0, 0.0}; + + i = j = k = 0; + if (std::abs(a[i]) > std::abs(b[j])) + u = a[i++]; + else + u = b[j++]; + if (std::abs(a[i]) > std::abs(b[j])) + v = a[i++]; + else + v = b[j++]; + + u = qd::quick_two_sum(u, v, v); + + while (k < 4) { + if (i >= 4 && j >= 4) { + x[k] = u; + if (k < 3) + x[++k] = v; + break; + } + + if (i >= 4) + t = b[j++]; + else if (j >= 4) + t = a[i++]; + else if (std::abs(a[i]) > std::abs(b[j])) { + t = a[i++]; + } else + t = b[j++]; + + s = qd::quick_three_accum(u, v, t); + + if (s != 0.0) { + x[k++] = s; + } + } + + /* add the rest. */ + for (k = i; k < 4; k++) + x[3] += a[k]; + for (k = j; k < 4; k++) + x[3] += b[k]; + + qd::renorm(x[0], x[1], x[2], x[3]); + return qd_real(x[0], x[1], x[2], x[3]); +} + +inline qd_real qd_real::sloppy_add(const qd_real &a, const qd_real &b) { + /* + double s0, s1, s2, s3; + double t0, t1, t2, t3; + + s0 = qd::two_sum(a[0], b[0], t0); + s1 = qd::two_sum(a[1], b[1], t1); + s2 = qd::two_sum(a[2], b[2], t2); + s3 = qd::two_sum(a[3], b[3], t3); + + s1 = qd::two_sum(s1, t0, t0); + qd::three_sum(s2, t0, t1); + qd::three_sum2(s3, t0, t2); + t0 = t0 + t1 + t3; + + qd::renorm(s0, s1, s2, s3, t0); + return qd_real(s0, s1, s2, s3, t0); + */ + + /* Same as above, but addition re-organized to minimize + data dependency ... unfortunately some compilers are + not very smart to do this automatically */ + double s0, s1, s2, s3; + double t0, t1, t2, t3; + + double v0, v1, v2, v3; + double u0, u1, u2, u3; + double w0, w1, w2, w3; + + s0 = a[0] + b[0]; + s1 = a[1] + b[1]; + s2 = a[2] + b[2]; + s3 = a[3] + b[3]; + + v0 = s0 - a[0]; + v1 = s1 - a[1]; + v2 = s2 - a[2]; + v3 = s3 - a[3]; + + u0 = s0 - v0; + u1 = s1 - v1; + u2 = s2 - v2; + u3 = s3 - v3; + + w0 = a[0] - u0; + w1 = a[1] - u1; + w2 = a[2] - u2; + w3 = a[3] - u3; + + u0 = b[0] - v0; + u1 = b[1] - v1; + u2 = b[2] - v2; + u3 = b[3] - v3; + + t0 = w0 + u0; + t1 = w1 + u1; + t2 = w2 + u2; + t3 = w3 + u3; + + s1 = qd::two_sum(s1, t0, t0); + qd::three_sum(s2, t0, t1); + qd::three_sum2(s3, t0, t2); + t0 = t0 + t1 + t3; + + /* renormalize */ + qd::renorm(s0, s1, s2, s3, t0); + return qd_real(s0, s1, s2, s3); +} + +/* quad-double + quad-double */ +inline qd_real operator+(const qd_real &a, const qd_real &b) { +#ifndef QD_IEEE_ADD + return qd_real::sloppy_add(a, b); +#else + return qd_real::ieee_add(a, b); +#endif +} + + + +/********** Self-Additions ************/ +/* quad-double += double */ +inline qd_real &qd_real::operator+=(double a) { + *this = *this + a; + return *this; +} + +/* quad-double += double-double */ +inline qd_real &qd_real::operator+=(const dd_real &a) { + *this = *this + a; + return *this; +} + +/* quad-double += quad-double */ +inline qd_real &qd_real::operator+=(const qd_real &a) { + *this = *this + a; + return *this; +} + +/********** Unary Minus **********/ +inline qd_real qd_real::operator-() const { + return qd_real(-x[0], -x[1], -x[2], -x[3]); +} + +/********** Subtractions **********/ +inline qd_real operator-(const qd_real &a, double b) { + return (a + (-b)); +} + +inline qd_real operator-(double a, const qd_real &b) { + return (a + (-b)); +} + +inline qd_real operator-(const qd_real &a, const dd_real &b) { + return (a + (-b)); +} + +inline qd_real operator-(const dd_real &a, const qd_real &b) { + return (a + (-b)); +} + +inline qd_real operator-(const qd_real &a, const qd_real &b) { + return (a + (-b)); +} + +/********** Self-Subtractions **********/ +inline qd_real &qd_real::operator-=(double a) { + return ((*this) += (-a)); +} + +inline qd_real &qd_real::operator-=(const dd_real &a) { + return ((*this) += (-a)); +} + +inline qd_real &qd_real::operator-=(const qd_real &a) { + return ((*this) += (-a)); +} + + +inline qd_real operator*(double a, const qd_real &b) { + return (b * a); +} + +inline qd_real operator*(const dd_real &a, const qd_real &b) { + return (b * a); +} + +inline qd_real mul_pwr2(const qd_real &a, double b) { + return qd_real(a[0] * b, a[1] * b, a[2] * b, a[3] * b); +} + +/********** Multiplications **********/ +inline qd_real operator*(const qd_real &a, double b) { + double p0, p1, p2, p3; + double q0, q1, q2; + double s0, s1, s2, s3, s4; + + p0 = qd::two_prod(a[0], b, q0); + p1 = qd::two_prod(a[1], b, q1); + p2 = qd::two_prod(a[2], b, q2); + p3 = a[3] * b; + + s0 = p0; + + s1 = qd::two_sum(q0, p1, s2); + + qd::three_sum(s2, q1, p2); + + qd::three_sum2(q1, q2, p3); + s3 = q1; + + s4 = q2 + p2; + + qd::renorm(s0, s1, s2, s3, s4); + return qd_real(s0, s1, s2, s3); + +} + +/* quad-double * double-double */ +/* a0 * b0 0 + a0 * b1 1 + a1 * b0 2 + a1 * b1 3 + a2 * b0 4 + a2 * b1 5 + a3 * b0 6 + a3 * b1 7 */ +inline qd_real operator*(const qd_real &a, const dd_real &b) { + double p0, p1, p2, p3, p4; + double q0, q1, q2, q3, q4; + double s0, s1, s2; + double t0, t1; + + p0 = qd::two_prod(a[0], b._hi(), q0); + p1 = qd::two_prod(a[0], b._lo(), q1); + p2 = qd::two_prod(a[1], b._hi(), q2); + p3 = qd::two_prod(a[1], b._lo(), q3); + p4 = qd::two_prod(a[2], b._hi(), q4); + + qd::three_sum(p1, p2, q0); + + /* Five-Three-Sum */ + qd::three_sum(p2, p3, p4); + q1 = qd::two_sum(q1, q2, q2); + s0 = qd::two_sum(p2, q1, t0); + s1 = qd::two_sum(p3, q2, t1); + s1 = qd::two_sum(s1, t0, t0); + s2 = t0 + t1 + p4; + p2 = s0; + + p3 = a[2] * b._hi() + a[3] * b._lo() + q3 + q4; + qd::three_sum2(p3, q0, s1); + p4 = q0 + s2; + + qd::renorm(p0, p1, p2, p3, p4); + return qd_real(p0, p1, p2, p3); +} + +/* quad-double * quad-double */ +/* a0 * b0 0 + a0 * b1 1 + a1 * b0 2 + a0 * b2 3 + a1 * b1 4 + a2 * b0 5 + a0 * b3 6 + a1 * b2 7 + a2 * b1 8 + a3 * b0 9 */ +inline qd_real qd_real::sloppy_mul(const qd_real &a, const qd_real &b) { + double p0, p1, p2, p3, p4, p5; + double q0, q1, q2, q3, q4, q5; + double t0, t1; + double s0, s1, s2; + + p0 = qd::two_prod(a[0], b[0], q0); + + p1 = qd::two_prod(a[0], b[1], q1); + p2 = qd::two_prod(a[1], b[0], q2); + + p3 = qd::two_prod(a[0], b[2], q3); + p4 = qd::two_prod(a[1], b[1], q4); + p5 = qd::two_prod(a[2], b[0], q5); + + /* Start Accumulation */ + qd::three_sum(p1, p2, q0); + + /* Six-Three Sum of p2, q1, q2, p3, p4, p5. */ + qd::three_sum(p2, q1, q2); + qd::three_sum(p3, p4, p5); + /* compute (s0, s1, s2) = (p2, q1, q2) + (p3, p4, p5). */ + s0 = qd::two_sum(p2, p3, t0); + s1 = qd::two_sum(q1, p4, t1); + s2 = q2 + p5; + s1 = qd::two_sum(s1, t0, t0); + s2 += (t0 + t1); + + /* O(eps^3) order terms */ + s1 += a[0]*b[3] + a[1]*b[2] + a[2]*b[1] + a[3]*b[0] + q0 + q3 + q4 + q5; + qd::renorm(p0, p1, s0, s1, s2); + return qd_real(p0, p1, s0, s1); +} + +inline qd_real qd_real::accurate_mul(const qd_real &a, const qd_real &b) { + double p0, p1, p2, p3, p4, p5; + double q0, q1, q2, q3, q4, q5; + double p6, p7, p8, p9; + double q6, q7, q8, q9; + double r0, r1; + double t0, t1; + double s0, s1, s2; + + p0 = qd::two_prod(a[0], b[0], q0); + + p1 = qd::two_prod(a[0], b[1], q1); + p2 = qd::two_prod(a[1], b[0], q2); + + p3 = qd::two_prod(a[0], b[2], q3); + p4 = qd::two_prod(a[1], b[1], q4); + p5 = qd::two_prod(a[2], b[0], q5); + + /* Start Accumulation */ + qd::three_sum(p1, p2, q0); + + /* Six-Three Sum of p2, q1, q2, p3, p4, p5. */ + qd::three_sum(p2, q1, q2); + qd::three_sum(p3, p4, p5); + /* compute (s0, s1, s2) = (p2, q1, q2) + (p3, p4, p5). */ + s0 = qd::two_sum(p2, p3, t0); + s1 = qd::two_sum(q1, p4, t1); + s2 = q2 + p5; + s1 = qd::two_sum(s1, t0, t0); + s2 += (t0 + t1); + + /* O(eps^3) order terms */ + p6 = qd::two_prod(a[0], b[3], q6); + p7 = qd::two_prod(a[1], b[2], q7); + p8 = qd::two_prod(a[2], b[1], q8); + p9 = qd::two_prod(a[3], b[0], q9); + + /* Nine-Two-Sum of q0, s1, q3, q4, q5, p6, p7, p8, p9. */ + q0 = qd::two_sum(q0, q3, q3); + q4 = qd::two_sum(q4, q5, q5); + p6 = qd::two_sum(p6, p7, p7); + p8 = qd::two_sum(p8, p9, p9); + /* Compute (t0, t1) = (q0, q3) + (q4, q5). */ + t0 = qd::two_sum(q0, q4, t1); + t1 += (q3 + q5); + /* Compute (r0, r1) = (p6, p7) + (p8, p9). */ + r0 = qd::two_sum(p6, p8, r1); + r1 += (p7 + p9); + /* Compute (q3, q4) = (t0, t1) + (r0, r1). */ + q3 = qd::two_sum(t0, r0, q4); + q4 += (t1 + r1); + /* Compute (t0, t1) = (q3, q4) + s1. */ + t0 = qd::two_sum(q3, s1, t1); + t1 += q4; + + /* O(eps^4) terms -- Nine-One-Sum */ + t1 += a[1] * b[3] + a[2] * b[2] + a[3] * b[1] + q6 + q7 + q8 + q9 + s2; + + qd::renorm(p0, p1, s0, t0, t1); + return qd_real(p0, p1, s0, t0); +} + +inline qd_real operator*(const qd_real &a, const qd_real &b) { +#ifdef QD_SLOPPY_MUL + return qd_real::sloppy_mul(a, b); +#else + return qd_real::accurate_mul(a, b); +#endif +} + +/* quad-double ^ 2 = (x0 + x1 + x2 + x3) ^ 2 + = x0 ^ 2 + 2 x0 * x1 + (2 x0 * x2 + x1 ^ 2) + + (2 x0 * x3 + 2 x1 * x2) */ +inline qd_real sqr(const qd_real &a) { + double p0, p1, p2, p3, p4, p5; + double q0, q1, q2, q3; + double s0, s1; + double t0, t1; + + p0 = qd::two_sqr(a[0], q0); + p1 = qd::two_prod(2.0 * a[0], a[1], q1); + p2 = qd::two_prod(2.0 * a[0], a[2], q2); + p3 = qd::two_sqr(a[1], q3); + + p1 = qd::two_sum(q0, p1, q0); + + q0 = qd::two_sum(q0, q1, q1); + p2 = qd::two_sum(p2, p3, p3); + + s0 = qd::two_sum(q0, p2, t0); + s1 = qd::two_sum(q1, p3, t1); + + s1 = qd::two_sum(s1, t0, t0); + t0 += t1; + + s1 = qd::quick_two_sum(s1, t0, t0); + p2 = qd::quick_two_sum(s0, s1, t1); + p3 = qd::quick_two_sum(t1, t0, q0); + + p4 = 2.0 * a[0] * a[3]; + p5 = 2.0 * a[1] * a[2]; + + p4 = qd::two_sum(p4, p5, p5); + q2 = qd::two_sum(q2, q3, q3); + + t0 = qd::two_sum(p4, q2, t1); + t1 = t1 + p5 + q3; + + p3 = qd::two_sum(p3, t0, p4); + p4 = p4 + q0 + t1; + + qd::renorm(p0, p1, p2, p3, p4); + return qd_real(p0, p1, p2, p3); + +} + +/********** Self-Multiplication **********/ +/* quad-double *= double */ +inline qd_real &qd_real::operator*=(double a) { + *this = (*this * a); + return *this; +} + +/* quad-double *= double-double */ +inline qd_real &qd_real::operator*=(const dd_real &a) { + *this = (*this * a); + return *this; +} + +/* quad-double *= quad-double */ +inline qd_real &qd_real::operator*=(const qd_real &a) { + *this = *this * a; + return *this; +} + +inline qd_real operator/ (const qd_real &a, const dd_real &b) { +#ifdef QD_SLOPPY_DIV + return qd_real::sloppy_div(a, b); +#else + return qd_real::accurate_div(a, b); +#endif +} + +inline qd_real operator/(const qd_real &a, const qd_real &b) { +#ifdef QD_SLOPPY_DIV + return qd_real::sloppy_div(a, b); +#else + return qd_real::accurate_div(a, b); +#endif +} + +/* double / quad-double */ +inline qd_real operator/(double a, const qd_real &b) { + return qd_real(a) / b; +} + +/* double-double / quad-double */ +inline qd_real operator/(const dd_real &a, const qd_real &b) { + return qd_real(a) / b; +} + +/********** Self-Divisions **********/ +/* quad-double /= double */ +inline qd_real &qd_real::operator/=(double a) { + *this = (*this / a); + return *this; +} + +/* quad-double /= double-double */ +inline qd_real &qd_real::operator/=(const dd_real &a) { + *this = (*this / a); + return *this; +} + +/* quad-double /= quad-double */ +inline qd_real &qd_real::operator/=(const qd_real &a) { + *this = (*this / a); + return *this; +} + + +/********** Exponentiation **********/ +inline qd_real qd_real::operator^(int n) const { + return pow(*this, n); +} + +/********** Miscellaneous **********/ +inline qd_real abs(const qd_real &a) { + return (a[0] < 0.0) ? -a : a; +} + +inline qd_real fabs(const qd_real &a) { + return abs(a); +} + +/* Quick version. May be off by one when qd is very close + to the middle of two integers. */ +inline qd_real quick_nint(const qd_real &a) { + qd_real r = qd_real(qd::nint(a[0]), qd::nint(a[1]), + qd::nint(a[2]), qd::nint(a[3])); + r.renorm(); + return r; +} + +/*********** Assignments ************/ +/* quad-double = double */ +inline qd_real &qd_real::operator=(double a) { + x[0] = a; + x[1] = x[2] = x[3] = 0.0; + return *this; +} + +/* quad-double = double-double */ +inline qd_real &qd_real::operator=(const dd_real &a) { + x[0] = a._hi(); + x[1] = a._lo(); + x[2] = x[3] = 0.0; + return *this; +} + +/********** Equality Comparison **********/ +inline bool operator==(const qd_real &a, double b) { + return (a[0] == b && a[1] == 0.0 && a[2] == 0.0 && a[3] == 0.0); +} + +inline bool operator==(double a, const qd_real &b) { + return (b == a); +} + +inline bool operator==(const qd_real &a, const dd_real &b) { + return (a[0] == b._hi() && a[1] == b._lo() && + a[2] == 0.0 && a[3] == 0.0); +} + +inline bool operator==(const dd_real &a, const qd_real &b) { + return (b == a); +} + +inline bool operator==(const qd_real &a, const qd_real &b) { + return (a[0] == b[0] && a[1] == b[1] && + a[2] == b[2] && a[3] == b[3]); +} + + +/********** Less-Than Comparison ***********/ +inline bool operator<(const qd_real &a, double b) { + return (a[0] < b || (a[0] == b && a[1] < 0.0)); +} + +inline bool operator<(double a, const qd_real &b) { + return (b > a); +} + +inline bool operator<(const qd_real &a, const dd_real &b) { + return (a[0] < b._hi() || + (a[0] == b._hi() && (a[1] < b._lo() || + (a[1] == b._lo() && a[2] < 0.0)))); +} + +inline bool operator<(const dd_real &a, const qd_real &b) { + return (b > a); +} + +inline bool operator<(const qd_real &a, const qd_real &b) { + return (a[0] < b[0] || + (a[0] == b[0] && (a[1] < b[1] || + (a[1] == b[1] && (a[2] < b[2] || + (a[2] == b[2] && a[3] < b[3])))))); +} + +/********** Greater-Than Comparison ***********/ +inline bool operator>(const qd_real &a, double b) { + return (a[0] > b || (a[0] == b && a[1] > 0.0)); +} + +inline bool operator>(double a, const qd_real &b) { + return (b < a); +} + +inline bool operator>(const qd_real &a, const dd_real &b) { + return (a[0] > b._hi() || + (a[0] == b._hi() && (a[1] > b._lo() || + (a[1] == b._lo() && a[2] > 0.0)))); +} + +inline bool operator>(const dd_real &a, const qd_real &b) { + return (b < a); +} + +inline bool operator>(const qd_real &a, const qd_real &b) { + return (a[0] > b[0] || + (a[0] == b[0] && (a[1] > b[1] || + (a[1] == b[1] && (a[2] > b[2] || + (a[2] == b[2] && a[3] > b[3])))))); +} + + +/********** Less-Than-Or-Equal-To Comparison **********/ +inline bool operator<=(const qd_real &a, double b) { + return (a[0] < b || (a[0] == b && a[1] <= 0.0)); +} + +inline bool operator<=(double a, const qd_real &b) { + return (b >= a); +} + +inline bool operator<=(const qd_real &a, const dd_real &b) { + return (a[0] < b._hi() || + (a[0] == b._hi() && (a[1] < b._lo() || + (a[1] == b._lo() && a[2] <= 0.0)))); +} + +inline bool operator<=(const dd_real &a, const qd_real &b) { + return (b >= a); +} + +inline bool operator<=(const qd_real &a, const qd_real &b) { + return (a[0] < b[0] || + (a[0] == b[0] && (a[1] < b[1] || + (a[1] == b[1] && (a[2] < b[2] || + (a[2] == b[2] && a[3] <= b[3])))))); +} + +/********** Greater-Than-Or-Equal-To Comparison **********/ +inline bool operator>=(const qd_real &a, double b) { + return (a[0] > b || (a[0] == b && a[1] >= 0.0)); +} + +inline bool operator>=(double a, const qd_real &b) { + return (b <= a); +} + +inline bool operator>=(const qd_real &a, const dd_real &b) { + return (a[0] > b._hi() || + (a[0] == b._hi() && (a[1] > b._lo() || + (a[1] == b._lo() && a[2] >= 0.0)))); +} + +inline bool operator>=(const dd_real &a, const qd_real &b) { + return (b <= a); +} + +inline bool operator>=(const qd_real &a, const qd_real &b) { + return (a[0] > b[0] || + (a[0] == b[0] && (a[1] > b[1] || + (a[1] == b[1] && (a[2] > b[2] || + (a[2] == b[2] && a[3] >= b[3])))))); +} + + + +/********** Not-Equal-To Comparison **********/ +inline bool operator!=(const qd_real &a, double b) { + return !(a == b); +} + +inline bool operator!=(double a, const qd_real &b) { + return !(a == b); +} + +inline bool operator!=(const qd_real &a, const dd_real &b) { + return !(a == b); +} + +inline bool operator!=(const dd_real &a, const qd_real &b) { + return !(a == b); +} + +inline bool operator!=(const qd_real &a, const qd_real &b) { + return !(a == b); +} + + + +inline qd_real aint(const qd_real &a) { + return (a[0] >= 0) ? floor(a) : ceil(a); +} + +inline bool qd_real::is_zero() const { + return (x[0] == 0.0); +} + +inline bool qd_real::is_one() const { + return (x[0] == 1.0 && x[1] == 0.0 && x[2] == 0.0 && x[3] == 0.0); +} + +inline bool qd_real::is_positive() const { + return (x[0] > 0.0); +} + +inline bool qd_real::is_negative() const { + return (x[0] < 0.0); +} + +inline qd_real::operator bool() const { + return (x[0] != 0.0); +} + +inline qd_real::operator double() const { + return to_double(*this); +} + +inline dd_real to_dd_real(const qd_real &a) { + return dd_real(a[0], a[1]); +} + +inline double to_double(const qd_real &a) { + return a[0]; +} + +inline int to_int(const qd_real &a) { + return static_cast(a[0]); +} + +inline qd_real inv(const qd_real &qd) { + return 1.0 / qd; +} + +inline qd_real max(const qd_real &a, const qd_real &b) { + return (a > b) ? a : b; +} + +inline qd_real max(const qd_real &a, const qd_real &b, + const qd_real &c) { + return (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c); +} + +inline qd_real min(const qd_real &a, const qd_real &b) { + return (a < b) ? a : b; +} + +inline qd_real min(const qd_real &a, const qd_real &b, + const qd_real &c) { + return (a < b) ? ((a < c) ? a : c) : ((b < c) ? b : c); +} + +/* Random number generator */ +inline qd_real qd_real::rand() { + return qdrand(); +} + +inline qd_real ldexp(const qd_real &a, int n) { + return qd_real(std::ldexp(a[0], n), std::ldexp(a[1], n), + std::ldexp(a[2], n), std::ldexp(a[3], n)); +} + +#endif /* _QD_QD_INLINE_H */ diff --git a/src/external/PackedCSparse/qd/qd_real.cc b/src/external/PackedCSparse/qd/qd_real.cc new file mode 100644 index 00000000..02cb7aa3 --- /dev/null +++ b/src/external/PackedCSparse/qd/qd_real.cc @@ -0,0 +1,2624 @@ +/* + * src/qd_real.cc + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2007 + * + * Contains implementation of non-inlined functions of quad-double + * package. Inlined functions are found in qd_inline.h (in include directory). + */ +#include +#include +#include +#include +#include +#include +#include + +#include "qd_config.h" +#include "qd_real.h" +#include "util.h" + +#include "bits.h" + +#ifndef QD_INLINE +#include "qd_inline.h" +#endif + +using std::cout; +using std::cerr; +using std::endl; +using std::istream; +using std::ostream; +using std::ios_base; +using std::string; +using std::setw; + +using namespace qd; + +void qd_real::error(const char *msg) { + //if (msg) { cerr << "ERROR " << msg << endl; } +} + +/********** Multiplications **********/ + +qd_real nint(const qd_real &a) { + double x0, x1, x2, x3; + + x0 = nint(a[0]); + x1 = x2 = x3 = 0.0; + + if (x0 == a[0]) { + /* First double is already an integer. */ + x1 = nint(a[1]); + + if (x1 == a[1]) { + /* Second double is already an integer. */ + x2 = nint(a[2]); + + if (x2 == a[2]) { + /* Third double is already an integer. */ + x3 = nint(a[3]); + } else { + if (std::abs(x2 - a[2]) == 0.5 && a[3] < 0.0) { + x2 -= 1.0; + } + } + + } else { + if (std::abs(x1 - a[1]) == 0.5 && a[2] < 0.0) { + x1 -= 1.0; + } + } + + } else { + /* First double is not an integer. */ + if (std::abs(x0 - a[0]) == 0.5 && a[1] < 0.0) { + x0 -= 1.0; + } + } + + renorm(x0, x1, x2, x3); + return qd_real(x0, x1, x2, x3); +} + +qd_real floor(const qd_real &a) { + double x0, x1, x2, x3; + x1 = x2 = x3 = 0.0; + x0 = std::floor(a[0]); + + if (x0 == a[0]) { + x1 = std::floor(a[1]); + + if (x1 == a[1]) { + x2 = std::floor(a[2]); + + if (x2 == a[2]) { + x3 = std::floor(a[3]); + } + } + + renorm(x0, x1, x2, x3); + return qd_real(x0, x1, x2, x3); + } + + return qd_real(x0, x1, x2, x3); +} + +qd_real ceil(const qd_real &a) { + double x0, x1, x2, x3; + x1 = x2 = x3 = 0.0; + x0 = std::ceil(a[0]); + + if (x0 == a[0]) { + x1 = std::ceil(a[1]); + + if (x1 == a[1]) { + x2 = std::ceil(a[2]); + + if (x2 == a[2]) { + x3 = std::ceil(a[3]); + } + } + + renorm(x0, x1, x2, x3); + return qd_real(x0, x1, x2, x3); + } + + return qd_real(x0, x1, x2, x3); +} + + + +/********** Divisions **********/ +/* quad-double / double */ +qd_real operator/(const qd_real &a, double b) { + /* Strategy: compute approximate quotient using high order + doubles, and then correct it 3 times using the remainder. + (Analogous to long division.) */ + double t0, t1; + double q0, q1, q2, q3; + qd_real r; + + q0 = a[0] / b; /* approximate quotient */ + + /* Compute the remainder a - q0 * b */ + t0 = two_prod(q0, b, t1); + r = a - dd_real(t0, t1); + + /* Compute the first correction */ + q1 = r[0] / b; + t0 = two_prod(q1, b, t1); + r -= dd_real(t0, t1); + + /* Second correction to the quotient. */ + q2 = r[0] / b; + t0 = two_prod(q2, b, t1); + r -= dd_real(t0, t1); + + /* Final correction to the quotient. */ + q3 = r[0] / b; + + renorm(q0, q1, q2, q3); + return qd_real(q0, q1, q2, q3); +} + +qd_real::qd_real(const char *s) { + if (qd_real::read(s, *this)) { + qd_real::error("(qd_real::qd_real): INPUT ERROR."); + *this = qd_real::_nan; + } +} + +qd_real &qd_real::operator=(const char *s) { + if (qd_real::read(s, *this)) { + qd_real::error("(qd_real::operator=): INPUT ERROR."); + *this = qd_real::_nan; + } + return *this; +} + +istream &operator>>(istream &s, qd_real &qd) { + char str[255]; + s >> str; + qd = qd_real(str); + return s; +} + +ostream &operator<<(ostream &os, const qd_real &qd) { + bool showpos = (os.flags() & ios_base::showpos) != 0; + bool uppercase = (os.flags() & ios_base::uppercase) != 0; + return os << qd.to_string((int)os.precision(), (int)os.width(), os.flags(), + showpos, uppercase, os.fill()); +} + +/* Read a quad-double from s. */ +int qd_real::read(const char *s, qd_real &qd) { + const char *p = s; + char ch; + int sign = 0; + int point = -1; /* location of decimal point */ + int nd = 0; /* number of digits read */ + int e = 0; /* exponent. */ + bool done = false; + qd_real r = 0.0; /* number being read */ + + /* Skip any leading spaces */ + while (*p == ' ') p++; + + while (!done && (ch = *p) != '\0') { + if (ch >= '0' && ch <= '9') { + /* It's a digit */ + int d = ch - '0'; + r *= 10.0; + r += static_cast(d); + nd++; + } else { + /* Non-digit */ + switch (ch) { + case '.': + if (point >= 0) + return -1; /* we've already encountered a decimal point. */ + point = nd; + break; + case '-': + case '+': + if (sign != 0 || nd > 0) + return -1; /* we've already encountered a sign, or if its + not at first position. */ + sign = (ch == '-') ? -1 : 1; + break; + case 'E': + case 'e': + int nread; + nread = std::sscanf(p+1, "%d", &e); + done = true; + if (nread != 1) + return -1; /* read of exponent failed. */ + break; + case ' ': + done = true; + break; + default: + return -1; + + } + } + + p++; + } + + + + /* Adjust exponent to account for decimal point */ + if (point >= 0) { + e -= (nd - point); + } + + /* Multiply the the exponent */ + if (e != 0) { + r *= (qd_real(10.0) ^ e); + } + + qd = (sign < 0) ? -r : r; + return 0; +} + +void qd_real::to_digits(char *s, int &expn, int precision) const { + int D = precision + 1; /* number of digits to compute */ + + qd_real r = abs(*this); + int e; /* exponent */ + int i, d; + + if (x[0] == 0.0) { + /* this == 0.0 */ + expn = 0; + for (i = 0; i < precision; i++) s[i] = '0'; + return; + } + + /* First determine the (approximate) exponent. */ + e = static_cast(std::floor(std::log10(std::abs(x[0])))); + + if (e < -300) { + r *= qd_real(10.0) ^ 300; + r /= qd_real(10.0) ^ (e + 300); + } else if (e > 300) { + r = ldexp(r, -53); + r /= qd_real(10.0) ^ e; + r = ldexp(r, 53); + } else { + r /= qd_real(10.0) ^ e; + } + + /* Fix exponent if we are off by one */ + if (r >= 10.0) { + r /= 10.0; + e++; + } else if (r < 1.0) { + r *= 10.0; + e--; + } + + if (r >= 10.0 || r < 1.0) { + qd_real::error("(qd_real::to_digits): can't compute exponent."); + return; + } + + /* Extract the digits */ + for (i = 0; i < D; i++) { + d = static_cast(r[0]); + r -= d; + r *= 10.0; + + s[i] = static_cast(d + '0'); + } + + /* Fix out of range digits. */ + for (i = D-1; i > 0; i--) { + if (s[i] < '0') { + s[i-1]--; + s[i] += 10; + } else if (s[i] > '9') { + s[i-1]++; + s[i] -= 10; + } + } + + if (s[0] <= '0') { + qd_real::error("(qd_real::to_digits): non-positive leading digit."); + return; + } + + /* Round, handle carry */ + if (s[D-1] >= '5') { + s[D-2]++; + + i = D-2; + while (i > 0 && s[i] > '9') { + s[i] -= 10; + s[--i]++; + } + } + + /* If first digit is 10, shift everything. */ + if (s[0] > '9') { + e++; + for (i = precision; i >= 2; i--) s[i] = s[i-1]; + s[0] = '1'; + s[1] = '0'; + } + + s[precision] = 0; + expn = e; +} + +/* Writes the quad-double number into the character array s of length len. + The integer d specifies how many significant digits to write. + The string s must be able to hold at least (d+8) characters. + showpos indicates whether to use the + sign, and uppercase indicates + whether the E or e is to be used for the exponent. */ +void qd_real::write(char *s, int len, int precision, + bool showpos, bool uppercase) const { + string str = to_string(precision, 0, ios_base::scientific, showpos, uppercase); + strncpy(s, str.c_str(), len-1); + s[len-1] = 0; +} + +void round_string_qd(char *s, int precision, int *offset){ + /* + Input string must be all digits or errors will occur. + */ + + int i; + int D = precision ; + + /* Round, handle carry */ + if (D>0 && s[D] >= '5') { + s[D-1]++; + + i = D-1; + while (i > 0 && s[i] > '9') { + s[i] -= 10; + s[--i]++; + } + } + + /* If first digit is 10, shift everything. */ + if (s[0] > '9') { + // e++; // don't modify exponent here + for (i = precision; i >= 1; i--) s[i+1] = s[i]; + s[0] = '1'; + s[1] = '0'; + + (*offset)++ ; // now offset needs to be increased by one + precision++ ; + } + + s[precision] = 0; // add terminator for array +} + + +string qd_real::to_string(int precision, int width, ios_base::fmtflags fmt, + bool showpos, bool uppercase, char fill) const { + string s; + bool fixed = (fmt & ios_base::fixed) != 0; + bool sgn = true; + int i, e = 0; + + if (isinf()) { + if (*this < 0.0) + s += '-'; + else if (showpos) + s += '+'; + else + sgn = false; + s += uppercase ? "INF" : "inf"; + } else if (isnan()) { + s = uppercase ? "NAN" : "nan"; + sgn = false; + } else { + if (*this < 0.0) + s += '-'; + else if (showpos) + s += '+'; + else + sgn = false; + + if (*this == 0.0) { + /* Zero case */ + s += '0'; + if (precision > 0) { + s += '.'; + s.append(precision, '0'); + } + } else { + /* Non-zero case */ + int off = (fixed ? (1 + to_int(floor(log10(abs(*this))))) : 1); + int d = precision + off; + + int d_with_extra = d; + if(fixed) + d_with_extra = std::max(120, d); // longer than the max accuracy for DD + + // highly special case - fixed mode, precision is zero, abs(*this) < 1.0 + // without this trap a number like 0.9 printed fixed with 0 precision prints as 0 + // should be rounded to 1. + if(fixed && (precision == 0) && (abs(*this) < 1.0)){ + if(abs(*this) >= 0.5) + s += '1'; + else + s += '0'; + + return s; + } + + // handle near zero to working precision (but not exactly zero) + if (fixed && d <= 0) { + s += '0'; + if (precision > 0) { + s += '.'; + s.append(precision, '0'); + } + } else { // default + + char *t ; // = new char[d+1]; + int j; + + if(fixed){ + t = new char[d_with_extra+1]; + to_digits(t, e, d_with_extra); + } + else{ + t = new char[d+1]; + to_digits(t, e, d); + } + + off = e + 1; + + if (fixed) { + // fix the string if it's been computed incorrectly + // round here in the decimal string if required + round_string_qd(t, d, &off); + + if (off > 0) { + for (i = 0; i < off; i++) s += t[i]; + if (precision > 0) { + s += '.'; + for (j = 0; j < precision; j++, i++) s += t[i]; + } + } else { + s += "0."; + if (off < 0) s.append(-off, '0'); + for (i = 0; i < d; i++) s += t[i]; + } + } else { + s += t[0]; + if (precision > 0) s += '.'; + + for (i = 1; i <= precision; i++) + s += t[i]; + + } + delete [] t; + } + } + + // trap for improper offset with large values + // without this trap, output of values of the for 10^j - 1 fail for j > 28 + // and are output with the point in the wrong place, leading to a dramatically off value + if(fixed && (precision > 0)){ + // make sure that the value isn't dramatically larger + double from_string = atof(s.c_str()); + + // if this ratio is large, then we've got problems + if( fabs( from_string / this->x[0] ) > 3.0 ){ + + // loop on the string, find the point, move it up one + // don't act on the first character + for(i=1; i < (int)s.length(); i++){ + if(s[i] == '.'){ + s[i] = s[i-1] ; + s[i-1] = '.' ; + break; + } + } + + from_string = atof(s.c_str()); + // if this ratio is large, then the string has not been fixed + if( fabs( from_string / this->x[0] ) > 3.0 ){ + dd_real::error("Re-rounding unsuccessful in large number fixed point trap.") ; + } + } + } + + if (!fixed) { + /* Fill in exponent part */ + s += uppercase ? 'E' : 'e'; + append_expn(s, e); + } + } + + /* Fill in the blanks */ + size_t len = s.length(); + if (len < width) { + int delta = width - len; + if (fmt & ios_base::internal) { + if (sgn) + s.insert(static_cast(1), delta, fill); + else + s.insert(static_cast(0), delta, fill); + } else if (fmt & ios_base::left) { + s.append(delta, fill); + } else { + s.insert(static_cast(0), delta, fill); + } + } + + return s; +} + +/* Computes qd^n, where n is an integer. */ +qd_real pow(const qd_real &a, int n) { + if (n == 0) + return 1.0; + + qd_real r = a; /* odd-case multiplier */ + qd_real s = 1.0; /* current answer */ + int N = std::abs(n); + + if (N > 1) { + + /* Use binary exponentiation. */ + while (N > 0) { + if (N % 2 == 1) { + /* If odd, multiply by r. Note eventually N = 1, so this + eventually executes. */ + s *= r; + } + N /= 2; + if (N > 0) + r = sqr(r); + } + + } else { + s = r; + } + + if (n < 0) + return (1.0 / s); + + return s; +} + +qd_real pow(const qd_real &a, const qd_real &b) { + return exp(b * log(a)); +} + +qd_real npwr(const qd_real &a, int n) { + return pow(a, n); +} + +/* Debugging routines */ +void qd_real::dump_bits(const string &name, std::ostream &os) const { + string::size_type len = name.length(); + if (len > 0) { + os << name << " = "; + len += 3; + } + os << "[ "; + len += 2; + for (int j = 0; j < 4; j++) { + if (j > 0) for (string::size_type i = 0; i < len; i++) os << ' '; + print_double_info(os, x[j]); + if (j < 3) + os << endl; + else + os << " ]" << endl; + } +} + +void qd_real::dump(const string &name, std::ostream &os) const { + std::ios_base::fmtflags old_flags = os.flags(); + std::streamsize old_prec = os.precision(19); + os << std::scientific; + + string::size_type len = name.length(); + if (len > 0) { + os << name << " = "; + len += 3; + } + os << "[ "; + len += 2; + os << setw(27) << x[0] << ", " << setw(26) << x[1] << "," << endl; + for (string::size_type i = 0; i < len; i++) os << ' '; + os << setw(27) << x[2] << ", " << setw(26) << x[3] << " ]" << endl; + + os.precision(old_prec); + os.flags(old_flags); +} + +/* Divisions */ +/* quad-double / double-double */ +qd_real qd_real::sloppy_div(const qd_real &a, const dd_real &b) { + double q0, q1, q2, q3; + qd_real r; + qd_real qd_b(b); + + q0 = a[0] / b._hi(); + r = a - q0 * qd_b; + + q1 = r[0] / b._hi(); + r -= (q1 * qd_b); + + q2 = r[0] / b._hi(); + r -= (q2 * qd_b); + + q3 = r[0] / b._hi(); + + ::renorm(q0, q1, q2, q3); + return qd_real(q0, q1, q2, q3); +} + +qd_real qd_real::accurate_div(const qd_real &a, const dd_real &b) { + double q0, q1, q2, q3, q4; + qd_real r; + qd_real qd_b(b); + + q0 = a[0] / b._hi(); + r = a - q0 * qd_b; + + q1 = r[0] / b._hi(); + r -= (q1 * qd_b); + + q2 = r[0] / b._hi(); + r -= (q2 * qd_b); + + q3 = r[0] / b._hi(); + r -= (q3 * qd_b); + + q4 = r[0] / b._hi(); + + ::renorm(q0, q1, q2, q3, q4); + return qd_real(q0, q1, q2, q3); +} + +/* quad-double / quad-double */ +qd_real qd_real::sloppy_div(const qd_real &a, const qd_real &b) { + double q0, q1, q2, q3; + + qd_real r; + + q0 = a[0] / b[0]; + r = a - (b * q0); + + q1 = r[0] / b[0]; + r -= (b * q1); + + q2 = r[0] / b[0]; + r -= (b * q2); + + q3 = r[0] / b[0]; + + ::renorm(q0, q1, q2, q3); + + return qd_real(q0, q1, q2, q3); +} + +qd_real qd_real::accurate_div(const qd_real &a, const qd_real &b) { + double q0, q1, q2, q3; + + qd_real r; + + q0 = a[0] / b[0]; + r = a - (b * q0); + + q1 = r[0] / b[0]; + r -= (b * q1); + + q2 = r[0] / b[0]; + r -= (b * q2); + + q3 = r[0] / b[0]; + + r -= (b * q3); + double q4 = r[0] / b[0]; + + ::renorm(q0, q1, q2, q3, q4); + + return qd_real(q0, q1, q2, q3); +} + +QD_API qd_real sqrt(const qd_real &a) { + /* Strategy: + + Perform the following Newton iteration: + + x' = x + (1 - a * x^2) * x / 2; + + which converges to 1/sqrt(a), starting with the + double precision approximation to 1/sqrt(a). + Since Newton's iteration more or less doubles the + number of correct digits, we only need to perform it + twice. + */ + + if (a.is_zero()) + return 0.0; + + if (a.is_negative()) { + qd_real::error("(qd_real::sqrt): Negative argument."); + return qd_real::_nan; + } + + qd_real r = (1.0 / std::sqrt(a[0])); + qd_real h = mul_pwr2(a, 0.5); + + r += ((0.5 - h * sqr(r)) * r); + r += ((0.5 - h * sqr(r)) * r); + r += ((0.5 - h * sqr(r)) * r); + + r *= a; + return r; +} + + +/* Computes the n-th root of a */ +qd_real nroot(const qd_real &a, int n) { + /* Strategy: Use Newton's iteration to solve + + 1/(x^n) - a = 0 + + Newton iteration becomes + + x' = x + x * (1 - a * x^n) / n + + Since Newton's iteration converges quadratically, + we only need to perform it twice. + + */ + if (n <= 0) { + qd_real::error("(qd_real::nroot): N must be positive."); + return qd_real::_nan; + } + + if (n % 2 == 0 && a.is_negative()) { + qd_real::error("(qd_real::nroot): Negative argument."); + return qd_real::_nan; + } + + if (n == 1) { + return a; + } + if (n == 2) { + return sqrt(a); + } + if (a.is_zero()) { + return qd_real(0.0); + } + + + /* Note a^{-1/n} = exp(-log(a)/n) */ + qd_real r = abs(a); + qd_real x = std::exp(-std::log(r.x[0]) / n); + + /* Perform Newton's iteration. */ + double dbl_n = static_cast(n); + x += x * (1.0 - r * npwr(x, n)) / dbl_n; + x += x * (1.0 - r * npwr(x, n)) / dbl_n; + x += x * (1.0 - r * npwr(x, n)) / dbl_n; + if (a[0] < 0.0){ + x = -x; + } + return 1.0 / x; +} + +static const int n_inv_fact = 15; +static const qd_real inv_fact[n_inv_fact] = { + qd_real( 1.66666666666666657e-01, 9.25185853854297066e-18, + 5.13581318503262866e-34, 2.85094902409834186e-50), + qd_real( 4.16666666666666644e-02, 2.31296463463574266e-18, + 1.28395329625815716e-34, 7.12737256024585466e-51), + qd_real( 8.33333333333333322e-03, 1.15648231731787138e-19, + 1.60494162032269652e-36, 2.22730392507682967e-53), + qd_real( 1.38888888888888894e-03, -5.30054395437357706e-20, + -1.73868675534958776e-36, -1.63335621172300840e-52), + qd_real( 1.98412698412698413e-04, 1.72095582934207053e-22, + 1.49269123913941271e-40, 1.29470326746002471e-58), + qd_real( 2.48015873015873016e-05, 2.15119478667758816e-23, + 1.86586404892426588e-41, 1.61837908432503088e-59), + qd_real( 2.75573192239858925e-06, -1.85839327404647208e-22, + 8.49175460488199287e-39, -5.72661640789429621e-55), + qd_real( 2.75573192239858883e-07, 2.37677146222502973e-23, + -3.26318890334088294e-40, 1.61435111860404415e-56), + qd_real( 2.50521083854417202e-08, -1.44881407093591197e-24, + 2.04267351467144546e-41, -8.49632672007163175e-58), + qd_real( 2.08767569878681002e-09, -1.20734505911325997e-25, + 1.70222792889287100e-42, 1.41609532150396700e-58), + qd_real( 1.60590438368216133e-10, 1.25852945887520981e-26, + -5.31334602762985031e-43, 3.54021472597605528e-59), + qd_real( 1.14707455977297245e-11, 2.06555127528307454e-28, + 6.88907923246664603e-45, 5.72920002655109095e-61), + qd_real( 7.64716373181981641e-13, 7.03872877733453001e-30, + -7.82753927716258345e-48, 1.92138649443790242e-64), + qd_real( 4.77947733238738525e-14, 4.39920548583408126e-31, + -4.89221204822661465e-49, 1.20086655902368901e-65), + qd_real( 2.81145725434552060e-15, 1.65088427308614326e-31, + -2.87777179307447918e-50, 4.27110689256293549e-67) +}; + +qd_real exp(const qd_real &a) { + /* Strategy: We first reduce the size of x by noting that + + exp(kr + m * log(2)) = 2^m * exp(r)^k + + where m and k are integers. By choosing m appropriately + we can make |kr| <= log(2) / 2 = 0.347. Then exp(r) is + evaluated using the familiar Taylor series. Reducing the + argument substantially speeds up the convergence. */ + + const double k = ldexp(1.0, 16); + const double inv_k = 1.0 / k; + + if (a[0] <= -709.0) + return 0.0; + + if (a[0] >= 709.0) + return qd_real::_inf; + + if (a.is_zero()) + return 1.0; + + if (a.is_one()) + return qd_real::_e; + + double m = std::floor(a.x[0] / qd_real::_log2.x[0] + 0.5); + qd_real r = mul_pwr2(a - qd_real::_log2 * m, inv_k); + qd_real s, p, t; + double thresh = inv_k * qd_real::_eps; + + p = sqr(r); + s = r + mul_pwr2(p, 0.5); + int i = 0; + do { + p *= r; + t = p * inv_fact[i++]; + s += t; + } while (std::abs(to_double(t)) > thresh && i < 9); + + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s = mul_pwr2(s, 2.0) + sqr(s); + s += 1.0; + return ldexp(s, static_cast(m)); +} + +/* Logarithm. Computes log(x) in quad-double precision. + This is a natural logarithm (i.e., base e). */ +qd_real log(const qd_real &a) { + /* Strategy. The Taylor series for log converges much more + slowly than that of exp, due to the lack of the factorial + term in the denominator. Hence this routine instead tries + to determine the root of the function + + f(x) = exp(x) - a + + using Newton iteration. The iteration is given by + + x' = x - f(x)/f'(x) + = x - (1 - a * exp(-x)) + = x + a * exp(-x) - 1. + + Two iteration is needed, since Newton's iteration + approximately doubles the number of digits per iteration. */ + + if (a.is_one()) { + return 0.0; + } + + if (a[0] <= 0.0) { + qd_real::error("(qd_real::log): Non-positive argument."); + return qd_real::_nan; + } + + if (a[0] == 0.0) { + return -qd_real::_inf; + } + + qd_real x = std::log(a[0]); /* Initial approximation */ + + x = x + a * exp(-x) - 1.0; + x = x + a * exp(-x) - 1.0; + x = x + a * exp(-x) - 1.0; + + return x; +} + +qd_real log10(const qd_real &a) { + return log(a) / qd_real::_log10; +} + +static const qd_real _pi1024 = qd_real( + 3.067961575771282340e-03, 1.195944139792337116e-19, + -2.924579892303066080e-36, 1.086381075061880158e-52); + +/* Table of sin(k * pi/1024) and cos(k * pi/1024). */ +static const qd_real sin_table [] = { + qd_real( 3.0679567629659761e-03, 1.2690279085455925e-19, + 5.2879464245328389e-36, -1.7820334081955298e-52), + qd_real( 6.1358846491544753e-03, 9.0545257482474933e-20, + 1.6260113133745320e-37, -9.7492001208767410e-55), + qd_real( 9.2037547820598194e-03, -1.2136591693535934e-19, + 5.5696903949425567e-36, 1.2505635791936951e-52), + qd_real( 1.2271538285719925e-02, 6.9197907640283170e-19, + -4.0203726713435555e-36, -2.0688703606952816e-52), + qd_real( 1.5339206284988102e-02, -8.4462578865401696e-19, + 4.6535897505058629e-35, -1.3923682978570467e-51), + qd_real( 1.8406729905804820e-02, 7.4195533812833160e-19, + 3.9068476486787607e-35, 3.6393321292898614e-52), + qd_real( 2.1474080275469508e-02, -4.5407960207688566e-19, + -2.2031770119723005e-35, 1.2709814654833741e-51), + qd_real( 2.4541228522912288e-02, -9.1868490125778782e-20, + 4.8706148704467061e-36, -2.8153947855469224e-52), + qd_real( 2.7608145778965743e-02, -1.5932358831389269e-18, + -7.0475416242776030e-35, -2.7518494176602744e-51), + qd_real( 3.0674803176636626e-02, -1.6936054844107918e-20, + -2.0039543064442544e-36, -1.6267505108658196e-52), + qd_real( 3.3741171851377587e-02, -2.0096074292368340e-18, + -1.3548237016537134e-34, 6.5554881875899973e-51), + qd_real( 3.6807222941358832e-02, 6.1060088803529842e-19, + -4.0448721259852727e-35, -2.1111056765671495e-51), + qd_real( 3.9872927587739811e-02, 4.6657453481183289e-19, + 3.4119333562288684e-35, 2.4007534726187511e-51), + qd_real( 4.2938256934940820e-02, 2.8351940588660907e-18, + 1.6991309601186475e-34, 6.8026536098672629e-51), + qd_real( 4.6003182130914630e-02, -1.1182813940157788e-18, + 7.5235020270378946e-35, 4.1187304955493722e-52), + qd_real( 4.9067674327418015e-02, -6.7961037205182801e-19, + -4.4318868124718325e-35, -9.9376628132525316e-52), + qd_real( 5.2131704680283324e-02, -2.4243695291953779e-18, + -1.3675405320092298e-34, -8.3938137621145070e-51), + qd_real( 5.5195244349689941e-02, -1.3340299860891103e-18, + -3.4359574125665608e-35, 1.1911462755409369e-51), + qd_real( 5.8258264500435759e-02, 2.3299905496077492e-19, + 1.9376108990628660e-36, -5.1273775710095301e-53), + qd_real( 6.1320736302208578e-02, -5.1181134064638108e-19, + -4.2726335866706313e-35, 2.6368495557440691e-51), + qd_real( 6.4382630929857465e-02, -4.2325997000052705e-18, + 3.3260117711855937e-35, 1.4736267706718352e-51), + qd_real( 6.7443919563664065e-02, -6.9221796556983636e-18, + 1.5909286358911040e-34, -7.8828946891835218e-51), + qd_real( 7.0504573389613870e-02, -6.8552791107342883e-18, + -1.9961177630841580e-34, 2.0127129580485300e-50), + qd_real( 7.3564563599667426e-02, -2.7784941506273593e-18, + -9.1240375489852821e-35, -1.9589752023546795e-51), + qd_real( 7.6623861392031492e-02, 2.3253700287958801e-19, + -1.3186083921213440e-36, -4.9927872608099673e-53), + qd_real( 7.9682437971430126e-02, -4.4867664311373041e-18, + 2.8540789143650264e-34, 2.8491348583262741e-51), + qd_real( 8.2740264549375692e-02, 1.4735983530877760e-18, + 3.7284093452233713e-35, 2.9024430036724088e-52), + qd_real( 8.5797312344439894e-02, -3.3881893830684029e-18, + -1.6135529531508258e-34, 7.7294651620588049e-51), + qd_real( 8.8853552582524600e-02, -3.7501775830290691e-18, + 3.7543606373911573e-34, 2.2233701854451859e-50), + qd_real( 9.1908956497132724e-02, 4.7631594854274564e-18, + 1.5722874642939344e-34, -4.8464145447831456e-51), + qd_real( 9.4963495329639006e-02, -6.5885886400417564e-18, + -2.1371116991641965e-34, 1.3819370559249300e-50), + qd_real( 9.8017140329560604e-02, -1.6345823622442560e-18, + -1.3209238810006454e-35, -3.5691060049117942e-52), + qd_real( 1.0106986275482782e-01, 3.3164325719308656e-18, + -1.2004224885132282e-34, 7.2028828495418631e-51), + qd_real( 1.0412163387205457e-01, 6.5760254085385100e-18, + 1.7066246171219214e-34, -4.9499340996893514e-51), + qd_real( 1.0717242495680884e-01, 6.4424044279026198e-18, + -8.3956976499698139e-35, -4.0667730213318321e-51), + qd_real( 1.1022220729388306e-01, -5.6789503537823233e-19, + 1.0380274792383233e-35, 1.5213997918456695e-52), + qd_real( 1.1327095217756435e-01, 2.7100481012132900e-18, + 1.5323292999491619e-35, 4.9564432810360879e-52), + qd_real( 1.1631863091190477e-01, 1.0294914877509705e-18, + -9.3975734948993038e-35, 1.3534827323719708e-52), + qd_real( 1.1936521481099137e-01, -3.9500089391898506e-18, + 3.5317349978227311e-34, 1.8856046807012275e-51), + qd_real( 1.2241067519921620e-01, 2.8354501489965335e-18, + 1.8151655751493305e-34, -2.8716592177915192e-51), + qd_real( 1.2545498341154623e-01, 4.8686751763148235e-18, + 5.9878105258097936e-35, -3.3534629098722107e-51), + qd_real( 1.2849811079379317e-01, 3.8198603954988802e-18, + -1.8627501455947798e-34, -2.4308161133527791e-51), + qd_real( 1.3154002870288312e-01, -5.0039708262213813e-18, + -1.2983004159245552e-34, -4.6872034915794122e-51), + qd_real( 1.3458070850712620e-01, -9.1670359171480699e-18, + 1.5916493007073973e-34, 4.0237002484366833e-51), + qd_real( 1.3762012158648604e-01, 6.6253255866774482e-18, + -2.3746583031401459e-34, -9.3703876173093250e-52), + qd_real( 1.4065823933284924e-01, -7.9193932965524741e-18, + 6.0972464202108397e-34, 2.4566623241035797e-50), + qd_real( 1.4369503315029444e-01, 1.1472723016618666e-17, + -5.1884954557576435e-35, -4.2220684832186607e-51), + qd_real( 1.4673047445536175e-01, 3.7269471470465677e-18, + 3.7352398151250827e-34, -4.0881822289508634e-51), + qd_real( 1.4976453467732151e-01, 8.0812114131285151e-18, + 1.2979142554917325e-34, 9.9380667487736254e-51), + qd_real( 1.5279718525844344e-01, -7.6313573938416838e-18, + 5.7714690450284125e-34, -3.7731132582986687e-50), + qd_real( 1.5582839765426523e-01, 3.0351307187678221e-18, + -1.0976942315176184e-34, 7.8734647685257867e-51), + qd_real( 1.5885814333386145e-01, -4.0163200573859079e-18, + -9.2840580257628812e-35, -2.8567420029274875e-51), + qd_real( 1.6188639378011183e-01, 1.1850519643573528e-17, + -5.0440990519162957e-34, 3.0510028707928009e-50), + qd_real( 1.6491312048996992e-01, -7.0405288319166738e-19, + 3.3211107491245527e-35, 8.6663299254686031e-52), + qd_real( 1.6793829497473117e-01, 5.4284533721558139e-18, + -3.3263339336181369e-34, -1.8536367335123848e-50), + qd_real( 1.7096188876030122e-01, 9.1919980181759094e-18, + -6.7688743940982606e-34, -1.0377711384318389e-50), + qd_real( 1.7398387338746382e-01, 5.8151994618107928e-18, + -1.6751014298301606e-34, -6.6982259797164963e-51), + qd_real( 1.7700422041214875e-01, 6.7329300635408167e-18, + 2.8042736644246623e-34, 3.6786888232793599e-51), + qd_real( 1.8002290140569951e-01, 7.9701826047392143e-18, + -7.0765920110524977e-34, 1.9622512608461784e-50), + qd_real( 1.8303988795514095e-01, 7.7349918688637383e-18, + -4.4803769968145083e-34, 1.1201148793328890e-50), + qd_real( 1.8605515166344666e-01, -1.2564893007679552e-17, + 7.5953844248530810e-34, -3.8471695132415039e-51), + qd_real( 1.8906866414980622e-01, -7.6208955803527778e-18, + -4.4792298656662981e-34, -4.4136824096645007e-50), + qd_real( 1.9208039704989244e-01, 4.3348343941174903e-18, + -2.3404121848139937e-34, 1.5789970962611856e-50), + qd_real( 1.9509032201612828e-01, -7.9910790684617313e-18, + 6.1846270024220713e-34, -3.5840270918032937e-50), + qd_real( 1.9809841071795359e-01, -1.8434411800689445e-18, + 1.4139031318237285e-34, 1.0542811125343809e-50), + qd_real( 2.0110463484209190e-01, 1.1010032669300739e-17, + -3.9123576757413791e-34, 2.4084852500063531e-51), + qd_real( 2.0410896609281687e-01, 6.0941297773957752e-18, + -2.8275409970449641e-34, 4.6101008563532989e-51), + qd_real( 2.0711137619221856e-01, -1.0613362528971356e-17, + 2.2456805112690884e-34, 1.3483736125280904e-50), + qd_real( 2.1011183688046961e-01, 1.1561548476512844e-17, + 6.0355905610401254e-34, 3.3329909618405675e-50), + qd_real( 2.1311031991609136e-01, 1.2031873821063860e-17, + -3.4142699719695635e-34, -1.2436262780241778e-50), + qd_real( 2.1610679707621952e-01, -1.0111196082609117e-17, + 7.2789545335189643e-34, -2.9347540365258610e-50), + qd_real( 2.1910124015686980e-01, -3.6513812299150776e-19, + -2.3359499418606442e-35, 3.1785298198458653e-52), + qd_real( 2.2209362097320354e-01, -3.0337210995812162e-18, + 6.6654668033632998e-35, 2.0110862322656942e-51), + qd_real( 2.2508391135979283e-01, 3.9507040822556510e-18, + 2.4287993958305375e-35, 5.6662797513020322e-52), + qd_real( 2.2807208317088573e-01, 8.2361837339258012e-18, + 6.9786781316397937e-34, -6.4122962482639504e-51), + qd_real( 2.3105810828067111e-01, 1.0129787149761869e-17, + -6.9359234615816044e-34, -2.8877355604883782e-50), + qd_real( 2.3404195858354343e-01, -6.9922402696101173e-18, + -5.7323031922750280e-34, 5.3092579966872727e-51), + qd_real( 2.3702360599436720e-01, 8.8544852285039918e-18, + 1.3588480826354134e-34, 1.0381022520213867e-50), + qd_real( 2.4000302244874150e-01, -1.2137758975632164e-17, + -2.6448807731703891e-34, -1.9929733800670473e-51), + qd_real( 2.4298017990326390e-01, -8.7514315297196632e-18, + -6.5723260373079431e-34, -1.0333158083172177e-50), + qd_real( 2.4595505033579462e-01, -1.1129044052741832e-17, + 4.3805998202883397e-34, 1.2219399554686291e-50), + qd_real( 2.4892760574572018e-01, -8.1783436100020990e-18, + 5.5666875261111840e-34, 3.8080473058748167e-50), + qd_real( 2.5189781815421697e-01, -1.7591436032517039e-17, + -1.0959681232525285e-33, 5.6209426020232456e-50), + qd_real( 2.5486565960451457e-01, -1.3602299806901461e-19, + -6.0073844642762535e-36, -3.0072751311893878e-52), + qd_real( 2.5783110216215899e-01, 1.8480038630879957e-17, + 3.3201664714047599e-34, -5.5547819290576764e-51), + qd_real( 2.6079411791527551e-01, 4.2721420983550075e-18, + 5.6782126934777920e-35, 3.1428338084365397e-51), + qd_real( 2.6375467897483140e-01, -1.8837947680038700e-17, + 1.3720129045754794e-33, -8.2763406665966033e-50), + qd_real( 2.6671275747489837e-01, 2.0941222578826688e-17, + -1.1303466524727989e-33, 1.9954224050508963e-50), + qd_real( 2.6966832557291509e-01, 1.5765657618133259e-17, + -6.9696142173370086e-34, -4.0455346879146776e-50), + qd_real( 2.7262135544994898e-01, 7.8697166076387850e-18, + 6.6179388602933372e-35, -2.7642903696386267e-51), + qd_real( 2.7557181931095814e-01, 1.9320328962556582e-17, + 1.3932094180100280e-33, 1.3617253920018116e-50), + qd_real( 2.7851968938505312e-01, -1.0030273719543544e-17, + 7.2592115325689254e-34, -1.0068516296655851e-50), + qd_real( 2.8146493792575800e-01, -1.2322299641274009e-17, + -1.0564788706386435e-34, 7.5137424251265885e-51), + qd_real( 2.8440753721127182e-01, 2.2209268510661475e-17, + -9.1823095629523708e-34, -5.2192875308892218e-50), + qd_real( 2.8734745954472951e-01, 1.5461117367645717e-17, + -6.3263973663444076e-34, -2.2982538416476214e-50), + qd_real( 2.9028467725446239e-01, -1.8927978707774251e-17, + 1.1522953157142315e-33, 7.4738655654716596e-50), + qd_real( 2.9321916269425863e-01, 2.2385430811901833e-17, + 1.3662484646539680e-33, -4.2451325253996938e-50), + qd_real( 2.9615088824362384e-01, -2.0220736360876938e-17, + -7.9252212533920413e-35, -2.8990577729572470e-51), + qd_real( 2.9907982630804048e-01, 1.6701181609219447e-18, + 8.6091151117316292e-35, 3.9931286230012102e-52), + qd_real( 3.0200594931922808e-01, -1.7167666235262474e-17, + 2.3336182149008069e-34, 8.3025334555220004e-51), + qd_real( 3.0492922973540243e-01, -2.2989033898191262e-17, + -1.4598901099661133e-34, 3.7760487693121827e-51), + qd_real( 3.0784964004153487e-01, 2.7074088527245185e-17, + 1.2568858206899284e-33, 7.2931815105901645e-50), + qd_real( 3.1076715274961147e-01, 2.0887076364048513e-17, + -3.0130590791065942e-34, 1.3876739009935179e-51), + qd_real( 3.1368174039889146e-01, 1.4560447299968912e-17, + 3.6564186898011595e-34, 1.1654264734999375e-50), + qd_real( 3.1659337555616585e-01, 2.1435292512726283e-17, + 1.2338169231377316e-33, 3.3963542100989293e-50), + qd_real( 3.1950203081601569e-01, -1.3981562491096626e-17, + 8.1730000697411350e-34, -7.7671096270210952e-50), + qd_real( 3.2240767880106985e-01, -4.0519039937959398e-18, + 3.7438302780296796e-34, 8.7936731046639195e-51), + qd_real( 3.2531029216226293e-01, 7.9171249463765892e-18, + -6.7576622068146391e-35, 2.3021655066929538e-51), + qd_real( 3.2820984357909255e-01, -2.6693140719641896e-17, + 7.8928851447534788e-34, 2.5525163821987809e-51), + qd_real( 3.3110630575987643e-01, -2.7469465474778694e-17, + -1.3401245916610206e-33, 6.5531762489976163e-50), + qd_real( 3.3399965144200938e-01, 2.2598986806288142e-17, + 7.8063057192586115e-34, 2.0427600895486683e-50), + qd_real( 3.3688985339222005e-01, -4.2000940033475092e-19, + -2.9178652969985438e-36, -1.1597376437036749e-52), + qd_real( 3.3977688440682685e-01, 6.6028679499418282e-18, + 1.2575009988669683e-34, 2.5569067699008304e-51), + qd_real( 3.4266071731199438e-01, 1.9261518449306319e-17, + -9.2754189135990867e-34, 8.5439996687390166e-50), + qd_real( 3.4554132496398904e-01, 2.7251143672916123e-17, + 7.0138163601941737e-34, -1.4176292197454015e-50), + qd_real( 3.4841868024943456e-01, 3.6974420514204918e-18, + 3.5532146878499996e-34, 1.9565462544501322e-50), + qd_real( 3.5129275608556715e-01, -2.2670712098795844e-17, + -1.6994216673139631e-34, -1.2271556077284517e-50), + qd_real( 3.5416352542049040e-01, -1.6951763305764860e-17, + 1.2772331777814617e-33, -3.3703785435843310e-50), + qd_real( 3.5703096123343003e-01, -4.8218191137919166e-19, + -4.1672436994492361e-35, -7.1531167149364352e-52), + qd_real( 3.5989503653498817e-01, -1.7601687123839282e-17, + 1.3375125473046791e-33, 7.9467815593584340e-50), + qd_real( 3.6275572436739723e-01, -9.1668352663749849e-18, + -7.4317843956936735e-34, -2.0199582511804564e-50), + qd_real( 3.6561299780477385e-01, 1.6217898770457546e-17, + 1.1286970151961055e-33, -7.1825287318139010e-50), + qd_real( 3.6846682995337232e-01, 1.0463640796159268e-17, + 2.0554984738517304e-35, 1.0441861305618769e-51), + qd_real( 3.7131719395183754e-01, 3.4749239648238266e-19, + -7.5151053042866671e-37, -2.8153468438650851e-53), + qd_real( 3.7416406297145799e-01, 8.0114103761962118e-18, + 5.3429599813406052e-34, 1.0351378796539210e-50), + qd_real( 3.7700741021641826e-01, -2.7255302041956930e-18, + 6.3646586445018137e-35, 8.3048657176503559e-52), + qd_real( 3.7984720892405116e-01, 9.9151305855172370e-18, + 4.8761409697224886e-34, 1.4025084000776705e-50), + qd_real( 3.8268343236508978e-01, -1.0050772696461588e-17, + -2.0605316302806695e-34, -1.2717724698085205e-50), + qd_real( 3.8551605384391885e-01, 1.5177665396472313e-17, + 1.4198230518016535e-33, 5.8955167159904235e-50), + qd_real( 3.8834504669882630e-01, -1.0053770598398717e-17, + 7.5942999255057131e-34, -3.1967974046654219e-50), + qd_real( 3.9117038430225387e-01, 1.7997787858243995e-17, + -1.0613482402609856e-33, -5.4582148817791032e-50), + qd_real( 3.9399204006104810e-01, 9.7649241641239336e-18, + -2.1233599441284617e-34, -5.5529836795340819e-51), + qd_real( 3.9680998741671031e-01, 2.0545063670840126e-17, + 6.1347058801922842e-34, 1.0733788150636430e-50), + qd_real( 3.9962419984564684e-01, -1.5065497476189372e-17, + -9.9653258881867298e-34, -5.7524323712725355e-50), + qd_real( 4.0243465085941843e-01, 1.0902619339328270e-17, + 7.3998528125989765e-34, 2.2745784806823499e-50), + qd_real( 4.0524131400498986e-01, 9.9111401942899884e-18, + -2.5169070895434648e-34, 9.2772984818436573e-53), + qd_real( 4.0804416286497869e-01, -7.0006015137351311e-18, + -1.4108207334268228e-34, 1.5175546997577136e-52), + qd_real( 4.1084317105790397e-01, -2.4219835190355499e-17, + -1.1418902925313314e-33, -2.0996843165093468e-50), + qd_real( 4.1363831223843456e-01, -1.0393984940597871e-17, + -1.1481681174503880e-34, -2.0281052851028680e-51), + qd_real( 4.1642956009763721e-01, -2.5475580413131732e-17, + -3.4482678506112824e-34, 7.1788619351865480e-51), + qd_real( 4.1921688836322396e-01, -4.2232463750110590e-18, + -3.6053023045255790e-34, -2.2209673210025631e-50), + qd_real( 4.2200027079979968e-01, 4.3543266994128527e-18, + 3.1734310272251190e-34, -1.3573247980738668e-50), + qd_real( 4.2477968120910881e-01, 2.7462312204277281e-17, + -4.6552847802111948e-34, 6.5961781099193122e-51), + qd_real( 4.2755509343028208e-01, 9.4111898162954726e-18, + -1.7446682426598801e-34, -2.2054492626480169e-51), + qd_real( 4.3032648134008261e-01, 2.2259686974092690e-17, + 8.5972591314085075e-34, -2.9420897889003020e-50), + qd_real( 4.3309381885315196e-01, 1.1224283329847517e-17, + 5.3223748041075651e-35, 5.3926192627014212e-51), + qd_real( 4.3585707992225547e-01, 1.6230515450644527e-17, + -6.4371449063579431e-35, -6.9102436481386757e-51), + qd_real( 4.3861623853852766e-01, -2.0883315831075090e-17, + -1.4259583540891877e-34, 6.3864763590657077e-52), + qd_real( 4.4137126873171667e-01, 2.2360783886964969e-17, + 1.1864769603515770e-34, -3.8087003266189232e-51), + qd_real( 4.4412214457042926e-01, -2.4218874422178315e-17, + 2.2205230838703907e-34, 9.2133035911356258e-51), + qd_real( 4.4686884016237421e-01, -1.9222136142309382e-17, + -4.4425678589732049e-35, -1.3673609292149535e-51), + qd_real( 4.4961132965460660e-01, 4.8831924232035243e-18, + 2.7151084498191381e-34, -1.5653993171613154e-50), + qd_real( 4.5234958723377089e-01, -1.4827977472196122e-17, + -7.6947501088972324e-34, 1.7656856882031319e-50), + qd_real( 4.5508358712634384e-01, -1.2379906758116472e-17, + 5.5289688955542643e-34, -8.5382312840209386e-51), + qd_real( 4.5781330359887723e-01, -8.4554254922295949e-18, + -6.3770394246764263e-34, 3.1778253575564249e-50), + qd_real( 4.6053871095824001e-01, 1.8488777492177872e-17, + -1.0527732154209725e-33, 3.3235593490947102e-50), + qd_real( 4.6325978355186020e-01, -7.3514924533231707e-18, + 6.7175396881707035e-34, 3.9594127612123379e-50), + qd_real( 4.6597649576796618e-01, -3.3023547778235135e-18, + 3.4904677050476886e-35, 3.4483855263874246e-51), + qd_real( 4.6868882203582796e-01, -2.2949251681845054e-17, + -1.1364757641823658e-33, 6.8840522501918612e-50), + qd_real( 4.7139673682599764e-01, 6.5166781360690130e-18, + 2.9457546966235984e-34, -6.2159717738836630e-51), + qd_real( 4.7410021465055002e-01, -8.1451601548978075e-18, + -3.4789448555614422e-34, -1.1681943974658508e-50), + qd_real( 4.7679923006332214e-01, -1.0293515338305794e-17, + -3.6582045008369952e-34, 1.7424131479176475e-50), + qd_real( 4.7949375766015301e-01, 1.8419999662684771e-17, + -1.3040838621273312e-33, 1.0977131822246471e-50), + qd_real( 4.8218377207912277e-01, -2.5861500925520442e-17, + -6.2913197606500007e-36, 4.0802359808684726e-52), + qd_real( 4.8486924800079112e-01, -1.8034004203262245e-17, + -3.5244276906958044e-34, -1.7138318654749246e-50), + qd_real( 4.8755016014843594e-01, 1.4231090931273653e-17, + -1.8277733073262697e-34, -1.5208291790429557e-51), + qd_real( 4.9022648328829116e-01, -5.1496145643440404e-18, + -3.6903027405284104e-34, 1.5172940095151304e-50), + qd_real( 4.9289819222978404e-01, -1.0257831676562186e-18, + 6.9520817760885069e-35, -2.4260961214090389e-51), + qd_real( 4.9556526182577254e-01, -9.4323241942365362e-18, + 3.1212918657699143e-35, 4.2009072375242736e-52), + qd_real( 4.9822766697278187e-01, -1.6126383830540798e-17, + -1.5092897319298871e-33, 1.1049298890895917e-50), + qd_real( 5.0088538261124083e-01, -3.9604015147074639e-17, + -2.2208395201898007e-33, 1.3648202735839417e-49), + qd_real( 5.0353838372571758e-01, -1.6731308204967497e-17, + -1.0140233644074786e-33, 4.0953071937671477e-50), + qd_real( 5.0618664534515534e-01, -4.8321592986493711e-17, + 9.2858107226642252e-34, 4.2699802401037005e-50), + qd_real( 5.0883014254310699e-01, 4.7836968268014130e-17, + -1.0727022928806035e-33, 2.7309374513672757e-50), + qd_real( 5.1146885043797041e-01, -1.3088001221007579e-17, + 4.0929033363366899e-34, -3.7952190153477926e-50), + qd_real( 5.1410274419322177e-01, -4.5712707523615624e-17, + 1.5488279442238283e-33, -2.5853959305521130e-50), + qd_real( 5.1673179901764987e-01, 8.3018617233836515e-18, + 5.8251027467695202e-34, -2.2812397190535076e-50), + qd_real( 5.1935599016558964e-01, -5.5331248144171145e-17, + -3.1628375609769026e-35, -2.4091972051188571e-51), + qd_real( 5.2197529293715439e-01, -4.6555795692088883e-17, + 4.6378980936850430e-34, -3.3470542934689532e-51), + qd_real( 5.2458968267846895e-01, -4.3068869040082345e-17, + -4.2013155291932055e-34, -1.5096069926700274e-50), + qd_real( 5.2719913478190139e-01, -4.2202983480560619e-17, + 8.5585916184867295e-34, 7.9974339336732307e-50), + qd_real( 5.2980362468629472e-01, -4.8067841706482342e-17, + 5.8309721046630296e-34, -8.9740761521756660e-51), + qd_real( 5.3240312787719801e-01, -4.1020306135800895e-17, + -1.9239996374230821e-33, -1.5326987913812184e-49), + qd_real( 5.3499761988709726e-01, -5.3683132708358134e-17, + -1.3900569918838112e-33, 2.7154084726474092e-50), + qd_real( 5.3758707629564551e-01, -2.2617365388403054e-17, + -5.9787279033447075e-34, 3.1204419729043625e-51), + qd_real( 5.4017147272989285e-01, 2.7072447965935839e-17, + 1.1698799709213829e-33, -5.9094668515881500e-50), + qd_real( 5.4275078486451589e-01, 1.7148261004757101e-17, + -1.3525905925200870e-33, 4.9724411290727323e-50), + qd_real( 5.4532498842204646e-01, -4.1517817538384258e-17, + -1.5318930219385941e-33, 6.3629921101413974e-50), + qd_real( 5.4789405917310019e-01, -2.4065878297113363e-17, + -3.5639213669362606e-36, -2.6013270854271645e-52), + qd_real( 5.5045797293660481e-01, -8.3319903015807663e-18, + -2.3058454035767633e-34, -2.1611290432369010e-50), + qd_real( 5.5301670558002758e-01, -4.7061536623798204e-17, + -1.0617111545918056e-33, -1.6196316144407379e-50), + qd_real( 5.5557023301960218e-01, 4.7094109405616768e-17, + -2.0640520383682921e-33, 1.2290163188567138e-49), + qd_real( 5.5811853122055610e-01, 1.3481176324765226e-17, + -5.5016743873011438e-34, -2.3484822739335416e-50), + qd_real( 5.6066157619733603e-01, -7.3956418153476152e-18, + 3.9680620611731193e-34, 3.1995952200836223e-50), + qd_real( 5.6319934401383409e-01, 2.3835775146854829e-17, + 1.3511793173769814e-34, 9.3201311581248143e-51), + qd_real( 5.6573181078361323e-01, -3.4096079596590466e-17, + -1.7073289744303546e-33, 8.9147089975404507e-50), + qd_real( 5.6825895267013160e-01, -5.0935673642769248e-17, + -1.6274356351028249e-33, 9.8183151561702966e-51), + qd_real( 5.7078074588696726e-01, 2.4568151455566208e-17, + -1.2844481247560350e-33, -1.8037634376936261e-50), + qd_real( 5.7329716669804220e-01, 8.5176611669306400e-18, + -6.4443208788026766e-34, 2.2546105543273003e-50), + qd_real( 5.7580819141784534e-01, -3.7909495458942734e-17, + -2.7433738046854309e-33, 1.1130841524216795e-49), + qd_real( 5.7831379641165559e-01, -2.6237691512372831e-17, + 1.3679051680738167e-33, -3.1409808935335900e-50), + qd_real( 5.8081395809576453e-01, 1.8585338586613408e-17, + 2.7673843114549181e-34, 1.9605349619836937e-50), + qd_real( 5.8330865293769829e-01, 3.4516601079044858e-18, + 1.8065977478946306e-34, -6.3953958038544646e-51), + qd_real( 5.8579785745643886e-01, -3.7485501964311294e-18, + 2.7965403775536614e-34, -7.1816936024157202e-51), + qd_real( 5.8828154822264533e-01, -2.9292166725006846e-17, + -2.3744954603693934e-33, -1.1571631191512480e-50), + qd_real( 5.9075970185887428e-01, -4.7013584170659542e-17, + 2.4808417611768356e-33, 1.2598907673643198e-50), + qd_real( 5.9323229503979980e-01, 1.2892320944189053e-17, + 5.3058364776359583e-34, 4.1141674699390052e-50), + qd_real( 5.9569930449243336e-01, -1.3438641936579467e-17, + -6.7877687907721049e-35, -5.6046937531684890e-51), + qd_real( 5.9816070699634227e-01, 3.8801885783000657e-17, + -1.2084165858094663e-33, -4.0456610843430061e-50), + qd_real( 6.0061647938386897e-01, -4.6398198229461932e-17, + -1.6673493003710801e-33, 5.1982824378491445e-50), + qd_real( 6.0306659854034816e-01, 3.7323357680559650e-17, + 2.7771920866974305e-33, -1.6194229649742458e-49), + qd_real( 6.0551104140432555e-01, -3.1202672493305677e-17, + 1.2761267338680916e-33, -4.0859368598379647e-50), + qd_real( 6.0794978496777363e-01, 3.5160832362096660e-17, + -2.5546242776778394e-34, -1.4085313551220694e-50), + qd_real( 6.1038280627630948e-01, -2.2563265648229169e-17, + 1.3185575011226730e-33, 8.2316691420063460e-50), + qd_real( 6.1281008242940971e-01, -4.2693476568409685e-18, + 2.5839965886650320e-34, 1.6884412005622537e-50), + qd_real( 6.1523159058062682e-01, 2.6231417767266950e-17, + -1.4095366621106716e-33, 7.2058690491304558e-50), + qd_real( 6.1764730793780398e-01, -4.7478594510902452e-17, + -7.2986558263123996e-34, -3.0152327517439154e-50), + qd_real( 6.2005721176328921e-01, -2.7983410837681118e-17, + 1.1649951056138923e-33, -5.4539089117135207e-50), + qd_real( 6.2246127937414997e-01, 5.2940728606573002e-18, + -4.8486411215945827e-35, 1.2696527641980109e-52), + qd_real( 6.2485948814238634e-01, 3.3671846037243900e-17, + -2.7846053391012096e-33, 5.6102718120012104e-50), + qd_real( 6.2725181549514408e-01, 3.0763585181253225e-17, + 2.7068930273498138e-34, -1.1172240309286484e-50), + qd_real( 6.2963823891492698e-01, 4.1115334049626806e-17, + -1.9167473580230747e-33, 1.1118424028161730e-49), + qd_real( 6.3201873593980906e-01, -4.0164942296463612e-17, + -7.2208643641736723e-34, 3.7828920470544344e-50), + qd_real( 6.3439328416364549e-01, 1.0420901929280035e-17, + 4.1174558929280492e-34, -1.4464152986630705e-51), + qd_real( 6.3676186123628420e-01, 3.1419048711901611e-17, + -2.2693738415126449e-33, -1.6023584204297388e-49), + qd_real( 6.3912444486377573e-01, 1.2416796312271043e-17, + -6.2095419626356605e-34, 2.7762065999506603e-50), + qd_real( 6.4148101280858316e-01, -9.9883430115943310e-18, + 4.1969230376730128e-34, 5.6980543799257597e-51), + qd_real( 6.4383154288979150e-01, -3.2084798795046886e-17, + -1.2595311907053305e-33, -4.0205885230841536e-50), + qd_real( 6.4617601298331639e-01, -2.9756137382280815e-17, + -1.0275370077518259e-33, 8.0852478665893014e-51), + qd_real( 6.4851440102211244e-01, 3.9870270313386831e-18, + 1.9408388509540788e-34, -5.1798420636193190e-51), + qd_real( 6.5084668499638088e-01, 3.9714670710500257e-17, + 2.9178546787002963e-34, 3.8140635508293278e-51), + qd_real( 6.5317284295377676e-01, 8.5695642060026238e-18, + -6.9165322305070633e-34, 2.3873751224185395e-50), + qd_real( 6.5549285299961535e-01, 3.5638734426385005e-17, + 1.2695365790889811e-33, 4.3984952865412050e-50), + qd_real( 6.5780669329707864e-01, 1.9580943058468545e-17, + -1.1944272256627192e-33, 2.8556402616436858e-50), + qd_real( 6.6011434206742048e-01, -1.3960054386823638e-19, + 6.1515777931494047e-36, 5.3510498875622660e-52), + qd_real( 6.6241577759017178e-01, -2.2615508885764591e-17, + 5.0177050318126862e-34, 2.9162532399530762e-50), + qd_real( 6.6471097820334490e-01, -3.6227793598034367e-17, + -9.0607934765540427e-34, 3.0917036342380213e-50), + qd_real( 6.6699992230363747e-01, 3.5284364997428166e-17, + -1.0382057232458238e-33, 7.3812756550167626e-50), + qd_real( 6.6928258834663612e-01, -5.4592652417447913e-17, + -2.5181014709695152e-33, -1.6867875999437174e-49), + qd_real( 6.7155895484701844e-01, -4.0489037749296692e-17, + 3.1995835625355681e-34, -1.4044414655670960e-50), + qd_real( 6.7382900037875604e-01, 2.3091901236161086e-17, + 5.7428037192881319e-34, 1.1240668354625977e-50), + qd_real( 6.7609270357531592e-01, 3.7256902248049466e-17, + 1.7059417895764375e-33, 9.7326347795300652e-50), + qd_real( 6.7835004312986147e-01, 1.8302093041863122e-17, + 9.5241675746813072e-34, 5.0328101116133503e-50), + qd_real( 6.8060099779545302e-01, 2.8473293354522047e-17, + 4.1331805977270903e-34, 4.2579030510748576e-50), + qd_real( 6.8284554638524808e-01, -1.2958058061524531e-17, + 1.8292386959330698e-34, 3.4536209116044487e-51), + qd_real( 6.8508366777270036e-01, 2.5948135194645137e-17, + -8.5030743129500702e-34, -6.9572086141009930e-50), + qd_real( 6.8731534089175916e-01, -5.5156158714917168e-17, + 1.1896489854266829e-33, -7.8505896218220662e-51), + qd_real( 6.8954054473706694e-01, -1.5889323294806790e-17, + 9.1242356240205712e-34, 3.8315454152267638e-50), + qd_real( 6.9175925836415775e-01, 2.7406078472410668e-17, + 1.3286508943202092e-33, 1.0651869129580079e-51), + qd_real( 6.9397146088965400e-01, 7.4345076956280137e-18, + 7.5061528388197460e-34, -1.5928000240686583e-50), + qd_real( 6.9617713149146299e-01, -4.1224081213582889e-17, + -3.1838716762083291e-35, -3.9625587412119131e-51), + qd_real( 6.9837624940897280e-01, 4.8988282435667768e-17, + 1.9134010413244152e-33, 2.6161153243793989e-50), + qd_real( 7.0056879394324834e-01, 3.1027960192992922e-17, + 9.5638250509179997e-34, 4.5896916138107048e-51), + qd_real( 7.0275474445722530e-01, 2.5278294383629822e-18, + -8.6985561210674942e-35, -5.6899862307812990e-51), + qd_real( 7.0493408037590488e-01, 2.7608725585748502e-17, + 2.9816599471629137e-34, 1.1533044185111206e-50), + qd_real( 7.0710678118654757e-01, -4.8336466567264567e-17, + 2.0693376543497068e-33, 2.4677734957341755e-50) +}; + +static const qd_real cos_table [] = { + qd_real( 9.9999529380957619e-01, -1.9668064285322189e-17, + -6.3053955095883481e-34, 5.3266110855726731e-52), + qd_real( 9.9998117528260111e-01, 3.3568103522895585e-17, + -1.4740132559368063e-35, 9.8603097594755596e-52), + qd_real( 9.9995764455196390e-01, -3.1527836866647287e-17, + 2.6363251186638437e-33, 1.0007504815488399e-49), + qd_real( 9.9992470183914450e-01, 3.7931082512668012e-17, + -8.5099918660501484e-35, -4.9956973223295153e-51), + qd_real( 9.9988234745421256e-01, -3.5477814872408538e-17, + 1.7102001035303974e-33, -1.0725388519026542e-49), + qd_real( 9.9983058179582340e-01, 1.8825140517551119e-17, + -5.1383513457616937e-34, -3.8378827995403787e-50), + qd_real( 9.9976940535121528e-01, 4.2681177032289012e-17, + 1.9062302359737099e-33, -6.0221153262881160e-50), + qd_real( 9.9969881869620425e-01, -2.9851486403799753e-17, + -1.9084787370733737e-33, 5.5980260344029202e-51), + qd_real( 9.9961882249517864e-01, -4.1181965521424734e-17, + 2.0915365593699916e-33, 8.1403390920903734e-50), + qd_real( 9.9952941750109314e-01, 2.0517917823755591e-17, + -4.7673802585706520e-34, -2.9443604198656772e-50), + qd_real( 9.9943060455546173e-01, 3.9644497752257798e-17, + -2.3757223716722428e-34, -1.2856759011361726e-51), + qd_real( 9.9932238458834954e-01, -4.2858538440845682e-17, + 3.3235101605146565e-34, -8.3554272377057543e-51), + qd_real( 9.9920475861836389e-01, 9.1796317110385693e-18, + 5.5416208185868570e-34, 8.0267046717615311e-52), + qd_real( 9.9907772775264536e-01, 2.1419007653587032e-17, + -7.9048203318529618e-34, -5.3166296181112712e-50), + qd_real( 9.9894129318685687e-01, -2.0610641910058638e-17, + -1.2546525485913485e-33, -7.5175888806157064e-50), + qd_real( 9.9879545620517241e-01, -1.2291693337075465e-17, + 2.4468446786491271e-34, 1.0723891085210268e-50), + qd_real( 9.9864021818026527e-01, -4.8690254312923302e-17, + -2.9470881967909147e-34, -1.3000650761346907e-50), + qd_real( 9.9847558057329477e-01, -2.2002931182778795e-17, + -1.2371509454944992e-33, -2.4911225131232065e-50), + qd_real( 9.9830154493389289e-01, -5.1869402702792278e-17, + 1.0480195493633452e-33, -2.8995649143155511e-50), + qd_real( 9.9811811290014918e-01, 2.7935487558113833e-17, + 2.4423341255830345e-33, -6.7646699175334417e-50), + qd_real( 9.9792528619859600e-01, 1.7143659778886362e-17, + 5.7885840902887460e-34, -9.2601432603894597e-51), + qd_real( 9.9772306664419164e-01, -2.6394475274898721e-17, + -1.6176223087661783e-34, -9.9924942889362281e-51), + qd_real( 9.9751145614030345e-01, 5.6007205919806937e-18, + -5.9477673514685690e-35, -1.4166807162743627e-54), + qd_real( 9.9729045667869021e-01, 9.1647695371101735e-18, + 6.7824134309739296e-34, -8.6191392795543357e-52), + qd_real( 9.9706007033948296e-01, 1.6734093546241963e-17, + -1.3169951440780028e-33, 1.0311048767952477e-50), + qd_real( 9.9682029929116567e-01, 4.7062820708615655e-17, + 2.8412041076474937e-33, -8.0006155670263622e-50), + qd_real( 9.9657114579055484e-01, 1.1707179088390986e-17, + -7.5934413263024663e-34, 2.8474848436926008e-50), + qd_real( 9.9631261218277800e-01, 1.1336497891624735e-17, + 3.4002458674414360e-34, 7.7419075921544901e-52), + qd_real( 9.9604470090125197e-01, 2.2870031707670695e-17, + -3.9184839405013148e-34, -3.7081260416246375e-50), + qd_real( 9.9576741446765982e-01, -2.3151908323094359e-17, + -1.6306512931944591e-34, -1.5925420783863192e-51), + qd_real( 9.9548075549192694e-01, 3.2084621412226554e-18, + -4.9501292146013023e-36, -2.7811428850878516e-52), + qd_real( 9.9518472667219693e-01, -4.2486913678304410e-17, + 1.3315510772504614e-33, 6.7927987417051888e-50), + qd_real( 9.9487933079480562e-01, 4.2130813284943662e-18, + -4.2062597488288452e-35, 2.5157064556087620e-51), + qd_real( 9.9456457073425542e-01, 3.6745069641528058e-17, + -3.0603304105471010e-33, 1.0397872280487526e-49), + qd_real( 9.9424044945318790e-01, 4.4129423472462673e-17, + -3.0107231708238066e-33, 7.4201582906861892e-50), + qd_real( 9.9390697000235606e-01, -1.8964849471123746e-17, + -1.5980853777937752e-35, -8.5374807150597082e-52), + qd_real( 9.9356413552059530e-01, 2.9752309927797428e-17, + -4.5066707331134233e-34, -3.3548191633805036e-50), + qd_real( 9.9321194923479450e-01, 3.3096906261272262e-17, + 1.5592811973249567e-33, 1.4373977733253592e-50), + qd_real( 9.9285041445986510e-01, -1.4094517733693302e-17, + -1.1954558131616916e-33, 1.8761873742867983e-50), + qd_real( 9.9247953459870997e-01, 3.1093055095428906e-17, + -1.8379594757818019e-33, -3.9885758559381314e-51), + qd_real( 9.9209931314219180e-01, -3.9431926149588778e-17, + -6.2758062911047230e-34, -1.2960929559212390e-50), + qd_real( 9.9170975366909953e-01, -2.3372891311883661e-18, + 2.7073298824968591e-35, -1.2569459441802872e-51), + qd_real( 9.9131085984611544e-01, -2.5192111583372105e-17, + -1.2852471567380887e-33, 5.2385212584310483e-50), + qd_real( 9.9090263542778001e-01, 1.5394565094566704e-17, + -1.0799984133184567e-33, 2.7451115960133595e-51), + qd_real( 9.9048508425645709e-01, -5.5411437553780867e-17, + -1.4614017210753585e-33, -3.8339374397387620e-50), + qd_real( 9.9005821026229712e-01, -1.7055485906233963e-17, + 1.3454939685758777e-33, 7.3117589137300036e-50), + qd_real( 9.8962201746320089e-01, -5.2398217968132530e-17, + 1.3463189211456219e-33, 5.8021640554894872e-50), + qd_real( 9.8917650996478101e-01, -4.0987309937047111e-17, + -4.4857560552048437e-34, -3.9414504502871125e-50), + qd_real( 9.8872169196032378e-01, -1.0976227206656125e-17, + 3.2311342577653764e-34, 9.6367946583575041e-51), + qd_real( 9.8825756773074946e-01, 2.7030607784372632e-17, + 7.7514866488601377e-35, 2.1019644956864938e-51), + qd_real( 9.8778414164457218e-01, -2.3600693397159021e-17, + -1.2323283769707861e-33, 3.0130900716803339e-50), + qd_real( 9.8730141815785843e-01, -5.2332261255715652e-17, + -2.7937644333152473e-33, 1.2074160567958408e-49), + qd_real( 9.8680940181418553e-01, -5.0287214351061075e-17, + -2.2681526238144461e-33, 4.4003694320169133e-50), + qd_real( 9.8630809724459867e-01, -2.1520877103013341e-17, + 1.1866528054187716e-33, -7.8532199199813836e-50), + qd_real( 9.8579750916756748e-01, -5.1439452979953012e-17, + 2.6276439309996725e-33, 7.5423552783286347e-50), + qd_real( 9.8527764238894122e-01, 2.3155637027900207e-17, + -7.5275971545764833e-34, 1.0582231660456094e-50), + qd_real( 9.8474850180190421e-01, 1.0548144061829957e-17, + 2.8786145266267306e-34, -3.6782210081466112e-51), + qd_real( 9.8421009238692903e-01, 4.7983922627050691e-17, + 2.2597419645070588e-34, 1.7573875814863400e-50), + qd_real( 9.8366241921173025e-01, 1.9864948201635255e-17, + -1.0743046281211033e-35, 1.7975662796558100e-52), + qd_real( 9.8310548743121629e-01, 4.2170007522888628e-17, + 8.2396265656440904e-34, -8.0803700139096561e-50), + qd_real( 9.8253930228744124e-01, 1.5149580813777224e-17, + -4.1802771422186237e-34, -2.2150174326226160e-50), + qd_real( 9.8196386910955524e-01, 2.1108443711513084e-17, + -1.5253013442896054e-33, -6.8388082079337969e-50), + qd_real( 9.8137919331375456e-01, 1.3428163260355633e-17, + -6.5294290469962986e-34, 2.7965412287456268e-51), + qd_real( 9.8078528040323043e-01, 1.8546939997825006e-17, + -1.0696564445530757e-33, 6.6668174475264961e-50), + qd_real( 9.8018213596811743e-01, -3.6801786963856159e-17, + 6.3245171387992842e-34, 1.8600176137175971e-50), + qd_real( 9.7956976568544052e-01, 1.5573991584990420e-17, + -1.3401066029782990e-33, -1.7263702199862149e-50), + qd_real( 9.7894817531906220e-01, -2.3817727961148053e-18, + -1.0694750370381661e-34, -8.2293047196087462e-51), + qd_real( 9.7831737071962765e-01, -2.1623082233344895e-17, + 1.0970403012028032e-33, 7.7091923099369339e-50), + qd_real( 9.7767735782450993e-01, 5.0514136167059628e-17, + -1.3254751701428788e-33, 7.0161254312124538e-50), + qd_real( 9.7702814265775439e-01, -4.3353875751555997e-17, + 5.4948839831535478e-34, -9.2755263105377306e-51), + qd_real( 9.7636973133002114e-01, 9.3093931526213780e-18, + -4.1184949155685665e-34, -3.1913926031393690e-50), + qd_real( 9.7570213003852857e-01, -2.5572556081259686e-17, + -9.3174244508942223e-34, -8.3675863211646863e-51), + qd_real( 9.7502534506699412e-01, 2.6642660651899135e-17, + 1.7819392739353853e-34, -3.3159625385648947e-51), + qd_real( 9.7433938278557586e-01, 2.3041221476151512e-18, + 1.0758686005031430e-34, 5.1074116432809478e-51), + qd_real( 9.7364424965081198e-01, -5.1729808691005871e-17, + -1.5508473005989887e-33, -1.6505125917675401e-49), + qd_real( 9.7293995220556018e-01, -3.1311211122281800e-17, + -2.6874087789006141e-33, -2.1652434818822145e-51), + qd_real( 9.7222649707893627e-01, 3.6461169785938221e-17, + 3.0309636883883133e-33, -1.2702716907967306e-51), + qd_real( 9.7150389098625178e-01, -7.9865421122289046e-18, + -4.3628417211263380e-34, 3.4307517798759352e-51), + qd_real( 9.7077214072895035e-01, -4.7992163325114922e-17, + 3.0347528910975783e-33, 8.5989199506479701e-50), + qd_real( 9.7003125319454397e-01, 1.8365300348428844e-17, + -1.4311097571944918e-33, 8.5846781998740697e-51), + qd_real( 9.6928123535654853e-01, -4.5663660261927896e-17, + 9.6147526917239387e-34, 8.1267605207871330e-51), + qd_real( 9.6852209427441727e-01, 4.9475074918244771e-17, + 2.8558738351911241e-33, 6.2948422316507461e-50), + qd_real( 9.6775383709347551e-01, -4.5512132825515820e-17, + -1.4127617988719093e-33, -8.4620609089704578e-50), + qd_real( 9.6697647104485207e-01, 3.8496228837337864e-17, + -5.3881631542745647e-34, -3.5221863171458959e-50), + qd_real( 9.6619000344541250e-01, 5.1298840401665493e-17, + 1.4564075904769808e-34, 1.0095973971377432e-50), + qd_real( 9.6539444169768940e-01, -2.3745389918392156e-17, + 5.9221515590053862e-34, -3.8811192556231094e-50), + qd_real( 9.6458979328981276e-01, -3.4189470735959786e-17, + 2.2982074155463522e-33, -4.5128791045607634e-50), + qd_real( 9.6377606579543984e-01, 2.6463950561220029e-17, + -2.9073234590199323e-36, -1.2938328629395601e-52), + qd_real( 9.6295326687368388e-01, 8.9341960404313634e-18, + -3.9071244661020126e-34, 1.6212091116847394e-50), + qd_real( 9.6212140426904158e-01, 1.5236770453846305e-17, + -1.3050173525597142e-33, 7.9016122394092666e-50), + qd_real( 9.6128048581132064e-01, 2.0933955216674039e-18, + 1.0768607469015692e-34, -5.9453639304361774e-51), + qd_real( 9.6043051941556579e-01, 2.4653904815317185e-17, + -1.3792169410906322e-33, -4.7726598378506903e-51), + qd_real( 9.5957151308198452e-01, 1.1000640085000957e-17, + -4.2036030828223975e-34, 4.0023704842606573e-51), + qd_real( 9.5870347489587160e-01, -4.3685014392372053e-17, + 2.2001800662729131e-33, -1.0553721324358075e-49), + qd_real( 9.5782641302753291e-01, -1.7696710075371263e-17, + 1.9164034110382190e-34, 8.1489235071754813e-51), + qd_real( 9.5694033573220882e-01, 4.0553869861875701e-17, + -1.7147013364302149e-33, 2.5736745295329455e-50), + qd_real( 9.5604525134999641e-01, 3.7705045279589067e-17, + 1.9678699997347571e-33, 8.5093177731230180e-50), + qd_real( 9.5514116830577067e-01, 5.0088652955014668e-17, + -2.6983181838059211e-33, 1.0102323575596493e-49), + qd_real( 9.5422809510910567e-01, -3.7545901690626874e-17, + 1.4951619241257764e-33, -8.2717333151394973e-50), + qd_real( 9.5330604035419386e-01, -2.5190738779919934e-17, + -1.4272239821134379e-33, -4.6717286809283155e-50), + qd_real( 9.5237501271976588e-01, -2.0269300462299272e-17, + -1.0635956887246246e-33, -3.5514537666487619e-50), + qd_real( 9.5143502096900834e-01, 3.1350584123266695e-17, + -2.4824833452737813e-33, 9.5450335525380613e-51), + qd_real( 9.5048607394948170e-01, 1.9410097562630436e-17, + -8.1559393949816789e-34, -1.0501209720164562e-50), + qd_real( 9.4952818059303667e-01, -7.5544151928043298e-18, + -5.1260245024046686e-34, 1.8093643389040406e-50), + qd_real( 9.4856134991573027e-01, 2.0668262262333232e-17, + -5.9440730243667306e-34, 1.4268853111554300e-50), + qd_real( 9.4758559101774109e-01, 4.3417993852125991e-17, + -2.7728667889840373e-34, 5.5709160196519968e-51), + qd_real( 9.4660091308328353e-01, 3.5056800210680730e-17, + 9.8578536940318117e-34, 6.6035911064585197e-50), + qd_real( 9.4560732538052128e-01, 4.6019102478523738e-17, + -6.2534384769452059e-34, 1.5758941215779961e-50), + qd_real( 9.4460483726148026e-01, 8.8100545476641165e-18, + 5.2291695602757842e-34, -3.3487256018407123e-50), + qd_real( 9.4359345816196039e-01, -2.4093127844404214e-17, + 1.0283279856803939e-34, -2.3398232614531355e-51), + qd_real( 9.4257319760144687e-01, 1.3235564806436886e-17, + -5.7048262885386911e-35, 3.9947050442753744e-51), + qd_real( 9.4154406518302081e-01, -2.7896379547698341e-17, + 1.6273236356733898e-33, -5.3075944708471203e-51), + qd_real( 9.4050607059326830e-01, 2.8610421567116268e-17, + 2.9261501147538827e-33, -2.6849867690896925e-50), + qd_real( 9.3945922360218992e-01, -7.0152867943098655e-18, + -5.6395693818011210e-34, 3.5568142678987651e-50), + qd_real( 9.3840353406310806e-01, 5.4242545044795490e-17, + -1.9039966607859759e-33, -1.5627792988341215e-49), + qd_real( 9.3733901191257496e-01, -3.6570926284362776e-17, + -1.1902940071273247e-33, -1.1215082331583223e-50), + qd_real( 9.3626566717027826e-01, -1.3013766145497654e-17, + 5.2229870061990595e-34, -3.3972777075634108e-51), + qd_real( 9.3518350993894761e-01, -3.2609395302485065e-17, + -8.1813015218875245e-34, 5.5642140024928139e-50), + qd_real( 9.3409255040425887e-01, 4.4662824360767511e-17, + -2.5903243047396916e-33, 8.1505209004343043e-50), + qd_real( 9.3299279883473885e-01, 4.2041415555384355e-17, + 9.0285896495521276e-34, 5.3019984977661259e-50), + qd_real( 9.3188426558166815e-01, -4.0785944377318095e-17, + 1.7631450298754169e-33, 2.5776403305507453e-50), + qd_real( 9.3076696107898371e-01, 1.9703775102838329e-17, + 6.5657908718278205e-34, -1.9480347966259524e-51), + qd_real( 9.2964089584318121e-01, 5.1282530016864107e-17, + 2.3719739891916261e-34, -1.7230065426917127e-50), + qd_real( 9.2850608047321559e-01, -2.3306639848485943e-17, + -7.7799084333208503e-34, -5.8597558009300305e-50), + qd_real( 9.2736252565040111e-01, -2.7677111692155437e-17, + 2.2110293450199576e-34, 2.0349190819680613e-50), + qd_real( 9.2621024213831138e-01, -3.7303754586099054e-17, + 2.0464457809993405e-33, 1.3831799631231817e-49), + qd_real( 9.2504924078267758e-01, 6.0529447412576159e-18, + -8.8256517760278541e-35, 1.8285462122388328e-51), + qd_real( 9.2387953251128674e-01, 1.7645047084336677e-17, + -5.0442537321586818e-34, -4.0478677716823890e-50), + qd_real( 9.2270112833387852e-01, 5.2963798918539814e-17, + -5.7135699628876685e-34, 3.0163671797219087e-50), + qd_real( 9.2151403934204190e-01, 4.1639843390684644e-17, + 1.1891485604702356e-33, 2.0862437594380324e-50), + qd_real( 9.2031827670911059e-01, -2.7806888779036837e-17, + 2.7011013677071274e-33, 1.1998578792455499e-49), + qd_real( 9.1911385169005777e-01, -2.6496484622344718e-17, + 6.5403604763461920e-34, -2.8997180201186078e-50), + qd_real( 9.1790077562139050e-01, -3.9074579680849515e-17, + 2.3004636541490264e-33, 3.9851762744443107e-50), + qd_real( 9.1667905992104270e-01, -4.1733978698287568e-17, + 1.2094444804381172e-33, 4.9356916826097816e-50), + qd_real( 9.1544871608826783e-01, -1.3591056692900894e-17, + 5.9923027475594735e-34, 2.1403295925962879e-50), + qd_real( 9.1420975570353069e-01, -3.6316182527814423e-17, + -1.9438819777122554e-33, 2.8340679287728316e-50), + qd_real( 9.1296219042839821e-01, -4.7932505228039469e-17, + -1.7753551889428638e-33, 4.0607782903868160e-51), + qd_real( 9.1170603200542988e-01, -2.6913273175034130e-17, + -5.1928101916162528e-35, 1.1338175936090630e-51), + qd_real( 9.1044129225806725e-01, -5.0433041673313820e-17, + 1.0938746257404305e-33, 9.5378272084170731e-51), + qd_real( 9.0916798309052238e-01, -3.6878564091359894e-18, + 2.9951330310507693e-34, -1.2225666136919926e-50), + qd_real( 9.0788611648766626e-01, -4.9459964301225840e-17, + -1.6599682707075313e-33, -5.1925202712634716e-50), + qd_real( 9.0659570451491533e-01, 3.0506718955442023e-17, + -1.4478836557141204e-33, 1.8906373784448725e-50), + qd_real( 9.0529675931811882e-01, -4.1153099826889901e-17, + 2.9859368705184223e-33, 5.1145293917439211e-50), + qd_real( 9.0398929312344334e-01, -6.6097544687484308e-18, + 1.2728013034680357e-34, -4.3026097234014823e-51), + qd_real( 9.0267331823725883e-01, -1.9250787033961483e-17, + 1.3242128993244527e-33, -5.2971030688703665e-50), + qd_real( 9.0134884704602203e-01, -1.3524789367698682e-17, + 6.3605353115880091e-34, 3.6227400654573828e-50), + qd_real( 9.0001589201616028e-01, -5.0639618050802273e-17, + 1.0783525384031576e-33, 2.8130016326515111e-50), + qd_real( 8.9867446569395382e-01, 2.6316906461033013e-17, + 3.7003137047796840e-35, -2.3447719900465938e-51), + qd_real( 8.9732458070541832e-01, -3.6396283314867290e-17, + -2.3611649895474815e-33, 1.1837247047900082e-49), + qd_real( 8.9596624975618511e-01, 4.9025099114811813e-17, + -1.9440489814795326e-33, -1.7070486667767033e-49), + qd_real( 8.9459948563138270e-01, -1.7516226396814919e-17, + -1.3200670047246923e-33, -1.5953009884324695e-50), + qd_real( 8.9322430119551532e-01, -4.1161239151908913e-18, + 2.5380253805715999e-34, 4.2849455510516192e-51), + qd_real( 8.9184070939234272e-01, 4.6690228137124547e-18, + 1.6150254286841982e-34, -3.9617448820725012e-51), + qd_real( 8.9044872324475788e-01, 1.1781931459051803e-17, + -1.3346142209571930e-34, -9.4982373530733431e-51), + qd_real( 8.8904835585466457e-01, -1.1164514966766675e-17, + -3.4797636107798736e-34, -1.5605079997040631e-50), + qd_real( 8.8763962040285393e-01, 1.2805091918587960e-17, + 3.9948742059584459e-35, 3.8940716325338136e-51), + qd_real( 8.8622253014888064e-01, -6.7307369600274315e-18, + 1.2385593432917413e-34, 2.0364014759133320e-51), + qd_real( 8.8479709843093779e-01, -9.4331469628972690e-18, + -5.7106541478701439e-34, 1.8260134111907397e-50), + qd_real( 8.8336333866573158e-01, 1.5822643380255127e-17, + -7.8921320007588250e-34, -1.4782321016179836e-50), + qd_real( 8.8192126434835505e-01, -1.9843248405890562e-17, + -7.0412114007673834e-34, -1.0636770169389104e-50), + qd_real( 8.8047088905216075e-01, 1.6311096602996350e-17, + -5.7541360594724172e-34, -4.0128611862170021e-50), + qd_real( 8.7901222642863353e-01, -4.7356837291118011e-17, + 1.4388771297975192e-33, -2.9085554304479134e-50), + qd_real( 8.7754529020726124e-01, 5.0113311846499550e-17, + 2.8382769008739543e-34, 1.5550640393164140e-50), + qd_real( 8.7607009419540660e-01, 5.8729024235147677e-18, + 2.7941144391738458e-34, -1.8536073846509828e-50), + qd_real( 8.7458665227817611e-01, -5.7216617730397065e-19, + -2.9705811503689596e-35, 8.7389593969796752e-52), + qd_real( 8.7309497841829009e-01, 7.8424672990129903e-18, + -4.8685015839797165e-34, -2.2815570587477527e-50), + qd_real( 8.7159508665595109e-01, -5.5272998038551050e-17, + -2.2104090204984907e-33, -9.7749763187643172e-50), + qd_real( 8.7008699110871146e-01, -4.1888510868549968e-17, + 7.0900185861878415e-34, 3.7600251115157260e-50), + qd_real( 8.6857070597134090e-01, 2.7192781689782903e-19, + -1.6710140396932428e-35, -1.2625514734637969e-51), + qd_real( 8.6704624551569265e-01, 3.0267859550930567e-18, + -1.1559438782171572e-34, -5.3580556397808012e-52), + qd_real( 8.6551362409056909e-01, -6.3723113549628899e-18, + 2.3725520321746832e-34, 1.5911880348395175e-50), + qd_real( 8.6397285612158670e-01, 4.1486355957361607e-17, + 2.2709976932210266e-33, -8.1228385659479984e-50), + qd_real( 8.6242395611104050e-01, 3.7008992527383130e-17, + 5.2128411542701573e-34, 2.6945600081026861e-50), + qd_real( 8.6086693863776731e-01, -3.0050048898573656e-17, + -8.8706183090892111e-34, 1.5005320558097301e-50), + qd_real( 8.5930181835700836e-01, 4.2435655816850687e-17, + 7.6181814059912025e-34, -3.9592127850658708e-50), + qd_real( 8.5772861000027212e-01, -4.8183447936336620e-17, + -1.1044130517687532e-33, -8.7400233444645562e-50), + qd_real( 8.5614732837519447e-01, 9.1806925616606261e-18, + 5.6328649785951470e-34, 2.3326646113217378e-51), + qd_real( 8.5455798836540053e-01, -1.2991124236396092e-17, + 1.2893407722948080e-34, -3.6506925747583053e-52), + qd_real( 8.5296060493036363e-01, 2.7152984251981370e-17, + 7.4336483283120719e-34, 4.2162417622350668e-50), + qd_real( 8.5135519310526520e-01, -5.3279874446016209e-17, + 2.2281156380919942e-33, -4.0281886404138477e-50), + qd_real( 8.4974176800085244e-01, 5.1812347659974015e-17, + 3.0810626087331275e-33, -2.5931308201994965e-50), + qd_real( 8.4812034480329723e-01, 1.8762563415239981e-17, + 1.4048773307919617e-33, -2.4915221509958691e-50), + qd_real( 8.4649093877405213e-01, -4.7969419958569345e-17, + -2.7518267097886703e-33, -7.3518959727313350e-50), + qd_real( 8.4485356524970712e-01, -4.3631360296879637e-17, + -2.0307726853367547e-33, 4.3097229819851761e-50), + qd_real( 8.4320823964184544e-01, 9.6536707005959077e-19, + 2.8995142431556364e-36, 9.6715076811480284e-53), + qd_real( 8.4155497743689844e-01, -3.4095465391321557e-17, + -8.4130208607579595e-34, -4.9447283960568686e-50), + qd_real( 8.3989379419599952e-01, -1.6673694881511411e-17, + -1.4759184141750289e-33, -7.5795098161914058e-50), + qd_real( 8.3822470555483808e-01, -3.5560085052855026e-17, + 1.1689791577022643e-33, -5.8627347359723411e-50), + qd_real( 8.3654772722351201e-01, -2.0899059027066533e-17, + -9.8104097821002585e-35, -3.1609177868229853e-51), + qd_real( 8.3486287498638001e-01, 4.6048430609159657e-17, + -5.1827423265239912e-34, -7.0505343435504109e-51), + qd_real( 8.3317016470191319e-01, 1.3275129507229764e-18, + 4.8589164115370863e-35, 4.5422281300506859e-51), + qd_real( 8.3146961230254524e-01, 1.4073856984728024e-18, + 4.6951315383980830e-35, 5.1431906049905658e-51), + qd_real( 8.2976123379452305e-01, -2.9349109376485597e-18, + 1.1496917934149818e-34, 3.5186665544980233e-51), + qd_real( 8.2804504525775580e-01, -4.4196593225871532e-17, + 2.7967864855211251e-33, 1.0030777287393502e-49), + qd_real( 8.2632106284566353e-01, -5.3957485453612902e-17, + 6.8976896130138550e-34, 3.8106164274199196e-50), + qd_real( 8.2458930278502529e-01, -2.6512360488868275e-17, + 1.6916964350914386e-34, 6.7693974813562649e-51), + qd_real( 8.2284978137582632e-01, 1.5193019034505495e-17, + 9.6890547246521685e-34, 5.6994562923653264e-50), + qd_real( 8.2110251499110465e-01, 3.0715131609697682e-17, + -1.7037168325855879e-33, -1.1149862443283853e-49), + qd_real( 8.1934752007679701e-01, -4.8200736995191133e-17, + -1.5574489646672781e-35, -9.5647853614522216e-53), + qd_real( 8.1758481315158371e-01, -1.4883149812426772e-17, + -7.8273262771298917e-34, 4.1332149161031594e-50), + qd_real( 8.1581441080673378e-01, 8.2652693782130871e-18, + -2.3028778135179471e-34, 1.5102071387249843e-50), + qd_real( 8.1403632970594841e-01, -5.2127351877042624e-17, + -1.9047670611316360e-33, -1.6937269585941507e-49), + qd_real( 8.1225058658520388e-01, 3.1054545609214803e-17, + 2.2649541922707251e-34, -7.4221684154649405e-51), + qd_real( 8.1045719825259477e-01, 2.3520367349840499e-17, + -7.7530070904846341e-34, -7.2792616357197140e-50), + qd_real( 8.0865618158817498e-01, 9.3251597879721674e-18, + -7.1823301933068394e-34, 2.3925440846132106e-50), + qd_real( 8.0684755354379922e-01, 4.9220603766095546e-17, + 2.9796016899903487e-33, 1.5220754223615788e-49), + qd_real( 8.0503133114296355e-01, 5.1368289568212149e-17, + 6.3082807402256524e-34, 7.3277646085129827e-51), + qd_real( 8.0320753148064494e-01, -3.3060609804814910e-17, + -1.2242726252420433e-33, 2.8413673268630117e-50), + qd_real( 8.0137617172314024e-01, -2.0958013413495834e-17, + -4.3798162198006931e-34, 2.0235690497752515e-50), + qd_real( 7.9953726910790501e-01, 2.0356723822005431e-17, + -9.7448513696896360e-34, 5.3608109599696008e-52), + qd_real( 7.9769084094339116e-01, -4.6730759884788944e-17, + 2.3075897077191757e-33, 3.1605567774640253e-51), + qd_real( 7.9583690460888357e-01, -3.0062724851910721e-17, + -2.2496210832042235e-33, -6.5881774117183040e-50), + qd_real( 7.9397547755433717e-01, -7.4194631759921416e-18, + 2.4124341304631069e-34, -4.9956808616244972e-51), + qd_real( 7.9210657730021239e-01, -3.7087850202326467e-17, + -1.4874457267228264e-33, 2.9323097289153505e-50), + qd_real( 7.9023022143731003e-01, 2.3056905954954492e-17, + 1.4481080533260193e-33, -7.6725237057203488e-50), + qd_real( 7.8834642762660623e-01, 3.4396993154059708e-17, + 1.7710623746737170e-33, 1.7084159098417402e-49), + qd_real( 7.8645521359908577e-01, -9.7841429939305265e-18, + 3.3906063272445472e-34, 5.7269505320382577e-51), + qd_real( 7.8455659715557524e-01, -8.5627965423173476e-18, + -2.1106834459001849e-34, -1.6890322182469603e-50), + qd_real( 7.8265059616657573e-01, 9.0745866975808825e-18, + 6.7623847404278666e-34, -1.7173237731987271e-50), + qd_real( 7.8073722857209449e-01, -9.9198782066678806e-18, + -2.1265794012162715e-36, 3.0772165598957647e-54), + qd_real( 7.7881651238147598e-01, -2.4891385579973807e-17, + 6.7665497024807980e-35, -6.5218594281701332e-52), + qd_real( 7.7688846567323244e-01, 7.7418602570672864e-18, + -5.9986517872157897e-34, 3.0566548232958972e-50), + qd_real( 7.7495310659487393e-01, -5.2209083189826433e-17, + -9.6653593393686612e-34, 3.7027750076562569e-50), + qd_real( 7.7301045336273699e-01, -3.2565907033649772e-17, + 1.3860807251523929e-33, -3.9971329917586022e-50), + qd_real( 7.7106052426181382e-01, -4.4558442347769265e-17, + -2.9863565614083783e-33, -6.8795262083596236e-50), + qd_real( 7.6910333764557959e-01, 5.1546455184564817e-17, + 2.6142829553524292e-33, -1.6199023632773298e-49), + qd_real( 7.6713891193582040e-01, -1.8885903683750782e-17, + -1.3659359331495433e-33, -2.2538834962921934e-50), + qd_real( 7.6516726562245896e-01, -3.2707225612534598e-17, + 1.1177117747079528e-33, -3.7005182280175715e-50), + qd_real( 7.6318841726338127e-01, 2.6314748416750748e-18, + 1.4048039063095910e-34, 8.9601886626630321e-52), + qd_real( 7.6120238548426178e-01, 3.5315510881690551e-17, + 1.2833566381864357e-33, 8.6221435180890613e-50), + qd_real( 7.5920918897838807e-01, -3.8558842175523123e-17, + 2.9720241208332759e-34, -1.2521388928220163e-50), + qd_real( 7.5720884650648457e-01, -1.9909098777335502e-17, + 3.9409283266158482e-34, 2.0744254207802976e-50), + qd_real( 7.5520137689653655e-01, -1.9402238001823017e-17, + -3.7756206444727573e-34, -2.1212242308178287e-50), + qd_real( 7.5318679904361252e-01, -3.7937789838736540e-17, + -6.7009539920231559e-34, -6.7128562115050214e-51), + qd_real( 7.5116513190968637e-01, 4.3499761158645868e-17, + 2.5227718971102212e-33, -6.5969709212757102e-50), + qd_real( 7.4913639452345937e-01, -4.4729078447011889e-17, + -2.4206025249983768e-33, 1.1336681351116422e-49), + qd_real( 7.4710060598018013e-01, 1.1874824875965430e-17, + 2.1992523849833518e-34, 1.1025018564644483e-50), + qd_real( 7.4505778544146595e-01, 1.5078686911877863e-17, + 8.0898987212942471e-34, 8.2677958765323532e-50), + qd_real( 7.4300795213512172e-01, -2.5144629669719265e-17, + 7.1128989512526157e-34, 3.0181629077821220e-50), + qd_real( 7.4095112535495911e-01, -1.4708616952297345e-17, + -4.9550433827142032e-34, 3.1434132533735671e-50), + qd_real( 7.3888732446061511e-01, 3.4324874808225091e-17, + -1.3706639444717610e-33, -3.3520827530718938e-51), + qd_real( 7.3681656887736990e-01, -2.8932468101656295e-17, + -3.4649887126202378e-34, -1.8484474476291476e-50), + qd_real( 7.3473887809596350e-01, -3.4507595976263941e-17, + -2.3718000676666409e-33, -3.9696090387165402e-50), + qd_real( 7.3265427167241282e-01, 1.8918673481573520e-17, + -1.5123719544119886e-33, -9.7922152011625728e-51), + qd_real( 7.3056276922782759e-01, -2.9689959904476928e-17, + -1.1276871244239744e-33, -3.0531520961539007e-50), + qd_real( 7.2846439044822520e-01, 1.1924642323370718e-19, + 5.9001892316611011e-36, 1.2178089069502704e-52), + qd_real( 7.2635915508434601e-01, -3.1917502443460542e-17, + 7.7047912412039396e-34, 4.1455880160182123e-50), + qd_real( 7.2424708295146689e-01, 2.9198471334403004e-17, + 2.3027324968739464e-33, -1.2928820533892183e-51), + qd_real( 7.2212819392921535e-01, -2.3871262053452047e-17, + 1.0636125432862273e-33, -4.4598638837802517e-50), + qd_real( 7.2000250796138165e-01, -2.5689658854462333e-17, + -9.1492566948567925e-34, 4.4403780801267786e-50), + qd_real( 7.1787004505573171e-01, 2.7006476062511453e-17, + -2.2854956580215348e-34, 9.1726903890287867e-51), + qd_real( 7.1573082528381871e-01, -5.1581018476410262e-17, + -1.3736271349300259e-34, -1.2734611344111297e-50), + qd_real( 7.1358486878079364e-01, -4.2342504403133584e-17, + -4.2690366101617268e-34, -2.6352370883066522e-50), + qd_real( 7.1143219574521643e-01, 7.9643298613856813e-18, + 2.9488239510721469e-34, 1.6985236437666356e-50), + qd_real( 7.0927282643886569e-01, -3.7597359110245730e-17, + 1.0613125954645119e-34, 8.9465480185486032e-51), + qd_real( 7.0710678118654757e-01, -4.8336466567264567e-17, + 2.0693376543497068e-33, 2.4677734957341755e-50) +}; + +/* Computes sin(a) and cos(a) using Taylor series. + Assumes |a| <= pi/2048. */ +static void sincos_taylor(const qd_real &a, + qd_real &sin_a, qd_real &cos_a) { + const double thresh = 0.5 * qd_real::_eps * std::abs(to_double(a)); + qd_real p, s, t, x; + + if (a.is_zero()) { + sin_a = 0.0; + cos_a = 1.0; + return; + } + + x = -sqr(a); + s = a; + p = a; + int i = 0; + do { + p *= x; + t = p * inv_fact[i]; + s += t; + i += 2; + } while (i < n_inv_fact && std::abs(to_double(t)) > thresh); + + sin_a = s; + cos_a = sqrt(1.0 - sqr(s)); +} + +static qd_real sin_taylor(const qd_real &a) { + const double thresh = 0.5 * qd_real::_eps * std::abs(to_double(a)); + qd_real p, s, t, x; + + if (a.is_zero()) { + return 0.0; + } + + x = -sqr(a); + s = a; + p = a; + int i = 0; + do { + p *= x; + t = p * inv_fact[i]; + s += t; + i += 2; + } while (i < n_inv_fact && std::abs(to_double(t)) > thresh); + + return s; +} + +static qd_real cos_taylor(const qd_real &a) { + const double thresh = 0.5 * qd_real::_eps; + qd_real p, s, t, x; + + if (a.is_zero()) { + return 1.0; + } + + x = -sqr(a); + s = 1.0 + mul_pwr2(x, 0.5); + p = x; + int i = 1; + do { + p *= x; + t = p * inv_fact[i]; + s += t; + i += 2; + } while (i < n_inv_fact && std::abs(to_double(t)) > thresh); + + return s; +} + +qd_real sin(const qd_real &a) { + + /* Strategy. To compute sin(x), we choose integers a, b so that + + x = s + a * (pi/2) + b * (pi/1024) + + and |s| <= pi/2048. Using a precomputed table of + sin(k pi / 1024) and cos(k pi / 1024), we can compute + sin(x) from sin(s) and cos(s). This greatly increases the + convergence of the sine Taylor series. */ + + if (a.is_zero()) { + return 0.0; + } + + // approximately reduce modulo 2*pi + qd_real z = nint(a / qd_real::_2pi); + qd_real r = a - qd_real::_2pi * z; + + // approximately reduce modulo pi/2 and then modulo pi/1024 + double q = std::floor(r.x[0] / qd_real::_pi2[0] + 0.5); + qd_real t = r - qd_real::_pi2 * q; + int j = static_cast(q); + q = std::floor(t.x[0] / _pi1024[0] + 0.5); + t -= _pi1024 * q; + int k = static_cast(q); + int abs_k = std::abs(k); + + if (j < -2 || j > 2) { + qd_real::error("(qd_real::sin): Cannot reduce modulo pi/2."); + return qd_real::_nan; + } + + if (abs_k > 256) { + qd_real::error("(qd_real::sin): Cannot reduce modulo pi/1024."); + return qd_real::_nan; + } + + if (k == 0) { + switch (j) { + case 0: + return sin_taylor(t); + case 1: + return cos_taylor(t); + case -1: + return -cos_taylor(t); + default: + return -sin_taylor(t); + } + } + + qd_real sin_t, cos_t; + qd_real u = cos_table[abs_k-1]; + qd_real v = sin_table[abs_k-1]; + sincos_taylor(t, sin_t, cos_t); + + if (j == 0) { + if (k > 0) { + r = u * sin_t + v * cos_t; + } else { + r = u * sin_t - v * cos_t; + } + } else if (j == 1) { + if (k > 0) { + r = u * cos_t - v * sin_t; + } else { + r = u * cos_t + v * sin_t; + } + } else if (j == -1) { + if (k > 0) { + r = v * sin_t - u * cos_t; + } else { + r = - u * cos_t - v * sin_t; + } + } else { + if (k > 0) { + r = - u * sin_t - v * cos_t; + } else { + r = v * cos_t - u * sin_t; + } + } + + return r; +} + +qd_real cos(const qd_real &a) { + + if (a.is_zero()) { + return 1.0; + } + + // approximately reduce modulo 2*pi + qd_real z = nint(a / qd_real::_2pi); + qd_real r = a - qd_real::_2pi * z; + + // approximately reduce modulo pi/2 and then modulo pi/1024 + double q = std::floor(r.x[0] / qd_real::_pi2.x[0] + 0.5); + qd_real t = r - qd_real::_pi2 * q; + int j = static_cast(q); + q = std::floor(t.x[0] / _pi1024.x[0] + 0.5); + t -= _pi1024 * q; + int k = static_cast(q); + int abs_k = std::abs(k); + + if (j < -2 || j > 2) { + qd_real::error("(qd_real::cos): Cannot reduce modulo pi/2."); + return qd_real::_nan; + } + + if (abs_k > 256) { + qd_real::error("(qd_real::cos): Cannot reduce modulo pi/1024."); + return qd_real::_nan; + } + + if (k == 0) { + switch (j) { + case 0: + return cos_taylor(t); + case 1: + return -sin_taylor(t); + case -1: + return sin_taylor(t); + default: + return -cos_taylor(t); + } + } + + qd_real sin_t, cos_t; + sincos_taylor(t, sin_t, cos_t); + + qd_real u = cos_table[abs_k-1]; + qd_real v = sin_table[abs_k-1]; + + if (j == 0) { + if (k > 0) { + r = u * cos_t - v * sin_t; + } else { + r = u * cos_t + v * sin_t; + } + } else if (j == 1) { + if (k > 0) { + r = - u * sin_t - v * cos_t; + } else { + r = v * cos_t - u * sin_t; + } + } else if (j == -1) { + if (k > 0) { + r = u * sin_t + v * cos_t; + } else { + r = u * sin_t - v * cos_t; + } + } else { + if (k > 0) { + r = v * sin_t - u * cos_t; + } else { + r = - u * cos_t - v * sin_t; + } + } + + return r; +} + +void sincos(const qd_real &a, qd_real &sin_a, qd_real &cos_a) { + + if (a.is_zero()) { + sin_a = 0.0; + cos_a = 1.0; + return; + } + + // approximately reduce by 2*pi + qd_real z = nint(a / qd_real::_2pi); + qd_real t = a - qd_real::_2pi * z; + + // approximately reduce by pi/2 and then by pi/1024. + double q = std::floor(t.x[0] / qd_real::_pi2.x[0] + 0.5); + t -= qd_real::_pi2 * q; + int j = static_cast(q); + q = std::floor(t.x[0] / _pi1024.x[0] + 0.5); + t -= _pi1024 * q; + int k = static_cast(q); + int abs_k = std::abs(k); + + if (j < -2 || j > 2) { + qd_real::error("(qd_real::sincos): Cannot reduce modulo pi/2."); + cos_a = sin_a = qd_real::_nan; + return; + } + + if (abs_k > 256) { + qd_real::error("(qd_real::sincos): Cannot reduce modulo pi/1024."); + cos_a = sin_a = qd_real::_nan; + return; + } + + qd_real sin_t, cos_t; + sincos_taylor(t, sin_t, cos_t); + + if (k == 0) { + if (j == 0) { + sin_a = sin_t; + cos_a = cos_t; + } else if (j == 1) { + sin_a = cos_t; + cos_a = -sin_t; + } else if (j == -1) { + sin_a = -cos_t; + cos_a = sin_t; + } else { + sin_a = -sin_t; + cos_a = -cos_t; + } + return; + } + + qd_real u = cos_table[abs_k-1]; + qd_real v = sin_table[abs_k-1]; + + if (j == 0) { + if (k > 0) { + sin_a = u * sin_t + v * cos_t; + cos_a = u * cos_t - v * sin_t; + } else { + sin_a = u * sin_t - v * cos_t; + cos_a = u * cos_t + v * sin_t; + } + } else if (j == 1) { + if (k > 0) { + cos_a = - u * sin_t - v * cos_t; + sin_a = u * cos_t - v * sin_t; + } else { + cos_a = v * cos_t - u * sin_t; + sin_a = u * cos_t + v * sin_t; + } + } else if (j == -1) { + if (k > 0) { + cos_a = u * sin_t + v * cos_t; + sin_a = v * sin_t - u * cos_t; + } else { + cos_a = u * sin_t - v * cos_t; + sin_a = - u * cos_t - v * sin_t; + } + } else { + if (k > 0) { + sin_a = - u * sin_t - v * cos_t; + cos_a = v * sin_t - u * cos_t; + } else { + sin_a = v * cos_t - u * sin_t; + cos_a = - u * cos_t - v * sin_t; + } + } +} + +qd_real atan(const qd_real &a) { + return atan2(a, qd_real(1.0)); +} + +qd_real atan2(const qd_real &y, const qd_real &x) { + /* Strategy: Instead of using Taylor series to compute + arctan, we instead use Newton's iteration to solve + the equation + + sin(z) = y/r or cos(z) = x/r + + where r = sqrt(x^2 + y^2). + The iteration is given by + + z' = z + (y - sin(z)) / cos(z) (for equation 1) + z' = z - (x - cos(z)) / sin(z) (for equation 2) + + Here, x and y are normalized so that x^2 + y^2 = 1. + If |x| > |y|, then first iteration is used since the + denominator is larger. Otherwise, the second is used. + */ + + if (x.is_zero()) { + + if (y.is_zero()) { + /* Both x and y is zero. */ + qd_real::error("(qd_real::atan2): Both arguments zero."); + return qd_real::_nan; + } + + return (y.is_positive()) ? qd_real::_pi2 : -qd_real::_pi2; + } else if (y.is_zero()) { + return (x.is_positive()) ? qd_real(0.0) : qd_real::_pi; + } + + if (x == y) { + return (y.is_positive()) ? qd_real::_pi4 : -qd_real::_3pi4; + } + + if (x == -y) { + return (y.is_positive()) ? qd_real::_3pi4 : -qd_real::_pi4; + } + + qd_real r = sqrt(sqr(x) + sqr(y)); + qd_real xx = x / r; + qd_real yy = y / r; + + /* Compute double precision approximation to atan. */ + qd_real z = std::atan2(to_double(y), to_double(x)); + qd_real sin_z, cos_z; + + if (std::abs(xx.x[0]) > std::abs(yy.x[0])) { + /* Use Newton iteration 1. z' = z + (y - sin(z)) / cos(z) */ + sincos(z, sin_z, cos_z); + z += (yy - sin_z) / cos_z; + sincos(z, sin_z, cos_z); + z += (yy - sin_z) / cos_z; + sincos(z, sin_z, cos_z); + z += (yy - sin_z) / cos_z; + } else { + /* Use Newton iteration 2. z' = z - (x - cos(z)) / sin(z) */ + sincos(z, sin_z, cos_z); + z -= (xx - cos_z) / sin_z; + sincos(z, sin_z, cos_z); + z -= (xx - cos_z) / sin_z; + sincos(z, sin_z, cos_z); + z -= (xx - cos_z) / sin_z; + } + + return z; +} + + +qd_real drem(const qd_real &a, const qd_real &b) { + qd_real n = nint(a/b); + return (a - n * b); +} + +qd_real divrem(const qd_real &a, const qd_real &b, qd_real &r) { + qd_real n = nint(a/b); + r = a - n * b; + return n; +} + +qd_real tan(const qd_real &a) { + qd_real s, c; + sincos(a, s, c); + return s/c; +} + +qd_real asin(const qd_real &a) { + qd_real abs_a = abs(a); + + if (abs_a > 1.0) { + qd_real::error("(qd_real::asin): Argument out of domain."); + return qd_real::_nan; + } + + if (abs_a.is_one()) { + return (a.is_positive()) ? qd_real::_pi2 : -qd_real::_pi2; + } + + return atan2(a, sqrt(1.0 - sqr(a))); +} + +qd_real acos(const qd_real &a) { + qd_real abs_a = abs(a); + + if (abs_a > 1.0) { + qd_real::error("(qd_real::acos): Argument out of domain."); + return qd_real::_nan; + } + + if (abs_a.is_one()) { + return (a.is_positive()) ? qd_real(0.0) : qd_real::_pi; + } + + return atan2(sqrt(1.0 - sqr(a)), a); +} + +qd_real sinh(const qd_real &a) { + if (a.is_zero()) { + return 0.0; + } + + if (abs(a) > 0.05) { + qd_real ea = exp(a); + return mul_pwr2(ea - inv(ea), 0.5); + } + + /* Since a is small, using the above formula gives + a lot of cancellation. So use Taylor series. */ + qd_real s = a; + qd_real t = a; + qd_real r = sqr(t); + double m = 1.0; + double thresh = std::abs(to_double(a) * qd_real::_eps); + + do { + m += 2.0; + t *= r; + t /= (m-1) * m; + + s += t; + } while (abs(t) > thresh); + + return s; +} + +qd_real cosh(const qd_real &a) { + if (a.is_zero()) { + return 1.0; + } + + qd_real ea = exp(a); + return mul_pwr2(ea + inv(ea), 0.5); +} + +qd_real tanh(const qd_real &a) { + if (a.is_zero()) { + return 0.0; + } + + if (std::abs(to_double(a)) > 0.05) { + qd_real ea = exp(a); + qd_real inv_ea = inv(ea); + return (ea - inv_ea) / (ea + inv_ea); + } else { + qd_real s, c; + s = sinh(a); + c = sqrt(1.0 + sqr(s)); + return s / c; + } +} + +void sincosh(const qd_real &a, qd_real &s, qd_real &c) { + if (std::abs(to_double(a)) <= 0.05) { + s = sinh(a); + c = sqrt(1.0 + sqr(s)); + } else { + qd_real ea = exp(a); + qd_real inv_ea = inv(ea); + s = mul_pwr2(ea - inv_ea, 0.5); + c = mul_pwr2(ea + inv_ea, 0.5); + } +} + +qd_real asinh(const qd_real &a) { + return log(a + sqrt(sqr(a) + 1.0)); +} + +qd_real acosh(const qd_real &a) { + if (a < 1.0) { + qd_real::error("(qd_real::acosh): Argument out of domain."); + return qd_real::_nan; + } + + return log(a + sqrt(sqr(a) - 1.0)); +} + +qd_real atanh(const qd_real &a) { + if (abs(a) >= 1.0) { + qd_real::error("(qd_real::atanh): Argument out of domain."); + return qd_real::_nan; + } + + return mul_pwr2(log((1.0 + a) / (1.0 - a)), 0.5); +} + +QD_API qd_real fmod(const qd_real &a, const qd_real &b) { + qd_real n = aint(a / b); + return (a - b * n); +} + +QD_API qd_real qdrand() { + static const double m_const = 4.6566128730773926e-10; /* = 2^{-31} */ + double m = m_const; + qd_real r = 0.0; + double d; + + /* Strategy: Generate 31 bits at a time, using lrand48 + random number generator. Shift the bits, and repeat + 7 times. */ + + for (int i = 0; i < 7; i++, m *= m_const) { + d = std::rand() * m; + r += d; + } + + return r; +} + + +/* polyeval(c, n, x) + Evaluates the given n-th degree polynomial at x. + The polynomial is given by the array of (n+1) coefficients. */ +qd_real polyeval(const qd_real *c, int n, const qd_real &x) { + /* Just use Horner's method of polynomial evaluation. */ + qd_real r = c[n]; + + for (int i = n-1; i >= 0; i--) { + r *= x; + r += c[i]; + } + + return r; +} + +/* polyroot(c, n, x0) + Given an n-th degree polynomial, finds a root close to + the given guess x0. Note that this uses simple Newton + iteration scheme, and does not work for multiple roots. */ +QD_API qd_real polyroot(const qd_real *c, int n, + const qd_real &x0, int max_iter, double thresh) { + qd_real x = x0; + qd_real f; + qd_real *d = new qd_real[n]; + bool conv = false; + int i; + double max_c = std::abs(to_double(c[0])); + double v; + + if (thresh == 0.0) thresh = qd_real::_eps; + + /* Compute the coefficients of the derivatives. */ + for (i = 1; i <= n; i++) { + v = std::abs(to_double(c[i])); + if (v > max_c) max_c = v; + d[i-1] = c[i] * static_cast(i); + } + thresh *= max_c; + + /* Newton iteration. */ + for (i = 0; i < max_iter; i++) { + f = polyeval(c, n, x); + + if (abs(f) < thresh) { + conv = true; + break; + } + x -= (f / polyeval(d, n-1, x)); + } + delete [] d; + + if (!conv) { + qd_real::error("(qd_real::polyroot): Failed to converge."); + return qd_real::_nan; + } + + return x; +} + +qd_real qd_real::debug_rand() { + if (std::rand() % 2 == 0) + return qdrand(); + + int expn = 0; + qd_real a = 0.0; + double d; + for (int i = 0; i < 4; i++) { + d = std::ldexp(std::rand() / static_cast(RAND_MAX), -expn); + a += d; + expn = expn + 54 + std::rand() % 200; + } + return a; +} + diff --git a/src/external/PackedCSparse/qd/qd_real.h b/src/external/PackedCSparse/qd/qd_real.h new file mode 100644 index 00000000..1ecfc732 --- /dev/null +++ b/src/external/PackedCSparse/qd/qd_real.h @@ -0,0 +1,296 @@ +/* + * include/qd_real.h + * + * This work was supported by the Director, Office of Science, Division + * of Mathematical, Information, and Computational Sciences of the + * U.S. Department of Energy under contract number DE-AC03-76SF00098. + * + * Copyright (c) 2000-2007 + * + * Quad-double precision (>= 212-bit significand) floating point arithmetic + * package, written in ANSI C++, taking full advantage of operator overloading. + * Uses similar techniques as that of David Bailey's double-double package + * and that of Jonathan Shewchuk's adaptive precision floating point + * arithmetic package. See + * + * http://www.nersc.gov/~dhbailey/mpdist/mpdist.html + * http://www.cs.cmu.edu/~quake/robust.html + * + * for more details. + * + * Yozo Hida + */ +#ifndef _QD_QD_REAL_H +#define _QD_QD_REAL_H + +#include +#include +#include +#include "qd_config.h" +#include "dd_real.h" + +struct QD_API qd_real { + double x[4]; /* The Components. */ + + /* Eliminates any zeros in the middle component(s). */ + void zero_elim(); + void zero_elim(double &e); + + void renorm(); + void renorm(double &e); + + void quick_accum(double d, double &e); + void quick_prod_accum(double a, double b, double &e); + + qd_real(double x0, double x1, double x2, double x3); + explicit qd_real(const double *xx); + + static const qd_real _2pi; + static const qd_real _pi; + static const qd_real _3pi4; + static const qd_real _pi2; + static const qd_real _pi4; + static const qd_real _e; + static const qd_real _log2; + static const qd_real _log10; + static const qd_real _nan; + static const qd_real _inf; + + static const double _eps; + static const double _min_normalized; + static const qd_real _max; + static const qd_real _safe_max; + static const int _ndigits; + + qd_real(); + qd_real(const char *s); + qd_real(const dd_real &dd); + qd_real(double d); + qd_real(int i); + + double operator[](int i) const; + double &operator[](int i); + + static void error(const char *msg); + + bool isnan() const; + bool isfinite() const { return QD_ISFINITE(x[0]); } + bool isinf() const { return QD_ISINF(x[0]); } + + static qd_real ieee_add(const qd_real &a, const qd_real &b); + static qd_real sloppy_add(const qd_real &a, const qd_real &b); + + qd_real &operator+=(double a); + qd_real &operator+=(const dd_real &a); + qd_real &operator+=(const qd_real &a); + + qd_real &operator-=(double a); + qd_real &operator-=(const dd_real &a); + qd_real &operator-=(const qd_real &a); + + static qd_real sloppy_mul(const qd_real &a, const qd_real &b); + static qd_real accurate_mul(const qd_real &a, const qd_real &b); + + qd_real &operator*=(double a); + qd_real &operator*=(const dd_real &a); + qd_real &operator*=(const qd_real &a); + + static qd_real sloppy_div(const qd_real &a, const dd_real &b); + static qd_real accurate_div(const qd_real &a, const dd_real &b); + static qd_real sloppy_div(const qd_real &a, const qd_real &b); + static qd_real accurate_div(const qd_real &a, const qd_real &b); + + qd_real &operator/=(double a); + qd_real &operator/=(const dd_real &a); + qd_real &operator/=(const qd_real &a); + + qd_real operator^(int n) const; + + qd_real operator-() const; + + qd_real &operator=(double a); + qd_real &operator=(const dd_real &a); + qd_real &operator=(const char *s); + + bool is_zero() const; + bool is_one() const; + bool is_positive() const; + bool is_negative() const; + + explicit operator bool() const; // new + explicit operator double() const; // new + + static qd_real rand(void); + + void to_digits(char *s, int &expn, int precision = _ndigits) const; + void write(char *s, int len, int precision = _ndigits, + bool showpos = false, bool uppercase = false) const; + std::string to_string(int precision = _ndigits, int width = 0, + std::ios_base::fmtflags fmt = static_cast(0), + bool showpos = false, bool uppercase = false, char fill = ' ') const; + static int read(const char *s, qd_real &a); + + /* Debugging methods */ + void dump(const std::string &name, std::ostream &os = std::cerr) const; + void dump_bits(const std::string &name, + std::ostream &os = std::cerr) const; + + static qd_real debug_rand(); + +}; + +namespace std { + template <> + class numeric_limits : public numeric_limits { + public: + inline static double epsilon() { return qd_real::_eps; } + inline static double min() { return qd_real::_min_normalized; } + inline static qd_real max() { return qd_real::_max; } + inline static qd_real safe_max() { return qd_real::_safe_max; } + static const int digits = 209; + static const int digits10 = 62; + }; +} + +QD_API qd_real polyeval(const qd_real *c, int n, const qd_real &x); +QD_API qd_real polyroot(const qd_real *c, int n, + const qd_real &x0, int max_iter = 64, double thresh = 0.0); + +QD_API qd_real qdrand(void); +QD_API qd_real sqrt(const qd_real &a); + +QD_API inline bool isnan(const qd_real &a) { return a.isnan(); } +QD_API inline bool isfinite(const qd_real &a) { return a.isfinite(); } +QD_API inline bool isinf(const qd_real &a) { return a.isinf(); } + +/* Computes qd * d where d is known to be a power of 2. + This can be done component wise. */ +QD_API qd_real mul_pwr2(const qd_real &qd, double d); + +QD_API qd_real operator+(const qd_real &a, const qd_real &b); +QD_API qd_real operator+(const dd_real &a, const qd_real &b); +QD_API qd_real operator+(const qd_real &a, const dd_real &b); +QD_API qd_real operator+(const qd_real &a, double b); +QD_API qd_real operator+(double a, const qd_real &b); + +QD_API qd_real operator-(const qd_real &a, const qd_real &b); +QD_API qd_real operator-(const dd_real &a, const qd_real &b); +QD_API qd_real operator-(const qd_real &a, const dd_real &b); +QD_API qd_real operator-(const qd_real &a, double b); +QD_API qd_real operator-(double a, const qd_real &b); + +QD_API qd_real operator*(const qd_real &a, const qd_real &b); +QD_API qd_real operator*(const dd_real &a, const qd_real &b); +QD_API qd_real operator*(const qd_real &a, const dd_real &b); +QD_API qd_real operator*(const qd_real &a, double b); +QD_API qd_real operator*(double a, const qd_real &b); + +QD_API qd_real operator/(const qd_real &a, const qd_real &b); +QD_API qd_real operator/(const dd_real &a, const qd_real &b); +QD_API qd_real operator/(const qd_real &a, const dd_real &b); +QD_API qd_real operator/(const qd_real &a, double b); +QD_API qd_real operator/(double a, const qd_real &b); + +QD_API qd_real sqr(const qd_real &a); +QD_API qd_real sqrt(const qd_real &a); +QD_API qd_real pow(const qd_real &a, int n); +QD_API qd_real pow(const qd_real &a, const qd_real &b); +QD_API qd_real npwr(const qd_real &a, int n); + +QD_API qd_real nroot(const qd_real &a, int n); + +QD_API qd_real rem(const qd_real &a, const qd_real &b); +QD_API qd_real drem(const qd_real &a, const qd_real &b); +QD_API qd_real divrem(const qd_real &a, const qd_real &b, qd_real &r); + +dd_real to_dd_real(const qd_real &a); +double to_double(const qd_real &a); +int to_int(const qd_real &a); + +QD_API bool operator==(const qd_real &a, const qd_real &b); +QD_API bool operator==(const qd_real &a, const dd_real &b); +QD_API bool operator==(const dd_real &a, const qd_real &b); +QD_API bool operator==(double a, const qd_real &b); +QD_API bool operator==(const qd_real &a, double b); + +QD_API bool operator<(const qd_real &a, const qd_real &b); +QD_API bool operator<(const qd_real &a, const dd_real &b); +QD_API bool operator<(const dd_real &a, const qd_real &b); +QD_API bool operator<(double a, const qd_real &b); +QD_API bool operator<(const qd_real &a, double b); + +QD_API bool operator>(const qd_real &a, const qd_real &b); +QD_API bool operator>(const qd_real &a, const dd_real &b); +QD_API bool operator>(const dd_real &a, const qd_real &b); +QD_API bool operator>(double a, const qd_real &b); +QD_API bool operator>(const qd_real &a, double b); + +QD_API bool operator<=(const qd_real &a, const qd_real &b); +QD_API bool operator<=(const qd_real &a, const dd_real &b); +QD_API bool operator<=(const dd_real &a, const qd_real &b); +QD_API bool operator<=(double a, const qd_real &b); +QD_API bool operator<=(const qd_real &a, double b); + +QD_API bool operator>=(const qd_real &a, const qd_real &b); +QD_API bool operator>=(const qd_real &a, const dd_real &b); +QD_API bool operator>=(const dd_real &a, const qd_real &b); +QD_API bool operator>=(double a, const qd_real &b); +QD_API bool operator>=(const qd_real &a, double b); + +QD_API bool operator!=(const qd_real &a, const qd_real &b); +QD_API bool operator!=(const qd_real &a, const dd_real &b); +QD_API bool operator!=(const dd_real &a, const qd_real &b); +QD_API bool operator!=(double a, const qd_real &b); +QD_API bool operator!=(const qd_real &a, double b); + +QD_API qd_real fabs(const qd_real &a); +QD_API qd_real abs(const qd_real &a); /* same as fabs */ + +QD_API qd_real ldexp(const qd_real &a, int n); + +QD_API qd_real nint(const qd_real &a); +QD_API qd_real quick_nint(const qd_real &a); +QD_API qd_real floor(const qd_real &a); +QD_API qd_real ceil(const qd_real &a); +QD_API qd_real aint(const qd_real &a); + +QD_API qd_real sin(const qd_real &a); +QD_API qd_real cos(const qd_real &a); +QD_API qd_real tan(const qd_real &a); +QD_API void sincos(const qd_real &a, qd_real &s, qd_real &c); + +QD_API qd_real asin(const qd_real &a); +QD_API qd_real acos(const qd_real &a); +QD_API qd_real atan(const qd_real &a); +QD_API qd_real atan2(const qd_real &y, const qd_real &x); + +QD_API qd_real exp(const qd_real &a); +QD_API qd_real log(const qd_real &a); +QD_API qd_real log10(const qd_real &a); + +QD_API qd_real sinh(const qd_real &a); +QD_API qd_real cosh(const qd_real &a); +QD_API qd_real tanh(const qd_real &a); +QD_API void sincosh(const qd_real &a, qd_real &sin_qd, qd_real &cos_qd); + +QD_API qd_real asinh(const qd_real &a); +QD_API qd_real acosh(const qd_real &a); +QD_API qd_real atanh(const qd_real &a); + +QD_API qd_real qdrand(void); + +QD_API qd_real max(const qd_real &a, const qd_real &b); +QD_API qd_real max(const qd_real &a, const qd_real &b, const qd_real &c); +QD_API qd_real min(const qd_real &a, const qd_real &b); +QD_API qd_real min(const qd_real &a, const qd_real &b, const qd_real &c); + +QD_API qd_real fmod(const qd_real &a, const qd_real &b); + +QD_API std::ostream &operator<<(std::ostream &s, const qd_real &a); +QD_API std::istream &operator>>(std::istream &s, qd_real &a); +#ifdef QD_INLINE +#include "qd_inline.h" +#endif + +#endif /* _QD_QD_REAL_H */ + diff --git a/src/external/PackedCSparse/qd/util.cc b/src/external/PackedCSparse/qd/util.cc new file mode 100644 index 00000000..ab962081 --- /dev/null +++ b/src/external/PackedCSparse/qd/util.cc @@ -0,0 +1,22 @@ +#include +#include "util.h" + +void append_expn(std::string &str, int expn) { + int k; + + str += (expn < 0 ? '-' : '+'); + expn = std::abs(expn); + + if (expn >= 100) { + k = (expn / 100); + str += '0' + k; + expn -= 100*k; + } + + k = (expn / 10); + str += '0' + k; + expn -= 10*k; + + str += '0' + expn; +} + diff --git a/src/external/PackedCSparse/qd/util.h b/src/external/PackedCSparse/qd/util.h new file mode 100644 index 00000000..7de35836 --- /dev/null +++ b/src/external/PackedCSparse/qd/util.h @@ -0,0 +1,4 @@ +#include + +void append_expn(std::string &str, int expn); + diff --git a/src/external/PackedCSparse/transpose.h b/src/external/PackedCSparse/transpose.h new file mode 100644 index 00000000..6e1502e3 --- /dev/null +++ b/src/external/PackedCSparse/transpose.h @@ -0,0 +1,90 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2022 Ioannis Iakovidis + +// This file is converted from PolytopeSamplerMatlab +//(https://github.com/ConstrainedSampler/PolytopeSamplerMatlab/blob/master/code/solver/PackedCSparse/PackedChol.h) by Ioannis Iakovidis +#pragma once +#include "SparseMatrix.h" + +// Problem: +// Compute M = A' + +// Algorithm: +// We precompute the mapping from entries of A to entries of At + +namespace PackedCSparse { + template + struct TransposeOutput : SparseMatrix + { + UniquePtr forward; + + template + void initialize(const SparseMatrix& A) + { + pcs_assert(A.initialized(), "transpose: bad inputs."); + SparseMatrix::initialize(A.n, A.m, A.nnz()); + + Ti Am = A.m, An = A.n, * Ap = A.p.get(), * Ai = A.i.get(); + Ti Bm = this->m, Bn = this->n, * Bp = this->p.get(), * Bi = this->i.get(); + Ti nz = A.nnz(); + + // compute row counts of A + Ti* count = new Ti[Bn + 1](); + + for (Ti p = 0; p < nz; p++) + count[Ai[p]]++; + + // compute this->p + Bp[0] = 0; + for (Ti i = 0; i < Bn; i++) + { + Bp[i + 1] = Bp[i] + count[i]; + count[i] = Bp[i]; // Now, cnt[i] stores the index of the first element in the i-th row + } + + // compute i and forward + if (!std::is_same::value) + forward.reset(new Ti[nz]); + for (Ti j = 0; j < An; j++) + { + for (Ti p = Ap[j]; p < Ap[j + 1]; p++) + { + Ti q = count[Ai[p]]; + Bi[q] = j; + if (!std::is_same::value) + forward[p] = q; + count[Ai[p]]++; + } + } + + delete[] count; + } + }; + + template + void transpose(TransposeOutput& o, const SparseMatrix& A) + { + if (!o.initialized()) + o.initialize(A); + + Tx* Ax = A.x.get(); Tx2 *Bx = o.x.get(); + Ti nz = o.nnz(), *forward = o.forward.get(); + + if (!std::is_same::value) + { + for (Ti s = 0; s < nz; s++) + Bx[forward[s]] = Tx2(Ax[s]); + } + } + + template + TransposeOutput transpose(const SparseMatrix& A) + { + TransposeOutput o; + transpose(o, A); + return o; + } +} diff --git a/src/external/Spectra/include/Spectra/GenEigsBase.h b/src/external/Spectra/include/Spectra/GenEigsBase.h new file mode 100644 index 00000000..19b12c15 --- /dev/null +++ b/src/external/Spectra/include/Spectra/GenEigsBase.h @@ -0,0 +1,479 @@ +// Copyright (C) 2018-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef GEN_EIGS_BASE_H +#define GEN_EIGS_BASE_H + +#include +#include // std::vector +#include // std::abs, std::pow, std::sqrt +#include // std::min, std::copy +#include // std::complex, std::conj, std::norm, std::abs +#include // std::invalid_argument + +#include "Util/TypeTraits.h" +#include "Util/SelectionRule.h" +#include "Util/CompInfo.h" +#include "Util/SimpleRandom.h" +#include "MatOp/internal/ArnoldiOp.h" +#include "LinAlg/UpperHessenbergQR.h" +#include "LinAlg/DoubleShiftQR.h" +#include "LinAlg/UpperHessenbergEigen.h" +#include "LinAlg/Arnoldi.h" + +namespace Spectra { + + +/// +/// \ingroup EigenSolver +/// +/// This is the base class for general eigen solvers, mainly for internal use. +/// It is kept here to provide the documentation for member functions of concrete eigen solvers +/// such as GenEigsSolver and GenEigsRealShiftSolver. +/// +template < typename Scalar, + int SelectionRule, + typename OpType, + typename BOpType > +class GenEigsBase +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Array Array; + typedef Eigen::Array BoolArray; + typedef Eigen::Map MapMat; + typedef Eigen::Map MapVec; + typedef Eigen::Map MapConstVec; + + typedef std::complex Complex; + typedef Eigen::Matrix ComplexMatrix; + typedef Eigen::Matrix ComplexVector; + + typedef ArnoldiOp ArnoldiOpType; + typedef Arnoldi ArnoldiFac; + +protected: + OpType* m_op; // object to conduct matrix operation, + // e.g. matrix-vector product + const Index m_n; // dimension of matrix A + const Index m_nev; // number of eigenvalues requested + const Index m_ncv; // dimension of Krylov subspace in the Arnoldi method + Index m_nmatop; // number of matrix operations called + Index m_niter; // number of restarting iterations + + ArnoldiFac m_fac; // Arnoldi factorization + + ComplexVector m_ritz_val; // Ritz values + ComplexMatrix m_ritz_vec; // Ritz vectors + ComplexVector m_ritz_est; // last row of m_ritz_vec + +private: + BoolArray m_ritz_conv; // indicator of the convergence of Ritz values + int m_info; // status of the computation + + const Scalar m_near_0; // a very small value, but 1.0 / m_near_0 does not overflow + // ~= 1e-307 for the "double" type + const Scalar m_eps; // the machine precision, ~= 1e-16 for the "double" type + const Scalar m_eps23; // m_eps^(2/3), used to test the convergence + + // Real Ritz values calculated from UpperHessenbergEigen have exact zero imaginary part + // Complex Ritz values have exact conjugate pairs + // So we use exact tests here + static bool is_complex(const Complex& v) { return v.imag() != Scalar(0); } + static bool is_conj(const Complex& v1, const Complex& v2) { return v1 == Eigen::numext::conj(v2); } + + // Implicitly restarted Arnoldi factorization + void restart(Index k) + { + using std::norm; + + if(k >= m_ncv) + return; + + DoubleShiftQR decomp_ds(m_ncv); + UpperHessenbergQR decomp_hb(m_ncv); + Matrix Q = Matrix::Identity(m_ncv, m_ncv); + + for(Index i = k; i < m_ncv; i++) + { + if(is_complex(m_ritz_val[i]) && is_conj(m_ritz_val[i], m_ritz_val[i + 1])) + { + // H - mu * I = Q1 * R1 + // H <- R1 * Q1 + mu * I = Q1' * H * Q1 + // H - conj(mu) * I = Q2 * R2 + // H <- R2 * Q2 + conj(mu) * I = Q2' * H * Q2 + // + // (H - mu * I) * (H - conj(mu) * I) = Q1 * Q2 * R2 * R1 = Q * R + const Scalar s = Scalar(2) * m_ritz_val[i].real(); + const Scalar t = norm(m_ritz_val[i]); + + decomp_ds.compute(m_fac.matrix_H(), s, t); + + // Q -> Q * Qi + decomp_ds.apply_YQ(Q); + // H -> Q'HQ + // Matrix Q = Matrix::Identity(m_ncv, m_ncv); + // decomp_ds.apply_YQ(Q); + // m_fac_H = Q.transpose() * m_fac_H * Q; + m_fac.compress_H(decomp_ds); + + i++; + } else { + // QR decomposition of H - mu * I, mu is real + decomp_hb.compute(m_fac.matrix_H(), m_ritz_val[i].real()); + + // Q -> Q * Qi + decomp_hb.apply_YQ(Q); + // H -> Q'HQ = RQ + mu * I + m_fac.compress_H(decomp_hb); + } + } + + m_fac.compress_V(Q); + m_fac.factorize_from(k, m_ncv, m_nmatop); + + retrieve_ritzpair(); + } + + // Calculates the number of converged Ritz values + Index num_converged(Scalar tol) + { + // thresh = tol * max(m_eps23, abs(theta)), theta for Ritz value + Array thresh = tol * m_ritz_val.head(m_nev).array().abs().max(m_eps23); + Array resid = m_ritz_est.head(m_nev).array().abs() * m_fac.f_norm(); + // Converged "wanted" Ritz values + m_ritz_conv = (resid < thresh); + + return m_ritz_conv.cast().sum(); + } + + // Returns the adjusted nev for restarting + Index nev_adjusted(Index nconv) + { + using std::abs; + + Index nev_new = m_nev; + for(Index i = m_nev; i < m_ncv; i++) + if(abs(m_ritz_est[i]) < m_near_0) nev_new++; + + // Adjust nev_new, according to dnaup2.f line 660~674 in ARPACK + nev_new += std::min(nconv, (m_ncv - nev_new) / 2); + if(nev_new == 1 && m_ncv >= 6) + nev_new = m_ncv / 2; + else if(nev_new == 1 && m_ncv > 3) + nev_new = 2; + + if(nev_new > m_ncv - 2) + nev_new = m_ncv - 2; + + // Increase nev by one if ritz_val[nev - 1] and + // ritz_val[nev] are conjugate pairs + if(is_complex(m_ritz_val[nev_new - 1]) && + is_conj(m_ritz_val[nev_new - 1], m_ritz_val[nev_new])) + { + nev_new++; + } + + return nev_new; + } + + // Retrieves and sorts Ritz values and Ritz vectors + void retrieve_ritzpair() + { + UpperHessenbergEigen decomp(m_fac.matrix_H()); + const ComplexVector& evals = decomp.eigenvalues(); + ComplexMatrix evecs = decomp.eigenvectors(); + + SortEigenvalue sorting(evals.data(), evals.size()); + std::vector ind = sorting.index(); + + // Copy the Ritz values and vectors to m_ritz_val and m_ritz_vec, respectively + for(Index i = 0; i < m_ncv; i++) + { + m_ritz_val[i] = evals[ind[i]]; + m_ritz_est[i] = evecs(m_ncv - 1, ind[i]); + } + for(Index i = 0; i < m_nev; i++) + { + m_ritz_vec.col(i).noalias() = evecs.col(ind[i]); + } + } + +protected: + // Sorts the first nev Ritz pairs in the specified order + // This is used to return the final results + virtual void sort_ritzpair(int sort_rule) + { + // First make sure that we have a valid index vector + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + std::vector ind = sorting.index(); + + switch(sort_rule) + { + case LARGEST_MAGN: + break; + case LARGEST_REAL: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + ind = sorting.index(); + } + break; + case LARGEST_IMAG: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + ind = sorting.index(); + } + break; + case SMALLEST_MAGN: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + ind = sorting.index(); + } + break; + case SMALLEST_REAL: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + ind = sorting.index(); + } + break; + case SMALLEST_IMAG: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + ind = sorting.index(); + } + break; + default: + throw std::invalid_argument("unsupported sorting rule"); + } + + ComplexVector new_ritz_val(m_ncv); + ComplexMatrix new_ritz_vec(m_ncv, m_nev); + BoolArray new_ritz_conv(m_nev); + + for(Index i = 0; i < m_nev; i++) + { + new_ritz_val[i] = m_ritz_val[ind[i]]; + new_ritz_vec.col(i).noalias() = m_ritz_vec.col(ind[i]); + new_ritz_conv[i] = m_ritz_conv[ind[i]]; + } + + m_ritz_val.swap(new_ritz_val); + m_ritz_vec.swap(new_ritz_vec); + m_ritz_conv.swap(new_ritz_conv); + } + +public: + /// \cond + + GenEigsBase(OpType* op, BOpType* Bop, Index nev, Index ncv) : + m_op(op), + m_n(m_op->rows()), + m_nev(nev), + m_ncv(ncv > m_n ? m_n : ncv), + m_nmatop(0), + m_niter(0), + m_fac(ArnoldiOpType(op, Bop), m_ncv), + m_info(NOT_COMPUTED), + m_near_0(TypeTraits::min() * Scalar(10)), + m_eps(Eigen::NumTraits::epsilon()), + m_eps23(Eigen::numext::pow(m_eps, Scalar(2.0) / 3)) + { + if(nev < 1 || nev > m_n - 2) + throw std::invalid_argument("nev must satisfy 1 <= nev <= n - 2, n is the size of matrix"); + + if(ncv < nev + 2 || ncv > m_n) + throw std::invalid_argument("ncv must satisfy nev + 2 <= ncv <= n, n is the size of matrix"); + } + + /// + /// Virtual destructor + /// + virtual ~GenEigsBase() {} + + /// \endcond + + /// + /// Initializes the solver by providing an initial residual vector. + /// + /// \param init_resid Pointer to the initial residual vector. + /// + /// **Spectra** (and also **ARPACK**) uses an iterative algorithm + /// to find eigenvalues. This function allows the user to provide the initial + /// residual vector. + /// + void init(const Scalar* init_resid) + { + // Reset all matrices/vectors to zero + m_ritz_val.resize(m_ncv); + m_ritz_vec.resize(m_ncv, m_nev); + m_ritz_est.resize(m_ncv); + m_ritz_conv.resize(m_nev); + + m_ritz_val.setZero(); + m_ritz_vec.setZero(); + m_ritz_est.setZero(); + m_ritz_conv.setZero(); + + m_nmatop = 0; + m_niter = 0; + + // Initialize the Arnoldi factorization + MapConstVec v0(init_resid, m_n); + m_fac.init(v0, m_nmatop); + } + + /// + /// Initializes the solver by providing a random initial residual vector. + /// + /// This overloaded function generates a random initial residual vector + /// (with a fixed random seed) for the algorithm. Elements in the vector + /// follow independent Uniform(-0.5, 0.5) distribution. + /// + void init() + { + SimpleRandom rng(0); + Vector init_resid = rng.random_vec(m_n); + init(init_resid.data()); + } + + /// + /// Conducts the major computation procedure. + /// + /// \param maxit Maximum number of iterations allowed in the algorithm. + /// \param tol Precision parameter for the calculated eigenvalues. + /// \param sort_rule Rule to sort the eigenvalues and eigenvectors. + /// Supported values are + /// `Spectra::LARGEST_MAGN`, `Spectra::LARGEST_REAL`, + /// `Spectra::LARGEST_IMAG`, `Spectra::SMALLEST_MAGN`, + /// `Spectra::SMALLEST_REAL` and `Spectra::SMALLEST_IMAG`, + /// for example `LARGEST_MAGN` indicates that eigenvalues + /// with largest magnitude come first. + /// Note that this argument is only used to + /// **sort** the final result, and the **selection** rule + /// (e.g. selecting the largest or smallest eigenvalues in the + /// full spectrum) is specified by the template parameter + /// `SelectionRule` of GenEigsSolver. + /// + /// \return Number of converged eigenvalues. + /// + Index compute(Index maxit = 1000, Scalar tol = 1e-10, int sort_rule = LARGEST_MAGN) + { + // The m-step Arnoldi factorization + m_fac.factorize_from(1, m_ncv, m_nmatop); + retrieve_ritzpair(); + // Restarting + Index i, nconv = 0, nev_adj; + for(i = 0; i < maxit; i++) + { + nconv = num_converged(tol); + if(nconv >= m_nev) + break; + + nev_adj = nev_adjusted(nconv); + restart(nev_adj); + } + // Sorting results + sort_ritzpair(sort_rule); + + m_niter += i + 1; + m_info = (nconv >= m_nev) ? SUCCESSFUL : NOT_CONVERGING; + + return std::min(m_nev, nconv); + } + + /// + /// Returns the status of the computation. + /// The full list of enumeration values can be found in \ref Enumerations. + /// + int info() const { return m_info; } + + /// + /// Returns the number of iterations used in the computation. + /// + Index num_iterations() const { return m_niter; } + + /// + /// Returns the number of matrix operations used in the computation. + /// + Index num_operations() const { return m_nmatop; } + + /// + /// Returns the converged eigenvalues. + /// + /// \return A complex-valued vector containing the eigenvalues. + /// Returned vector type will be `Eigen::Vector, ...>`, depending on + /// the template parameter `Scalar` defined. + /// + ComplexVector eigenvalues() const + { + const Index nconv = m_ritz_conv.cast().sum(); + ComplexVector res(nconv); + + if(!nconv) + return res; + + Index j = 0; + for(Index i = 0; i < m_nev; i++) + { + if(m_ritz_conv[i]) + { + res[j] = m_ritz_val[i]; + j++; + } + } + + return res; + } + + /// + /// Returns the eigenvectors associated with the converged eigenvalues. + /// + /// \param nvec The number of eigenvectors to return. + /// + /// \return A complex-valued matrix containing the eigenvectors. + /// Returned matrix type will be `Eigen::Matrix, ...>`, + /// depending on the template parameter `Scalar` defined. + /// + ComplexMatrix eigenvectors(Index nvec) const + { + const Index nconv = m_ritz_conv.cast().sum(); + nvec = std::min(nvec, nconv); + ComplexMatrix res(m_n, nvec); + + if(!nvec) + return res; + + ComplexMatrix ritz_vec_conv(m_ncv, nvec); + Index j = 0; + for(Index i = 0; i < m_nev && j < nvec; i++) + { + if(m_ritz_conv[i]) + { + ritz_vec_conv.col(j).noalias() = m_ritz_vec.col(i); + j++; + } + } + + res.noalias() = m_fac.matrix_V() * ritz_vec_conv; + + return res; + } + + /// + /// Returns all converged eigenvectors. + /// + ComplexMatrix eigenvectors() const + { + return eigenvectors(m_nev); + } +}; + + +} // namespace Spectra + +#endif // GEN_EIGS_BASE_H diff --git a/src/external/Spectra/include/Spectra/GenEigsComplexShiftSolver.h b/src/external/Spectra/include/Spectra/GenEigsComplexShiftSolver.h new file mode 100644 index 00000000..2c1aee7f --- /dev/null +++ b/src/external/Spectra/include/Spectra/GenEigsComplexShiftSolver.h @@ -0,0 +1,156 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef GEN_EIGS_COMPLEX_SHIFT_SOLVER_H +#define GEN_EIGS_COMPLEX_SHIFT_SOLVER_H + +#include + +#include "GenEigsBase.h" +#include "Util/SelectionRule.h" +#include "MatOp/DenseGenComplexShiftSolve.h" + +namespace Spectra { + + +/// +/// \ingroup EigenSolver +/// +/// This class implements the eigen solver for general real matrices with +/// a complex shift value in the **shift-and-invert mode**. The background +/// knowledge of the shift-and-invert mode can be found in the documentation +/// of the SymEigsShiftSolver class. +/// +/// \tparam Scalar The element type of the matrix. +/// Currently supported types are `float`, `double` and `long double`. +/// \tparam SelectionRule An enumeration value indicating the selection rule of +/// the shifted-and-inverted eigenvalues. +/// The full list of enumeration values can be found in +/// \ref Enumerations. +/// \tparam OpType The name of the matrix operation class. Users could either +/// use the DenseGenComplexShiftSolve wrapper class, or define their +/// own that implements all the public member functions as in +/// DenseGenComplexShiftSolve. +/// +template > +class GenEigsComplexShiftSolver: public GenEigsBase +{ +private: + typedef Eigen::Index Index; + typedef std::complex Complex; + typedef Eigen::Matrix Vector; + typedef Eigen::Matrix ComplexVector; + + const Scalar m_sigmar; + const Scalar m_sigmai; + + // First transform back the Ritz values, and then sort + void sort_ritzpair(int sort_rule) + { + using std::abs; + using std::sqrt; + using std::norm; + + // The eigenvalues we get from the iteration is + // nu = 0.5 * (1 / (lambda - sigma) + 1 / (lambda - conj(sigma))) + // So the eigenvalues of the original problem is + // 1 \pm sqrt(1 - 4 * nu^2 * sigmai^2) + // lambda = sigmar + ----------------------------------- + // 2 * nu + // We need to pick the correct root + // Let (lambdaj, vj) be the j-th eigen pair, then A * vj = lambdaj * vj + // and inv(A - r * I) * vj = 1 / (lambdaj - r) * vj + // where r is any shift value. + // We can use this identity to determine lambdaj + // + // op(v) computes Re(inv(A - r * I) * v) for any real v + // If r is real, then op(v) is also real. Let a = Re(vj), b = Im(vj), + // then op(vj) = op(a) + op(b) * i + // By comparing op(vj) and [1 / (lambdaj - r) * vj], we can determine + // which one is the correct root + + // Select a random shift value + SimpleRandom rng(0); + const Scalar shiftr = rng.random() * m_sigmar + rng.random(); + const Complex shift = Complex(shiftr, Scalar(0)); + this->m_op->set_shift(shiftr, Scalar(0)); + + // Calculate inv(A - r * I) * vj + Vector v_real(this->m_n), v_imag(this->m_n), OPv_real(this->m_n), OPv_imag(this->m_n); + const Scalar eps = Eigen::NumTraits::epsilon(); + for(Index i = 0; i < this->m_nev; i++) + { + v_real.noalias() = this->m_fac.matrix_V() * this->m_ritz_vec.col(i).real(); + v_imag.noalias() = this->m_fac.matrix_V() * this->m_ritz_vec.col(i).imag(); + this->m_op->perform_op(v_real.data(), OPv_real.data()); + this->m_op->perform_op(v_imag.data(), OPv_imag.data()); + + // Two roots computed from the quadratic equation + const Complex nu = this->m_ritz_val[i]; + const Complex root_part1 = m_sigmar + Scalar(0.5) / nu; + const Complex root_part2 = Scalar(0.5) * sqrt(Scalar(1) - Scalar(4) * m_sigmai * m_sigmai * (nu * nu)) / nu; + const Complex root1 = root_part1 + root_part2; + const Complex root2 = root_part1 - root_part2; + + // Test roots + Scalar err1 = Scalar(0), err2 = Scalar(0); + for(int k = 0; k < this->m_n; k++) + { + const Complex rhs1 = Complex(v_real[k], v_imag[k]) / (root1 - shift); + const Complex rhs2 = Complex(v_real[k], v_imag[k]) / (root2 - shift); + const Complex OPv = Complex(OPv_real[k], OPv_imag[k]); + err1 += norm(OPv - rhs1); + err2 += norm(OPv - rhs2); + } + + const Complex lambdaj = (err1 < err2) ? root1 : root2; + this->m_ritz_val[i] = lambdaj; + + if(abs(Eigen::numext::imag(lambdaj)) > eps) + { + this->m_ritz_val[i + 1] = Eigen::numext::conj(lambdaj); + i++; + } else { + this->m_ritz_val[i] = Complex(Eigen::numext::real(lambdaj), Scalar(0)); + } + } + + GenEigsBase::sort_ritzpair(sort_rule); + } +public: + /// + /// Constructor to create a eigen solver object using the shift-and-invert mode. + /// + /// \param op Pointer to the matrix operation object. This class should implement + /// the complex shift-solve operation of \f$A\f$: calculating + /// \f$\mathrm{Re}\{(A-\sigma I)^{-1}v\}\f$ for any vector \f$v\f$. Users could either + /// create the object from the DenseGenComplexShiftSolve wrapper class, or + /// define their own that implements all the public member functions + /// as in DenseGenComplexShiftSolve. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-2\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev+2 \le ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev + 1\f$. + /// \param sigmar The real part of the shift. + /// \param sigmai The imaginary part of the shift. + /// + GenEigsComplexShiftSolver(OpType* op, Index nev, Index ncv, const Scalar& sigmar, const Scalar& sigmai) : + GenEigsBase(op, NULL, nev, ncv), + m_sigmar(sigmar), m_sigmai(sigmai) + { + this->m_op->set_shift(m_sigmar, m_sigmai); + } +}; + + +} // namespace Spectra + +#endif // GEN_EIGS_COMPLEX_SHIFT_SOLVER_H diff --git a/src/external/Spectra/include/Spectra/GenEigsRealShiftSolver.h b/src/external/Spectra/include/Spectra/GenEigsRealShiftSolver.h new file mode 100644 index 00000000..a7e3da8e --- /dev/null +++ b/src/external/Spectra/include/Spectra/GenEigsRealShiftSolver.h @@ -0,0 +1,90 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef GEN_EIGS_REAL_SHIFT_SOLVER_H +#define GEN_EIGS_REAL_SHIFT_SOLVER_H + +#include + +#include "GenEigsBase.h" +#include "Util/SelectionRule.h" +#include "MatOp/DenseGenRealShiftSolve.h" + +namespace Spectra { + + +/// +/// \ingroup EigenSolver +/// +/// This class implements the eigen solver for general real matrices with +/// a real shift value in the **shift-and-invert mode**. The background +/// knowledge of the shift-and-invert mode can be found in the documentation +/// of the SymEigsShiftSolver class. +/// +/// \tparam Scalar The element type of the matrix. +/// Currently supported types are `float`, `double` and `long double`. +/// \tparam SelectionRule An enumeration value indicating the selection rule of +/// the shifted-and-inverted eigenvalues. +/// The full list of enumeration values can be found in +/// \ref Enumerations. +/// \tparam OpType The name of the matrix operation class. Users could either +/// use the wrapper classes such as DenseGenRealShiftSolve and +/// SparseGenRealShiftSolve, or define their +/// own that implements all the public member functions as in +/// DenseGenRealShiftSolve. +/// +template > +class GenEigsRealShiftSolver: public GenEigsBase +{ +private: + typedef Eigen::Index Index; + typedef std::complex Complex; + typedef Eigen::Array ComplexArray; + + const Scalar m_sigma; + + // First transform back the Ritz values, and then sort + void sort_ritzpair(int sort_rule) + { + // The eigenvalues we get from the iteration is nu = 1 / (lambda - sigma) + // So the eigenvalues of the original problem is lambda = 1 / nu + sigma + ComplexArray ritz_val_org = Scalar(1.0) / this->m_ritz_val.head(this->m_nev).array() + m_sigma; + this->m_ritz_val.head(this->m_nev) = ritz_val_org; + GenEigsBase::sort_ritzpair(sort_rule); + } +public: + /// + /// Constructor to create a eigen solver object using the shift-and-invert mode. + /// + /// \param op Pointer to the matrix operation object. This class should implement + /// the shift-solve operation of \f$A\f$: calculating + /// \f$(A-\sigma I)^{-1}v\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper class such as DenseGenRealShiftSolve, or + /// define their own that implements all the public member functions + /// as in DenseGenRealShiftSolve. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-2\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev+2 \le ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev + 1\f$. + /// \param sigma The real-valued shift. + /// + GenEigsRealShiftSolver(OpType* op, Index nev, Index ncv, Scalar sigma) : + GenEigsBase(op, NULL, nev, ncv), + m_sigma(sigma) + { + this->m_op->set_shift(m_sigma); + } +}; + + +} // namespace Spectra + +#endif // GEN_EIGS_REAL_SHIFT_SOLVER_H diff --git a/src/external/Spectra/include/Spectra/GenEigsSolver.h b/src/external/Spectra/include/Spectra/GenEigsSolver.h new file mode 100644 index 00000000..a6960acf --- /dev/null +++ b/src/external/Spectra/include/Spectra/GenEigsSolver.h @@ -0,0 +1,160 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef GEN_EIGS_SOLVER_H +#define GEN_EIGS_SOLVER_H + +#include + +#include "GenEigsBase.h" +#include "Util/SelectionRule.h" +#include "MatOp/DenseGenMatProd.h" + +namespace Spectra { + + +/// +/// \ingroup EigenSolver +/// +/// This class implements the eigen solver for general real matrices, i.e., +/// to solve \f$Ax=\lambda x\f$ for a possibly non-symmetric \f$A\f$ matrix. +/// +/// Most of the background information documented in the SymEigsSolver class +/// also applies to the GenEigsSolver class here, except that the eigenvalues +/// and eigenvectors of a general matrix can now be complex-valued. +/// +/// \tparam Scalar The element type of the matrix. +/// Currently supported types are `float`, `double` and `long double`. +/// \tparam SelectionRule An enumeration value indicating the selection rule of +/// the requested eigenvalues, for example `LARGEST_MAGN` +/// to retrieve eigenvalues with the largest magnitude. +/// The full list of enumeration values can be found in +/// \ref Enumerations. +/// \tparam OpType The name of the matrix operation class. Users could either +/// use the wrapper classes such as DenseGenMatProd and +/// SparseGenMatProd, or define their +/// own that implements all the public member functions as in +/// DenseGenMatProd. +/// +/// An example that illustrates the usage of GenEigsSolver is give below: +/// +/// \code{.cpp} +/// #include +/// #include +/// // is implicitly included +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // We are going to calculate the eigenvalues of M +/// Eigen::MatrixXd M = Eigen::MatrixXd::Random(10, 10); +/// +/// // Construct matrix operation object using the wrapper class +/// DenseGenMatProd op(M); +/// +/// // Construct eigen solver object, requesting the largest +/// // (in magnitude, or norm) three eigenvalues +/// GenEigsSolver< double, LARGEST_MAGN, DenseGenMatProd > eigs(&op, 3, 6); +/// +/// // Initialize and compute +/// eigs.init(); +/// int nconv = eigs.compute(); +/// +/// // Retrieve results +/// Eigen::VectorXcd evalues; +/// if(eigs.info() == SUCCESSFUL) +/// evalues = eigs.eigenvalues(); +/// +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// +/// return 0; +/// } +/// \endcode +/// +/// And also an example for sparse matrices: +/// +/// \code{.cpp} +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // A band matrix with 1 on the main diagonal, 2 on the below-main subdiagonal, +/// // and 3 on the above-main subdiagonal +/// const int n = 10; +/// Eigen::SparseMatrix M(n, n); +/// M.reserve(Eigen::VectorXi::Constant(n, 3)); +/// for(int i = 0; i < n; i++) +/// { +/// M.insert(i, i) = 1.0; +/// if(i > 0) +/// M.insert(i - 1, i) = 3.0; +/// if(i < n - 1) +/// M.insert(i + 1, i) = 2.0; +/// } +/// +/// // Construct matrix operation object using the wrapper class SparseGenMatProd +/// SparseGenMatProd op(M); +/// +/// // Construct eigen solver object, requesting the largest three eigenvalues +/// GenEigsSolver< double, LARGEST_MAGN, SparseGenMatProd > eigs(&op, 3, 6); +/// +/// // Initialize and compute +/// eigs.init(); +/// int nconv = eigs.compute(); +/// +/// // Retrieve results +/// Eigen::VectorXcd evalues; +/// if(eigs.info() == SUCCESSFUL) +/// evalues = eigs.eigenvalues(); +/// +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// +/// return 0; +/// } +/// \endcode +template < typename Scalar = double, + int SelectionRule = LARGEST_MAGN, + typename OpType = DenseGenMatProd > +class GenEigsSolver: public GenEigsBase +{ +private: + typedef Eigen::Index Index; + +public: + /// + /// Constructor to create a solver object. + /// + /// \param op Pointer to the matrix operation object, which should implement + /// the matrix-vector multiplication operation of \f$A\f$: + /// calculating \f$Av\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper class such as DenseGenMatProd, or + /// define their own that implements all the public member functions + /// as in DenseGenMatProd. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-2\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev+2 \le ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev + 1\f$. + /// + GenEigsSolver(OpType* op, Index nev, Index ncv) : + GenEigsBase(op, NULL, nev, ncv) + {} +}; + + +} // namespace Spectra + +#endif // GEN_EIGS_SOLVER_H diff --git a/src/external/Spectra/include/Spectra/LinAlg/Arnoldi.h b/src/external/Spectra/include/Spectra/LinAlg/Arnoldi.h new file mode 100644 index 00000000..b9fa75b5 --- /dev/null +++ b/src/external/Spectra/include/Spectra/LinAlg/Arnoldi.h @@ -0,0 +1,283 @@ +// Copyright (C) 2018-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef ARNOLDI_H +#define ARNOLDI_H + +#include +#include // std::sqrt +#include // std::invalid_argument +#include // std::stringstream + +#include "../MatOp/internal/ArnoldiOp.h" +#include "../Util/TypeTraits.h" +#include "../Util/SimpleRandom.h" +#include "UpperHessenbergQR.h" +#include "DoubleShiftQR.h" + +namespace Spectra { + + +// Arnoldi factorization A * V = V * H + f * e' +// A: n x n +// V: n x k +// H: k x k +// f: n x 1 +// e: [0, ..., 0, 1] +// V and H are allocated of dimension m, so the maximum value of k is m +template +class Arnoldi +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapMat; + typedef Eigen::Map MapVec; + typedef Eigen::Map MapConstMat; + typedef Eigen::Map MapConstVec; + +protected: + ArnoldiOpType m_op; // Operators for the Arnoldi factorization + + const Index m_n; // dimension of A + const Index m_m; // maximum dimension of subspace V + Index m_k; // current dimension of subspace V + + Matrix m_fac_V; // V matrix in the Arnoldi factorization + Matrix m_fac_H; // H matrix in the Arnoldi factorization + Vector m_fac_f; // residual in the Arnoldi factorization + Scalar m_beta; // ||f||, B-norm of f + + const Scalar m_near_0; // a very small value, but 1.0 / m_near_0 does not overflow + // ~= 1e-307 for the "double" type + const Scalar m_eps; // the machine precision, ~= 1e-16 for the "double" type + + // Given orthonormal basis functions V, find a nonzero vector f such that V'Bf = 0 + // Assume that f has been properly allocated + void expand_basis(MapConstMat& V, const Index seed, Vector& f, Scalar& fnorm) + { + using std::sqrt; + + const Scalar thresh = m_eps * sqrt(Scalar(m_n)); + Vector Vf(V.cols()); + for(Index iter = 0; iter < 5; iter++) + { + // Randomly generate a new vector and orthogonalize it against V + SimpleRandom rng(seed + 123 * iter); + f.noalias() = rng.random_vec(m_n); + // f <- f - V * V'Bf, so that f is orthogonal to V in B-norm + m_op.trans_product(V, f, Vf); + f.noalias() -= V * Vf; + // fnorm <- ||f|| + fnorm = m_op.norm(f); + + // If fnorm is too close to zero, we try a new random vector, + // otherwise return the result + if(fnorm >= thresh) + return; + } + } + +public: + Arnoldi(const ArnoldiOpType& op, Index m) : + m_op(op), m_n(op.rows()), m_m(m), m_k(0), + m_near_0(TypeTraits::min() * Scalar(10)), + m_eps(Eigen::NumTraits::epsilon()) + {} + + virtual ~Arnoldi() {} + + // Const-reference to internal structures + const Matrix& matrix_V() const { return m_fac_V; } + const Matrix& matrix_H() const { return m_fac_H; } + const Vector& vector_f() const { return m_fac_f; } + Scalar f_norm() const { return m_beta; } + Index subspace_dim() const { return m_k; } + + // Initialize with an operator and an initial vector + void init(MapConstVec& v0, Index& op_counter) + { + m_fac_V.resize(m_n, m_m); + m_fac_H.resize(m_m, m_m); + m_fac_f.resize(m_n); + m_fac_H.setZero(); + + // Verify the initial vector + const Scalar v0norm = m_op.norm(v0); + if(v0norm < m_near_0) + throw std::invalid_argument("initial residual vector cannot be zero"); + + // Points to the first column of V + MapVec v(m_fac_V.data(), m_n); + + // Normalize + v.noalias() = v0 / v0norm; + + // Compute H and f + Vector w(m_n); + m_op.perform_op(v.data(), w.data()); + op_counter++; + + m_fac_H(0, 0) = m_op.inner_product(v, w); + m_fac_f.noalias() = w - v * m_fac_H(0, 0); + + // In some cases f is zero in exact arithmetics, but due to rounding errors + // it may contain tiny fluctuations. When this happens, we force f to be zero + if(m_fac_f.cwiseAbs().maxCoeff() < m_eps) + { + m_fac_f.setZero(); + m_beta = Scalar(0); + } else { + m_beta = m_op.norm(m_fac_f); + } + + // Indicate that this is a step-1 factorization + m_k = 1; + } + + // Arnoldi factorization starting from step-k + virtual void factorize_from(Index from_k, Index to_m, Index& op_counter) + { + using std::sqrt; + + if(to_m <= from_k) return; + + if(from_k > m_k) + { + std::stringstream msg; + msg << "Arnoldi: from_k (= " << from_k << + ") is larger than the current subspace dimension (= " << + m_k << ")"; + throw std::invalid_argument(msg.str()); + } + + const Scalar beta_thresh = m_eps * sqrt(Scalar(m_n)); + + // Pre-allocate vectors + Vector Vf(to_m); + Vector w(m_n); + + // Keep the upperleft k x k submatrix of H and set other elements to 0 + m_fac_H.rightCols(m_m - from_k).setZero(); + m_fac_H.block(from_k, 0, m_m - from_k, from_k).setZero(); + + for(Index i = from_k; i <= to_m - 1; i++) + { + bool restart = false; + // If beta = 0, then the next V is not full rank + // We need to generate a new residual vector that is orthogonal + // to the current V, which we call a restart + if(m_beta < m_near_0) + { + MapConstMat V(m_fac_V.data(), m_n, i); // The first i columns + expand_basis(V, 2 * i, m_fac_f, m_beta); + restart = true; + } + + // v <- f / ||f|| + m_fac_V.col(i).noalias() = m_fac_f / m_beta; // The (i+1)-th column + + // Note that H[i+1, i] equals to the unrestarted beta + m_fac_H(i, i - 1) = restart ? Scalar(0) : m_beta; + + // w <- A * v, v = m_fac_V.col(i) + m_op.perform_op(&m_fac_V(0, i), w.data()); + op_counter++; + + const Index i1 = i + 1; + // First i+1 columns of V + MapConstMat Vs(m_fac_V.data(), m_n, i1); + // h = m_fac_H(0:i, i) + MapVec h(&m_fac_H(0, i), i1); + // h <- V'Bw + m_op.trans_product(Vs, w, h); + + // f <- w - V * h + m_fac_f.noalias() = w - Vs * h; + m_beta = m_op.norm(m_fac_f); + + if(m_beta > Scalar(0.717) * m_op.norm(h)) + continue; + + // f/||f|| is going to be the next column of V, so we need to test + // whether V'B(f/||f||) ~= 0 + m_op.trans_product(Vs, m_fac_f, Vf.head(i1)); + Scalar ortho_err = Vf.head(i1).cwiseAbs().maxCoeff(); + // If not, iteratively correct the residual + int count = 0; + while(count < 5 && ortho_err > m_eps * m_beta) + { + // There is an edge case: when beta=||f|| is close to zero, f mostly consists + // of noises of rounding errors, so the test [ortho_err < eps * beta] is very + // likely to fail. In particular, if beta=0, then the test is ensured to fail. + // Hence when this happens, we force f to be zero, and then restart in the + // next iteration. + if(m_beta < beta_thresh) + { + m_fac_f.setZero(); + m_beta = Scalar(0); + break; + } + + // f <- f - V * Vf + m_fac_f.noalias() -= Vs * Vf.head(i1); + // h <- h + Vf + h.noalias() += Vf.head(i1); + // beta <- ||f|| + m_beta = m_op.norm(m_fac_f); + + m_op.trans_product(Vs, m_fac_f, Vf.head(i1)); + ortho_err = Vf.head(i1).cwiseAbs().maxCoeff(); + count++; + } + } + + // Indicate that this is a step-m factorization + m_k = to_m; + } + + // Apply H -> Q'HQ, where Q is from a double shift QR decomposition + void compress_H(const DoubleShiftQR& decomp) + { + decomp.matrix_QtHQ(m_fac_H); + m_k -= 2; + } + + // Apply H -> Q'HQ, where Q is from an upper Hessenberg QR decomposition + void compress_H(const UpperHessenbergQR& decomp) + { + decomp.matrix_QtHQ(m_fac_H); + m_k--; + } + + // Apply V -> VQ and compute the new f. + // Should be called after compress_H(), since m_k is updated there. + // Only need to update the first k+1 columns of V + // The first (m - k + i) elements of the i-th column of Q are non-zero, + // and the rest are zero + void compress_V(const Matrix& Q) + { + Matrix Vs(m_n, m_k + 1); + for(Index i = 0; i < m_k; i++) + { + const Index nnz = m_m - m_k + i + 1; + MapConstVec q(&Q(0, i), nnz); + Vs.col(i).noalias() = m_fac_V.leftCols(nnz) * q; + } + Vs.col(m_k).noalias() = m_fac_V * Q.col(m_k); + m_fac_V.leftCols(m_k + 1).noalias() = Vs; + + Vector fk = m_fac_f * Q(m_m - 1, m_k - 1) + m_fac_V.col(m_k) * m_fac_H(m_k, m_k - 1); + m_fac_f.swap(fk); + m_beta = m_op.norm(m_fac_f); + } +}; + + +} // namespace Spectra + +#endif // ARNOLDI_H diff --git a/src/external/Spectra/include/Spectra/LinAlg/BKLDLT.h b/src/external/Spectra/include/Spectra/LinAlg/BKLDLT.h new file mode 100644 index 00000000..5509749b --- /dev/null +++ b/src/external/Spectra/include/Spectra/LinAlg/BKLDLT.h @@ -0,0 +1,522 @@ +// Copyright (C) 2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef BK_LDLT_H +#define BK_LDLT_H + +#include +#include +#include + +#include "../Util/CompInfo.h" + +namespace Spectra { + + +// Bunch-Kaufman LDLT decomposition +// References: +// 1. Bunch, J. R., & Kaufman, L. (1977). Some stable methods for calculating inertia and solving symmetric linear systems. +// Mathematics of computation, 31(137), 163-179. +// 2. Golub, G. H., & Van Loan, C. F. (2012). Matrix computations (Vol. 3). JHU press. Section 4.4. +// 3. Bunch-Parlett diagonal pivoting +// 4. Ashcraft, C., Grimes, R. G., & Lewis, J. G. (1998). Accurate symmetric indefinite linear equation solvers. +// SIAM Journal on Matrix Analysis and Applications, 20(2), 513-561. +template +class BKLDLT +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapVec; + typedef Eigen::Map MapConstVec; + + typedef Eigen::Matrix IntVector; + typedef Eigen::Ref GenericVector; + typedef Eigen::Ref GenericMatrix; + typedef const Eigen::Ref ConstGenericMatrix; + typedef const Eigen::Ref ConstGenericVector; + + Index m_n; + Vector m_data; // storage for a lower-triangular matrix + std::vector m_colptr; // pointers to columns + IntVector m_perm; // [-2, -1, 3, 1, 4, 5]: 0 <-> 2, 1 <-> 1, 2 <-> 3, 3 <-> 1, 4 <-> 4, 5 <-> 5 + std::vector< std::pair > m_permc; // compressed version of m_perm: [(0, 2), (2, 3), (3, 1)] + + bool m_computed; + int m_info; + + // Access to elements + // Pointer to the k-th column + Scalar* col_pointer(Index k) { return m_colptr[k]; } + // A[i, j] -> m_colptr[j][i - j], i >= j + Scalar& coeff(Index i, Index j) { return m_colptr[j][i - j]; } + const Scalar& coeff(Index i, Index j) const { return m_colptr[j][i - j]; } + // A[i, i] -> m_colptr[i][0] + Scalar& diag_coeff(Index i) { return m_colptr[i][0]; } + const Scalar& diag_coeff(Index i) const { return m_colptr[i][0]; } + + // Compute column pointers + void compute_pointer() + { + m_colptr.clear(); + m_colptr.reserve(m_n); + Scalar* head = m_data.data(); + + for(Index i = 0; i < m_n; i++) + { + m_colptr.push_back(head); + head += (m_n - i); + } + } + + // Copy mat - shift * I to m_data + void copy_data(ConstGenericMatrix& mat, int uplo, const Scalar& shift) + { + if(uplo == Eigen::Lower) + { + for(Index j = 0; j < m_n; j++) + { + const Scalar* begin = &mat.coeffRef(j, j); + const Index len = m_n - j; + std::copy(begin, begin + len, col_pointer(j)); + diag_coeff(j) -= shift; + } + } else { + Scalar* dest = m_data.data(); + for(Index i = 0; i < m_n; i++) + { + for(Index j = i; j < m_n; j++, dest++) + { + *dest = mat.coeff(i, j); + } + diag_coeff(i) -= shift; + } + } + } + + // Compute compressed permutations + void compress_permutation() + { + for(Index i = 0; i < m_n; i++) + { + // Recover the permutation action + const Index perm = (m_perm[i] >= 0) ? (m_perm[i]) : (-m_perm[i] - 1); + if(perm != i) + m_permc.push_back(std::make_pair(i, perm)); + } + } + + // Working on the A[k:end, k:end] submatrix + // Exchange k <-> r + // Assume r >= k + void pivoting_1x1(Index k, Index r) + { + // No permutation + if(k == r) + { + m_perm[k] = r; + return; + } + + // A[k, k] <-> A[r, r] + std::swap(diag_coeff(k), diag_coeff(r)); + + // A[(r+1):end, k] <-> A[(r+1):end, r] + std::swap_ranges(&coeff(r + 1, k), col_pointer(k + 1), &coeff(r + 1, r)); + + // A[(k+1):(r-1), k] <-> A[r, (k+1):(r-1)] + Scalar* src = &coeff(k + 1, k); + for(Index j = k + 1; j < r; j++, src++) + { + std::swap(*src, coeff(r, j)); + } + + m_perm[k] = r; + } + + // Working on the A[k:end, k:end] submatrix + // Exchange [k+1, k] <-> [r, p] + // Assume p >= k, r >= k+1 + void pivoting_2x2(Index k, Index r, Index p) + { + pivoting_1x1(k, p); + pivoting_1x1(k + 1, r); + + // A[k+1, k] <-> A[r, k] + std::swap(coeff(k + 1, k), coeff(r, k)); + + // Use negative signs to indicate a 2x2 block + // Also minus one to distinguish a negative zero from a positive zero + m_perm[k] = -m_perm[k] - 1; + m_perm[k + 1] = -m_perm[k + 1] - 1; + } + + // A[r1, c1:c2] <-> A[r2, c1:c2] + // Assume r2 >= r1 > c2 >= c1 + void interchange_rows(Index r1, Index r2, Index c1, Index c2) + { + if(r1 == r2) + return; + + for(Index j = c1; j <= c2; j++) + { + std::swap(coeff(r1, j), coeff(r2, j)); + } + } + + // lambda = |A[r, k]| = max{|A[k+1, k]|, ..., |A[end, k]|} + // Largest (in magnitude) off-diagonal element in the first column of the current reduced matrix + // r is the row index + // Assume k < end + Scalar find_lambda(Index k, Index& r) + { + using std::abs; + + const Scalar* head = col_pointer(k); // => A[k, k] + const Scalar* end = col_pointer(k + 1); + // Start with r=k+1, lambda=A[k+1, k] + r = k + 1; + Scalar lambda = abs(head[1]); + // Scan remaining elements + for(const Scalar* ptr = head + 2; ptr < end; ptr++) + { + const Scalar abs_elem = abs(*ptr); + if(lambda < abs_elem) + { + lambda = abs_elem; + r = k + (ptr - head); + } + } + + return lambda; + } + + // sigma = |A[p, r]| = max {|A[k, r]|, ..., |A[end, r]|} \ {A[r, r]} + // Largest (in magnitude) off-diagonal element in the r-th column of the current reduced matrix + // p is the row index + // Assume k < r < end + Scalar find_sigma(Index k, Index r, Index& p) + { + using std::abs; + + // First search A[r+1, r], ..., A[end, r], which has the same task as find_lambda() + // If r == end, we skip this search + Scalar sigma = Scalar(-1); + if(r < m_n - 1) + sigma = find_lambda(r, p); + + // Then search A[k, r], ..., A[r-1, r], which maps to A[r, k], ..., A[r, r-1] + for(Index j = k; j < r; j++) + { + const Scalar abs_elem = abs(coeff(r, j)); + if(sigma < abs_elem) + { + sigma = abs_elem; + p = j; + } + } + + return sigma; + } + + // Generate permutations and apply to A + // Return true if the resulting pivoting is 1x1, and false if 2x2 + bool permutate_mat(Index k, const Scalar& alpha) + { + using std::abs; + + Index r = k, p = k; + const Scalar lambda = find_lambda(k, r); + + // If lambda=0, no need to interchange + if(lambda > Scalar(0)) + { + const Scalar abs_akk = abs(diag_coeff(k)); + // If |A[k, k]| >= alpha * lambda, no need to interchange + if(abs_akk < alpha * lambda) + { + const Scalar sigma = find_sigma(k, r, p); + + // If sigma * |A[k, k]| >= alpha * lambda^2, no need to interchange + if(sigma * abs_akk < alpha * lambda * lambda) + { + if(abs_akk >= alpha * sigma) + { + // Permutation on A + pivoting_1x1(k, r); + + // Permutation on L + interchange_rows(k, r, 0, k - 1); + return true; + } else { + // There are two versions of permutation here + // 1. A[k+1, k] <-> A[r, k] + // 2. A[k+1, k] <-> A[r, p], where p >= k and r >= k+1 + // + // Version 1 and 2 are used by Ref[1] and Ref[2], respectively + + // Version 1 implementation + p = k; + + // Version 2 implementation + // [r, p] and [p, r] are symmetric, but we need to make sure + // p >= k and r >= k+1, so it is safe to always make r > p + // One exception is when min{r,p} == k+1, in which case we make + // r = k+1, so that only one permutation needs to be performed + /* const Index rp_min = std::min(r, p); + const Index rp_max = std::max(r, p); + if(rp_min == k + 1) + { + r = rp_min; p = rp_max; + } else { + r = rp_max; p = rp_min; + } */ + + // Right now we use Version 1 since it reduces the overhead of interchange + + // Permutation on A + pivoting_2x2(k, r, p); + // Permutation on L + interchange_rows(k, p, 0, k - 1); + interchange_rows(k + 1, r, 0, k - 1); + return false; + } + } + } + } + + return true; + } + + // E = [e11, e12] + // [e21, e22] + // Overwrite E with inv(E) + void inverse_inplace_2x2(Scalar& e11, Scalar& e21, Scalar& e22) const + { + // inv(E) = [d11, d12], d11 = e22/delta, d21 = -e21/delta, d22 = e11/delta + // [d21, d22] + const Scalar delta = e11 * e22 - e21 * e21; + std::swap(e11, e22); + e11 /= delta; + e22 /= delta; + e21 = -e21 / delta; + } + + // Return value is the status, SUCCESSFUL/NUMERICAL_ISSUE + int gaussian_elimination_1x1(Index k) + { + // D = 1 / A[k, k] + const Scalar akk = diag_coeff(k); + // Return NUMERICAL_ISSUE if not invertible + if(akk == Scalar(0)) + return NUMERICAL_ISSUE; + + diag_coeff(k) = Scalar(1) / akk; + + // B -= l * l' / A[k, k], B := A[(k+1):end, (k+1):end], l := L[(k+1):end, k] + Scalar* lptr = col_pointer(k) + 1; + const Index ldim = m_n - k - 1; + MapVec l(lptr, ldim); + for(Index j = 0; j < ldim; j++) + { + MapVec(col_pointer(j + k + 1), ldim - j).noalias() -= (lptr[j] / akk) * l.tail(ldim - j); + } + + // l /= A[k, k] + l /= akk; + + return SUCCESSFUL; + } + + // Return value is the status, SUCCESSFUL/NUMERICAL_ISSUE + int gaussian_elimination_2x2(Index k) + { + // D = inv(E) + Scalar& e11 = diag_coeff(k); + Scalar& e21 = coeff(k + 1, k); + Scalar& e22 = diag_coeff(k + 1); + // Return NUMERICAL_ISSUE if not invertible + if(e11 * e22 - e21 * e21 == Scalar(0)) + return NUMERICAL_ISSUE; + + inverse_inplace_2x2(e11, e21, e22); + + // X = l * inv(E), l := L[(k+2):end, k:(k+1)] + Scalar* l1ptr = &coeff(k + 2, k); + Scalar* l2ptr = &coeff(k + 2, k + 1); + const Index ldim = m_n - k - 2; + MapVec l1(l1ptr, ldim), l2(l2ptr, ldim); + + Eigen::Matrix X(ldim, 2); + X.col(0).noalias() = l1 * e11 + l2 * e21; + X.col(1).noalias() = l1 * e21 + l2 * e22; + + // B -= l * inv(E) * l' = X * l', B = A[(k+2):end, (k+2):end] + for(Index j = 0; j < ldim; j++) + { + MapVec(col_pointer(j + k + 2), ldim - j).noalias() -= (X.col(0).tail(ldim - j) * l1ptr[j] + X.col(1).tail(ldim - j) * l2ptr[j]); + } + + // l = X + l1.noalias() = X.col(0); + l2.noalias() = X.col(1); + + return SUCCESSFUL; + } + +public: + BKLDLT() : + m_n(0), m_computed(false), m_info(NOT_COMPUTED) + {} + + // Factorize mat - shift * I + BKLDLT(ConstGenericMatrix& mat, int uplo = Eigen::Lower, const Scalar& shift = Scalar(0)) : + m_n(mat.rows()), m_computed(false), m_info(NOT_COMPUTED) + { + compute(mat, uplo, shift); + } + + void compute(ConstGenericMatrix& mat, int uplo = Eigen::Lower, const Scalar& shift = Scalar(0)) + { + using std::abs; + + m_n = mat.rows(); + if(m_n != mat.cols()) + throw std::invalid_argument("BKLDLT: matrix must be square"); + + m_perm.setLinSpaced(m_n, 0, m_n - 1); + m_permc.clear(); + + // Copy data + m_data.resize((m_n * (m_n + 1)) / 2); + compute_pointer(); + copy_data(mat, uplo, shift); + + const Scalar alpha = (1.0 + std::sqrt(17.0)) / 8.0; + Index k = 0; + for(k = 0; k < m_n - 1; k++) + { + // 1. Interchange rows and columns of A, and save the result to m_perm + bool is_1x1 = permutate_mat(k, alpha); + + // 2. Gaussian elimination + if(is_1x1) + { + m_info = gaussian_elimination_1x1(k); + } else { + m_info = gaussian_elimination_2x2(k); + k++; + } + + // 3. Check status + if(m_info != SUCCESSFUL) + break; + } + // Invert the last 1x1 block if it exists + if(k == m_n - 1) + { + const Scalar akk = diag_coeff(k); + if(akk == Scalar(0)) + m_info = NUMERICAL_ISSUE; + + diag_coeff(k) = Scalar(1) / diag_coeff(k); + } + + compress_permutation(); + + m_computed = true; + } + + // Solve Ax=b + void solve_inplace(GenericVector b) const + { + if(!m_computed) + throw std::logic_error("BKLDLT: need to call compute() first"); + + // PAP' = LDL' + // 1. b -> Pb + Scalar* x = b.data(); + MapVec res(x, m_n); + Index npermc = m_permc.size(); + for(Index i = 0; i < npermc; i++) + { + std::swap(x[m_permc[i].first], x[m_permc[i].second]); + } + + // 2. Lz = Pb + // If m_perm[end] < 0, then end with m_n - 3, otherwise end with m_n - 2 + const Index end = (m_perm[m_n - 1] < 0) ? (m_n - 3) : (m_n - 2); + for(Index i = 0; i <= end; i++) + { + const Index b1size = m_n - i - 1; + const Index b2size = b1size - 1; + if(m_perm[i] >= 0) + { + MapConstVec l(&coeff(i + 1, i), b1size); + res.segment(i + 1, b1size).noalias() -= l * x[i]; + } else { + MapConstVec l1(&coeff(i + 2, i), b2size); + MapConstVec l2(&coeff(i + 2, i + 1), b2size); + res.segment(i + 2, b2size).noalias() -= (l1 * x[i] + l2 * x[i + 1]); + i++; + } + } + + // 3. Dw = z + for(Index i = 0; i < m_n; i++) + { + const Scalar e11 = diag_coeff(i); + if(m_perm[i] >= 0) + { + x[i] *= e11; + } else { + const Scalar e21 = coeff(i + 1, i), e22 = diag_coeff(i + 1); + const Scalar wi = x[i] * e11 + x[i + 1] * e21; + x[i + 1] = x[i] * e21 + x[i + 1] * e22; + x[i] = wi; + i++; + } + } + + // 4. L'y = w + // If m_perm[end] < 0, then start with m_n - 3, otherwise start with m_n - 2 + Index i = (m_perm[m_n - 1] < 0) ? (m_n - 3) : (m_n - 2); + for(; i >= 0; i--) + { + const Index ldim = m_n - i - 1; + MapConstVec l(&coeff(i + 1, i), ldim); + x[i] -= res.segment(i + 1, ldim).dot(l); + + if(m_perm[i] < 0) + { + MapConstVec l2(&coeff(i + 1, i - 1), ldim); + x[i - 1] -= res.segment(i + 1, ldim).dot(l2); + i--; + } + } + + // 5. x = P'y + for(Index i = npermc - 1; i >= 0; i--) + { + std::swap(x[m_permc[i].first], x[m_permc[i].second]); + } + } + + Vector solve(ConstGenericVector& b) const + { + Vector res = b; + solve_inplace(res); + return res; + } + + int info() const { return m_info; } +}; + + +} // namespace Spectra + +#endif // BK_LDLT_H diff --git a/src/external/Spectra/include/Spectra/LinAlg/DoubleShiftQR.h b/src/external/Spectra/include/Spectra/LinAlg/DoubleShiftQR.h new file mode 100644 index 00000000..2191909a --- /dev/null +++ b/src/external/Spectra/include/Spectra/LinAlg/DoubleShiftQR.h @@ -0,0 +1,378 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef DOUBLE_SHIFT_QR_H +#define DOUBLE_SHIFT_QR_H + +#include +#include // std::vector +#include // std::min, std::fill, std::copy +#include // std::abs, std::sqrt, std::pow +#include // std::invalid_argument, std::logic_error + +#include "../Util/TypeTraits.h" + +namespace Spectra { + + +template +class DoubleShiftQR +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Matrix3X; + typedef Eigen::Matrix Vector; + typedef Eigen::Array IntArray; + + typedef Eigen::Ref GenericMatrix; + typedef const Eigen::Ref ConstGenericMatrix; + + Index m_n; // Dimension of the matrix + Matrix m_mat_H; // A copy of the matrix to be factorized + Scalar m_shift_s; // Shift constant + Scalar m_shift_t; // Shift constant + Matrix3X m_ref_u; // Householder reflectors + IntArray m_ref_nr; // How many rows does each reflector affects + // 3 - A general reflector + // 2 - A Givens rotation + // 1 - An identity transformation + const Scalar m_near_0; // a very small value, but 1.0 / m_safe_min does not overflow + // ~= 1e-307 for the "double" type + const Scalar m_eps; // the machine precision, + // e.g. ~= 1e-16 for the "double" type + const Scalar m_eps_rel; + const Scalar m_eps_abs; + bool m_computed; // Whether matrix has been factorized + + void compute_reflector(const Scalar& x1, const Scalar& x2, const Scalar& x3, Index ind) + { + using std::abs; + + Scalar* u = &m_ref_u.coeffRef(0, ind); + unsigned char* nr = m_ref_nr.data(); + // In general case the reflector affects 3 rows + nr[ind] = 3; + Scalar x2x3 = Scalar(0); + // If x3 is zero, decrease nr by 1 + if(abs(x3) < m_near_0) + { + // If x2 is also zero, nr will be 1, and we can exit this function + if(abs(x2) < m_near_0) + { + nr[ind] = 1; + return; + } else { + nr[ind] = 2; + } + x2x3 = abs(x2); + } else { + x2x3 = Eigen::numext::hypot(x2, x3); + } + + // x1' = x1 - rho * ||x|| + // rho = -sign(x1), if x1 == 0, we choose rho = 1 + Scalar x1_new = x1 - ((x1 <= 0) - (x1 > 0)) * Eigen::numext::hypot(x1, x2x3); + Scalar x_norm = Eigen::numext::hypot(x1_new, x2x3); + // Double check the norm of new x + if(x_norm < m_near_0) + { + nr[ind] = 1; + return; + } + u[0] = x1_new / x_norm; + u[1] = x2 / x_norm; + u[2] = x3 / x_norm; + } + + void compute_reflector(const Scalar* x, Index ind) + { + compute_reflector(x[0], x[1], x[2], ind); + } + + // Update the block X = H(il:iu, il:iu) + void update_block(Index il, Index iu) + { + // Block size + const Index bsize = iu - il + 1; + + // If block size == 1, there is no need to apply reflectors + if(bsize == 1) + { + m_ref_nr.coeffRef(il) = 1; + return; + } + + const Scalar x00 = m_mat_H.coeff(il, il), + x01 = m_mat_H.coeff(il, il + 1), + x10 = m_mat_H.coeff(il + 1, il), + x11 = m_mat_H.coeff(il + 1, il + 1); + // m00 = x00 * (x00 - s) + x01 * x10 + t + const Scalar m00 = x00 * (x00 - m_shift_s) + x01 * x10 + m_shift_t; + // m10 = x10 * (x00 + x11 - s) + const Scalar m10 = x10 * (x00 + x11 - m_shift_s); + + // For block size == 2, do a Givens rotation on M = X * X - s * X + t * I + if(bsize == 2) + { + // This causes nr=2 + compute_reflector(m00, m10, 0, il); + // Apply the reflector to X + apply_PX(m_mat_H.block(il, il, 2, m_n - il), m_n, il); + apply_XP(m_mat_H.block(0, il, il + 2, 2), m_n, il); + + m_ref_nr.coeffRef(il + 1) = 1; + return; + } + + // For block size >=3, use the regular strategy + // m20 = x21 * x10 + const Scalar m20 = m_mat_H.coeff(il + 2, il + 1) * m_mat_H.coeff(il + 1, il); + compute_reflector(m00, m10, m20, il); + + // Apply the first reflector + apply_PX(m_mat_H.block(il, il, 3, m_n - il), m_n, il); + apply_XP(m_mat_H.block(0, il, il + std::min(bsize, Index(4)), 3), m_n, il); + + // Calculate the following reflectors + // If entering this loop, block size is at least 4. + for(Index i = 1; i < bsize - 2; i++) + { + compute_reflector(&m_mat_H.coeffRef(il + i, il + i - 1), il + i); + // Apply the reflector to X + apply_PX(m_mat_H.block(il + i, il + i - 1, 3, m_n - il - i + 1), m_n, il + i); + apply_XP(m_mat_H.block(0, il + i, il + std::min(bsize, Index(i + 4)), 3), m_n, il + i); + } + + // The last reflector + // This causes nr=2 + compute_reflector(m_mat_H.coeff(iu - 1, iu - 2), m_mat_H.coeff(iu, iu - 2), 0, iu - 1); + // Apply the reflector to X + apply_PX(m_mat_H.block(iu - 1, iu - 2, 2, m_n - iu + 2), m_n, iu - 1); + apply_XP(m_mat_H.block(0, iu - 1, il + bsize, 2), m_n, iu - 1); + + m_ref_nr.coeffRef(iu) = 1; + } + + // P = I - 2 * u * u' = P' + // PX = X - 2 * u * (u'X) + void apply_PX(GenericMatrix X, Index stride, Index u_ind) const + { + const Index nr = m_ref_nr.coeff(u_ind); + if(nr == 1) + return; + + const Scalar u0 = m_ref_u.coeff(0, u_ind), + u1 = m_ref_u.coeff(1, u_ind); + const Scalar u0_2 = Scalar(2) * u0, + u1_2 = Scalar(2) * u1; + + const Index nrow = X.rows(); + const Index ncol = X.cols(); + + Scalar* xptr = X.data(); + if(nr == 2 || nrow == 2) + { + for(Index i = 0; i < ncol; i++, xptr += stride) + { + const Scalar tmp = u0_2 * xptr[0] + u1_2 * xptr[1]; + xptr[0] -= tmp * u0; + xptr[1] -= tmp * u1; + } + } else { + const Scalar u2 = m_ref_u.coeff(2, u_ind); + const Scalar u2_2 = Scalar(2) * u2; + for(Index i = 0; i < ncol; i++, xptr += stride) + { + const Scalar tmp = u0_2 * xptr[0] + u1_2 * xptr[1] + u2_2 * xptr[2]; + xptr[0] -= tmp * u0; + xptr[1] -= tmp * u1; + xptr[2] -= tmp * u2; + } + } + } + + // x is a pointer to a vector + // Px = x - 2 * dot(x, u) * u + void apply_PX(Scalar* x, Index u_ind) const + { + const Index nr = m_ref_nr.coeff(u_ind); + if(nr == 1) + return; + + const Scalar u0 = m_ref_u.coeff(0, u_ind), + u1 = m_ref_u.coeff(1, u_ind), + u2 = m_ref_u.coeff(2, u_ind); + + // When the reflector only contains two elements, u2 has been set to zero + const bool nr_is_2 = (nr == 2); + const Scalar dot2 = Scalar(2) * (x[0] * u0 + x[1] * u1 + (nr_is_2 ? 0 : (x[2] * u2))); + x[0] -= dot2 * u0; + x[1] -= dot2 * u1; + if(!nr_is_2) + x[2] -= dot2 * u2; + } + + // XP = X - 2 * (X * u) * u' + void apply_XP(GenericMatrix X, Index stride, Index u_ind) const + { + const Index nr = m_ref_nr.coeff(u_ind); + if(nr == 1) + return; + + const Scalar u0 = m_ref_u.coeff(0, u_ind), + u1 = m_ref_u.coeff(1, u_ind); + const Scalar u0_2 = Scalar(2) * u0, + u1_2 = Scalar(2) * u1; + + const int nrow = X.rows(); + const int ncol = X.cols(); + Scalar *X0 = X.data(), *X1 = X0 + stride; // X0 => X.col(0), X1 => X.col(1) + + if(nr == 2 || ncol == 2) + { + // tmp = 2 * u0 * X0 + 2 * u1 * X1 + // X0 => X0 - u0 * tmp + // X1 => X1 - u1 * tmp + for(Index i = 0; i < nrow; i++) + { + const Scalar tmp = u0_2 * X0[i] + u1_2 * X1[i]; + X0[i] -= tmp * u0; + X1[i] -= tmp * u1; + } + } else { + Scalar* X2 = X1 + stride; // X2 => X.col(2) + const Scalar u2 = m_ref_u.coeff(2, u_ind); + const Scalar u2_2 = Scalar(2) * u2; + for(Index i = 0; i < nrow; i++) + { + const Scalar tmp = u0_2 * X0[i] + u1_2 * X1[i] + u2_2 * X2[i]; + X0[i] -= tmp * u0; + X1[i] -= tmp * u1; + X2[i] -= tmp * u2; + } + } + } + +public: + DoubleShiftQR(Index size) : + m_n(size), + m_near_0(TypeTraits::min() * Scalar(10)), + m_eps(Eigen::NumTraits::epsilon()), + m_eps_rel(m_eps), + m_eps_abs(m_near_0 * (m_n / m_eps)), + m_computed(false) + {} + + DoubleShiftQR(ConstGenericMatrix& mat, const Scalar& s, const Scalar& t) : + m_n(mat.rows()), + m_mat_H(m_n, m_n), + m_shift_s(s), + m_shift_t(t), + m_ref_u(3, m_n), + m_ref_nr(m_n), + m_near_0(TypeTraits::min() * Scalar(10)), + m_eps(Eigen::NumTraits::epsilon()), + m_eps_rel(m_eps), + m_eps_abs(m_near_0 * (m_n / m_eps)), + m_computed(false) + { + compute(mat, s, t); + } + + void compute(ConstGenericMatrix& mat, const Scalar& s, const Scalar& t) + { + using std::abs; + + m_n = mat.rows(); + if(m_n != mat.cols()) + throw std::invalid_argument("DoubleShiftQR: matrix must be square"); + + m_mat_H.resize(m_n, m_n); + m_shift_s = s; + m_shift_t = t; + m_ref_u.resize(3, m_n); + m_ref_nr.resize(m_n); + + // Make a copy of mat + std::copy(mat.data(), mat.data() + mat.size(), m_mat_H.data()); + + // Obtain the indices of zero elements in the subdiagonal, + // so that H can be divided into several blocks + std::vector zero_ind; + zero_ind.reserve(m_n - 1); + zero_ind.push_back(0); + Scalar* Hii = m_mat_H.data(); + for(Index i = 0; i < m_n - 2; i++, Hii += (m_n + 1)) + { + // Hii[1] => m_mat_H(i + 1, i) + const Scalar h = abs(Hii[1]); + if(h <= 0 || h <= m_eps_rel * (abs(Hii[0]) + abs(Hii[m_n + 1]))) + { + Hii[1] = 0; + zero_ind.push_back(i + 1); + } + // Make sure m_mat_H is upper Hessenberg + // Zero the elements below m_mat_H(i + 1, i) + std::fill(Hii + 2, Hii + m_n - i, Scalar(0)); + } + zero_ind.push_back(m_n); + + for(std::vector::size_type i = 0; i < zero_ind.size() - 1; i++) + { + const Index start = zero_ind[i]; + const Index end = zero_ind[i + 1] - 1; + // Compute refelctors and update each block + update_block(start, end); + } + + m_computed = true; + } + + void matrix_QtHQ(Matrix& dest) const + { + if(!m_computed) + throw std::logic_error("DoubleShiftQR: need to call compute() first"); + + dest.noalias() = m_mat_H; + } + + // Q = P0 * P1 * ... + // Q'y = P_{n-2} * ... * P1 * P0 * y + void apply_QtY(Vector& y) const + { + if(!m_computed) + throw std::logic_error("DoubleShiftQR: need to call compute() first"); + + Scalar* y_ptr = y.data(); + const Index n1 = m_n - 1; + for(Index i = 0; i < n1; i++, y_ptr++) + { + apply_PX(y_ptr, i); + } + } + + // Q = P0 * P1 * ... + // YQ = Y * P0 * P1 * ... + void apply_YQ(GenericMatrix Y) const + { + if(!m_computed) + throw std::logic_error("DoubleShiftQR: need to call compute() first"); + + const Index nrow = Y.rows(); + const Index n2 = m_n - 2; + for(Index i = 0; i < n2; i++) + { + apply_XP(Y.block(0, i, nrow, 3), nrow, i); + } + apply_XP(Y.block(0, n2, nrow, 2), nrow, n2); + } +}; + + +} // namespace Spectra + +#endif // DOUBLE_SHIFT_QR_H diff --git a/src/external/Spectra/include/Spectra/LinAlg/Lanczos.h b/src/external/Spectra/include/Spectra/LinAlg/Lanczos.h new file mode 100644 index 00000000..2301dd30 --- /dev/null +++ b/src/external/Spectra/include/Spectra/LinAlg/Lanczos.h @@ -0,0 +1,170 @@ +// Copyright (C) 2018-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef LANCZOS_H +#define LANCZOS_H + +#include +#include // std::sqrt +#include // std::invalid_argument +#include // std::stringstream + +#include "Arnoldi.h" + +namespace Spectra { + + +// Lanczos factorization A * V = V * H + f * e' +// A: n x n +// V: n x k +// H: k x k +// f: n x 1 +// e: [0, ..., 0, 1] +// V and H are allocated of dimension m, so the maximum value of k is m +template +class Lanczos: public Arnoldi +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapMat; + typedef Eigen::Map MapVec; + typedef Eigen::Map MapConstMat; + typedef Eigen::Map MapConstVec; + + using Arnoldi::m_op; + using Arnoldi::m_n; + using Arnoldi::m_m; + using Arnoldi::m_k; + using Arnoldi::m_fac_V; + using Arnoldi::m_fac_H; + using Arnoldi::m_fac_f; + using Arnoldi::m_beta; + using Arnoldi::m_near_0; + using Arnoldi::m_eps; + +public: + Lanczos(const ArnoldiOpType& op, Index m) : + Arnoldi(op, m) + {} + + // Lanczos factorization starting from step-k + void factorize_from(Index from_k, Index to_m, Index& op_counter) + { + using std::sqrt; + + if(to_m <= from_k) return; + + if(from_k > m_k) + { + std::stringstream msg; + msg << "Lanczos: from_k (= " << from_k << + ") is larger than the current subspace dimension (= " << + m_k << ")"; + throw std::invalid_argument(msg.str()); + } + + const Scalar beta_thresh = m_eps * sqrt(Scalar(m_n)); + + // Pre-allocate vectors + Vector Vf(to_m); + Vector w(m_n); + + // Keep the upperleft k x k submatrix of H and set other elements to 0 + m_fac_H.rightCols(m_m - from_k).setZero(); + m_fac_H.block(from_k, 0, m_m - from_k, from_k).setZero(); + + for(Index i = from_k; i <= to_m - 1; i++) + { + bool restart = false; + // If beta = 0, then the next V is not full rank + // We need to generate a new residual vector that is orthogonal + // to the current V, which we call a restart + if(m_beta < m_near_0) + { + MapConstMat V(m_fac_V.data(), m_n, i); // The first i columns + this->expand_basis(V, 2 * i, m_fac_f, m_beta); + restart = true; + } + + // v <- f / ||f|| + MapVec v(&m_fac_V(0, i), m_n); // The (i+1)-th column + v.noalias() = m_fac_f / m_beta; + + // Note that H[i+1, i] equals to the unrestarted beta + m_fac_H(i, i - 1) = restart ? Scalar(0) : m_beta; + + // w <- A * v + m_op.perform_op(v.data(), w.data()); + op_counter++; + + // H[i+1, i+1] = = v'Bw + m_fac_H(i - 1, i) = m_fac_H(i, i - 1); // Due to symmetry + m_fac_H(i, i) = m_op.inner_product(v, w); + + // f <- w - V * V'Bw = w - H[i+1, i] * V{i} - H[i+1, i+1] * V{i+1} + // If restarting, we know that H[i+1, i] = 0 + if(restart) + m_fac_f.noalias() = w - m_fac_H(i, i) * v; + else + m_fac_f.noalias() = w - m_fac_H(i, i - 1) * m_fac_V.col(i - 1) - m_fac_H(i, i) * v; + + m_beta = m_op.norm(m_fac_f); + + // f/||f|| is going to be the next column of V, so we need to test + // whether V'B(f/||f||) ~= 0 + const Index i1 = i + 1; + MapMat Vs(m_fac_V.data(), m_n, i1); // The first (i+1) columns + m_op.trans_product(Vs, m_fac_f, Vf.head(i1)); + Scalar ortho_err = Vf.head(i1).cwiseAbs().maxCoeff(); + // If not, iteratively correct the residual + int count = 0; + while(count < 5 && ortho_err > m_eps * m_beta) + { + // There is an edge case: when beta=||f|| is close to zero, f mostly consists + // of noises of rounding errors, so the test [ortho_err < eps * beta] is very + // likely to fail. In particular, if beta=0, then the test is ensured to fail. + // Hence when this happens, we force f to be zero, and then restart in the + // next iteration. + if(m_beta < beta_thresh) + { + m_fac_f.setZero(); + m_beta = Scalar(0); + break; + } + + // f <- f - V * Vf + m_fac_f.noalias() -= Vs * Vf.head(i1); + // h <- h + Vf + m_fac_H(i - 1, i) += Vf[i - 1]; + m_fac_H(i, i - 1) = m_fac_H(i - 1, i); + m_fac_H(i, i) += Vf[i]; + // beta <- ||f|| + m_beta = m_op.norm(m_fac_f); + + m_op.trans_product(Vs, m_fac_f, Vf.head(i1)); + ortho_err = Vf.head(i1).cwiseAbs().maxCoeff(); + count++; + } + } + + // Indicate that this is a step-m factorization + m_k = to_m; + } + + // Apply H -> Q'HQ, where Q is from a tridiagonal QR decomposition + void compress_H(const TridiagQR& decomp) + { + decomp.matrix_QtHQ(m_fac_H); + m_k--; + } +}; + + +} // namespace Spectra + +#endif // LANCZOS_H diff --git a/src/external/Spectra/include/Spectra/LinAlg/TridiagEigen.h b/src/external/Spectra/include/Spectra/LinAlg/TridiagEigen.h new file mode 100644 index 00000000..b79fe8d1 --- /dev/null +++ b/src/external/Spectra/include/Spectra/LinAlg/TridiagEigen.h @@ -0,0 +1,219 @@ +// The code was adapted from Eigen/src/Eigenvaleus/SelfAdjointEigenSolver.h +// +// Copyright (C) 2008-2010 Gael Guennebaud +// Copyright (C) 2010 Jitse Niesen +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef TRIDIAG_EIGEN_H +#define TRIDIAG_EIGEN_H + +#include +#include +#include + +#include "../Util/TypeTraits.h" + +namespace Spectra { + + +template +class TridiagEigen +{ +private: + typedef Eigen::Index Index; + // For convenience in adapting the tridiagonal_qr_step() function + typedef Scalar RealScalar; + + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + + typedef Eigen::Ref GenericMatrix; + typedef const Eigen::Ref ConstGenericMatrix; + + Index m_n; + Vector m_main_diag; // Main diagonal elements of the matrix + Vector m_sub_diag; // Sub-diagonal elements of the matrix + Matrix m_evecs; // To store eigenvectors + + bool m_computed; + const Scalar m_near_0; // a very small value, ~= 1e-307 for the "double" type + + // Adapted from Eigen/src/Eigenvaleus/SelfAdjointEigenSolver.h + static void tridiagonal_qr_step(RealScalar* diag, + RealScalar* subdiag, Index start, + Index end, Scalar* matrixQ, + Index n) + { + using std::abs; + + RealScalar td = (diag[end-1] - diag[end]) * RealScalar(0.5); + RealScalar e = subdiag[end-1]; + // Note that thanks to scaling, e^2 or td^2 cannot overflow, however they can still + // underflow thus leading to inf/NaN values when using the following commented code: + // RealScalar e2 = numext::abs2(subdiag[end-1]); + // RealScalar mu = diag[end] - e2 / (td + (td>0 ? 1 : -1) * sqrt(td*td + e2)); + // This explain the following, somewhat more complicated, version: + RealScalar mu = diag[end]; + if(td == Scalar(0)) + mu -= abs(e); + else + { + RealScalar e2 = Eigen::numext::abs2(subdiag[end-1]); + RealScalar h = Eigen::numext::hypot(td, e); + if(e2==RealScalar(0)) mu -= (e / (td + (td>RealScalar(0) ? RealScalar(1) : RealScalar(-1)))) * (e / h); + else mu -= e2 / (td + (td>RealScalar(0) ? h : -h)); + } + + RealScalar x = diag[start] - mu; + RealScalar z = subdiag[start]; + Eigen::Map q(matrixQ, n, n); + for(Index k = start; k < end; ++k) + { + Eigen::JacobiRotation rot; + rot.makeGivens(x, z); + + const RealScalar s = rot.s(); + const RealScalar c = rot.c(); + + // do T = G' T G + RealScalar sdk = s * diag[k] + c * subdiag[k]; + RealScalar dkp1 = s * subdiag[k] + c * diag[k + 1]; + + diag[k] = c * (c * diag[k] - s * subdiag[k]) - s * (c * subdiag[k] - s * diag[k + 1]); + diag[k + 1] = s * sdk + c * dkp1; + subdiag[k] = c * sdk - s * dkp1; + + if(k > start) + subdiag[k - 1] = c * subdiag[k - 1] - s * z; + + x = subdiag[k]; + + if(k < end - 1) + { + z = -s * subdiag[k+1]; + subdiag[k + 1] = c * subdiag[k + 1]; + } + + // apply the givens rotation to the unit matrix Q = Q * G + if(matrixQ) + q.applyOnTheRight(k, k + 1, rot); + } + } + +public: + TridiagEigen() : + m_n(0), m_computed(false), + m_near_0(TypeTraits::min() * Scalar(10)) + {} + + TridiagEigen(ConstGenericMatrix& mat) : + m_n(mat.rows()), m_computed(false), + m_near_0(TypeTraits::min() * Scalar(10)) + { + compute(mat); + } + + void compute(ConstGenericMatrix& mat) + { + using std::abs; + + m_n = mat.rows(); + if(m_n != mat.cols()) + throw std::invalid_argument("TridiagEigen: matrix must be square"); + + m_main_diag.resize(m_n); + m_sub_diag.resize(m_n - 1); + m_evecs.resize(m_n, m_n); + m_evecs.setIdentity(); + + // Scale matrix to improve stability + const Scalar scale = std::max(mat.diagonal().cwiseAbs().maxCoeff(), + mat.diagonal(-1).cwiseAbs().maxCoeff()); + // If scale=0, mat is a zero matrix, so we can early stop + if(scale < m_near_0) + { + // m_main_diag contains eigenvalues + m_main_diag.setZero(); + // m_evecs has been set identity + // m_evecs.setIdentity(); + m_computed = true; + return; + } + m_main_diag.noalias() = mat.diagonal() / scale; + m_sub_diag.noalias() = mat.diagonal(-1) / scale; + + Scalar* diag = m_main_diag.data(); + Scalar* subdiag = m_sub_diag.data(); + + Index end = m_n - 1; + Index start = 0; + Index iter = 0; // total number of iterations + int info = 0; // 0 for success, 1 for failure + + const Scalar considerAsZero = TypeTraits::min(); + const Scalar precision = Scalar(2) * Eigen::NumTraits::epsilon(); + + while(end > 0) + { + for(Index i = start; i < end; i++) + if(abs(subdiag[i]) <= considerAsZero || + abs(subdiag[i]) <= (abs(diag[i]) + abs(diag[i + 1])) * precision) + subdiag[i] = 0; + + // find the largest unreduced block + while(end > 0 && subdiag[end - 1] == Scalar(0)) + end--; + + if(end <= 0) + break; + + // if we spent too many iterations, we give up + iter++; + if(iter > 30 * m_n) + { + info = 1; + break; + } + + start = end - 1; + while(start > 0 && subdiag[start - 1] != Scalar(0)) + start--; + + tridiagonal_qr_step(diag, subdiag, start, end, m_evecs.data(), m_n); + } + + if(info > 0) + throw std::runtime_error("TridiagEigen: eigen decomposition failed"); + + // Scale eigenvalues back + m_main_diag *= scale; + + m_computed = true; + } + + const Vector& eigenvalues() const + { + if(!m_computed) + throw std::logic_error("TridiagEigen: need to call compute() first"); + + // After calling compute(), main_diag will contain the eigenvalues. + return m_main_diag; + } + + const Matrix& eigenvectors() const + { + if(!m_computed) + throw std::logic_error("TridiagEigen: need to call compute() first"); + + return m_evecs; + } +}; + + +} // namespace Spectra + +#endif // TRIDIAG_EIGEN_H diff --git a/src/external/Spectra/include/Spectra/LinAlg/UpperHessenbergEigen.h b/src/external/Spectra/include/Spectra/LinAlg/UpperHessenbergEigen.h new file mode 100644 index 00000000..4e099f56 --- /dev/null +++ b/src/external/Spectra/include/Spectra/LinAlg/UpperHessenbergEigen.h @@ -0,0 +1,317 @@ +// The code was adapted from Eigen/src/Eigenvaleus/EigenSolver.h +// +// Copyright (C) 2008 Gael Guennebaud +// Copyright (C) 2010,2012 Jitse Niesen +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef UPPER_HESSENBERG_EIGEN_H +#define UPPER_HESSENBERG_EIGEN_H + +#include +#include +#include + +namespace Spectra { + + +template +class UpperHessenbergEigen +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + + typedef Eigen::Ref GenericMatrix; + typedef const Eigen::Ref ConstGenericMatrix; + + typedef std::complex Complex; + typedef Eigen::Matrix ComplexMatrix; + typedef Eigen::Matrix ComplexVector; + + Index m_n; // Size of the matrix + Eigen::RealSchur m_realSchur; // Schur decomposition solver + Matrix m_matT; // Schur T matrix + Matrix m_eivec; // Storing eigenvectors + ComplexVector m_eivalues; // Eigenvalues + + bool m_computed; + + void doComputeEigenvectors() + { + using std::abs; + + const Index size = m_eivec.cols(); + const Scalar eps = Eigen::NumTraits::epsilon(); + + // inefficient! this is already computed in RealSchur + Scalar norm(0); + for(Index j = 0; j < size; ++j) + { + norm += m_matT.row(j).segment((std::max)(j-1, Index(0)), size-(std::max)(j-1, Index(0))).cwiseAbs().sum(); + } + + // Backsubstitute to find vectors of upper triangular form + if(norm == Scalar(0)) + return; + + for(Index n = size - 1; n >= 0; n--) + { + Scalar p = m_eivalues.coeff(n).real(); + Scalar q = m_eivalues.coeff(n).imag(); + + // Scalar vector + if(q == Scalar(0)) + { + Scalar lastr(0), lastw(0); + Index l = n; + + m_matT.coeffRef(n,n) = Scalar(1); + for(Index i = n-1; i >= 0; i--) + { + Scalar w = m_matT.coeff(i,i) - p; + Scalar r = m_matT.row(i).segment(l,n-l+1).dot(m_matT.col(n).segment(l, n-l+1)); + + if(m_eivalues.coeff(i).imag() < Scalar(0)) + { + lastw = w; + lastr = r; + } else { + l = i; + if(m_eivalues.coeff(i).imag() == Scalar(0)) + { + if (w != Scalar(0)) + m_matT.coeffRef(i,n) = -r / w; + else + m_matT.coeffRef(i,n) = -r / (eps * norm); + } + else // Solve real equations + { + Scalar x = m_matT.coeff(i,i+1); + Scalar y = m_matT.coeff(i+1,i); + Scalar denom = (m_eivalues.coeff(i).real() - p) * (m_eivalues.coeff(i).real() - p) + m_eivalues.coeff(i).imag() * m_eivalues.coeff(i).imag(); + Scalar t = (x * lastr - lastw * r) / denom; + m_matT.coeffRef(i,n) = t; + if(abs(x) > abs(lastw)) + m_matT.coeffRef(i+1,n) = (-r - w * t) / x; + else + m_matT.coeffRef(i+1,n) = (-lastr - y * t) / lastw; + } + + // Overflow control + Scalar t = abs(m_matT.coeff(i,n)); + if((eps * t) * t > Scalar(1)) + m_matT.col(n).tail(size-i) /= t; + } + } + } else if(q < Scalar(0) && n > 0) { // Complex vector + Scalar lastra(0), lastsa(0), lastw(0); + Index l = n-1; + + // Last vector component imaginary so matrix is triangular + if(abs(m_matT.coeff(n,n-1)) > abs(m_matT.coeff(n-1,n))) + { + m_matT.coeffRef(n-1,n-1) = q / m_matT.coeff(n,n-1); + m_matT.coeffRef(n-1,n) = -(m_matT.coeff(n,n) - p) / m_matT.coeff(n,n-1); + } + else + { + Complex cc = Complex(Scalar(0),-m_matT.coeff(n-1,n)) / Complex(m_matT.coeff(n-1,n-1)-p,q); + m_matT.coeffRef(n-1,n-1) = Eigen::numext::real(cc); + m_matT.coeffRef(n-1,n) = Eigen::numext::imag(cc); + } + m_matT.coeffRef(n,n-1) = Scalar(0); + m_matT.coeffRef(n,n) = Scalar(1); + for(Index i = n-2; i >= 0; i--) + { + Scalar ra = m_matT.row(i).segment(l, n-l+1).dot(m_matT.col(n-1).segment(l, n-l+1)); + Scalar sa = m_matT.row(i).segment(l, n-l+1).dot(m_matT.col(n).segment(l, n-l+1)); + Scalar w = m_matT.coeff(i,i) - p; + + if(m_eivalues.coeff(i).imag() < Scalar(0)) + { + lastw = w; + lastra = ra; + lastsa = sa; + } + else + { + l = i; + if(m_eivalues.coeff(i).imag() == Scalar(0)) + { + Complex cc = Complex(-ra,-sa) / Complex(w,q); + m_matT.coeffRef(i,n-1) = Eigen::numext::real(cc); + m_matT.coeffRef(i,n) = Eigen::numext::imag(cc); + } + else + { + // Solve complex equations + Scalar x = m_matT.coeff(i,i+1); + Scalar y = m_matT.coeff(i+1,i); + Scalar vr = (m_eivalues.coeff(i).real() - p) * (m_eivalues.coeff(i).real() - p) + m_eivalues.coeff(i).imag() * m_eivalues.coeff(i).imag() - q * q; + Scalar vi = (m_eivalues.coeff(i).real() - p) * Scalar(2) * q; + if((vr == Scalar(0)) && (vi == Scalar(0))) + vr = eps * norm * (abs(w) + abs(q) + abs(x) + abs(y) + abs(lastw)); + + Complex cc = Complex(x*lastra-lastw*ra+q*sa,x*lastsa-lastw*sa-q*ra) / Complex(vr,vi); + m_matT.coeffRef(i,n-1) = Eigen::numext::real(cc); + m_matT.coeffRef(i,n) = Eigen::numext::imag(cc); + if(abs(x) > (abs(lastw) + abs(q))) + { + m_matT.coeffRef(i+1,n-1) = (-ra - w * m_matT.coeff(i,n-1) + q * m_matT.coeff(i,n)) / x; + m_matT.coeffRef(i+1,n) = (-sa - w * m_matT.coeff(i,n) - q * m_matT.coeff(i,n-1)) / x; + } + else + { + cc = Complex(-lastra-y*m_matT.coeff(i,n-1),-lastsa-y*m_matT.coeff(i,n)) / Complex(lastw,q); + m_matT.coeffRef(i+1,n-1) = Eigen::numext::real(cc); + m_matT.coeffRef(i+1,n) = Eigen::numext::imag(cc); + } + } + + // Overflow control + Scalar t = std::max(abs(m_matT.coeff(i,n-1)), abs(m_matT.coeff(i,n))); + if((eps * t) * t > Scalar(1)) + m_matT.block(i, n-1, size-i, 2) /= t; + + } + } + + // We handled a pair of complex conjugate eigenvalues, so need to skip them both + n--; + } + } + + // Back transformation to get eigenvectors of original matrix + Vector m_tmp(size); + for(Index j = size-1; j >= 0; j--) + { + m_tmp.noalias() = m_eivec.leftCols(j+1) * m_matT.col(j).segment(0, j+1); + m_eivec.col(j) = m_tmp; + } + } + +public: + + UpperHessenbergEigen() : + m_n(0), m_computed(false) + {} + + UpperHessenbergEigen(ConstGenericMatrix& mat) : + m_n(mat.rows()), m_computed(false) + { + compute(mat); + } + + void compute(ConstGenericMatrix& mat) + { + using std::abs; + using std::sqrt; + + if(mat.rows() != mat.cols()) + throw std::invalid_argument("UpperHessenbergEigen: matrix must be square"); + + m_n = mat.rows(); + // Scale matrix prior to the Schur decomposition + const Scalar scale = mat.cwiseAbs().maxCoeff(); + + // Reduce to real Schur form + Matrix Q = Matrix::Identity(m_n, m_n); + m_realSchur.computeFromHessenberg(mat / scale, Q, true); + if(m_realSchur.info() != Eigen::Success) + throw std::runtime_error("UpperHessenbergEigen: eigen decomposition failed"); + + m_matT = m_realSchur.matrixT(); + m_eivec = m_realSchur.matrixU(); + + // Compute eigenvalues from matT + m_eivalues.resize(m_n); + Index i = 0; + while(i < m_n) + { + // Real eigenvalue + if(i == m_n - 1 || m_matT.coeff(i+1, i) == Scalar(0)) + { + m_eivalues.coeffRef(i) = m_matT.coeff(i, i); + ++i; + } + else // Complex eigenvalues + { + Scalar p = Scalar(0.5) * (m_matT.coeff(i, i) - m_matT.coeff(i+1, i+1)); + Scalar z; + // Compute z = sqrt(abs(p * p + m_matT.coeff(i+1, i) * m_matT.coeff(i, i+1))); + // without overflow + { + Scalar t0 = m_matT.coeff(i+1, i); + Scalar t1 = m_matT.coeff(i, i+1); + Scalar maxval = std::max(abs(p), std::max(abs(t0), abs(t1))); + t0 /= maxval; + t1 /= maxval; + Scalar p0 = p / maxval; + z = maxval * sqrt(abs(p0 * p0 + t0 * t1)); + } + m_eivalues.coeffRef(i) = Complex(m_matT.coeff(i+1, i+1) + p, z); + m_eivalues.coeffRef(i+1) = Complex(m_matT.coeff(i+1, i+1) + p, -z); + i += 2; + } + } + + // Compute eigenvectors + doComputeEigenvectors(); + + // Scale eigenvalues back + m_eivalues *= scale; + + m_computed = true; + } + + const ComplexVector& eigenvalues() const + { + if(!m_computed) + throw std::logic_error("UpperHessenbergEigen: need to call compute() first"); + + return m_eivalues; + } + + ComplexMatrix eigenvectors() + { + using std::abs; + + if(!m_computed) + throw std::logic_error("UpperHessenbergEigen: need to call compute() first"); + + Index n = m_eivec.cols(); + ComplexMatrix matV(n, n); + for(Index j = 0; j < n; ++j) + { + // imaginary part of real eigenvalue is already set to exact zero + if(Eigen::numext::imag(m_eivalues.coeff(j)) == Scalar(0) || j + 1 == n) + { + // we have a real eigen value + matV.col(j) = m_eivec.col(j).template cast(); + matV.col(j).normalize(); + } else { + // we have a pair of complex eigen values + for(Index i = 0; i < n; ++i) + { + matV.coeffRef(i,j) = Complex(m_eivec.coeff(i,j), m_eivec.coeff(i,j+1)); + matV.coeffRef(i,j+1) = Complex(m_eivec.coeff(i,j), -m_eivec.coeff(i,j+1)); + } + matV.col(j).normalize(); + matV.col(j+1).normalize(); + ++j; + } + } + + return matV; + } +}; + + +} // namespace Spectra + +#endif // UPPER_HESSENBERG_EIGEN_H diff --git a/src/external/Spectra/include/Spectra/LinAlg/UpperHessenbergQR.h b/src/external/Spectra/include/Spectra/LinAlg/UpperHessenbergQR.h new file mode 100644 index 00000000..a66d9598 --- /dev/null +++ b/src/external/Spectra/include/Spectra/LinAlg/UpperHessenbergQR.h @@ -0,0 +1,670 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef UPPER_HESSENBERG_QR_H +#define UPPER_HESSENBERG_QR_H + +#include +#include // std::sqrt +#include // std::fill, std::copy +#include // std::logic_error + +namespace Spectra { + + +/// +/// \defgroup Internals Internal Classes +/// +/// Classes for internal use. May be useful to developers. +/// + +/// +/// \ingroup Internals +/// @{ +/// + +/// +/// \defgroup LinearAlgebra Linear Algebra +/// +/// A number of classes for linear algebra operations. +/// + +/// +/// \ingroup LinearAlgebra +/// +/// Perform the QR decomposition of an upper Hessenberg matrix. +/// +/// \tparam Scalar The element type of the matrix. +/// Currently supported types are `float`, `double` and `long double`. +/// +template +class UpperHessenbergQR +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Matrix RowVector; + typedef Eigen::Array Array; + + typedef Eigen::Ref GenericMatrix; + typedef const Eigen::Ref ConstGenericMatrix; + + Matrix m_mat_T; + +protected: + Index m_n; + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + // Q = G1 * G2 * ... * G_{n-1} + Scalar m_shift; + Array m_rot_cos; + Array m_rot_sin; + bool m_computed; + + // Given x and y, compute 1) r = sqrt(x^2 + y^2), 2) c = x / r, 3) s = -y / r + // If both x and y are zero, set c = 1 and s = 0 + // We must implement it in a numerically stable way + static void compute_rotation(const Scalar& x, const Scalar& y, Scalar& r, Scalar& c, Scalar& s) + { + using std::sqrt; + + const Scalar xsign = (x > Scalar(0)) - (x < Scalar(0)); + const Scalar ysign = (y > Scalar(0)) - (y < Scalar(0)); + const Scalar xabs = x * xsign; + const Scalar yabs = y * ysign; + if(xabs > yabs) + { + // In this case xabs != 0 + const Scalar ratio = yabs / xabs; // so that 0 <= ratio < 1 + const Scalar common = sqrt(Scalar(1) + ratio * ratio); + c = xsign / common; + r = xabs * common; + s = -y / r; + } else { + if(yabs == Scalar(0)) + { + r = Scalar(0); c = Scalar(1); s = Scalar(0); + return; + } + const Scalar ratio = xabs / yabs; // so that 0 <= ratio <= 1 + const Scalar common = sqrt(Scalar(1) + ratio * ratio); + s = -ysign / common; + r = yabs * common; + c = x / r; + } + } + +public: + /// + /// Constructor to preallocate memory. Computation can + /// be performed later by calling the compute() method. + /// + UpperHessenbergQR(Index size) : + m_n(size), + m_rot_cos(m_n - 1), + m_rot_sin(m_n - 1), + m_computed(false) + {} + + /// + /// Constructor to create an object that performs and stores the + /// QR decomposition of an upper Hessenberg matrix `mat`, with an + /// optional shift: \f$H-sI=QR\f$. Here \f$H\f$ stands for the matrix + /// `mat`, and \f$s\f$ is the shift. + /// + /// \param mat Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// Only the upper triangular and the lower subdiagonal parts of + /// the matrix are used. + /// + UpperHessenbergQR(ConstGenericMatrix& mat, const Scalar& shift = Scalar(0)) : + m_n(mat.rows()), + m_shift(shift), + m_rot_cos(m_n - 1), + m_rot_sin(m_n - 1), + m_computed(false) + { + compute(mat, shift); + } + + /// + /// Virtual destructor. + /// + virtual ~UpperHessenbergQR() {}; + + /// + /// Conduct the QR factorization of an upper Hessenberg matrix with + /// an optional shift. + /// + /// \param mat Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// Only the upper triangular and the lower subdiagonal parts of + /// the matrix are used. + /// + virtual void compute(ConstGenericMatrix& mat, const Scalar& shift = Scalar(0)) + { + m_n = mat.rows(); + if(m_n != mat.cols()) + throw std::invalid_argument("UpperHessenbergQR: matrix must be square"); + + m_shift = shift; + m_mat_T.resize(m_n, m_n); + m_rot_cos.resize(m_n - 1); + m_rot_sin.resize(m_n - 1); + + // Make a copy of mat - s * I + std::copy(mat.data(), mat.data() + mat.size(), m_mat_T.data()); + m_mat_T.diagonal().array() -= m_shift; + + Scalar xi, xj, r, c, s; + Scalar *Tii, *ptr; + const Index n1 = m_n - 1; + for(Index i = 0; i < n1; i++) + { + Tii = &m_mat_T.coeffRef(i, i); + + // Make sure mat_T is upper Hessenberg + // Zero the elements below mat_T(i + 1, i) + std::fill(Tii + 2, Tii + m_n - i, Scalar(0)); + + xi = Tii[0]; // mat_T(i, i) + xj = Tii[1]; // mat_T(i + 1, i) + compute_rotation(xi, xj, r, c, s); + m_rot_cos[i] = c; + m_rot_sin[i] = s; + + // For a complete QR decomposition, + // we first obtain the rotation matrix + // G = [ cos sin] + // [-sin cos] + // and then do T[i:(i + 1), i:(n - 1)] = G' * T[i:(i + 1), i:(n - 1)] + + // Gt << c, -s, s, c; + // m_mat_T.block(i, i, 2, m_n - i) = Gt * m_mat_T.block(i, i, 2, m_n - i); + Tii[0] = r; // m_mat_T(i, i) => r + Tii[1] = 0; // m_mat_T(i + 1, i) => 0 + ptr = Tii + m_n; // m_mat_T(i, k), k = i+1, i+2, ..., n-1 + for(Index j = i + 1; j < m_n; j++, ptr += m_n) + { + Scalar tmp = ptr[0]; + ptr[0] = c * tmp - s * ptr[1]; + ptr[1] = s * tmp + c * ptr[1]; + } + + // If we do not need to calculate the R matrix, then + // only the cos and sin sequences are required. + // In such case we only update T[i + 1, (i + 1):(n - 1)] + // m_mat_T.block(i + 1, i + 1, 1, m_n - i - 1) *= c; + // m_mat_T.block(i + 1, i + 1, 1, m_n - i - 1) += s * mat_T.block(i, i + 1, 1, m_n - i - 1); + } + + m_computed = true; + } + + /// + /// Return the \f$R\f$ matrix in the QR decomposition, which is an + /// upper triangular matrix. + /// + /// \return Returned matrix type will be `Eigen::Matrix`, depending on + /// the template parameter `Scalar` defined. + /// + virtual Matrix matrix_R() const + { + if(!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + return m_mat_T; + } + + /// + /// Overwrite `dest` with \f$Q'HQ = RQ + sI\f$, where \f$H\f$ is the input matrix `mat`, + /// and \f$s\f$ is the shift. The result is an upper Hessenberg matrix. + /// + /// \param mat The matrix to be overwritten, whose type should be `Eigen::Matrix`, + /// depending on the template parameter `Scalar` defined. + /// + virtual void matrix_QtHQ(Matrix& dest) const + { + if(!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + // Make a copy of the R matrix + dest.resize(m_n, m_n); + std::copy(m_mat_T.data(), m_mat_T.data() + m_mat_T.size(), dest.data()); + + // Compute the RQ matrix + const Index n1 = m_n - 1; + for(Index i = 0; i < n1; i++) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // RQ[, i:(i + 1)] = RQ[, i:(i + 1)] * Gi + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + Scalar *Yi, *Yi1; + Yi = &dest.coeffRef(0, i); + Yi1 = Yi + m_n; // RQ(0, i + 1) + const Index i2 = i + 2; + for(Index j = 0; j < i2; j++) + { + const Scalar tmp = Yi[j]; + Yi[j] = c * tmp - s * Yi1[j]; + Yi1[j] = s * tmp + c * Yi1[j]; + } + + /* Vector dest = RQ.block(0, i, i + 2, 1); + dest.block(0, i, i + 2, 1) = c * Yi - s * dest.block(0, i + 1, i + 2, 1); + dest.block(0, i + 1, i + 2, 1) = s * Yi + c * dest.block(0, i + 1, i + 2, 1); */ + } + + // Add the shift to the diagonal + dest.diagonal().array() += m_shift; + } + + /// + /// Apply the \f$Q\f$ matrix to a vector \f$y\f$. + /// + /// \param Y A vector that will be overwritten by the matrix product \f$Qy\f$. + /// + /// Vector type can be `Eigen::Vector`, depending on + /// the template parameter `Scalar` defined. + /// + // Y -> QY = G1 * G2 * ... * Y + void apply_QY(Vector& Y) const + { + if(!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + for(Index i = m_n - 2; i >= 0; i--) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[i:(i + 1)] = Gi * Y[i:(i + 1)] + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + const Scalar tmp = Y[i]; + Y[i] = c * tmp + s * Y[i + 1]; + Y[i + 1] = -s * tmp + c * Y[i + 1]; + } + } + + /// + /// Apply the \f$Q\f$ matrix to a vector \f$y\f$. + /// + /// \param Y A vector that will be overwritten by the matrix product \f$Q'y\f$. + /// + /// Vector type can be `Eigen::Vector`, depending on + /// the template parameter `Scalar` defined. + /// + // Y -> Q'Y = G_{n-1}' * ... * G2' * G1' * Y + void apply_QtY(Vector& Y) const + { + if(!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + const Index n1 = m_n - 1; + for(Index i = 0; i < n1; i++) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[i:(i + 1)] = Gi' * Y[i:(i + 1)] + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + const Scalar tmp = Y[i]; + Y[i] = c * tmp - s * Y[i + 1]; + Y[i + 1] = s * tmp + c * Y[i + 1]; + } + } + + /// + /// Apply the \f$Q\f$ matrix to another matrix \f$Y\f$. + /// + /// \param Y A matrix that will be overwritten by the matrix product \f$QY\f$. + /// + /// Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + // Y -> QY = G1 * G2 * ... * Y + void apply_QY(GenericMatrix Y) const + { + if(!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + RowVector Yi(Y.cols()), Yi1(Y.cols()); + for(Index i = m_n - 2; i >= 0; i--) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[i:(i + 1), ] = Gi * Y[i:(i + 1), ] + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + Yi.noalias() = Y.row(i); + Yi1.noalias() = Y.row(i + 1); + Y.row(i) = c * Yi + s * Yi1; + Y.row(i + 1) = -s * Yi + c * Yi1; + } + } + + /// + /// Apply the \f$Q\f$ matrix to another matrix \f$Y\f$. + /// + /// \param Y A matrix that will be overwritten by the matrix product \f$Q'Y\f$. + /// + /// Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + // Y -> Q'Y = G_{n-1}' * ... * G2' * G1' * Y + void apply_QtY(GenericMatrix Y) const + { + if(!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + RowVector Yi(Y.cols()), Yi1(Y.cols()); + const Index n1 = m_n - 1; + for(Index i = 0; i < n1; i++) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[i:(i + 1), ] = Gi' * Y[i:(i + 1), ] + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + Yi.noalias() = Y.row(i); + Yi1.noalias() = Y.row(i + 1); + Y.row(i) = c * Yi - s * Yi1; + Y.row(i + 1) = s * Yi + c * Yi1; + } + } + + /// + /// Apply the \f$Q\f$ matrix to another matrix \f$Y\f$. + /// + /// \param Y A matrix that will be overwritten by the matrix product \f$YQ\f$. + /// + /// Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + // Y -> YQ = Y * G1 * G2 * ... + void apply_YQ(GenericMatrix Y) const + { + if(!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + /*Vector Yi(Y.rows()); + for(Index i = 0; i < m_n - 1; i++) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[, i:(i + 1)] = Y[, i:(i + 1)] * Gi + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + Yi.noalias() = Y.col(i); + Y.col(i) = c * Yi - s * Y.col(i + 1); + Y.col(i + 1) = s * Yi + c * Y.col(i + 1); + }*/ + Scalar *Y_col_i, *Y_col_i1; + const Index n1 = m_n - 1; + const Index nrow = Y.rows(); + for(Index i = 0; i < n1; i++) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + + Y_col_i = &Y.coeffRef(0, i); + Y_col_i1 = &Y.coeffRef(0, i + 1); + for(Index j = 0; j < nrow; j++) + { + Scalar tmp = Y_col_i[j]; + Y_col_i[j] = c * tmp - s * Y_col_i1[j]; + Y_col_i1[j] = s * tmp + c * Y_col_i1[j]; + } + } + } + + /// + /// Apply the \f$Q\f$ matrix to another matrix \f$Y\f$. + /// + /// \param Y A matrix that will be overwritten by the matrix product \f$YQ'\f$. + /// + /// Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + // Y -> YQ' = Y * G_{n-1}' * ... * G2' * G1' + void apply_YQt(GenericMatrix Y) const + { + if(!m_computed) + throw std::logic_error("UpperHessenbergQR: need to call compute() first"); + + Vector Yi(Y.rows()); + for(Index i = m_n - 2; i >= 0; i--) + { + const Scalar c = m_rot_cos.coeff(i); + const Scalar s = m_rot_sin.coeff(i); + // Y[, i:(i + 1)] = Y[, i:(i + 1)] * Gi' + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + Yi.noalias() = Y.col(i); + Y.col(i) = c * Yi + s * Y.col(i + 1); + Y.col(i + 1) = -s * Yi + c * Y.col(i + 1); + } + } +}; + + + +/// +/// \ingroup LinearAlgebra +/// +/// Perform the QR decomposition of a tridiagonal matrix, a special +/// case of upper Hessenberg matrices. +/// +/// \tparam Scalar The element type of the matrix. +/// Currently supported types are `float`, `double` and `long double`. +/// +template +class TridiagQR: public UpperHessenbergQR +{ +private: + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef const Eigen::Ref ConstGenericMatrix; + + typedef typename Matrix::Index Index; + + Vector m_T_diag; // diagonal elements of T + Vector m_T_lsub; // lower subdiagonal of T + Vector m_T_usub; // upper subdiagonal of T + Vector m_T_usub2; // 2nd upper subdiagonal of T + +public: + /// + /// Constructor to preallocate memory. Computation can + /// be performed later by calling the compute() method. + /// + TridiagQR(Index size) : + UpperHessenbergQR(size) + {} + + /// + /// Constructor to create an object that performs and stores the + /// QR decomposition of an upper Hessenberg matrix `mat`, with an + /// optional shift: \f$H-sI=QR\f$. Here \f$H\f$ stands for the matrix + /// `mat`, and \f$s\f$ is the shift. + /// + /// \param mat Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// Only the major- and sub- diagonal parts of + /// the matrix are used. + /// + TridiagQR(ConstGenericMatrix& mat, const Scalar& shift = Scalar(0)) : + UpperHessenbergQR(mat.rows()) + { + this->compute(mat, shift); + } + + /// + /// Conduct the QR factorization of a tridiagonal matrix with an + /// optional shift. + /// + /// \param mat Matrix type can be `Eigen::Matrix` (e.g. + /// `Eigen::MatrixXd` and `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// Only the major- and sub- diagonal parts of + /// the matrix are used. + /// + void compute(ConstGenericMatrix& mat, const Scalar& shift = Scalar(0)) + { + this->m_n = mat.rows(); + if(this->m_n != mat.cols()) + throw std::invalid_argument("TridiagQR: matrix must be square"); + + this->m_shift = shift; + m_T_diag.resize(this->m_n); + m_T_lsub.resize(this->m_n - 1); + m_T_usub.resize(this->m_n - 1); + m_T_usub2.resize(this->m_n - 2); + this->m_rot_cos.resize(this->m_n - 1); + this->m_rot_sin.resize(this->m_n - 1); + + m_T_diag.array() = mat.diagonal().array() - this->m_shift; + m_T_lsub.noalias() = mat.diagonal(-1); + m_T_usub.noalias() = m_T_lsub; + + // A number of pointers to avoid repeated address calculation + Scalar *c = this->m_rot_cos.data(), // pointer to the cosine vector + *s = this->m_rot_sin.data(), // pointer to the sine vector + r; + const Index n1 = this->m_n - 1; + for(Index i = 0; i < n1; i++) + { + // diag[i] == T[i, i] + // lsub[i] == T[i + 1, i] + // r = sqrt(T[i, i]^2 + T[i + 1, i]^2) + // c = T[i, i] / r, s = -T[i + 1, i] / r + this->compute_rotation(m_T_diag.coeff(i), m_T_lsub.coeff(i), r, *c, *s); + + // For a complete QR decomposition, + // we first obtain the rotation matrix + // G = [ cos sin] + // [-sin cos] + // and then do T[i:(i + 1), i:(i + 2)] = G' * T[i:(i + 1), i:(i + 2)] + + // Update T[i, i] and T[i + 1, i] + // The updated value of T[i, i] is known to be r + // The updated value of T[i + 1, i] is known to be 0 + m_T_diag.coeffRef(i) = r; + m_T_lsub.coeffRef(i) = Scalar(0); + // Update T[i, i + 1] and T[i + 1, i + 1] + // usub[i] == T[i, i + 1] + // diag[i + 1] == T[i + 1, i + 1] + const Scalar tmp = m_T_usub.coeff(i); + m_T_usub.coeffRef(i) = (*c) * tmp - (*s) * m_T_diag.coeff(i + 1); + m_T_diag.coeffRef(i + 1) = (*s) * tmp + (*c) * m_T_diag.coeff(i + 1); + // Update T[i, i + 2] and T[i + 1, i + 2] + // usub2[i] == T[i, i + 2] + // usub[i + 1] == T[i + 1, i + 2] + if(i < n1 - 1) + { + m_T_usub2.coeffRef(i) = -(*s) * m_T_usub.coeff(i + 1); + m_T_usub.coeffRef(i + 1) *= (*c); + } + + c++; + s++; + + // If we do not need to calculate the R matrix, then + // only the cos and sin sequences are required. + // In such case we only update T[i + 1, (i + 1):(i + 2)] + // T[i + 1, i + 1] = c * T[i + 1, i + 1] + s * T[i, i + 1]; + // T[i + 1, i + 2] *= c; + } + + this->m_computed = true; + } + + /// + /// Return the \f$R\f$ matrix in the QR decomposition, which is an + /// upper triangular matrix. + /// + /// \return Returned matrix type will be `Eigen::Matrix`, depending on + /// the template parameter `Scalar` defined. + /// + Matrix matrix_R() const + { + if(!this->m_computed) + throw std::logic_error("TridiagQR: need to call compute() first"); + + Matrix R = Matrix::Zero(this->m_n, this->m_n); + R.diagonal().noalias() = m_T_diag; + R.diagonal(1).noalias() = m_T_usub; + R.diagonal(2).noalias() = m_T_usub2; + + return R; + } + + /// + /// Overwrite `dest` with \f$Q'HQ = RQ + sI\f$, where \f$H\f$ is the input matrix `mat`, + /// and \f$s\f$ is the shift. The result is a tridiagonal matrix. + /// + /// \param mat The matrix to be overwritten, whose type should be `Eigen::Matrix`, + /// depending on the template parameter `Scalar` defined. + /// + void matrix_QtHQ(Matrix& dest) const + { + if(!this->m_computed) + throw std::logic_error("TridiagQR: need to call compute() first"); + + // Make a copy of the R matrix + dest.resize(this->m_n, this->m_n); + dest.setZero(); + dest.diagonal().noalias() = m_T_diag; + // The upper diagonal refers to m_T_usub + // The 2nd upper subdiagonal will be zero in RQ + + // Compute the RQ matrix + // [m11 m12] points to RQ[i:(i+1), i:(i+1)] + // [0 m22] + // + // Gi = [ cos[i] sin[i]] + // [-sin[i] cos[i]] + const Index n1 = this->m_n - 1; + for(Index i = 0; i < n1; i++) + { + const Scalar c = this->m_rot_cos.coeff(i); + const Scalar s = this->m_rot_sin.coeff(i); + const Scalar m11 = dest.coeff(i, i), + m12 = m_T_usub.coeff(i), + m22 = m_T_diag.coeff(i + 1); + + // Update the diagonal and the lower subdiagonal of dest + dest.coeffRef(i , i ) = c * m11 - s * m12; + dest.coeffRef(i + 1, i ) = - s * m22; + dest.coeffRef(i + 1, i + 1) = c * m22; + } + + // Copy the lower subdiagonal to upper subdiagonal + dest.diagonal(1).noalias() = dest.diagonal(-1); + + // Add the shift to the diagonal + dest.diagonal().array() += this->m_shift; + } +}; + +/// +/// @} +/// + + +} // namespace Spectra + +#endif // UPPER_HESSENBERG_QR_H diff --git a/src/external/Spectra/include/Spectra/MatOp/DenseCholesky.h b/src/external/Spectra/include/Spectra/MatOp/DenseCholesky.h new file mode 100644 index 00000000..354c9508 --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/DenseCholesky.h @@ -0,0 +1,110 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef DENSE_CHOLESKY_H +#define DENSE_CHOLESKY_H + +#include +#include +#include +#include "../Util/CompInfo.h" + +namespace Spectra { + + +/// +/// \ingroup MatOp +/// +/// This class defines the operations related to Cholesky decomposition on a +/// positive definite matrix, \f$B=LL'\f$, where \f$L\f$ is a lower triangular +/// matrix. It is mainly used in the SymGEigsSolver generalized eigen solver +/// in the Cholesky decomposition mode. +/// +template +class DenseCholesky +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstMat; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef const Eigen::Ref ConstGenericMatrix; + + const Index m_n; + Eigen::LLT m_decomp; + int m_info; // status of the decomposition + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + DenseCholesky(ConstGenericMatrix& mat) : + m_n(mat.rows()), m_info(NOT_COMPUTED) + { + if(mat.rows() != mat.cols()) + throw std::invalid_argument("DenseCholesky: matrix must be square"); + + m_decomp.compute(mat); + m_info = (m_decomp.info() == Eigen::Success) ? + SUCCESSFUL : + NUMERICAL_ISSUE; + } + + /// + /// Returns the number of rows of the underlying matrix. + /// + Index rows() const { return m_n; } + /// + /// Returns the number of columns of the underlying matrix. + /// + Index cols() const { return m_n; } + + /// + /// Returns the status of the computation. + /// The full list of enumeration values can be found in \ref Enumerations. + /// + int info() const { return m_info; } + + /// + /// Performs the lower triangular solving operation \f$y=L^{-1}x\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(L) * x_in + void lower_triangular_solve(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_decomp.matrixL().solve(x); + } + + /// + /// Performs the upper triangular solving operation \f$y=(L')^{-1}x\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(L') * x_in + void upper_triangular_solve(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_decomp.matrixU().solve(x); + } +}; + + +} // namespace Spectra + +#endif // DENSE_CHOLESKY_H diff --git a/src/external/Spectra/include/Spectra/MatOp/DenseGenComplexShiftSolve.h b/src/external/Spectra/include/Spectra/MatOp/DenseGenComplexShiftSolve.h new file mode 100644 index 00000000..7f189067 --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/DenseGenComplexShiftSolve.h @@ -0,0 +1,104 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef DENSE_GEN_COMPLEX_SHIFT_SOLVE_H +#define DENSE_GEN_COMPLEX_SHIFT_SOLVE_H + +#include +#include +#include + +namespace Spectra { + + +/// +/// \ingroup MatOp +/// +/// This class defines the complex shift-solve operation on a general real matrix \f$A\f$, +/// i.e., calculating \f$y=\mathrm{Re}\{(A-\sigma I)^{-1}x\}\f$ for any complex-valued +/// \f$\sigma\f$ and real-valued vector \f$x\f$. It is mainly used in the +/// GenEigsComplexShiftSolver eigen solver. +/// +template +class DenseGenComplexShiftSolve +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef const Eigen::Ref ConstGenericMatrix; + + typedef std::complex Complex; + typedef Eigen::Matrix ComplexMatrix; + typedef Eigen::Matrix ComplexVector; + + typedef Eigen::PartialPivLU ComplexSolver; + + ConstGenericMatrix m_mat; + const Index m_n; + ComplexSolver m_solver; + ComplexVector m_x_cache; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + DenseGenComplexShiftSolve(ConstGenericMatrix& mat) : + m_mat(mat), m_n(mat.rows()) + { + if(mat.rows() != mat.cols()) + throw std::invalid_argument("DenseGenComplexShiftSolve: matrix must be square"); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_n; } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_n; } + + /// + /// Set the complex shift \f$\sigma\f$. + /// + /// \param sigmar Real part of \f$\sigma\f$. + /// \param sigmai Imaginary part of \f$\sigma\f$. + /// + void set_shift(Scalar sigmar, Scalar sigmai) + { + m_solver.compute(m_mat.template cast() - Complex(sigmar, sigmai) * ComplexMatrix::Identity(m_n, m_n)); + m_x_cache.resize(m_n); + m_x_cache.setZero(); + } + + /// + /// Perform the complex shift-solve operation + /// \f$y=\mathrm{Re}\{(A-\sigma I)^{-1}x\}\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = Re( inv(A - sigma * I) * x_in ) + void perform_op(const Scalar* x_in, Scalar* y_out) + { + m_x_cache.real() = MapConstVec(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_solver.solve(m_x_cache).real(); + } +}; + + +} // namespace Spectra + +#endif // DENSE_GEN_COMPLEX_SHIFT_SOLVE_H diff --git a/src/external/Spectra/include/Spectra/MatOp/DenseGenMatProd.h b/src/external/Spectra/include/Spectra/MatOp/DenseGenMatProd.h new file mode 100644 index 00000000..6933dac0 --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/DenseGenMatProd.h @@ -0,0 +1,82 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef DENSE_GEN_MAT_PROD_H +#define DENSE_GEN_MAT_PROD_H + +#include + +namespace Spectra { + + +/// +/// \defgroup MatOp Matrix Operations +/// +/// Define matrix operations on existing matrix objects +/// + +/// +/// \ingroup MatOp +/// +/// This class defines the matrix-vector multiplication operation on a +/// general real matrix \f$A\f$, i.e., calculating \f$y=Ax\f$ for any vector +/// \f$x\f$. It is mainly used in the GenEigsSolver and +/// SymEigsSolver eigen solvers. +/// +template +class DenseGenMatProd +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef const Eigen::Ref ConstGenericMatrix; + + ConstGenericMatrix m_mat; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + DenseGenMatProd(ConstGenericMatrix& mat) : + m_mat(mat) + {} + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_mat.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_mat.cols(); } + + /// + /// Perform the matrix-vector multiplication operation \f$y=Ax\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_mat.cols()); + MapVec y(y_out, m_mat.rows()); + y.noalias() = m_mat * x; + } +}; + + +} // namespace Spectra + +#endif // DENSE_GEN_MAT_PROD_H diff --git a/src/external/Spectra/include/Spectra/MatOp/DenseGenRealShiftSolve.h b/src/external/Spectra/include/Spectra/MatOp/DenseGenRealShiftSolve.h new file mode 100644 index 00000000..d7ba8f2a --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/DenseGenRealShiftSolve.h @@ -0,0 +1,90 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef DENSE_GEN_REAL_SHIFT_SOLVE_H +#define DENSE_GEN_REAL_SHIFT_SOLVE_H + +#include +#include +#include + +namespace Spectra { + + +/// +/// \ingroup MatOp +/// +/// This class defines the shift-solve operation on a general real matrix \f$A\f$, +/// i.e., calculating \f$y=(A-\sigma I)^{-1}x\f$ for any real \f$\sigma\f$ and +/// vector \f$x\f$. It is mainly used in the GenEigsRealShiftSolver eigen solver. +/// +template +class DenseGenRealShiftSolve +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef const Eigen::Ref ConstGenericMatrix; + + ConstGenericMatrix m_mat; + const Index m_n; + Eigen::PartialPivLU m_solver; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + DenseGenRealShiftSolve(ConstGenericMatrix& mat) : + m_mat(mat), m_n(mat.rows()) + { + if(mat.rows() != mat.cols()) + throw std::invalid_argument("DenseGenRealShiftSolve: matrix must be square"); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_n; } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_n; } + + /// + /// Set the real shift \f$\sigma\f$. + /// + void set_shift(Scalar sigma) + { + m_solver.compute(m_mat - sigma * Matrix::Identity(m_n, m_n)); + } + + /// + /// Perform the shift-solve operation \f$y=(A-\sigma I)^{-1}x\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(A - sigma * I) * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_solver.solve(x); + } +}; + + +} // namespace Spectra + +#endif // DENSE_GEN_REAL_SHIFT_SOLVE_H diff --git a/src/external/Spectra/include/Spectra/MatOp/DenseSymMatProd.h b/src/external/Spectra/include/Spectra/MatOp/DenseSymMatProd.h new file mode 100644 index 00000000..23ca36e4 --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/DenseSymMatProd.h @@ -0,0 +1,75 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef DENSE_SYM_MAT_PROD_H +#define DENSE_SYM_MAT_PROD_H + +#include + +namespace Spectra { + + +/// +/// \ingroup MatOp +/// +/// This class defines the matrix-vector multiplication operation on a +/// symmetric real matrix \f$A\f$, i.e., calculating \f$y=Ax\f$ for any vector +/// \f$x\f$. It is mainly used in the SymEigsSolver eigen solver. +/// +template +class DenseSymMatProd +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef const Eigen::Ref ConstGenericMatrix; + + ConstGenericMatrix m_mat; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + DenseSymMatProd(ConstGenericMatrix& mat) : + m_mat(mat) + {} + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_mat.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_mat.cols(); } + + /// + /// Perform the matrix-vector multiplication operation \f$y=Ax\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_mat.cols()); + MapVec y(y_out, m_mat.rows()); + y.noalias() = m_mat.template selfadjointView() * x; + } +}; + + +} // namespace Spectra + +#endif // DENSE_SYM_MAT_PROD_H diff --git a/src/external/Spectra/include/Spectra/MatOp/DenseSymShiftSolve.h b/src/external/Spectra/include/Spectra/MatOp/DenseSymShiftSolve.h new file mode 100644 index 00000000..2e2c67f6 --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/DenseSymShiftSolve.h @@ -0,0 +1,94 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef DENSE_SYM_SHIFT_SOLVE_H +#define DENSE_SYM_SHIFT_SOLVE_H + +#include +#include + +#include "../LinAlg/BKLDLT.h" +#include "../Util/CompInfo.h" + +namespace Spectra { + + +/// +/// \ingroup MatOp +/// +/// This class defines the shift-solve operation on a real symmetric matrix \f$A\f$, +/// i.e., calculating \f$y=(A-\sigma I)^{-1}x\f$ for any real \f$\sigma\f$ and +/// vector \f$x\f$. It is mainly used in the SymEigsShiftSolver eigen solver. +/// +template +class DenseSymShiftSolve +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef const Eigen::Ref ConstGenericMatrix; + + ConstGenericMatrix m_mat; + const int m_n; + BKLDLT m_solver; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** matrix object, whose type can be + /// `Eigen::Matrix` (e.g. `Eigen::MatrixXd` and + /// `Eigen::MatrixXf`), or its mapped version + /// (e.g. `Eigen::Map`). + /// + DenseSymShiftSolve(ConstGenericMatrix& mat) : + m_mat(mat), m_n(mat.rows()) + { + if(mat.rows() != mat.cols()) + throw std::invalid_argument("DenseSymShiftSolve: matrix must be square"); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_n; } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_n; } + + /// + /// Set the real shift \f$\sigma\f$. + /// + void set_shift(Scalar sigma) + { + m_solver.compute(m_mat, Uplo, sigma); + if(m_solver.info() != SUCCESSFUL) + throw std::invalid_argument("DenseSymShiftSolve: factorization failed with the given shift"); + } + + /// + /// Perform the shift-solve operation \f$y=(A-\sigma I)^{-1}x\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(A - sigma * I) * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_solver.solve(x); + } +}; + + +} // namespace Spectra + +#endif // DENSE_SYM_SHIFT_SOLVE_H diff --git a/src/external/Spectra/include/Spectra/MatOp/SparseCholesky.h b/src/external/Spectra/include/Spectra/MatOp/SparseCholesky.h new file mode 100644 index 00000000..0788596d --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/SparseCholesky.h @@ -0,0 +1,111 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SPARSE_CHOLESKY_H +#define SPARSE_CHOLESKY_H + +#include +#include +#include +#include +#include "../Util/CompInfo.h" + +namespace Spectra { + + +/// +/// \ingroup MatOp +/// +/// This class defines the operations related to Cholesky decomposition on a +/// sparse positive definite matrix, \f$B=LL'\f$, where \f$L\f$ is a lower triangular +/// matrix. It is mainly used in the SymGEigsSolver generalized eigen solver +/// in the Cholesky decomposition mode. +/// +template +class SparseCholesky +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef Eigen::SparseMatrix SparseMatrix; + typedef const Eigen::Ref ConstGenericSparseMatrix; + + const Index m_n; + Eigen::SimplicialLLT m_decomp; + int m_info; // status of the decomposition + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + SparseCholesky(ConstGenericSparseMatrix& mat) : + m_n(mat.rows()) + { + if(mat.rows() != mat.cols()) + throw std::invalid_argument("SparseCholesky: matrix must be square"); + + m_decomp.compute(mat); + m_info = (m_decomp.info() == Eigen::Success) ? + SUCCESSFUL : + NUMERICAL_ISSUE; + } + + /// + /// Returns the number of rows of the underlying matrix. + /// + Index rows() const { return m_n; } + /// + /// Returns the number of columns of the underlying matrix. + /// + Index cols() const { return m_n; } + + /// + /// Returns the status of the computation. + /// The full list of enumeration values can be found in \ref Enumerations. + /// + int info() const { return m_info; } + + /// + /// Performs the lower triangular solving operation \f$y=L^{-1}x\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(L) * x_in + void lower_triangular_solve(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_decomp.permutationP() * x; + m_decomp.matrixL().solveInPlace(y); + } + + /// + /// Performs the upper triangular solving operation \f$y=(L')^{-1}x\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(L') * x_in + void upper_triangular_solve(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_decomp.matrixU().solve(x); + y = m_decomp.permutationPinv() * y; + } +}; + + +} // namespace Spectra + +#endif // SPARSE_CHOLESKY_H diff --git a/src/external/Spectra/include/Spectra/MatOp/SparseGenMatProd.h b/src/external/Spectra/include/Spectra/MatOp/SparseGenMatProd.h new file mode 100644 index 00000000..90881395 --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/SparseGenMatProd.h @@ -0,0 +1,76 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SPARSE_GEN_MAT_PROD_H +#define SPARSE_GEN_MAT_PROD_H + +#include +#include + +namespace Spectra { + + +/// +/// \ingroup MatOp +/// +/// This class defines the matrix-vector multiplication operation on a +/// sparse real matrix \f$A\f$, i.e., calculating \f$y=Ax\f$ for any vector +/// \f$x\f$. It is mainly used in the GenEigsSolver and SymEigsSolver +/// eigen solvers. +/// +template +class SparseGenMatProd +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef Eigen::SparseMatrix SparseMatrix; + typedef const Eigen::Ref ConstGenericSparseMatrix; + + ConstGenericSparseMatrix m_mat; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + SparseGenMatProd(ConstGenericSparseMatrix& mat) : + m_mat(mat) + {} + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_mat.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_mat.cols(); } + + /// + /// Perform the matrix-vector multiplication operation \f$y=Ax\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_mat.cols()); + MapVec y(y_out, m_mat.rows()); + y.noalias() = m_mat * x; + } +}; + + +} // namespace Spectra + +#endif // SPARSE_GEN_MAT_PROD_H diff --git a/src/external/Spectra/include/Spectra/MatOp/SparseGenRealShiftSolve.h b/src/external/Spectra/include/Spectra/MatOp/SparseGenRealShiftSolve.h new file mode 100644 index 00000000..df4ec6cf --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/SparseGenRealShiftSolve.h @@ -0,0 +1,95 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SPARSE_GEN_REAL_SHIFT_SOLVE_H +#define SPARSE_GEN_REAL_SHIFT_SOLVE_H + +#include +#include +#include +#include + +namespace Spectra { + + +/// +/// \ingroup MatOp +/// +/// This class defines the shift-solve operation on a sparse real matrix \f$A\f$, +/// i.e., calculating \f$y=(A-\sigma I)^{-1}x\f$ for any real \f$\sigma\f$ and +/// vector \f$x\f$. It is mainly used in the GenEigsRealShiftSolver eigen solver. +/// +template +class SparseGenRealShiftSolve +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef Eigen::SparseMatrix SparseMatrix; + typedef const Eigen::Ref ConstGenericSparseMatrix; + + ConstGenericSparseMatrix m_mat; + const int m_n; + Eigen::SparseLU m_solver; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + SparseGenRealShiftSolve(ConstGenericSparseMatrix& mat) : + m_mat(mat), m_n(mat.rows()) + { + if(mat.rows() != mat.cols()) + throw std::invalid_argument("SparseGenRealShiftSolve: matrix must be square"); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_n; } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_n; } + + /// + /// Set the real shift \f$\sigma\f$. + /// + void set_shift(Scalar sigma) + { + SparseMatrix I(m_n, m_n); + I.setIdentity(); + + m_solver.compute(m_mat - sigma * I); + if(m_solver.info() != Eigen::Success) + throw std::invalid_argument("SparseGenRealShiftSolve: factorization failed with the given shift"); + } + + /// + /// Perform the shift-solve operation \f$y=(A-\sigma I)^{-1}x\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(A - sigma * I) * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_solver.solve(x); + } +}; + + +} // namespace Spectra + +#endif // SPARSE_GEN_REAL_SHIFT_SOLVE_H diff --git a/src/external/Spectra/include/Spectra/MatOp/SparseRegularInverse.h b/src/external/Spectra/include/Spectra/MatOp/SparseRegularInverse.h new file mode 100644 index 00000000..ec6614a5 --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/SparseRegularInverse.h @@ -0,0 +1,102 @@ +// Copyright (C) 2017-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SPARSE_REGULAR_INVERSE_H +#define SPARSE_REGULAR_INVERSE_H + +#include +#include +#include +#include + +namespace Spectra { + + +/// +/// \ingroup MatOp +/// +/// This class defines matrix operations required by the generalized eigen solver +/// in the regular inverse mode. For a sparse and positive definite matrix \f$B\f$, +/// it implements the matrix-vector product \f$y=Bx\f$ and the linear equation +/// solving operation \f$y=B^{-1}x\f$. +/// +/// This class is intended to be used with the SymGEigsSolver generalized eigen solver +/// in the regular inverse mode. +/// +template +class SparseRegularInverse +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef Eigen::SparseMatrix SparseMatrix; + typedef const Eigen::Ref ConstGenericSparseMatrix; + + ConstGenericSparseMatrix m_mat; + const int m_n; + Eigen::ConjugateGradient m_cg; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + SparseRegularInverse(ConstGenericSparseMatrix& mat) : + m_mat(mat), m_n(mat.rows()) + { + if(mat.rows() != mat.cols()) + throw std::invalid_argument("SparseRegularInverse: matrix must be square"); + + m_cg.compute(mat); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_n; } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_n; } + + /// + /// Perform the solving operation \f$y=B^{-1}x\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(B) * x_in + void solve(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_cg.solve(x); + } + + /// + /// Perform the matrix-vector multiplication operation \f$y=Bx\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = B * x_in + void mat_prod(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_mat.template selfadjointView() * x; + } +}; + + +} // namespace Spectra + +#endif // SPARSE_REGULAR_INVERSE_H diff --git a/src/external/Spectra/include/Spectra/MatOp/SparseSymMatProd.h b/src/external/Spectra/include/Spectra/MatOp/SparseSymMatProd.h new file mode 100644 index 00000000..ef8f96ee --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/SparseSymMatProd.h @@ -0,0 +1,75 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SPARSE_SYM_MAT_PROD_H +#define SPARSE_SYM_MAT_PROD_H + +#include +#include + +namespace Spectra { + + +/// +/// \ingroup MatOp +/// +/// This class defines the matrix-vector multiplication operation on a +/// sparse real symmetric matrix \f$A\f$, i.e., calculating \f$y=Ax\f$ for any vector +/// \f$x\f$. It is mainly used in the SymEigsSolver eigen solver. +/// +template +class SparseSymMatProd +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef Eigen::SparseMatrix SparseMatrix; + typedef const Eigen::Ref ConstGenericSparseMatrix; + + ConstGenericSparseMatrix m_mat; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + SparseSymMatProd(ConstGenericSparseMatrix& mat) : + m_mat(mat) + {} + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_mat.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_mat.cols(); } + + /// + /// Perform the matrix-vector multiplication operation \f$y=Ax\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_mat.cols()); + MapVec y(y_out, m_mat.rows()); + y.noalias() = m_mat.template selfadjointView() * x; + } +}; + + +} // namespace Spectra + +#endif // SPARSE_SYM_MAT_PROD_H diff --git a/src/external/Spectra/include/Spectra/MatOp/SparseSymShiftSolve.h b/src/external/Spectra/include/Spectra/MatOp/SparseSymShiftSolve.h new file mode 100644 index 00000000..1821cc32 --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/SparseSymShiftSolve.h @@ -0,0 +1,97 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SPARSE_SYM_SHIFT_SOLVE_H +#define SPARSE_SYM_SHIFT_SOLVE_H + +#include +#include +#include +#include + +namespace Spectra { + + +/// +/// \ingroup MatOp +/// +/// This class defines the shift-solve operation on a sparse real symmetric matrix \f$A\f$, +/// i.e., calculating \f$y=(A-\sigma I)^{-1}x\f$ for any real \f$\sigma\f$ and +/// vector \f$x\f$. It is mainly used in the SymEigsShiftSolver eigen solver. +/// +template +class SparseSymShiftSolve +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef Eigen::SparseMatrix SparseMatrix; + typedef const Eigen::Ref ConstGenericSparseMatrix; + + ConstGenericSparseMatrix m_mat; + const int m_n; + Eigen::SparseLU m_solver; + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param mat An **Eigen** sparse matrix object, whose type can be + /// `Eigen::SparseMatrix` or its mapped version + /// `Eigen::Map >`. + /// + SparseSymShiftSolve(ConstGenericSparseMatrix& mat) : + m_mat(mat), m_n(mat.rows()) + { + if(mat.rows() != mat.cols()) + throw std::invalid_argument("SparseSymShiftSolve: matrix must be square"); + } + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_n; } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_n; } + + /// + /// Set the real shift \f$\sigma\f$. + /// + void set_shift(Scalar sigma) + { + SparseMatrix mat = m_mat.template selfadjointView(); + SparseMatrix identity(m_n, m_n); + identity.setIdentity(); + mat = mat - sigma * identity; + m_solver.isSymmetric(true); + m_solver.compute(mat); + if(m_solver.info() != Eigen::Success) + throw std::invalid_argument("SparseSymShiftSolve: factorization failed with the given shift"); + } + + /// + /// Perform the shift-solve operation \f$y=(A-\sigma I)^{-1}x\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(A - sigma * I) * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) const + { + MapConstVec x(x_in, m_n); + MapVec y(y_out, m_n); + y.noalias() = m_solver.solve(x); + } +}; + + +} // namespace Spectra + +#endif // SPARSE_SYM_SHIFT_SOLVE_H diff --git a/src/external/Spectra/include/Spectra/MatOp/internal/ArnoldiOp.h b/src/external/Spectra/include/Spectra/MatOp/internal/ArnoldiOp.h new file mode 100644 index 00000000..b79704b5 --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/internal/ArnoldiOp.h @@ -0,0 +1,155 @@ +// Copyright (C) 2018-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef ARNOLDI_OP_H +#define ARNOLDI_OP_H + +#include +#include // std::sqrt + +namespace Spectra { + + +/// +/// \ingroup Internals +/// @{ +/// + +/// +/// \defgroup Operators Operators +/// +/// Different types of operators. +/// + +/// +/// \ingroup Operators +/// +/// Operators used in the Arnoldi factorization. +/// +template +class ArnoldiOp +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Vector; + + OpType& m_op; + BOpType& m_Bop; + Vector m_cache; + +public: + ArnoldiOp(OpType* op, BOpType* Bop) : + m_op(*op), m_Bop(*Bop), m_cache(op->rows()) + {} + + inline Index rows() const { return m_op.rows(); } + + // In generalized eigenvalue problem Ax=lambda*Bx, define the inner product to be = x'By. + // For regular eigenvalue problems, it is the usual inner product = x'y + + // Compute = x'By + // x and y are two vectors + template + Scalar inner_product(const Arg1& x, const Arg2& y) + { + m_Bop.mat_prod(y.data(), m_cache.data()); + return x.dot(m_cache); + } + + // Compute res = = X'By + // X is a matrix, y is a vector, res is a vector + template + void trans_product(const Arg1& x, const Arg2& y, Eigen::Ref res) + { + m_Bop.mat_prod(y.data(), m_cache.data()); + res.noalias() = x.transpose() * m_cache; + } + + // B-norm of a vector, ||x||_B = sqrt(x'Bx) + template + Scalar norm(const Arg& x) + { + using std::sqrt; + return sqrt(inner_product(x, x)); + } + + // The "A" operator to generate the Krylov subspace + inline void perform_op(const Scalar* x_in, Scalar* y_out) + { + m_op.perform_op(x_in, y_out); + } +}; + + + +/// +/// \ingroup Operators +/// +/// Placeholder for the B-operator when \f$B = I\f$. +/// +class IdentityBOp {}; + + + +/// +/// \ingroup Operators +/// +/// Partial specialization for the case \f$B = I\f$. +/// +template +class ArnoldiOp +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Vector; + + OpType& m_op; + +public: + ArnoldiOp(OpType* op, IdentityBOp* /*Bop*/) : + m_op(*op) + {} + + inline Index rows() const { return m_op.rows(); } + + // Compute = x'y + // x and y are two vectors + template + Scalar inner_product(const Arg1& x, const Arg2& y) const + { + return x.dot(y); + } + + // Compute res = = X'y + // X is a matrix, y is a vector, res is a vector + template + void trans_product(const Arg1& x, const Arg2& y, Eigen::Ref res) const + { + res.noalias() = x.transpose() * y; + } + + // B-norm of a vector. For regular eigenvalue problems it is simply the L2 norm + template + Scalar norm(const Arg& x) + { + return x.norm(); + } + + // The "A" operator to generate the Krylov subspace + inline void perform_op(Scalar* x_in, Scalar* y_out) + { + m_op.perform_op(x_in, y_out); + } +}; + +/// +/// @} +/// + + +} // namespace Spectra + +#endif // ARNOLDI_OP_H diff --git a/src/external/Spectra/include/Spectra/MatOp/internal/SymGEigsCholeskyOp.h b/src/external/Spectra/include/Spectra/MatOp/internal/SymGEigsCholeskyOp.h new file mode 100644 index 00000000..d7acdb2a --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/internal/SymGEigsCholeskyOp.h @@ -0,0 +1,77 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SYM_GEIGS_CHOLESKY_OP_H +#define SYM_GEIGS_CHOLESKY_OP_H + +#include +#include "../DenseSymMatProd.h" +#include "../DenseCholesky.h" + +namespace Spectra { + + +/// +/// \ingroup Operators +/// +/// This class defines the matrix operation for generalized eigen solver in the +/// Cholesky decomposition mode. It calculates \f$y=L^{-1}A(L')^{-1}x\f$ for any +/// vector \f$x\f$, where \f$L\f$ is the Cholesky decomposition of \f$B\f$. +/// This class is intended for internal use. +/// +template < typename Scalar = double, + typename OpType = DenseSymMatProd, + typename BOpType = DenseCholesky > +class SymGEigsCholeskyOp +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + + OpType& m_op; + BOpType& m_Bop; + Vector m_cache; // temporary working space + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param op Pointer to the \f$A\f$ matrix operation object. + /// \param Bop Pointer to the \f$B\f$ matrix operation object. + /// + SymGEigsCholeskyOp(OpType& op, BOpType& Bop) : + m_op(op), m_Bop(Bop), m_cache(op.rows()) + {} + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_Bop.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_Bop.rows(); } + + /// + /// Perform the matrix operation \f$y=L^{-1}A(L')^{-1}x\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(L) * A * inv(L') * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) + { + m_Bop.upper_triangular_solve(x_in, y_out); + m_op.perform_op(y_out, m_cache.data()); + m_Bop.lower_triangular_solve(m_cache.data(), y_out); + } +}; + + +} // namespace Spectra + +#endif // SYM_GEIGS_CHOLESKY_OP_H diff --git a/src/external/Spectra/include/Spectra/MatOp/internal/SymGEigsRegInvOp.h b/src/external/Spectra/include/Spectra/MatOp/internal/SymGEigsRegInvOp.h new file mode 100644 index 00000000..ac00dcb2 --- /dev/null +++ b/src/external/Spectra/include/Spectra/MatOp/internal/SymGEigsRegInvOp.h @@ -0,0 +1,74 @@ +// Copyright (C) 2017-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SYM_GEIGS_REG_INV_OP_H +#define SYM_GEIGS_REG_INV_OP_H + +#include +#include "../SparseSymMatProd.h" +#include "../SparseRegularInverse.h" + +namespace Spectra { + + +/// +/// \ingroup Operators +/// +/// This class defines the matrix operation for generalized eigen solver in the +/// regular inverse mode. This class is intended for internal use. +/// +template < typename Scalar = double, + typename OpType = SparseSymMatProd, + typename BOpType = SparseRegularInverse > +class SymGEigsRegInvOp +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + + OpType& m_op; + BOpType& m_Bop; + Vector m_cache; // temporary working space + +public: + /// + /// Constructor to create the matrix operation object. + /// + /// \param op Pointer to the \f$A\f$ matrix operation object. + /// \param Bop Pointer to the \f$B\f$ matrix operation object. + /// + SymGEigsRegInvOp(OpType& op, BOpType& Bop) : + m_op(op), m_Bop(Bop), m_cache(op.rows()) + {} + + /// + /// Return the number of rows of the underlying matrix. + /// + Index rows() const { return m_Bop.rows(); } + /// + /// Return the number of columns of the underlying matrix. + /// + Index cols() const { return m_Bop.rows(); } + + /// + /// Perform the matrix operation \f$y=B^{-1}Ax\f$. + /// + /// \param x_in Pointer to the \f$x\f$ vector. + /// \param y_out Pointer to the \f$y\f$ vector. + /// + // y_out = inv(B) * A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) + { + m_op.perform_op(x_in, m_cache.data()); + m_Bop.solve(m_cache.data(), y_out); + } +}; + + +} // namespace Spectra + +#endif // SYM_GEIGS_REG_INV_OP_H diff --git a/src/external/Spectra/include/Spectra/SymEigsBase.h b/src/external/Spectra/include/Spectra/SymEigsBase.h new file mode 100644 index 00000000..24d5b505 --- /dev/null +++ b/src/external/Spectra/include/Spectra/SymEigsBase.h @@ -0,0 +1,447 @@ +// Copyright (C) 2018-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SYM_EIGS_BASE_H +#define SYM_EIGS_BASE_H + +#include +#include // std::vector +#include // std::abs, std::pow, std::sqrt +#include // std::min, std::copy +#include // std::invalid_argument + +#include "Util/TypeTraits.h" +#include "Util/SelectionRule.h" +#include "Util/CompInfo.h" +#include "Util/SimpleRandom.h" +#include "MatOp/internal/ArnoldiOp.h" +#include "LinAlg/UpperHessenbergQR.h" +#include "LinAlg/TridiagEigen.h" +#include "LinAlg/Lanczos.h" + +namespace Spectra { + + +/// +/// \defgroup EigenSolver Eigen Solvers +/// +/// Eigen solvers for different types of problems. +/// + +/// +/// \ingroup EigenSolver +/// +/// This is the base class for symmetric eigen solvers, mainly for internal use. +/// It is kept here to provide the documentation for member functions of concrete eigen solvers +/// such as SymEigsSolver and SymEigsShiftSolver. +/// +template < typename Scalar, + int SelectionRule, + typename OpType, + typename BOpType > +class SymEigsBase +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef Eigen::Array Array; + typedef Eigen::Array BoolArray; + typedef Eigen::Map MapMat; + typedef Eigen::Map MapVec; + typedef Eigen::Map MapConstVec; + + typedef ArnoldiOp ArnoldiOpType; + typedef Lanczos LanczosFac; + +protected: + OpType* m_op; // object to conduct matrix operation, + // e.g. matrix-vector product + const Index m_n; // dimension of matrix A + const Index m_nev; // number of eigenvalues requested + const Index m_ncv; // dimension of Krylov subspace in the Lanczos method + Index m_nmatop; // number of matrix operations called + Index m_niter; // number of restarting iterations + + LanczosFac m_fac; // Lanczos factorization + Vector m_ritz_val; // Ritz values + +private: + Matrix m_ritz_vec; // Ritz vectors + Vector m_ritz_est; // last row of m_ritz_vec, also called the Ritz estimates + BoolArray m_ritz_conv; // indicator of the convergence of Ritz values + int m_info; // status of the computation + + const Scalar m_near_0; // a very small value, but 1.0 / m_near_0 does not overflow + // ~= 1e-307 for the "double" type + const Scalar m_eps; // the machine precision, ~= 1e-16 for the "double" type + const Scalar m_eps23; // m_eps^(2/3), used to test the convergence + + // Implicitly restarted Lanczos factorization + void restart(Index k) + { + if(k >= m_ncv) + return; + + TridiagQR decomp(m_ncv); + Matrix Q = Matrix::Identity(m_ncv, m_ncv); + + for(Index i = k; i < m_ncv; i++) + { + // QR decomposition of H-mu*I, mu is the shift + decomp.compute(m_fac.matrix_H(), m_ritz_val[i]); + + // Q -> Q * Qi + decomp.apply_YQ(Q); + // H -> Q'HQ + // Since QR = H - mu * I, we have H = QR + mu * I + // and therefore Q'HQ = RQ + mu * I + m_fac.compress_H(decomp); + } + + m_fac.compress_V(Q); + m_fac.factorize_from(k, m_ncv, m_nmatop); + + retrieve_ritzpair(); + } + + // Calculates the number of converged Ritz values + Index num_converged(Scalar tol) + { + // thresh = tol * max(m_eps23, abs(theta)), theta for Ritz value + Array thresh = tol * m_ritz_val.head(m_nev).array().abs().max(m_eps23); + Array resid = m_ritz_est.head(m_nev).array().abs() * m_fac.f_norm(); + // Converged "wanted" Ritz values + m_ritz_conv = (resid < thresh); + + return m_ritz_conv.cast().sum(); + } + + // Returns the adjusted nev for restarting + Index nev_adjusted(Index nconv) + { + using std::abs; + + Index nev_new = m_nev; + for(Index i = m_nev; i < m_ncv; i++) + if(abs(m_ritz_est[i]) < m_near_0) nev_new++; + + // Adjust nev_new, according to dsaup2.f line 677~684 in ARPACK + nev_new += std::min(nconv, (m_ncv - nev_new) / 2); + if(nev_new == 1 && m_ncv >= 6) + nev_new = m_ncv / 2; + else if(nev_new == 1 && m_ncv > 2) + nev_new = 2; + + if(nev_new > m_ncv - 1) + nev_new = m_ncv - 1; + + return nev_new; + } + + // Retrieves and sorts Ritz values and Ritz vectors + void retrieve_ritzpair() + { + TridiagEigen decomp(m_fac.matrix_H()); + const Vector& evals = decomp.eigenvalues(); + const Matrix& evecs = decomp.eigenvectors(); + + SortEigenvalue sorting(evals.data(), evals.size()); + std::vector ind = sorting.index(); + + // For BOTH_ENDS, the eigenvalues are sorted according + // to the LARGEST_ALGE rule, so we need to move those smallest + // values to the left + // The order would be + // Largest => Smallest => 2nd largest => 2nd smallest => ... + // We keep this order since the first k values will always be + // the wanted collection, no matter k is nev_updated (used in restart()) + // or is nev (used in sort_ritzpair()) + if(SelectionRule == BOTH_ENDS) + { + std::vector ind_copy(ind); + for(Index i = 0; i < m_ncv; i++) + { + // If i is even, pick values from the left (large values) + // If i is odd, pick values from the right (small values) + if(i % 2 == 0) + ind[i] = ind_copy[i / 2]; + else + ind[i] = ind_copy[m_ncv - 1 - i / 2]; + } + } + + // Copy the Ritz values and vectors to m_ritz_val and m_ritz_vec, respectively + for(Index i = 0; i < m_ncv; i++) + { + m_ritz_val[i] = evals[ind[i]]; + m_ritz_est[i] = evecs(m_ncv - 1, ind[i]); + } + for(Index i = 0; i < m_nev; i++) + { + m_ritz_vec.col(i).noalias() = evecs.col(ind[i]); + } + } + +protected: + // Sorts the first nev Ritz pairs in the specified order + // This is used to return the final results + virtual void sort_ritzpair(int sort_rule) + { + // First make sure that we have a valid index vector + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + std::vector ind = sorting.index(); + + switch(sort_rule) + { + case LARGEST_ALGE: + break; + case LARGEST_MAGN: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + ind = sorting.index(); + } + break; + case SMALLEST_ALGE: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + ind = sorting.index(); + } + break; + case SMALLEST_MAGN: + { + SortEigenvalue sorting(m_ritz_val.data(), m_nev); + ind = sorting.index(); + } + break; + default: + throw std::invalid_argument("unsupported sorting rule"); + } + + Vector new_ritz_val(m_ncv); + Matrix new_ritz_vec(m_ncv, m_nev); + BoolArray new_ritz_conv(m_nev); + + for(Index i = 0; i < m_nev; i++) + { + new_ritz_val[i] = m_ritz_val[ind[i]]; + new_ritz_vec.col(i).noalias() = m_ritz_vec.col(ind[i]); + new_ritz_conv[i] = m_ritz_conv[ind[i]]; + } + + m_ritz_val.swap(new_ritz_val); + m_ritz_vec.swap(new_ritz_vec); + m_ritz_conv.swap(new_ritz_conv); + } + +public: + /// \cond + + SymEigsBase(OpType* op, BOpType* Bop, Index nev, Index ncv) : + m_op(op), + m_n(m_op->rows()), + m_nev(nev), + m_ncv(ncv > m_n ? m_n : ncv), + m_nmatop(0), + m_niter(0), + m_fac(ArnoldiOpType(op, Bop), m_ncv), + m_info(NOT_COMPUTED), + m_near_0(TypeTraits::min() * Scalar(10)), + m_eps(Eigen::NumTraits::epsilon()), + m_eps23(Eigen::numext::pow(m_eps, Scalar(2.0) / 3)) + { + if(nev < 1 || nev > m_n - 1) + throw std::invalid_argument("nev must satisfy 1 <= nev <= n - 1, n is the size of matrix"); + + if(ncv <= nev || ncv > m_n) + throw std::invalid_argument("ncv must satisfy nev < ncv <= n, n is the size of matrix"); + } + + /// + /// Virtual destructor + /// + virtual ~SymEigsBase() {} + + /// \endcond + + /// + /// Initializes the solver by providing an initial residual vector. + /// + /// \param init_resid Pointer to the initial residual vector. + /// + /// **Spectra** (and also **ARPACK**) uses an iterative algorithm + /// to find eigenvalues. This function allows the user to provide the initial + /// residual vector. + /// + void init(const Scalar* init_resid) + { + // Reset all matrices/vectors to zero + m_ritz_val.resize(m_ncv); + m_ritz_vec.resize(m_ncv, m_nev); + m_ritz_est.resize(m_ncv); + m_ritz_conv.resize(m_nev); + + m_ritz_val.setZero(); + m_ritz_vec.setZero(); + m_ritz_est.setZero(); + m_ritz_conv.setZero(); + + m_nmatop = 0; + m_niter = 0; + + // Initialize the Lanczos factorization + MapConstVec v0(init_resid, m_n); + m_fac.init(v0, m_nmatop); + } + + /// + /// Initializes the solver by providing a random initial residual vector. + /// + /// This overloaded function generates a random initial residual vector + /// (with a fixed random seed) for the algorithm. Elements in the vector + /// follow independent Uniform(-0.5, 0.5) distribution. + /// + void init() + { + SimpleRandom rng(0); + Vector init_resid = rng.random_vec(m_n); + init(init_resid.data()); + } + + /// + /// Conducts the major computation procedure. + /// + /// \param maxit Maximum number of iterations allowed in the algorithm. + /// \param tol Precision parameter for the calculated eigenvalues. + /// \param sort_rule Rule to sort the eigenvalues and eigenvectors. + /// Supported values are + /// `Spectra::LARGEST_ALGE`, `Spectra::LARGEST_MAGN`, + /// `Spectra::SMALLEST_ALGE` and `Spectra::SMALLEST_MAGN`, + /// for example `LARGEST_ALGE` indicates that largest eigenvalues + /// come first. Note that this argument is only used to + /// **sort** the final result, and the **selection** rule + /// (e.g. selecting the largest or smallest eigenvalues in the + /// full spectrum) is specified by the template parameter + /// `SelectionRule` of SymEigsSolver. + /// + /// \return Number of converged eigenvalues. + /// + Index compute(Index maxit = 1000, Scalar tol = 1e-10, int sort_rule = LARGEST_ALGE) + { + // The m-step Lanczos factorization + m_fac.factorize_from(1, m_ncv, m_nmatop); + retrieve_ritzpair(); + // Restarting + Index i, nconv = 0, nev_adj; + for(i = 0; i < maxit; i++) + { + nconv = num_converged(tol); + if(nconv >= m_nev) + break; + + nev_adj = nev_adjusted(nconv); + restart(nev_adj); + } + // Sorting results + sort_ritzpair(sort_rule); + + m_niter += i + 1; + m_info = (nconv >= m_nev) ? SUCCESSFUL : NOT_CONVERGING; + + return std::min(m_nev, nconv); + } + + /// + /// Returns the status of the computation. + /// The full list of enumeration values can be found in \ref Enumerations. + /// + int info() const { return m_info; } + + /// + /// Returns the number of iterations used in the computation. + /// + Index num_iterations() const { return m_niter; } + + /// + /// Returns the number of matrix operations used in the computation. + /// + Index num_operations() const { return m_nmatop; } + + /// + /// Returns the converged eigenvalues. + /// + /// \return A vector containing the eigenvalues. + /// Returned vector type will be `Eigen::Vector`, depending on + /// the template parameter `Scalar` defined. + /// + Vector eigenvalues() const + { + const Index nconv = m_ritz_conv.cast().sum(); + Vector res(nconv); + + if(!nconv) + return res; + + Index j = 0; + for(Index i = 0; i < m_nev; i++) + { + if(m_ritz_conv[i]) + { + res[j] = m_ritz_val[i]; + j++; + } + } + + return res; + } + + /// + /// Returns the eigenvectors associated with the converged eigenvalues. + /// + /// \param nvec The number of eigenvectors to return. + /// + /// \return A matrix containing the eigenvectors. + /// Returned matrix type will be `Eigen::Matrix`, + /// depending on the template parameter `Scalar` defined. + /// + virtual Matrix eigenvectors(Index nvec) const + { + const Index nconv = m_ritz_conv.cast().sum(); + nvec = std::min(nvec, nconv); + Matrix res(m_n, nvec); + + if(!nvec) + return res; + + Matrix ritz_vec_conv(m_ncv, nvec); + Index j = 0; + for(Index i = 0; i < m_nev && j < nvec; i++) + { + if(m_ritz_conv[i]) + { + ritz_vec_conv.col(j).noalias() = m_ritz_vec.col(i); + j++; + } + } + + res.noalias() = m_fac.matrix_V() * ritz_vec_conv; + + return res; + } + + /// + /// Returns all converged eigenvectors. + /// + virtual Matrix eigenvectors() const + { + return eigenvectors(m_nev); + } +}; + + +} // namespace Spectra + +#endif // SYM_EIGS_BASE_H diff --git a/src/external/Spectra/include/Spectra/SymEigsShiftSolver.h b/src/external/Spectra/include/Spectra/SymEigsShiftSolver.h new file mode 100644 index 00000000..56bd44ec --- /dev/null +++ b/src/external/Spectra/include/Spectra/SymEigsShiftSolver.h @@ -0,0 +1,205 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SYM_EIGS_SHIFT_SOLVER_H +#define SYM_EIGS_SHIFT_SOLVER_H + +#include + +#include "SymEigsBase.h" +#include "Util/SelectionRule.h" +#include "MatOp/DenseSymShiftSolve.h" + +namespace Spectra { + + +/// +/// \ingroup EigenSolver +/// +/// This class implements the eigen solver for real symmetric matrices using +/// the **shift-and-invert mode**. The background information of the symmetric +/// eigen solver is documented in the SymEigsSolver class. Here we focus on +/// explaining the shift-and-invert mode. +/// +/// The shift-and-invert mode is based on the following fact: +/// If \f$\lambda\f$ and \f$x\f$ are a pair of eigenvalue and eigenvector of +/// matrix \f$A\f$, such that \f$Ax=\lambda x\f$, then for any \f$\sigma\f$, +/// we have +/// \f[(A-\sigma I)^{-1}x=\nu x\f] +/// where +/// \f[\nu=\frac{1}{\lambda-\sigma}\f] +/// which indicates that \f$(\nu, x)\f$ is an eigenpair of the matrix +/// \f$(A-\sigma I)^{-1}\f$. +/// +/// Therefore, if we pass the matrix operation \f$(A-\sigma I)^{-1}y\f$ +/// (rather than \f$Ay\f$) to the eigen solver, then we would get the desired +/// values of \f$\nu\f$, and \f$\lambda\f$ can also be easily obtained by noting +/// that \f$\lambda=\sigma+\nu^{-1}\f$. +/// +/// The reason why we need this type of manipulation is that +/// the algorithm of **Spectra** (and also **ARPACK**) +/// is good at finding eigenvalues with large magnitude, but may fail in looking +/// for eigenvalues that are close to zero. However, if we really need them, we +/// can set \f$\sigma=0\f$, find the largest eigenvalues of \f$A^{-1}\f$, and then +/// transform back to \f$\lambda\f$, since in this case largest values of \f$\nu\f$ +/// implies smallest values of \f$\lambda\f$. +/// +/// To summarize, in the shift-and-invert mode, the selection rule will apply to +/// \f$\nu=1/(\lambda-\sigma)\f$ rather than \f$\lambda\f$. So a selection rule +/// of `LARGEST_MAGN` combined with shift \f$\sigma\f$ will find eigenvalues of +/// \f$A\f$ that are closest to \f$\sigma\f$. But note that the eigenvalues() +/// method will always return the eigenvalues in the original problem (i.e., +/// returning \f$\lambda\f$ rather than \f$\nu\f$), and eigenvectors are the +/// same for both the original problem and the shifted-and-inverted problem. +/// +/// \tparam Scalar The element type of the matrix. +/// Currently supported types are `float`, `double` and `long double`. +/// \tparam SelectionRule An enumeration value indicating the selection rule of +/// the shifted-and-inverted eigenvalues. +/// The full list of enumeration values can be found in +/// \ref Enumerations. +/// \tparam OpType The name of the matrix operation class. Users could either +/// use the wrapper classes such as DenseSymShiftSolve and +/// SparseSymShiftSolve, or define their +/// own that implements all the public member functions as in +/// DenseSymShiftSolve. +/// +/// Below is an example that illustrates the use of the shift-and-invert mode: +/// +/// \code{.cpp} +/// #include +/// #include +/// // is implicitly included +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // A size-10 diagonal matrix with elements 1, 2, ..., 10 +/// Eigen::MatrixXd M = Eigen::MatrixXd::Zero(10, 10); +/// for(int i = 0; i < M.rows(); i++) +/// M(i, i) = i + 1; +/// +/// // Construct matrix operation object using the wrapper class +/// DenseSymShiftSolve op(M); +/// +/// // Construct eigen solver object with shift 0 +/// // This will find eigenvalues that are closest to 0 +/// SymEigsShiftSolver< double, LARGEST_MAGN, +/// DenseSymShiftSolve > eigs(&op, 3, 6, 0.0); +/// +/// eigs.init(); +/// eigs.compute(); +/// if(eigs.info() == SUCCESSFUL) +/// { +/// Eigen::VectorXd evalues = eigs.eigenvalues(); +/// // Will get (3.0, 2.0, 1.0) +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// } +/// +/// return 0; +/// } +/// \endcode +/// +/// Also an example for user-supplied matrix shift-solve operation class: +/// +/// \code{.cpp} +/// #include +/// #include +/// #include +/// +/// using namespace Spectra; +/// +/// // M = diag(1, 2, ..., 10) +/// class MyDiagonalTenShiftSolve +/// { +/// private: +/// double sigma_; +/// public: +/// int rows() { return 10; } +/// int cols() { return 10; } +/// void set_shift(double sigma) { sigma_ = sigma; } +/// // y_out = inv(A - sigma * I) * x_in +/// // inv(A - sigma * I) = diag(1/(1-sigma), 1/(2-sigma), ...) +/// void perform_op(double *x_in, double *y_out) +/// { +/// for(int i = 0; i < rows(); i++) +/// { +/// y_out[i] = x_in[i] / (i + 1 - sigma_); +/// } +/// } +/// }; +/// +/// int main() +/// { +/// MyDiagonalTenShiftSolve op; +/// // Find three eigenvalues that are closest to 3.14 +/// SymEigsShiftSolver eigs(&op, 3, 6, 3.14); +/// eigs.init(); +/// eigs.compute(); +/// if(eigs.info() == SUCCESSFUL) +/// { +/// Eigen::VectorXd evalues = eigs.eigenvalues(); +/// // Will get (4.0, 3.0, 2.0) +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// } +/// +/// return 0; +/// } +/// \endcode +/// +template > +class SymEigsShiftSolver: public SymEigsBase +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Array Array; + + const Scalar m_sigma; + + // First transform back the Ritz values, and then sort + void sort_ritzpair(int sort_rule) + { + Array m_ritz_val_org = Scalar(1.0) / this->m_ritz_val.head(this->m_nev).array() + m_sigma; + this->m_ritz_val.head(this->m_nev) = m_ritz_val_org; + SymEigsBase::sort_ritzpair(sort_rule); + } + +public: + /// + /// Constructor to create a eigen solver object using the shift-and-invert mode. + /// + /// \param op Pointer to the matrix operation object, which should implement + /// the shift-solve operation of \f$A\f$: calculating + /// \f$(A-\sigma I)^{-1}v\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper class such as DenseSymShiftSolve, or + /// define their own that implements all the public member functions + /// as in DenseSymShiftSolve. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-1\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv_` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev < ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev\f$. + /// \param sigma The value of the shift. + /// + SymEigsShiftSolver(OpType* op, Index nev, Index ncv, Scalar sigma) : + SymEigsBase(op, NULL, nev, ncv), + m_sigma(sigma) + { + this->m_op->set_shift(m_sigma); + } +}; + + +} // namespace Spectra + +#endif // SYM_EIGS_SHIFT_SOLVER_H diff --git a/src/external/Spectra/include/Spectra/SymEigsSolver.h b/src/external/Spectra/include/Spectra/SymEigsSolver.h new file mode 100644 index 00000000..8ba3e509 --- /dev/null +++ b/src/external/Spectra/include/Spectra/SymEigsSolver.h @@ -0,0 +1,174 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SYM_EIGS_SOLVER_H +#define SYM_EIGS_SOLVER_H + +#include + +#include "SymEigsBase.h" +#include "Util/SelectionRule.h" +#include "MatOp/DenseSymMatProd.h" + +namespace Spectra { + + +/// +/// \ingroup EigenSolver +/// +/// This class implements the eigen solver for real symmetric matrices, i.e., +/// to solve \f$Ax=\lambda x\f$ where \f$A\f$ is symmetric. +/// +/// **Spectra** is designed to calculate a specified number (\f$k\f$) +/// of eigenvalues of a large square matrix (\f$A\f$). Usually \f$k\f$ is much +/// less than the size of the matrix (\f$n\f$), so that only a few eigenvalues +/// and eigenvectors are computed. +/// +/// Rather than providing the whole \f$A\f$ matrix, the algorithm only requires +/// the matrix-vector multiplication operation of \f$A\f$. Therefore, users of +/// this solver need to supply a class that computes the result of \f$Av\f$ +/// for any given vector \f$v\f$. The name of this class should be given to +/// the template parameter `OpType`, and instance of this class passed to +/// the constructor of SymEigsSolver. +/// +/// If the matrix \f$A\f$ is already stored as a matrix object in **Eigen**, +/// for example `Eigen::MatrixXd`, then there is an easy way to construct such +/// matrix operation class, by using the built-in wrapper class DenseSymMatProd +/// which wraps an existing matrix object in **Eigen**. This is also the +/// default template parameter for SymEigsSolver. For sparse matrices, the +/// wrapper class SparseSymMatProd can be used similarly. +/// +/// If the users need to define their own matrix-vector multiplication operation +/// class, it should implement all the public member functions as in DenseSymMatProd. +/// +/// \tparam Scalar The element type of the matrix. +/// Currently supported types are `float`, `double` and `long double`. +/// \tparam SelectionRule An enumeration value indicating the selection rule of +/// the requested eigenvalues, for example `LARGEST_MAGN` +/// to retrieve eigenvalues with the largest magnitude. +/// The full list of enumeration values can be found in +/// \ref Enumerations. +/// \tparam OpType The name of the matrix operation class. Users could either +/// use the wrapper classes such as DenseSymMatProd and +/// SparseSymMatProd, or define their +/// own that implements all the public member functions as in +/// DenseSymMatProd. +/// +/// Below is an example that demonstrates the usage of this class. +/// +/// \code{.cpp} +/// #include +/// #include +/// // is implicitly included +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // We are going to calculate the eigenvalues of M +/// Eigen::MatrixXd A = Eigen::MatrixXd::Random(10, 10); +/// Eigen::MatrixXd M = A + A.transpose(); +/// +/// // Construct matrix operation object using the wrapper class DenseSymMatProd +/// DenseSymMatProd op(M); +/// +/// // Construct eigen solver object, requesting the largest three eigenvalues +/// SymEigsSolver< double, LARGEST_ALGE, DenseSymMatProd > eigs(&op, 3, 6); +/// +/// // Initialize and compute +/// eigs.init(); +/// int nconv = eigs.compute(); +/// +/// // Retrieve results +/// Eigen::VectorXd evalues; +/// if(eigs.info() == SUCCESSFUL) +/// evalues = eigs.eigenvalues(); +/// +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// +/// return 0; +/// } +/// \endcode +/// +/// And here is an example for user-supplied matrix operation class. +/// +/// \code{.cpp} +/// #include +/// #include +/// #include +/// +/// using namespace Spectra; +/// +/// // M = diag(1, 2, ..., 10) +/// class MyDiagonalTen +/// { +/// public: +/// int rows() { return 10; } +/// int cols() { return 10; } +/// // y_out = M * x_in +/// void perform_op(double *x_in, double *y_out) +/// { +/// for(int i = 0; i < rows(); i++) +/// { +/// y_out[i] = x_in[i] * (i + 1); +/// } +/// } +/// }; +/// +/// int main() +/// { +/// MyDiagonalTen op; +/// SymEigsSolver eigs(&op, 3, 6); +/// eigs.init(); +/// eigs.compute(); +/// if(eigs.info() == SUCCESSFUL) +/// { +/// Eigen::VectorXd evalues = eigs.eigenvalues(); +/// // Will get (10, 9, 8) +/// std::cout << "Eigenvalues found:\n" << evalues << std::endl; +/// } +/// +/// return 0; +/// } +/// \endcode +/// +template < typename Scalar = double, + int SelectionRule = LARGEST_MAGN, + typename OpType = DenseSymMatProd > +class SymEigsSolver: public SymEigsBase +{ +private: + typedef Eigen::Index Index; + +public: + /// + /// Constructor to create a solver object. + /// + /// \param op Pointer to the matrix operation object, which should implement + /// the matrix-vector multiplication operation of \f$A\f$: + /// calculating \f$Av\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper class such as DenseSymMatProd, or + /// define their own that implements all the public member functions + /// as in DenseSymMatProd. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-1\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev < ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev\f$. + /// + SymEigsSolver(OpType* op, Index nev, Index ncv) : + SymEigsBase(op, NULL, nev, ncv) + {} + +}; + + +} // namespace Spectra + +#endif // SYM_EIGS_SOLVER_H diff --git a/src/external/Spectra/include/Spectra/SymGEigsSolver.h b/src/external/Spectra/include/Spectra/SymGEigsSolver.h new file mode 100644 index 00000000..8e774284 --- /dev/null +++ b/src/external/Spectra/include/Spectra/SymGEigsSolver.h @@ -0,0 +1,334 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SYM_GEIGS_SOLVER_H +#define SYM_GEIGS_SOLVER_H + +#include "SymEigsBase.h" +#include "Util/GEigsMode.h" +#include "MatOp/internal/SymGEigsCholeskyOp.h" +#include "MatOp/internal/SymGEigsRegInvOp.h" + +namespace Spectra { + + +/// +/// \defgroup GEigenSolver Generalized Eigen Solvers +/// +/// Generalized eigen solvers for different types of problems. +/// + +/// +/// \ingroup GEigenSolver +/// +/// This class implements the generalized eigen solver for real symmetric +/// matrices, i.e., to solve \f$Ax=\lambda Bx\f$ where \f$A\f$ is symmetric and +/// \f$B\f$ is positive definite. +/// +/// There are two modes of this solver, specified by the template parameter +/// GEigsMode. See the pages for the specialized classes for details. +/// - The Cholesky mode assumes that \f$B\f$ can be factorized using Cholesky +/// decomposition, which is the preferred mode when the decomposition is +/// available. (This can be easily done in Eigen using the dense or sparse +/// Cholesky solver.) +/// See \ref SymGEigsSolver "SymGEigsSolver (Cholesky mode)" for more details. +/// - The regular inverse mode requires the matrix-vector product \f$Bv\f$ and the +/// linear equation solving operation \f$B^{-1}v\f$. This mode should only be +/// used when the Cholesky decomposition of \f$B\f$ is hard to implement, or +/// when computing \f$B^{-1}v\f$ is much faster than the Cholesky decomposition. +/// See \ref SymGEigsSolver "SymGEigsSolver (Regular inverse mode)" for more details. + +// Empty class template +template < typename Scalar, + int SelectionRule, + typename OpType, + typename BOpType, + int GEigsMode > +class SymGEigsSolver +{}; + + + +/// +/// \ingroup GEigenSolver +/// +/// This class implements the generalized eigen solver for real symmetric +/// matrices using Cholesky decomposition, i.e., to solve \f$Ax=\lambda Bx\f$ +/// where \f$A\f$ is symmetric and \f$B\f$ is positive definite with the Cholesky +/// decomposition \f$B=LL'\f$. +/// +/// This solver requires two matrix operation objects: one for \f$A\f$ that implements +/// the matrix multiplication \f$Av\f$, and one for \f$B\f$ that implements the lower +/// and upper triangular solving \f$L^{-1}v\f$ and \f$(L')^{-1}v\f$. +/// +/// If \f$A\f$ and \f$B\f$ are stored as Eigen matrices, then the first operation +/// can be created using the DenseSymMatProd or SparseSymMatProd classes, and +/// the second operation can be created using the DenseCholesky or SparseCholesky +/// classes. If the users need to define their own operation classes, then they +/// should implement all the public member functions as in those built-in classes. +/// +/// \tparam Scalar The element type of the matrix. +/// Currently supported types are `float`, `double` and `long double`. +/// \tparam SelectionRule An enumeration value indicating the selection rule of +/// the requested eigenvalues, for example `LARGEST_MAGN` +/// to retrieve eigenvalues with the largest magnitude. +/// The full list of enumeration values can be found in +/// \ref Enumerations. +/// \tparam OpType The name of the matrix operation class for \f$A\f$. Users could either +/// use the wrapper classes such as DenseSymMatProd and +/// SparseSymMatProd, or define their +/// own that implements all the public member functions as in +/// DenseSymMatProd. +/// \tparam BOpType The name of the matrix operation class for \f$B\f$. Users could either +/// use the wrapper classes such as DenseCholesky and +/// SparseCholesky, or define their +/// own that implements all the public member functions as in +/// DenseCholesky. +/// \tparam GEigsMode Mode of the generalized eigen solver. In this solver +/// it is Spectra::GEIGS_CHOLESKY. +/// +/// Below is an example that demonstrates the usage of this class. +/// +/// \code{.cpp} +/// #include +/// #include +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// using namespace Spectra; +/// +/// int main() +/// { +/// // We are going to solve the generalized eigenvalue problem A * x = lambda * B * x +/// const int n = 100; +/// +/// // Define the A matrix +/// Eigen::MatrixXd M = Eigen::MatrixXd::Random(n, n); +/// Eigen::MatrixXd A = M + M.transpose(); +/// +/// // Define the B matrix, a band matrix with 2 on the diagonal and 1 on the subdiagonals +/// Eigen::SparseMatrix B(n, n); +/// B.reserve(Eigen::VectorXi::Constant(n, 3)); +/// for(int i = 0; i < n; i++) +/// { +/// B.insert(i, i) = 2.0; +/// if(i > 0) +/// B.insert(i - 1, i) = 1.0; +/// if(i < n - 1) +/// B.insert(i + 1, i) = 1.0; +/// } +/// +/// // Construct matrix operation object using the wrapper classes +/// DenseSymMatProd op(A); +/// SparseCholesky Bop(B); +/// +/// // Construct generalized eigen solver object, requesting the largest three generalized eigenvalues +/// SymGEigsSolver, SparseCholesky, GEIGS_CHOLESKY> +/// geigs(&op, &Bop, 3, 6); +/// +/// // Initialize and compute +/// geigs.init(); +/// int nconv = geigs.compute(); +/// +/// // Retrieve results +/// Eigen::VectorXd evalues; +/// Eigen::MatrixXd evecs; +/// if(geigs.info() == SUCCESSFUL) +/// { +/// evalues = geigs.eigenvalues(); +/// evecs = geigs.eigenvectors(); +/// } +/// +/// std::cout << "Generalized eigenvalues found:\n" << evalues << std::endl; +/// std::cout << "Generalized eigenvectors found:\n" << evecs.topRows(10) << std::endl; +/// +/// // Verify results using the generalized eigen solver in Eigen +/// Eigen::MatrixXd Bdense = B; +/// Eigen::GeneralizedSelfAdjointEigenSolver es(A, Bdense); +/// +/// std::cout << "Generalized eigenvalues:\n" << es.eigenvalues().tail(3) << std::endl; +/// std::cout << "Generalized eigenvectors:\n" << es.eigenvectors().rightCols(3).topRows(10) << std::endl; +/// +/// return 0; +/// } +/// \endcode + +// Partial specialization for GEigsMode = GEIGS_CHOLESKY +template < typename Scalar, + int SelectionRule, + typename OpType, + typename BOpType > +class SymGEigsSolver: + public SymEigsBase, IdentityBOp> +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + + BOpType* m_Bop; + +public: + /// + /// Constructor to create a solver object. + /// + /// \param op Pointer to the \f$A\f$ matrix operation object. It + /// should implement the matrix-vector multiplication operation of \f$A\f$: + /// calculating \f$Av\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper classes such as DenseSymMatProd, or + /// define their own that implements all the public member functions + /// as in DenseSymMatProd. + /// \param Bop Pointer to the \f$B\f$ matrix operation object. It + /// represents a Cholesky decomposition of \f$B\f$, and should + /// implement the lower and upper triangular solving operations: + /// calculating \f$L^{-1}v\f$ and \f$(L')^{-1}v\f$ for any vector + /// \f$v\f$, where \f$LL'=B\f$. Users could either + /// create the object from the wrapper classes such as DenseCholesky, or + /// define their own that implements all the public member functions + /// as in DenseCholesky. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-1\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev < ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev\f$. + /// + SymGEigsSolver(OpType* op, BOpType* Bop, Index nev, Index ncv) : + SymEigsBase, IdentityBOp>( + new SymGEigsCholeskyOp(*op, *Bop), NULL, nev, ncv + ), + m_Bop(Bop) + {} + + /// \cond + + ~SymGEigsSolver() + { + // m_op contains the constructed SymGEigsCholeskyOp object + delete this->m_op; + } + + Matrix eigenvectors(Index nvec) const + { + Matrix res = SymEigsBase, IdentityBOp>::eigenvectors(nvec); + Vector tmp(res.rows()); + const Index nconv = res.cols(); + for(Index i = 0; i < nconv; i++) + { + m_Bop->upper_triangular_solve(&res(0, i), tmp.data()); + res.col(i).noalias() = tmp; + } + + return res; + } + + Matrix eigenvectors() const + { + return SymGEigsSolver::eigenvectors(this->m_nev); + } + + /// \endcond +}; + + + +/// +/// \ingroup GEigenSolver +/// +/// This class implements the generalized eigen solver for real symmetric +/// matrices in the regular inverse mode, i.e., to solve \f$Ax=\lambda Bx\f$ +/// where \f$A\f$ is symmetric, and \f$B\f$ is positive definite with the operations +/// defined below. +/// +/// This solver requires two matrix operation objects: one for \f$A\f$ that implements +/// the matrix multiplication \f$Av\f$, and one for \f$B\f$ that implements the +/// matrix-vector product \f$Bv\f$ and the linear equation solving operation \f$B^{-1}v\f$. +/// +/// If \f$A\f$ and \f$B\f$ are stored as Eigen matrices, then the first operation +/// can be created using the DenseSymMatProd or SparseSymMatProd classes, and +/// the second operation can be created using the SparseRegularInverse class. There is no +/// wrapper class for a dense \f$B\f$ matrix since in this case the Cholesky mode +/// is always preferred. If the users need to define their own operation classes, then they +/// should implement all the public member functions as in those built-in classes. +/// +/// \tparam Scalar The element type of the matrix. +/// Currently supported types are `float`, `double` and `long double`. +/// \tparam SelectionRule An enumeration value indicating the selection rule of +/// the requested eigenvalues, for example `LARGEST_MAGN` +/// to retrieve eigenvalues with the largest magnitude. +/// The full list of enumeration values can be found in +/// \ref Enumerations. +/// \tparam OpType The name of the matrix operation class for \f$A\f$. Users could either +/// use the wrapper classes such as DenseSymMatProd and +/// SparseSymMatProd, or define their +/// own that implements all the public member functions as in +/// DenseSymMatProd. +/// \tparam BOpType The name of the matrix operation class for \f$B\f$. Users could either +/// use the wrapper class SparseRegularInverse, or define their +/// own that implements all the public member functions as in +/// SparseRegularInverse. +/// \tparam GEigsMode Mode of the generalized eigen solver. In this solver +/// it is Spectra::GEIGS_REGULAR_INVERSE. +/// + +// Partial specialization for GEigsMode = GEIGS_REGULAR_INVERSE +template < typename Scalar, + int SelectionRule, + typename OpType, + typename BOpType > +class SymGEigsSolver: + public SymEigsBase, BOpType> +{ +private: + typedef Eigen::Index Index; + +public: + /// + /// Constructor to create a solver object. + /// + /// \param op Pointer to the \f$A\f$ matrix operation object. It + /// should implement the matrix-vector multiplication operation of \f$A\f$: + /// calculating \f$Av\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper classes such as DenseSymMatProd, or + /// define their own that implements all the public member functions + /// as in DenseSymMatProd. + /// \param Bop Pointer to the \f$B\f$ matrix operation object. It should + /// implement the multiplication operation \f$Bv\f$ and the linear equation + /// solving operation \f$B^{-1}v\f$ for any vector \f$v\f$. Users could either + /// create the object from the wrapper class SparseRegularInverse, or + /// define their own that implements all the public member functions + /// as in SparseRegularInverse. + /// \param nev Number of eigenvalues requested. This should satisfy \f$1\le nev \le n-1\f$, + /// where \f$n\f$ is the size of matrix. + /// \param ncv Parameter that controls the convergence speed of the algorithm. + /// Typically a larger `ncv` means faster convergence, but it may + /// also result in greater memory use and more matrix operations + /// in each iteration. This parameter must satisfy \f$nev < ncv \le n\f$, + /// and is advised to take \f$ncv \ge 2\cdot nev\f$. + /// + SymGEigsSolver(OpType* op, BOpType* Bop, Index nev, Index ncv) : + SymEigsBase, BOpType>( + new SymGEigsRegInvOp(*op, *Bop), Bop, nev, ncv + ) + {} + + /// \cond + ~SymGEigsSolver() + { + // m_op contains the constructed SymGEigsRegInvOp object + delete this->m_op; + } + /// \endcond +}; + + +} // namespace Spectra + +#endif // SYM_GEIGS_SOLVER_H diff --git a/src/external/Spectra/include/Spectra/Util/CompInfo.h b/src/external/Spectra/include/Spectra/Util/CompInfo.h new file mode 100644 index 00000000..b8e639d6 --- /dev/null +++ b/src/external/Spectra/include/Spectra/Util/CompInfo.h @@ -0,0 +1,37 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef COMP_INFO_H +#define COMP_INFO_H + +namespace Spectra { + + +/// +/// \ingroup Enumerations +/// +/// The enumeration to report the status of computation. +/// +enum COMPUTATION_INFO +{ + SUCCESSFUL = 0, ///< Computation was successful. + + NOT_COMPUTED, ///< Used in eigen solvers, indicating that computation + ///< has not been conducted. Users should call + ///< the `compute()` member function of solvers. + + NOT_CONVERGING, ///< Used in eigen solvers, indicating that some eigenvalues + ///< did not converge. The `compute()` + ///< function returns the number of converged eigenvalues. + + NUMERICAL_ISSUE ///< Used in Cholesky decomposition, indicating that the + ///< matrix is not positive definite. +}; + + +} // namespace Spectra + +#endif // COMP_INFO_H diff --git a/src/external/Spectra/include/Spectra/Util/GEigsMode.h b/src/external/Spectra/include/Spectra/Util/GEigsMode.h new file mode 100644 index 00000000..d03f269d --- /dev/null +++ b/src/external/Spectra/include/Spectra/Util/GEigsMode.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef GEIGS_MODE_H +#define GEIGS_MODE_H + +namespace Spectra { + + +/// +/// \ingroup Enumerations +/// +/// The enumeration to specify the mode of generalized eigenvalue solver. +/// +enum GEIGS_MODE +{ + GEIGS_CHOLESKY = 0, ///< Using Cholesky decomposition to solve generalized eigenvalues. + + GEIGS_REGULAR_INVERSE, ///< Regular inverse mode for generalized eigenvalue solver. + + GEIGS_SHIFT_INVERT, ///< Shift-and-invert mode for generalized eigenvalue solver. + + GEIGS_BUCKLING, ///< Buckling mode for generalized eigenvalue solver. + + GEIGS_CAYLEY ///< Cayley transformation mode for generalized eigenvalue solver. +}; + + +} // namespace Spectra + +#endif // GEIGS_MODE_H diff --git a/src/external/Spectra/include/Spectra/Util/SelectionRule.h b/src/external/Spectra/include/Spectra/Util/SelectionRule.h new file mode 100644 index 00000000..19f71dcf --- /dev/null +++ b/src/external/Spectra/include/Spectra/Util/SelectionRule.h @@ -0,0 +1,277 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SELECTION_RULE_H +#define SELECTION_RULE_H + +#include // std::vector +#include // std::abs +#include // std::sort +#include // std::complex +#include // std::pair +#include // std::invalid_argument + +namespace Spectra { + + +/// +/// \defgroup Enumerations +/// +/// Enumeration types for the selection rule of eigenvalues. +/// + +/// +/// \ingroup Enumerations +/// +/// The enumeration of selection rules of desired eigenvalues. +/// +enum SELECT_EIGENVALUE +{ + LARGEST_MAGN = 0, ///< Select eigenvalues with largest magnitude. Magnitude + ///< means the absolute value for real numbers and norm for + ///< complex numbers. Applies to both symmetric and general + ///< eigen solvers. + + LARGEST_REAL, ///< Select eigenvalues with largest real part. Only for general eigen solvers. + + LARGEST_IMAG, ///< Select eigenvalues with largest imaginary part (in magnitude). Only for general eigen solvers. + + LARGEST_ALGE, ///< Select eigenvalues with largest algebraic value, considering + ///< any negative sign. Only for symmetric eigen solvers. + + SMALLEST_MAGN, ///< Select eigenvalues with smallest magnitude. Applies to both symmetric and general + ///< eigen solvers. + + SMALLEST_REAL, ///< Select eigenvalues with smallest real part. Only for general eigen solvers. + + SMALLEST_IMAG, ///< Select eigenvalues with smallest imaginary part (in magnitude). Only for general eigen solvers. + + SMALLEST_ALGE, ///< Select eigenvalues with smallest algebraic value. Only for symmetric eigen solvers. + + BOTH_ENDS ///< Select eigenvalues half from each end of the spectrum. When + ///< `nev` is odd, compute more from the high end. Only for symmetric eigen solvers. +}; + +/// +/// \ingroup Enumerations +/// +/// The enumeration of selection rules of desired eigenvalues. Alias for `SELECT_EIGENVALUE`. +/// +enum SELECT_EIGENVALUE_ALIAS +{ + WHICH_LM = 0, ///< Alias for `LARGEST_MAGN` + WHICH_LR, ///< Alias for `LARGEST_REAL` + WHICH_LI, ///< Alias for `LARGEST_IMAG` + WHICH_LA, ///< Alias for `LARGEST_ALGE` + WHICH_SM, ///< Alias for `SMALLEST_MAGN` + WHICH_SR, ///< Alias for `SMALLEST_REAL` + WHICH_SI, ///< Alias for `SMALLEST_IMAG` + WHICH_SA, ///< Alias for `SMALLEST_ALGE` + WHICH_BE ///< Alias for `BOTH_ENDS` +}; + +/// \cond + +// Get the element type of a "scalar" +// ElemType => double +// ElemType< std::complex > => double +template +class ElemType +{ +public: + typedef T type; +}; + +template +class ElemType< std::complex > +{ +public: + typedef T type; +}; + +// When comparing eigenvalues, we first calculate the "target" +// to sort. For example, if we want to choose the eigenvalues with +// largest magnitude, the target will be -abs(x). +// The minus sign is due to the fact that std::sort() sorts in ascending order. + +// Default target: throw an exception +template +class SortingTarget +{ +public: + static typename ElemType::type get(const Scalar& val) + { + using std::abs; + throw std::invalid_argument("incompatible selection rule"); + return -abs(val); + } +}; + +// Specialization for LARGEST_MAGN +// This covers [float, double, complex] x [LARGEST_MAGN] +template +class SortingTarget +{ +public: + static typename ElemType::type get(const Scalar& val) + { + using std::abs; + return -abs(val); + } +}; + +// Specialization for LARGEST_REAL +// This covers [complex] x [LARGEST_REAL] +template +class SortingTarget, LARGEST_REAL> +{ +public: + static RealType get(const std::complex& val) + { + return -val.real(); + } +}; + +// Specialization for LARGEST_IMAG +// This covers [complex] x [LARGEST_IMAG] +template +class SortingTarget, LARGEST_IMAG> +{ +public: + static RealType get(const std::complex& val) + { + using std::abs; + return -abs(val.imag()); + } +}; + +// Specialization for LARGEST_ALGE +// This covers [float, double] x [LARGEST_ALGE] +template +class SortingTarget +{ +public: + static Scalar get(const Scalar& val) + { + return -val; + } +}; + +// Here BOTH_ENDS is the same as LARGEST_ALGE, but +// we need some additional steps, which are done in +// SymEigsSolver.h => retrieve_ritzpair(). +// There we move the smallest values to the proper locations. +template +class SortingTarget +{ +public: + static Scalar get(const Scalar& val) + { + return -val; + } +}; + +// Specialization for SMALLEST_MAGN +// This covers [float, double, complex] x [SMALLEST_MAGN] +template +class SortingTarget +{ +public: + static typename ElemType::type get(const Scalar& val) + { + using std::abs; + return abs(val); + } +}; + +// Specialization for SMALLEST_REAL +// This covers [complex] x [SMALLEST_REAL] +template +class SortingTarget, SMALLEST_REAL> +{ +public: + static RealType get(const std::complex& val) + { + return val.real(); + } +}; + +// Specialization for SMALLEST_IMAG +// This covers [complex] x [SMALLEST_IMAG] +template +class SortingTarget, SMALLEST_IMAG> +{ +public: + static RealType get(const std::complex& val) + { + using std::abs; + return abs(val.imag()); + } +}; + +// Specialization for SMALLEST_ALGE +// This covers [float, double] x [SMALLEST_ALGE] +template +class SortingTarget +{ +public: + static Scalar get(const Scalar& val) + { + return val; + } +}; + +// Sort eigenvalues and return the order index +template +class PairComparator +{ +public: + bool operator() (const PairType& v1, const PairType& v2) + { + return v1.first < v2.first; + } +}; + +template +class SortEigenvalue +{ +private: + typedef typename ElemType::type TargetType; // Type of the sorting target, will be + // a floating number type, e.g. "double" + typedef std::pair PairType; // Type of the sorting pair, including + // the sorting target and the index + + std::vector pair_sort; + +public: + SortEigenvalue(const T* start, int size) : + pair_sort(size) + { + for(int i = 0; i < size; i++) + { + pair_sort[i].first = SortingTarget::get(start[i]); + pair_sort[i].second = i; + } + PairComparator comp; + std::sort(pair_sort.begin(), pair_sort.end(), comp); + } + + std::vector index() + { + std::vector ind(pair_sort.size()); + for(unsigned int i = 0; i < ind.size(); i++) + ind[i] = pair_sort[i].second; + + return ind; + } +}; + +/// \endcond + + +} // namespace Spectra + +#endif // SELECTION_RULE_H diff --git a/src/external/Spectra/include/Spectra/Util/SimpleRandom.h b/src/external/Spectra/include/Spectra/Util/SimpleRandom.h new file mode 100644 index 00000000..7b1e6162 --- /dev/null +++ b/src/external/Spectra/include/Spectra/Util/SimpleRandom.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef SIMPLE_RANDOM_H +#define SIMPLE_RANDOM_H + +#include + +/// \cond + +namespace Spectra { + + +// We need a simple pseudo random number generator here: +// 1. It is used to generate initial and restarted residual vector. +// 2. It is not necessary to be so "random" and advanced. All we hope +// is that the residual vector is not in the space spanned by the +// current Krylov space. This should be met almost surely. +// 3. We don't want to call RNG in C++, since we actually want the +// algorithm to be deterministic. Also, calling RNG in C/C++ is not +// allowed in R packages submitted to CRAN. +// 4. The method should be as simple as possible, so an LCG is enough. +// 5. Based on public domain code by Ray Gardner +// http://stjarnhimlen.se/snippets/rg_rand.c + + +template +class SimpleRandom +{ +private: + typedef Eigen::Index Index; + typedef Eigen::Matrix Vector; + + const unsigned int m_a; // multiplier + const unsigned long m_max; // 2^31 - 1 + long m_rand; + + inline long next_long_rand(long seed) + { + unsigned long lo, hi; + + lo = m_a * (long)(seed & 0xFFFF); + hi = m_a * (long)((unsigned long)seed >> 16); + lo += (hi & 0x7FFF) << 16; + if(lo > m_max) + { + lo &= m_max; + ++lo; + } + lo += hi >> 15; + if(lo > m_max) + { + lo &= m_max; + ++lo; + } + return (long)lo; + } +public: + SimpleRandom(unsigned long init_seed) : + m_a(16807), + m_max(2147483647L), + m_rand(init_seed ? (init_seed & m_max) : 1) + {} + + Scalar random() + { + m_rand = next_long_rand(m_rand); + return Scalar(m_rand) / Scalar(m_max) - Scalar(0.5); + } + + // Vector of random numbers of type Scalar + // Ranging from -0.5 to 0.5 + Vector random_vec(const Index len) + { + Vector res(len); + for(Index i = 0; i < len; i++) + { + m_rand = next_long_rand(m_rand); + res[i] = Scalar(m_rand) / Scalar(m_max) - Scalar(0.5); + } + return res; + } +}; + + +} // namespace Spectra + +/// \endcond + +#endif // SIMPLE_RANDOM_H diff --git a/src/external/Spectra/include/Spectra/Util/TypeTraits.h b/src/external/Spectra/include/Spectra/Util/TypeTraits.h new file mode 100644 index 00000000..a4cc05b2 --- /dev/null +++ b/src/external/Spectra/include/Spectra/Util/TypeTraits.h @@ -0,0 +1,73 @@ +// Copyright (C) 2018-2019 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef TYPE_TRAITS_H +#define TYPE_TRAITS_H + +#include +#include + +/// \cond + +namespace Spectra { + + +// For a real value type "Scalar", we want to know its smallest +// positive value, i.e., std::numeric_limits::min(). +// However, we must take non-standard value types into account, +// so we rely on Eigen::NumTraits. +// +// Eigen::NumTraits has defined epsilon() and lowest(), but +// lowest() means negative highest(), which is a very small +// negative value. +// +// Therefore, we manually define this limit, and use eplison()^3 +// to mimic it for non-standard types. + +// Generic definition +template +struct TypeTraits +{ + static inline Scalar min() + { + return Eigen::numext::pow(Eigen::NumTraits::epsilon(), Scalar(3)); + } +}; + +// Full specialization +template <> +struct TypeTraits +{ + static inline float min() + { + return std::numeric_limits::min(); + } +}; + +template <> +struct TypeTraits +{ + static inline double min() + { + return std::numeric_limits::min(); + } +}; + +template <> +struct TypeTraits +{ + static inline long double min() + { + return std::numeric_limits::min(); + } +}; + + +} // namespace Spectra + +/// \endcond + +#endif // TYPE_TRAITS_H diff --git a/src/external/Spectra/include/Spectra/contrib/LOBPCGSolver.h b/src/external/Spectra/include/Spectra/contrib/LOBPCGSolver.h new file mode 100644 index 00000000..5ca001f6 --- /dev/null +++ b/src/external/Spectra/include/Spectra/contrib/LOBPCGSolver.h @@ -0,0 +1,501 @@ +// Written by Anna Araslanova +// Modified by Yixuan Qiu +// License: MIT + +#ifndef LOBPCG_SOLVER +#define LOBPCG_SOLVER + +#include +#include + +#include +#include +#include +#include +#include + +#include "../SymGEigsSolver.h" + + +namespace Spectra { + + /// + /// \ingroup EigenSolver + /// + + /// *** METHOD + /// The class represent the LOBPCG algorithm, which was invented by Andrew Knyazev + /// Theoretical background of the procedure can be found in the articles below + /// - Knyazev, A.V., 2001. Toward the optimal preconditioned eigensolver : Locally optimal block preconditioned conjugate gradient method.SIAM journal on scientific computing, 23(2), pp.517 - 541. + /// - Knyazev, A.V., Argentati, M.E., Lashuk, I. and Ovtchinnikov, E.E., 2007. Block locally optimal preconditioned eigenvalue xolvers(BLOPEX) in HYPRE and PETSc.SIAM Journal on Scientific Computing, 29(5), pp.2224 - 2239. + /// + /// *** CONDITIONS OF USE + /// Locally Optimal Block Preconditioned Conjugate Gradient(LOBPCG) is a method for finding the M smallest eigenvalues + /// and eigenvectors of a large symmetric positive definite generalized eigenvalue problem + /// \f$Ax=\lambda Bx,\f$ + /// where \f$A_{NxN}\f$ is a symmetric matrix, \f$B\f$ is symmetric and positive - definite. \f$A and B\f$ are also assumed large and sparse + /// \f$\textit{M}\f$ should be \f$\<< textit{N}\f$ (at least \f$\textit{5M} < \textit{N} \f$) + /// + /// *** ARGUMENTS + /// Eigen::SparseMatrix A; // N*N - Ax = lambda*Bx, lrage and sparse + /// Eigen::SparseMatrix X; // N*M - initial approximations to eigenvectors (random in general case) + /// Spectra::LOBPCGSolver solver(A, X); + /// *Eigen::SparseMatrix B; // N*N - Ax = lambda*Bx, sparse, positive definite + /// solver.setConstraints(B); + /// *Eigen::SparseMatrix Y; // N*K - constraints, already found eigenvectors + /// solver.setB(B); + /// *Eigen::SparseMatrix T; // N*N - preconditioner ~ A^-1 + /// solver.setPreconditioner(T); + /// + /// *** OUTCOMES + /// solver.solve(); // compute eigenpairs // void + /// solver.info(); // state of converjance // int + /// solver.residuals(); // get residuals to evaluate biases // Eigen::Matrix + /// solver.eigenvalues(); // get eigenvalues // Eigen::Matrix + /// solver.eigenvectors(); // get eigenvectors // Eigen::Matrix + /// + /// *** EXAMPLE + /// \code{.cpp} + /// #include + /// + /// // random A + /// Matrix a; + /// a = (Matrix::Random(10, 10).array() > 0.6).cast() * Matrix::Random(10, 10).array() * 5; + /// a = Matrix((a).triangularView()) + Matrix((a).triangularView()).transpose(); + /// for (int i = 0; i < 10; i++) + /// a(i, i) = i + 0.5; + /// std::cout << a << "\n"; + /// Eigen::SparseMatrix A(a.sparseView()); + /// // random X + /// Eigen::Matrix x; + /// x = Matrix::Random(10, 2).array(); + /// Eigen::SparseMatrix X(x.sparseView()); + /// // solve Ax = lambda*x + /// Spectra::LOBPCGSolver solver(A, X); + /// solver.compute(10, 1e-4); // 10 iterations, L2_tolerance = 1e-4*N + /// std::cout << "info\n" << solver.info() << std::endl; + /// std::cout << "eigenvalues\n" << solver.eigenvalues() << std::endl; + /// std::cout << "eigenvectors\n" << solver.eigenvectors() << std::endl; + /// std::cout << "residuals\n" << solver.residuals() << std::endl; + /// \endcode + /// + + template < typename Scalar = long double> + class LOBPCGSolver { + private: + + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + + typedef std::complex Complex; + typedef Eigen::Matrix ComplexMatrix; + typedef Eigen::Matrix ComplexVector; + + typedef Eigen::SparseMatrix SparseMatrix; + typedef Eigen::SparseMatrix SparseComplexMatrix; + + const int m_n; // dimension of matrix A + const int m_nev; // number of eigenvalues requested + SparseMatrix A, X; + SparseMatrix m_Y, m_B, m_preconditioner; + bool flag_with_constraints, flag_with_B, flag_with_preconditioner; + + public: + SparseMatrix m_residuals; + Matrix m_evectors; + Vector m_evalues; + int m_info; + + private: + + // B-orthonormalize matrix M + int orthogonalizeInPlace(SparseMatrix &M, SparseMatrix &B, \ + SparseMatrix &true_BM, bool has_true_BM = false) { + + SparseMatrix BM; + + if (has_true_BM == false) { + if (flag_with_B) { BM = B * M; } + else { BM = M; } + } + else { + BM = true_BM; + } + + Eigen::SimplicialLDLT chol_MBM(M.transpose() * BM); + + if (chol_MBM.info() != SUCCESSFUL) { + // LDLT decomposition fail + m_info = chol_MBM.info(); + return chol_MBM.info(); + } + + SparseComplexMatrix Upper_MBM = chol_MBM.matrixU().template cast(); + ComplexVector D_MBM_vec = chol_MBM.vectorD().template cast(); + + D_MBM_vec = D_MBM_vec.cwiseSqrt(); + + for (int i = 0; i < D_MBM_vec.rows(); i++) { + D_MBM_vec(i) = Complex(1.0, 0.0) / D_MBM_vec(i); + } + + SparseComplexMatrix D_MBM_mat(D_MBM_vec.asDiagonal()); + + SparseComplexMatrix U_inv(Upper_MBM.rows(), Upper_MBM.cols()); + U_inv.setIdentity(); + Upper_MBM.template triangularView().solveInPlace(U_inv); + + SparseComplexMatrix right_product = U_inv * D_MBM_mat; + M = M*right_product.real(); + if (flag_with_B) { true_BM = B * M; } + else { true_BM = M; } + + return SUCCESSFUL; + } + + void applyConstraintsInPlace(SparseMatrix &X, SparseMatrix&Y, \ + SparseMatrix&B) { + SparseMatrix BY; + if (flag_with_B) { BY = B * Y; } + else { BY = Y; } + + SparseMatrix YBY = Y.transpose() * BY; + SparseMatrix BYX = BY.transpose() * X; + + SparseMatrix YBY_XYX = (Matrix(YBY).bdcSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(Matrix(BYX))).sparseView(); + X = X - Y * YBY_XYX; + } + + /* + return + 'AB + CD' + */ + Matrix stack_4_matricies(Matrix A, Matrix B, \ + Matrix C, Matrix D) { + Matrix result(A.rows() + C.rows(), A.cols() + B.cols()); + result.topLeftCorner(A.rows(), A.cols()) = A; + result.topRightCorner(B.rows(), B.cols()) = B; + result.bottomLeftCorner(C.rows(), C.cols()) = C; + result.bottomRightCorner(D.rows(), D.cols()) = D; + return result; + } + + Matrix stack_9_matricies(Matrix A, Matrix B, Matrix C, \ + Matrix D, Matrix E, Matrix F, \ + Matrix G, Matrix H, Matrix I) { + + Matrix result(A.rows() + D.rows() + G.rows(), A.cols() + B.cols() + C.cols()); + result.block(0, 0, A.rows(), A.cols()) = A; + result.block(0, A.cols(), B.rows(), B.cols()) = B; + result.block(0, A.cols() + B.cols(), C.rows(), C.cols()) = C; + result.block(A.rows(), 0, D.rows(), D.cols()) = D; + result.block(A.rows(), A.cols(), E.rows(), E.cols()) = E; + result.block(A.rows(), A.cols() + B.cols(), F.rows(), F.cols()) = F; + result.block(A.rows() + D.rows(), 0, G.rows(), G.cols()) = G; + result.block(A.rows() + D.rows(), A.cols(), H.rows(), H.cols()) = H; + result.block(A.rows() + D.rows(), A.cols() + B.cols(), I.rows(), I.cols()) = I; + + return result; + } + + void sort_epairs(Vector &evalues, Matrix &evectors, int SelectionRule) { + + std::function cmp; + if (SelectionRule == SMALLEST_ALGE) + cmp = std::less{}; + else + cmp = std::greater{}; + + std::map epairs(cmp); + for (int i = 0; i < m_evectors.cols(); ++i) + epairs.insert(std::make_pair(evalues(i), evectors.col(i))); + + int i = 0; + for (auto& epair : epairs) { + evectors.col(i) = epair.second; + evalues(i) = epair.first; + i++; + } + } + + void removeColumns(SparseMatrix& matrix, std::vector& colToRemove) + { + // remove columns through matrix multiplication + SparseMatrix new_matrix(matrix.cols(), matrix.cols() - int(colToRemove.size())); + int iCol = 0; + std::vector> tripletList; + tripletList.reserve(matrix.cols() - int(colToRemove.size())); + + for (int iRow = 0; iRow < matrix.cols(); iRow++) { + if (std::find(colToRemove.begin(), colToRemove.end(), iRow) == colToRemove.end()) { + tripletList.push_back(Eigen::Triplet(iRow, iCol, 1)); + iCol++; + } + } + + new_matrix.setFromTriplets(tripletList.begin(), tripletList.end()); + matrix = matrix * new_matrix; + } + + int checkConvergence_getBlocksize(SparseMatrix & m_residuals, Scalar tolerance_L2, std::vector & columnsToDelete) { + // square roots from sum of squares by column + int BlockSize = m_nev; + Scalar sum, buffer; + + for (int iCol = 0; iCol < m_nev; iCol++) { + sum = 0; + for (int iRow = 0; iRow < m_n; iRow++) { + buffer = m_residuals.coeff(iRow, iCol); + sum += buffer * buffer; + } + + if (sqrt(sum) < tolerance_L2) { + BlockSize--; + columnsToDelete.push_back(iCol); + } + } + return BlockSize; + } + + + public: + + LOBPCGSolver(const SparseMatrix& A, const SparseMatrix X) : + m_n(A.rows()), + m_nev(X.cols()), + m_info(NOT_COMPUTED), + flag_with_constraints(false), + flag_with_B(false), + flag_with_preconditioner(false), + A(A), + X(X) + { + if (A.rows() != X.rows() || A.rows() != A.cols()) + throw std::invalid_argument("Wrong size"); + + //if (m_n < 5* m_nev) + // throw std::invalid_argument("The problem size is small compared to the block size. Use standard eigensolver"); + } + + void setConstraints(const SparseMatrix& Y) { + m_Y = Y; + flag_with_constraints = true; + } + + void setB(const SparseMatrix& B) { + if (B.rows() != A.rows() || B.cols() != A.cols()) + throw std::invalid_argument("Wrong size"); + m_B = B; + flag_with_B = true; + } + + void setPreconditioner(const SparseMatrix& preconditioner) { + m_preconditioner = preconditioner; + flag_with_preconditioner = true; + } + + void compute(int maxit = 10, Scalar tol_div_n = 1e-7) { + + Scalar tolerance_L2 = tol_div_n * m_n; + int BlockSize; + int max_iter = std::min(m_n, maxit); + + SparseMatrix directions, AX, AR, BX, AD, ADD, DD, BDD, BD, XAD, RAD, DAD, XBD, RBD, BR, sparse_eVecX, sparse_eVecR, sparse_eVecD, inverse_matrix; + Matrix XAR, RAR, XBR, gramA, gramB, eVecX, eVecR, eVecD; + std::vector columnsToDelete; + + if (flag_with_constraints) { + // Apply the constraints Y to X + applyConstraintsInPlace(X, m_Y, m_B); + } + + // Make initial vectors orthonormal + // implicit BX declaration + if (orthogonalizeInPlace(X, m_B, BX) != SUCCESSFUL) { + max_iter = 0; + } + + AX = A * X; + // Solve the following NxN eigenvalue problem for all N eigenvalues and -vectors: + // first approximation via a dense problem + Eigen::EigenSolver eigs(Matrix(X.transpose() * AX)); + + if (eigs.info() != SUCCESSFUL) { + m_info = eigs.info(); + max_iter = 0; + } + else { + m_evalues = eigs.eigenvalues().real(); + m_evectors = eigs.eigenvectors().real(); + sort_epairs(m_evalues, m_evectors, SMALLEST_ALGE); + sparse_eVecX = m_evectors.sparseView(); + + X = X * sparse_eVecX; + AX = AX * sparse_eVecX; + BX = BX * sparse_eVecX; + } + + + for (int iter_num = 0; iter_num < max_iter; iter_num++) { + m_residuals.resize(m_n, m_nev); + for (int i = 0; i < m_nev; i++) { + m_residuals.col(i) = AX.col(i) - m_evalues(i) * BX.col(i); + } + BlockSize = checkConvergence_getBlocksize(m_residuals, tolerance_L2, columnsToDelete); + + if (BlockSize == 0) { + m_info = SUCCESSFUL; + break; + } + + // substitution of the original active mask + if (columnsToDelete.size() > 0) { + removeColumns(m_residuals, columnsToDelete); + if (iter_num > 0) { + removeColumns(directions, columnsToDelete); + removeColumns(AD, columnsToDelete); + removeColumns(BD, columnsToDelete); + } + columnsToDelete.clear(); // for next iteration + } + + if (flag_with_preconditioner) { + // Apply the preconditioner to the residuals + m_residuals = m_preconditioner * m_residuals; + } + + if (flag_with_constraints) { + // Apply the constraints Y to residuals + applyConstraintsInPlace(m_residuals, m_Y, m_B); + } + + if (orthogonalizeInPlace(m_residuals, m_B, BR) != SUCCESSFUL) { + break; + } + AR = A * m_residuals; + + // Orthonormalize conjugate directions + if (iter_num > 0) { + if (orthogonalizeInPlace(directions, m_B, BD, true) != SUCCESSFUL) { + break; + } + AD = A * directions; + } + + // Perform the Rayleigh Ritz Procedure + XAR = Matrix(X.transpose() * AR); + RAR = Matrix(m_residuals.transpose() * AR); + XBR = Matrix(X.transpose() * BR); + + if (iter_num > 0) { + + XAD = X.transpose() * AD; + RAD = m_residuals.transpose() * AD; + DAD = directions.transpose() * AD; + XBD = X.transpose() * BD; + RBD = m_residuals.transpose() * BD; + + gramA = stack_9_matricies(m_evalues.asDiagonal(), XAR, XAD, XAR.transpose(), RAR, RAD, XAD.transpose(), RAD.transpose(), DAD.transpose()); + gramB = stack_9_matricies(Matrix::Identity(m_nev, m_nev), XBR, XBD, XBR.transpose(), Matrix::Identity(BlockSize, BlockSize), RBD, XBD.transpose(), RBD.transpose(), Matrix::Identity(BlockSize, BlockSize)); + + } + else { + gramA = stack_4_matricies(m_evalues.asDiagonal(), XAR, XAR.transpose(), RAR); + gramB = stack_4_matricies(Matrix::Identity(m_nev, m_nev), XBR, XBR.transpose(), Matrix::Identity(BlockSize, BlockSize)); + } + + //calculate the lowest/largest m eigenpairs; Solve the generalized eigenvalue problem. + DenseSymMatProd Aop(gramA); + DenseCholesky Bop(gramB); + + SymGEigsSolver, \ + DenseCholesky, GEIGS_CHOLESKY> geigs(&Aop, &Bop, m_nev, std::min(10, int(gramA.rows()) - 1)); + + geigs.init(); + int nconv = geigs.compute(); + + //Mat evecs; + if (geigs.info() == SUCCESSFUL) { + m_evalues = geigs.eigenvalues(); + m_evectors = geigs.eigenvectors(); + sort_epairs(m_evalues, m_evectors, SMALLEST_ALGE); + } + else { + // Problem With General EgenVec + m_info = geigs.info(); + break; + } + + // Compute Ritz vectors + if (iter_num > 0) { + eVecX = m_evectors.block(0, 0, m_nev, m_nev); + eVecR = m_evectors.block(m_nev, 0, BlockSize, m_nev); + eVecD = m_evectors.block(m_nev + BlockSize, 0, BlockSize, m_nev); + + sparse_eVecX = eVecX.sparseView(); + sparse_eVecR = eVecR.sparseView(); + sparse_eVecD = eVecD.sparseView(); + + DD = m_residuals * sparse_eVecR; // new conjugate directions + ADD = AR * sparse_eVecR; + BDD = BR * sparse_eVecR; + + DD = DD + directions * sparse_eVecD; + ADD = ADD + AD * sparse_eVecD; + BDD = BDD + BD * sparse_eVecD; + } + else { + eVecX = m_evectors.block(0, 0, m_nev, m_nev); + eVecR = m_evectors.block(m_nev, 0, BlockSize, m_nev); + + sparse_eVecX = eVecX.sparseView(); + sparse_eVecR = eVecR.sparseView(); + + DD = m_residuals * sparse_eVecR; + ADD = AR * sparse_eVecR; + BDD = BR * sparse_eVecR; + } + + X = X * sparse_eVecX + DD; + AX = AX * sparse_eVecX + ADD; + BX = BX * sparse_eVecX + BDD; + + directions = DD; + AD = ADD; + BD = BDD; + + } // iteration loop + + // calculate last residuals + m_residuals.resize(m_n, m_nev); + for (int i = 0; i < m_nev; i++) { + m_residuals.col(i) = AX.col(i) - m_evalues(i) * BX.col(i); + } + BlockSize = checkConvergence_getBlocksize(m_residuals, tolerance_L2, columnsToDelete); + + if (BlockSize == 0) { + m_info = SUCCESSFUL; + } + } // compute + + Vector eigenvalues() { + return m_evalues; + } + + Matrix eigenvectors() { + return m_evectors; + } + + Matrix residuals() { + return Matrix(m_residuals); + } + + int info() { return m_info; } + + }; + + +} // namespace Spectra + +#endif // LOBPCG_SOLVER diff --git a/src/external/Spectra/include/Spectra/contrib/PartialSVDSolver.h b/src/external/Spectra/include/Spectra/contrib/PartialSVDSolver.h new file mode 100644 index 00000000..dad5b400 --- /dev/null +++ b/src/external/Spectra/include/Spectra/contrib/PartialSVDSolver.h @@ -0,0 +1,203 @@ +// Copyright (C) 2018 Yixuan Qiu +// +// This Source Code Form is subject to the terms of the Mozilla +// Public License v. 2.0. If a copy of the MPL was not distributed +// with this file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#ifndef PARTIAL_SVD_SOLVER_H +#define PARTIAL_SVD_SOLVER_H + +#include +#include "../SymEigsSolver.h" + + +namespace Spectra { + + +// Abstract class for matrix operation +template +class SVDMatOp +{ +public: + virtual int rows() const = 0; + virtual int cols() const = 0; + + // y_out = A' * A * x_in or y_out = A * A' * x_in + virtual void perform_op(const Scalar* x_in, Scalar* y_out) = 0; + + virtual ~SVDMatOp() {} +}; + +// Operation of a tall matrix in SVD +// We compute the eigenvalues of A' * A +// MatrixType is either Eigen::Matrix or Eigen::SparseMatrix +template +class SVDTallMatOp: public SVDMatOp +{ +private: + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef const Eigen::Ref ConstGenericMatrix; + + ConstGenericMatrix m_mat; + const int m_dim; + Vector m_cache; + +public: + // Constructor + SVDTallMatOp(ConstGenericMatrix& mat) : + m_mat(mat), + m_dim(std::min(mat.rows(), mat.cols())), + m_cache(mat.rows()) + {} + + // These are the rows and columns of A' * A + int rows() const { return m_dim; } + int cols() const { return m_dim; } + + // y_out = A' * A * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) + { + MapConstVec x(x_in, m_mat.cols()); + MapVec y(y_out, m_mat.cols()); + m_cache.noalias() = m_mat * x; + y.noalias() = m_mat.transpose() * m_cache; + } +}; + +// Operation of a wide matrix in SVD +// We compute the eigenvalues of A * A' +// MatrixType is either Eigen::Matrix or Eigen::SparseMatrix +template +class SVDWideMatOp: public SVDMatOp +{ +private: + typedef Eigen::Matrix Vector; + typedef Eigen::Map MapConstVec; + typedef Eigen::Map MapVec; + typedef const Eigen::Ref ConstGenericMatrix; + + ConstGenericMatrix m_mat; + const int m_dim; + Vector m_cache; + +public: + // Constructor + SVDWideMatOp(ConstGenericMatrix& mat) : + m_mat(mat), + m_dim(std::min(mat.rows(), mat.cols())), + m_cache(mat.cols()) + {} + + // These are the rows and columns of A * A' + int rows() const { return m_dim; } + int cols() const { return m_dim; } + + // y_out = A * A' * x_in + void perform_op(const Scalar* x_in, Scalar* y_out) + { + MapConstVec x(x_in, m_mat.rows()); + MapVec y(y_out, m_mat.rows()); + m_cache.noalias() = m_mat.transpose() * x; + y.noalias() = m_mat * m_cache; + } +}; + +// Partial SVD solver +// MatrixType is either Eigen::Matrix or Eigen::SparseMatrix +template < typename Scalar = double, + typename MatrixType = Eigen::Matrix > +class PartialSVDSolver +{ +private: + typedef Eigen::Matrix Matrix; + typedef Eigen::Matrix Vector; + typedef const Eigen::Ref ConstGenericMatrix; + + ConstGenericMatrix m_mat; + const int m_m; + const int m_n; + SVDMatOp* m_op; + SymEigsSolver< Scalar, LARGEST_ALGE, SVDMatOp >* m_eigs; + int m_nconv; + Matrix m_evecs; + +public: + // Constructor + PartialSVDSolver(ConstGenericMatrix& mat, int ncomp, int ncv) : + m_mat(mat), m_m(mat.rows()), m_n(mat.cols()), m_evecs(0, 0) + { + // Determine the matrix type, tall or wide + if(m_m > m_n) + { + m_op = new SVDTallMatOp(mat); + } else { + m_op = new SVDWideMatOp(mat); + } + + // Solver object + m_eigs = new SymEigsSolver< Scalar, LARGEST_ALGE, SVDMatOp >(m_op, ncomp, ncv); + } + + // Destructor + virtual ~PartialSVDSolver() + { + delete m_eigs; + delete m_op; + } + + // Computation + int compute(int maxit = 1000, Scalar tol = 1e-10) + { + m_eigs->init(); + m_nconv = m_eigs->compute(maxit, tol); + + return m_nconv; + } + + // The converged singular values + Vector singular_values() const + { + Vector svals = m_eigs->eigenvalues().cwiseSqrt(); + + return svals; + } + + // The converged left singular vectors + Matrix matrix_U(int nu) + { + if(m_evecs.cols() < 1) + { + m_evecs = m_eigs->eigenvectors(); + } + nu = std::min(nu, m_nconv); + if(m_m <= m_n) + { + return m_evecs.leftCols(nu); + } + + return m_mat * (m_evecs.leftCols(nu).array().rowwise() / m_eigs->eigenvalues().head(nu).transpose().array().sqrt()).matrix(); + } + + // The converged right singular vectors + Matrix matrix_V(int nv) + { + if(m_evecs.cols() < 1) + { + m_evecs = m_eigs->eigenvectors(); + } + nv = std::min(nv, m_nconv); + if(m_m > m_n) + { + return m_evecs.leftCols(nv); + } + + return m_mat.transpose() * (m_evecs.leftCols(nv).array().rowwise() / m_eigs->eigenvalues().head(nv).transpose().array().sqrt()).matrix(); + } +}; + + +} // namespace Spectra + +#endif // PARTIAL_SVD_SOLVER_H diff --git a/src/external/minimum_ellipsoid/bnmin_main.h b/src/external/minimum_ellipsoid/bnmin_main.h new file mode 100644 index 00000000..a9a22a93 --- /dev/null +++ b/src/external/minimum_ellipsoid/bnmin_main.h @@ -0,0 +1,87 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +// This file is converted from BNMin1 (https://www.mrao.cam.ac.uk/~bn204/oof/bnmin1.html) by Apostolos Chalkis + +// Original copyright notice: + +/** + Bojan Nikolic + Initial version 2008 + + This file is part of BNMin1 and is licensed under GNU General + Public License version 2. + + \file bnmin_main.cxx + +*/ +#ifndef BNMIN_MAIN_H +#define BNMIN_MAIN_H + +#include +#include + +#include + +//#include "bnmin_main1.h" +//#include "config.h" + +//namespace Minim { + + inline const char * version(void) + { + //return PACKAGE_VERSION; + return "11"; + } + + class BaseErr: + public std::runtime_error + { + public: + BaseErr(const std::string &s): + std::runtime_error(s) + { + } + + }; + + class NParsErr: + public BaseErr + { + public: + NParsErr(const std::string &fname, + size_t expected, + size_t received): + BaseErr( (boost::format("In function %s expected %i but received %i pars ") + % fname + % expected + % received).str()) + { + } + + + }; + + /*BaseErr::BaseErr(const std::string &s): + std::runtime_error(s) + { + } + + NParsErr::NParsErr(const std::string &fname, + size_t expected, + size_t received): + BaseErr( (boost::format("In function %s expected %i but received %i pars ") + % fname + % expected + % received).str()) + { + }*/ + + +#endif + +//} + + diff --git a/src/external/minimum_ellipsoid/khach.h b/src/external/minimum_ellipsoid/khach.h new file mode 100644 index 00000000..88492b8e --- /dev/null +++ b/src/external/minimum_ellipsoid/khach.h @@ -0,0 +1,220 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +// This file is converted from BNMin1 (https://www.mrao.cam.ac.uk/~bn204/oof/bnmin1.html) by Apostolos Chalkis + +// Original copyright notice: + +/** + Bojan Nikolic + Initial version 2010 + + This file is part of BNMin1 and is licensed under GNU General + Public License version 2 + + \file ellipsoids.cxx + + Computation and use of ellipsoids releated to sets of points +*/ +#ifndef KHACH_H +#define KHACH_H + +#include +#include +#include + +//#include "khach1.h" +//#include "mcpoint1.h" +#include "mcpoint.h" +//#include "bnmin_main1.h" +//#include "bnmin_main2.h" + +//#include "../bnmin_main.hxx" + +//namespace Minim { + + template + using MTT = Eigen::Matrix; + + template + using VTT = Eigen::Matrix; + + struct KhachiyanEllipsoid + { + Eigen::Matrix Q; + Eigen::Matrix c; + }; + + template + inline bool is_nan(const Eigen::MatrixBase& x) + { + return ((x.array() == x.array())).all(); + } + + template + bool InvertMatrix(const MTT &input, + MTT &inverse) + { + inverse = input.inverse(); + return !is_nan(inverse); + } + + + inline void InvertLP(const MTT &Lambdap, + MTT &LpInv) + { + bool res = InvertMatrix(Lambdap, LpInv); + if (not res) + { + // throw an error of your choice here! + // throw MatrixErr("Could not invert matrix: ", + // Lambdap); + } + } + + inline void Lift(const MTT &A, MTT &Ap) + { + Ap.resize(A.rows()+1, A.cols()); + Ap.topLeftCorner(A.rows(), A.cols()) = A; + Ap.row(Ap.rows()-1).setConstant(1.0); + } + + inline void genDiag(const VTT &p, MTT &res) + { + res.setZero(p.size(), p.size()); + + for(size_t i=0; i &Ap, + const VTT &p, + MTT &Lambdap) + { + + MTT dp(p.size(), p.size()); + genDiag(p, dp); + + dp = dp * Ap.transpose(); + Lambdap.noalias() = Ap * dp; + } + + inline double KhachiyanIter(const MTT &Ap, VTT &p) + { + /// Dimensionality of the problem + const size_t d = Ap.rows()-1; + + MTT Lp; + MTT M; + KaLambda(Ap, p, Lp); + MTT ILp(Lp.rows(), Lp.cols()); + InvertLP(Lp, ILp); + M.noalias() = ILp * Ap; + M = Ap.transpose() * M; + + double maxval=0; + size_t maxi=0; + for(size_t i=0; i maxval) + { + maxval=M(i,i); + maxi=i; + } + } + const double step_size=(maxval -d - 1)/((d+1)*(maxval-1)); + VTT newp = p*(1-step_size); + newp(maxi) += step_size; + + const double err= (newp-p).norm(); + p = newp; + return err; + + } + + inline void KaInvertDual(const MTT &A, + const VTT &p, + MTT &Q, + VTT &c) + { + const size_t d = A.rows(); + MTT dp(p.size(), p.size()); + genDiag(p, dp); + + MTT PN; + PN.noalias() = dp * A.transpose(); + PN = A * PN; + + VTT M2; + M2.noalias() = A * p; + + MTT M3; + M3.noalias() = M2 * M2.transpose(); + + MTT invert(PN.rows(), PN.cols()); + InvertLP(PN- M3, invert); + Q.noalias() = (invert/d); + c.noalias() = A * p; + + } + + inline double KhachiyanAlgo(const MTT &A, + double eps, + size_t maxiter, + MTT &Q, + VTT &c) + { + VTT p(A.cols()); + p.setConstant(1.0/A.cols()); + + MTT Ap; + Lift(A, Ap); + + double ceps=eps*2; + for (size_t i=0; ieps; ++i) + { + ceps=KhachiyanIter(Ap, p); + } + + KaInvertDual(A, p, Q, c); + + return ceps; + + + } + + inline double KhachiyanAlgo(const std::set &ss, + double eps, + size_t maxiter, + KhachiyanEllipsoid &res) + { + const size_t d=ss.begin()->p.size(); + MTT A(d, ss.size()); + + size_t j=0; + for (std::set::const_iterator i=ss.begin(); + i != ss.end(); + ++i) + { + for(size_t k=0; k p[k]; + ++j; + } + + MTT Q(d,d); + VTT c(d); + + const double ceps=KhachiyanAlgo(A, eps, maxiter, + Q, c); + res.Q=Q; + res.c=c; + return ceps; + } + +#endif + +//} diff --git a/src/external/minimum_ellipsoid/mcpoint.h b/src/external/minimum_ellipsoid/mcpoint.h new file mode 100644 index 00000000..65176143 --- /dev/null +++ b/src/external/minimum_ellipsoid/mcpoint.h @@ -0,0 +1,477 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +// This file is converted from BNMin1 (https://www.mrao.cam.ac.uk/~bn204/oof/bnmin1.html) by Apostolos Chalkis + +// Original copyright notice: + +/** + Bojan Nikolic + Initial version 2009 + + This file is part of BNMin1 and is licensed under GNU General + Public License version 2 + + \file mcpoint.cxx +*/ +#ifndef MCPOINT_H +#define MCPOINT_H + +#include +#include +#include + +#include +#include + +//exclude gsl library Apostolos Chalkis +//#include +//#include + +//#include "mcpoint1.h" +//#include "mcpoint2.h" +#include "bnmin_main.h" +//#include "bnmin_main2.h" + +//namespace Minim { + struct MCPoint + { + /// The actual parameters + std::vector p; + /// Log-likelihood of this point + double ll; + /// A vector to store derived quantities at sample of the + /// distribution + std::vector fval; + + /// Default constructor allowed, fill in the data later + MCPoint(void): + p(0), + ll(-9999), + fval(0) + { + } + + /** \Construct with supplied position vector + */ + MCPoint(const std::vector &p): + p(p), + ll(-9999), + fval(0) + { + } + + /** \brief The parameter vector has n values + */ + MCPoint(size_t np): + p(np), + ll(-9999), + fval(0) + { + } + + MCPoint(const MCPoint &other): + p(other.p), + ll(other.ll), + fval(other.fval) + { + } + + MCPoint & operator=(const MCPoint &other) + { + p=other.p; + ll=other.ll; + fval=other.fval; + return *this; + } + + + }; + + inline bool operator< (const MCPoint &a, const MCPoint &b) + { + return a.ll < b.ll; + } + + struct WPPoint: + public MCPoint + { + /** \brief Weight factor + */ + double w; + + WPPoint(void): + w(0.0) + { + } + + WPPoint(const std::vector &p, + double w): + MCPoint(p), + w(w) + { + } + + /** \brief Construct from MCPoint and a supplied weight + */ + WPPoint(const MCPoint &mp, + double w): + MCPoint(mp), + w(w) + { + } + + }; + + /* + MCPoint::MCPoint(void): + p(0), + ll(-9999), + fval(0) + { + } + + MCPoint::MCPoint(const std::vector &p): + p(p), + ll(-9999), + fval(0) + { + } + + MCPoint::MCPoint(size_t np): + p(np), + ll(-9999), + fval(0) + { + } + + MCPoint::MCPoint(const MCPoint &other): + p(other.p), + ll(other.ll), + fval(other.fval) + { + } + + MCPoint &MCPoint::operator=(const MCPoint &other) + { + p=other.p; + ll=other.ll; + fval=other.fval; + return *this; + }*/ + + + inline void moment1(const std::list &l, + std::vector &res) + { + const size_t n=l.begin()->p.size(); + res=std::vector(n, 0.0); + for(std::list::const_iterator i=l.begin(); + i!= l.end(); + ++i) + { + for (size_t j=0; jp[j] * i->w * exp(- i->ll)); + } + } + } + + inline void moment1(const std::list &l, + double Z, + std::vector &res) + { + moment1(l,res); + for(size_t j=0; j &l, + const std::vector &m1, + std::vector &res) + { + const size_t n=m1.size(); + res=std::vector(n, 0.0); + for(std::list::const_iterator i=l.begin(); + i!= l.end(); + ++i) + { + for (size_t j=0; jp[j]-m1[j],2.0) * i->w * exp(- i->ll)); + } + } + } + + inline void moment2(const std::list &l, + const std::vector &m1, + double Z, + std::vector &res) + { + moment2(l, m1, res); + for(size_t j=0; j &s, + std::vector &res) + { + const size_t n=s.begin()->p.size(); + res=std::vector(n, 0.0); + + size_t N=0; + for(std::set::const_iterator i=s.begin(); + i!= s.end(); + ++i) + { + if(i->p.size() != n) + { + throw NParsErr("moment1", n, i->p.size()); + } + for (size_t j=0; jp[j]); + } + ++N; + } + + for(size_t j=0; j &s, + const std::vector &m1, + std::vector &res) + { + const size_t n=m1.size(); + res=std::vector(n, 0.0); + + size_t N=0; + for(std::set::const_iterator i=s.begin(); + i!= s.end(); + ++i) + { + for (size_t j=0; jp[j]-m1[j], 2); + } + ++N; + } + + for(size_t j=0; j &s, + const std::vector &m1, + std::vector &res) + { + const size_t n=m1.size(); + res=std::vector(n*n, 0.0); + + size_t N=0; + for(std::set::const_iterator i=s.begin(); + i!= s.end(); + ++i) + { + for (size_t j=0; jp[j]-m1[j])*(i->p[k]-m1[k]); + } + } + ++N; + } + + for(size_t j=0; j &s, + std::vector &res) + { + std::vector m1; + moment1(s, m1); + omoment2(s, m1, res); + } + + + inline void StdDev(const std::set &s, + std::vector &res) + { + std::vector m1, m2; + moment1(s, m1); + moment2(s, m1, m2); + res.resize(m2.size()); + for(size_t j=0; j &cv, + std::vector &eigvals, + std::vector &eigvects) + { + const size_t n=sqrt(cv.size()); + gsl_matrix_view m + = gsl_matrix_view_array (const_cast(&cv[0]), n, n); + + gsl_vector *eval = gsl_vector_alloc (n); + gsl_matrix *evec = gsl_matrix_alloc (n, n); + + gsl_eigen_symmv_workspace * w = + gsl_eigen_symmv_alloc (n); + + gsl_eigen_symmv (&m.matrix, + eval, + evec, + w); + + gsl_eigen_symmv_free (w); + + gsl_eigen_symmv_sort (eval, + evec, + GSL_EIGEN_SORT_ABS_ASC); + + eigvals.resize(n); + eigvects.resize(n*n); + for(size_t j=0; j &l, + double Z, + const std::vector &low, + const std::vector &high, + size_t nbins, + std::vector &res) + { + const size_t ndim=low.size(); + + //res.resize(pow(nbins, static_cast(ndim))); + res.resize( static_cast( pow(static_cast(nbins), static_cast(ndim)) ) ); + std::fill(res.begin(), res.end(), 0.0); + + + std::vector deltas(ndim); + for(size_t i=0; i::const_iterator i=l.begin(); + i!= l.end(); + ++i) + { + bool inside=true; + size_t k=0; + for (size_t j=0; jp[j]-low[j])/deltas[j]); + if (dimi >= 0 and dimi < (int)nbins) + { + k+= dimi * static_cast( pow(static_cast(nbins), static_cast(ndim-j-1)) ); + } + else + { + inside=false; + } + } + if (inside) + { + res[k]+= i->w * exp(- i->ll); + } + } + } + + + inline void marginHist(const std::list &l, + size_t pi, + double Z, + double low, + double high, + size_t nbins, + std::vector &res) + { + res.resize(nbins); + std::fill(res.begin(), res.end(), + 0.0); + + const double d=(high-low)/nbins; + for(std::list::const_iterator i=l.begin(); + i!= l.end(); + ++i) + { + int k=int((i->p[pi]-low)/d); + if (k > 0 and k < (int)nbins) + { + res[k]+= i->w * exp(- i->ll); + } + } + + for(size_t i=0; i &l, + double Z, + size_t i, + double ilow, + double ihigh, + size_t j, + double jlow, + double jhigh, + size_t nbins, + std::vector &res) + { + // Two dimensions only + res.resize( static_cast( pow(static_cast(nbins), static_cast(2)) ) ); + std::fill(res.begin(), res.end(), + 0.0); + const double idelta=(ihigh-ilow)/nbins; + const double jdelta=(jhigh-jlow)/nbins; + + for(std::list::const_iterator p=l.begin(); + p!= l.end(); + ++p) + { + + int dimi = int((p->p[i]-ilow)/idelta); + int dimj = int((p->p[j]-jlow)/jdelta); + + if (dimi >= 0 and dimi<((int)nbins) and dimj >= 0 and dimj < ((int)nbins)) + { + const size_t k= dimi*nbins + dimj; + res[k]+= p->w * exp(- p->ll); + } + + } + } +//} + +#endif diff --git a/src/volesti b/src/volesti deleted file mode 160000 index 5bf9188f..00000000 --- a/src/volesti +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5bf9188fb11fddab17de98fabc505e993216ca5f diff --git a/src/volesti/include/SDPAFormatManager.h b/src/volesti/include/SDPAFormatManager.h new file mode 100644 index 00000000..c38e2d59 --- /dev/null +++ b/src/volesti/include/SDPAFormatManager.h @@ -0,0 +1,271 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLESTI_SDPA_FORMAT_MANAGER_H +#define VOLESTI_SDPA_FORMAT_MANAGER_H + + +#include "convex_bodies/spectrahedra/spectrahedron.h" + +#include +#include + + +/// Reads/writes files according to the SDPA format for sdps. +/// Currently supported Format: +/// +/// +/// +/// +/// +/// For example: +/// 2 +/// 3 +/// 1 1 +/// +/// +/// \tparam NT Numerical Type +template +class SdpaFormatManager { +private: + typedef std::string::iterator string_it; + typedef std::list listVector; + + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + + /// Return the first non white space/tab character and advance the iterator one position + /// @param[in, out] it Current position + /// @param[in] end End of string + /// @return First non white space/tab character + char consumeSymbol(string_it &at, string_it & end) { + while (at != end) { + if (*at != ' ' && *at != '\t') { + char c = *at; + at++; + return c; + } + + at++; + } + + return '\0'; + } + + + /// Determine if current line is a comment + /// @param[in] line The current line + /// @return true if line is a comment, false otherwise + bool isCommentLine(std::string & line) { + string_it at = line.begin(); + string_it end = line.end(); + char c = consumeSymbol(at, end); + return c == '"' || c == '*'; + } + + + /// Get an integer from the string + /// \param[in] string + /// \return an integer + int fetchNumber(std::string &string) { + std::stringstream stream(string); + int num; + stream >> num; + return num; + } + + + /// Read a vector of the form {val1, val2, ..., valn} + /// @param string Contains the vector + /// @return a list with the n numbers + listVector readVector(std::string &string) { + std::stringstream stream(string); + listVector vector; + NT value; + + while (stream >> value) { + vector.push_back(value); + } + + return vector; + } + +public: + + /// Reads an SDPA format file + /// \param[in] is An open stram pointing to the file + /// \param[out] matrices the matrices A0, A1, A2, ..., An + /// \param[out] objectiveFunction The objective function of the sdp + void loadSDPAFormatFile(std::ifstream &is, std::vector &matrices, VT &objectiveFunction) { + std::string line; + std::string::size_type sz; + + std::getline(is, line, '\n'); + + //skip comments + while (isCommentLine(line)) { + std::getline(is, line, '\n'); + } + + //read variables number + int variablesNum = fetchNumber(line); + + if (std::getline(is, line, '\n').eof()) + throw std::runtime_error("Unexpected end of file"); + + //read number of blocks + int blocksNum = fetchNumber(line); + + if (std::getline(is, line, '\n').eof()) + throw std::runtime_error("Unexpected end of file"); + + //read block structure vector + listVector blockStructure = readVector(line); + + if (blockStructure.size() != blocksNum) + throw std::runtime_error("Wrong number of blocks"); + + if (std::getline(is, line, '\n').eof()) + throw std::runtime_error("Unexpected end of file"); + + //read constant vector + listVector constantVector = readVector(line); + + while (constantVector.size() < variablesNum) { + if (std::getline(is, line, '\n').eof()) + throw std::runtime_error("Unexpected end of file"); + + listVector t = readVector(line); + constantVector.insert(std::end(constantVector), std::begin(t), std::end(t)); + } + + matrices = std::vector(variablesNum + 1); + int matrixDim = 0; + for (auto x : blockStructure) + matrixDim += std::abs((int) x); + + //read constraint matrices + for (int atMatrix = 0; atMatrix < matrices.size(); atMatrix++) { + MT matrix; + matrix.setZero(matrixDim, matrixDim); + + int offset = 0; + + for (auto blockSize : blockStructure) { + + if (blockSize > 0) { //read a block blockSize x blockSize + int at = 0; + int i = 0, j = 0; + + while (at < blockSize * blockSize) { + if (std::getline(is, line, '\n').eof()) + throw 1; + + listVector vec = readVector(line); + + for (double value : vec) { + matrix(offset + i, offset + j) = value; + at++; + if (at % (int) blockSize == 0) { // new row + i++; + j = 0; + } else { //new column + j++; + } + } + } /* while (at0, I want it <0 + if (atMatrix == 0) //F0 has - before it in SDPA format, the rest have + + matrices[atMatrix] = matrix; + else + matrices[atMatrix] = -1 * matrix; + } + + // return lmi and objective function + objectiveFunction.setZero(variablesNum); + int at = 0; + + for (auto value : constantVector) + objectiveFunction(at++) = value; + } + + /// Create a SDPA format file + /// \param[in] os Open stream to file + /// \param[in] matrices The matrices A0, ..., An + /// \param[in] objectiveFunction The objective function of the sdp + void writeSDPAFormatFile(std::ostream &os, std::vector const & matrices, VT const & objectiveFunction) { + int dim = matrices.size() - 1; + MT A0 = matrices[0]; + + os << dim << "\n"; + os << 1 << "\n"; + os << A0.rows() << "\n"; + + os << objectiveFunction.transpose() << "\n"; + + for (int i = 0; i < A0.rows(); i++) + os << A0.row(i) << "\n"; + + for (int at=1 ; at + void loadSDPAFormatFile(std::ifstream &is, Spectrahedron &spectrahedron, Point &objectiveFunction) { + std::vector matrices; + VT coeffs; + loadSDPAFormatFile(is, matrices, coeffs); + LMI lmi(matrices); + spectrahedron = Spectrahedron(lmi); + objectiveFunction = Point(coeffs); + } + + + /// Write a spectrahedron and a vector (objective function) to a SDPA format output file + /// \tparam Point + /// \param[in] is opened stream to output file + /// \param[in] spectrahedron + /// \param[in] objectiveFunction + template + void writeSDPAFormatFile(std::ostream &os, Spectrahedron const & spectrahedron, Point const & objectiveFunction) { + writeSDPAFormatFile(os, spectrahedron.getLMI().getMatrices(), objectiveFunction.getCoefficients()); + } +}; + + +#endif //VOLESTI_SDPA_FORMAT_MANAGER_H + diff --git a/src/volesti/include/cartesian_geom/cartesian_kernel.h b/src/volesti/include/cartesian_geom/cartesian_kernel.h new file mode 100644 index 00000000..3e1049b2 --- /dev/null +++ b/src/volesti/include/cartesian_geom/cartesian_kernel.h @@ -0,0 +1,39 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2018 Vissarion Fisikopoulos, Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. + +// VolEsti is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// VolEsti is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// See the file COPYING.LESSER for the text of the GNU Lesser General +// Public License. If you did not receive this file along with HeaDDaCHe, +// see . + + +#ifndef CARTESIAN_KERNEL_H +#define CARTESIAN_KERNEL_H + +#include "point.h" + +/// This class represents a cartesian kernel parameterized by a numerical type e.g. double +/// \tparam K Numerical Type +template +class Cartesian +{ +public: + typedef Cartesian Self; + typedef K FT; + typedef point Point; + +}; + +#endif diff --git a/src/volesti/include/cartesian_geom/point.h b/src/volesti/include/cartesian_geom/point.h new file mode 100644 index 00000000..b8c8e32b --- /dev/null +++ b/src/volesti/include/cartesian_geom/point.h @@ -0,0 +1,238 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2018-2020 Vissarion Fisikopoulos, Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef POINT_H +#define POINT_H + +#include +#include + +/// This class manipulates a point parameterized by a number type e.g. double +/// \tparam K Numerical Type +template +class point +{ +private: + unsigned int d; + + Eigen::Matrix coeffs; + typedef typename std::vector::iterator iter; +public: + typedef Eigen::Matrix Coeff; + typedef typename K::FT FT; + + point() {} + + point(const unsigned int dim) + { + d = dim; + coeffs.setZero(d); + } + + point(const unsigned int dim, iter begin, iter endit) + { + d = dim; + coeffs.resize(d); + int i = 0; + + for (iter it=begin ; it != endit ; it++) + coeffs(i++) = *it; + } + + point(const Coeff& coeffs) + { + d = coeffs.rows(); + this->coeffs = coeffs; + } + + point(const unsigned int dim, std::vector cofs) + { + d = dim; + coeffs.resize(d); + iter it = cofs.begin(); + int i=0; + + for (; it != cofs.end(); it++,i++) + coeffs(i) = *it; + + } + + void add(const Coeff& coeffs) + { + this->coeffs += coeffs; + } + + const Coeff& getCoefficients() const + { + return coeffs; + } + + int dimension() const + { + return d; + } + + void set_dimension(const unsigned int dim) + { + d = dim; + coeffs.setZero(d); + } + + void set_coord(const unsigned int i, FT coord) + { + coeffs(i) = coord; + } + + void set_coeffs (const Coeff& coeffs2) { + d = coeffs2.rows(); + coeffs = coeffs2; + } + + void set_to_origin() { + coeffs.setZero(d); + } + + FT operator[] (const unsigned int i) const + { + return coeffs(i); + } + + FT* pointerToData() + { + return coeffs.data(); + } + + FT sum() const { + return coeffs.sum(); + } + + void operator+= (const point& p) + { + coeffs += p.getCoefficients(); + } + + void operator+= (const Coeff& coeffs) + { + this->coeffs += coeffs; + } + + void operator-= (const point& p) + { + coeffs -= p.getCoefficients(); + } + + void operator-= (const Coeff& coeffs) + { + this->coeffs -= coeffs; + } + + void operator= (const Coeff& coeffs) + { + this->coeffs = coeffs; + d = coeffs.rows(); + } + + //TODO: avoid point construction in operators +,-,* + point operator+ (const point& p) const + { + point temp; + temp.d = d; + temp.coeffs = coeffs + p.getCoefficients(); + return temp; + } + + point operator- (const point& p) const + { + point temp; + temp.d = d; + temp.coeffs = coeffs - p.getCoefficients(); + return temp; + } + + point operator* (const FT k) const + { + point temp; + temp.d = d; + temp.coeffs = coeffs * k; + return temp; + } + + void operator*= (const FT k) + { + coeffs *= k; + } + + void operator/= (const FT k) + { + coeffs /= k; + } + + bool operator== (point& p) const + { + int i=0; + const Coeff & coeffs = p.getCoefficients(); + + /* degree of approximation in + "The art of computer programming" (vol II), p. 234, Donald. E. Knuth. */ + FT e = 0.00000000001; + for (i=0 ; icoeffs(i) - coeffs(i)) > e *std::abs(this->coeffs(i)) || + std::abs(this->coeffs(i) - coeffs(i)) > e *std::abs(coeffs(i))) + return false; + } + + return true; + } + + FT distance(point const & p) { + return (this->coeffs - p.coeffs).norm(); + } + + FT dot(const point& p) const + { + return coeffs.dot(p.getCoefficients()); + } + + FT dot(const Coeff& coeffs) const + { + return this->coeffs.dot(coeffs); + } + + FT squared_length() const { + FT lsq = length(); + return lsq * lsq; + } + + FT length() const { + return coeffs.norm(); + } + + void print() const + { + for(unsigned int i=0; i +point operator* (const typename K::FT& k, point const& p) +{ + return p * k; +} + +#endif diff --git a/src/volesti/include/convex_bodies/ball.h b/src/volesti/include/convex_bodies/ball.h new file mode 100644 index 00000000..51dc2647 --- /dev/null +++ b/src/volesti/include/convex_bodies/ball.h @@ -0,0 +1,176 @@ +// volesti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018-19 programs. +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef BALL_H +#define BALL_H + +#include + +/// This class represents a ball parameterized by a point type +/// \tparam Point Point Type +template +class Ball{ +public: + typedef Point PointType; + typedef typename Point::FT NT; + typedef typename std::vector::iterator viterator; + typedef Eigen::Matrix VT; + typedef Eigen::Matrix MT; + + Ball() {} + + Ball(Point cc, NT RR) : c(cc), R(RR) {} + + std::pair InnerBall() const + { + return std::pair(c, R); + } + + Point center() const + { + return c; + } + + NT squared_radius() const + { + return R; + } + + NT radius() const + { + return std::sqrt(R); + } + + int dimension() const + { + return c.dimension(); + } + + int is_in(Point const& p) const + { + if (p.squared_length() <= R) + return -1; + else return 0; + } + + std::pair line_intersect(Point const& r, Point const& v) const + { + + NT vrc(0), v2(0), rc2(0); + + vrc = v.dot(r); + v2 = v.dot(v); + rc2 = r.dot(r); + + NT disc_sqrt = std::sqrt(std::pow(vrc,2) - v2 * (rc2 - R)); + return std::pair ((NT(-1)*vrc + disc_sqrt)/v2, + (NT(-1)*vrc - disc_sqrt)/v2); + } + + std::pair line_intersect(Point const& r, + Point const& v, + const VT &Ar, + const VT &Av) const + { + return line_intersect(r, v); + } + + + std::pair line_intersect(Point const& r, + Point const& v, + const VT &Ar, + const VT &Av, + NT &lambda_prev) const + { + return line_intersect(r, v); + } + + std::pair line_positive_intersect(Point const& r, + Point const& v) const + { + return std::pair(line_intersect(r, v).first, 0); + } + + std::pair line_positive_intersect(Point const& r, + Point const& v, + const VT &Ar, + const VT &Av) const + { + return line_positive_intersect(r, v); + } + + std::pair line_positive_intersect(Point const& r, + Point const& v, + const VT &Ar, + const VT &Av, + NT &lambda_prev) const + { + return line_positive_intersect(r, v); + } + + std::pair line_intersect_coord(Point const& r, + unsigned int const& rand_coord) const + { + + NT vrc = r[rand_coord]; + NT rc2(R); + rc2 -= r.dot(r); + + + NT disc_sqrt = std::sqrt(std::pow(vrc,2) + rc2); + return std::pair (NT(-1)*vrc + disc_sqrt, NT(-1)*vrc - disc_sqrt); + + } + + std::pair line_intersect_coord(Point const& r, + unsigned int const& rand_coord, + const VT &lamdas) const + { + return line_intersect_coord(r, rand_coord); + } + + std::pair line_intersect_coord(Point const& r, + Point const& r_prev, + unsigned int const& rand_coord, + unsigned int const& rand_coord_prev, + const VT &lamdas) const + { + return line_intersect_coord(r, rand_coord); + } + + int num_of_hyperplanes() const + { + return 0; + } + + void compute_reflection (Point& v, Point const& p) const + { + Point s = p; + s *= (1.0 / std::sqrt(s.squared_length())); + s *= (-2.0 * v.dot(s)); + v += s; + } + + template + void compute_reflection (Point &v, Point const& p, update_parameters ¶ms) const { + + params.ball_inner_norm = p.length(); + params.inner_vi_ak = v.dot(p) / params.ball_inner_norm; + v += (p * (-2.0 * params.inner_vi_ak * (1.0 / params.ball_inner_norm))); + } + +private: + Point c; //center + NT R; //SQUARED radius !!! +}; + + +#endif diff --git a/src/volesti/include/convex_bodies/ballintersectconvex.h b/src/volesti/include/convex_bodies/ballintersectconvex.h new file mode 100644 index 00000000..b61ae532 --- /dev/null +++ b/src/volesti/include/convex_bodies/ballintersectconvex.h @@ -0,0 +1,401 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018-19 programs. +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef BALLINTERSECTCONVEX_H +#define BALLINTERSECTCONVEX_H + +/// This class represents a polytope intersected with a ball +/// \tparam Polytope Polytope Type +/// \tparam CBall Ball Type +template +class BallIntersectPolytope { +private: + Polytope P; + CBall B; +public: + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename CBall::NT NT; + typedef typename CBall::PointType PointType; + + BallIntersectPolytope() {} + + BallIntersectPolytope(Polytope& PP, CBall &BB) : P(PP), B(BB) {}; + + Polytope first() const { return P; } + CBall second() const { return B; } + + std::pair InnerBall() const + { + return P.InnerBall(); + } + + MT get_mat() const { + return P.get_mat(); + } + + MT get_T() const { + return P.get_mat(); + } + + MT get_vec() const { + return P.get_vec(); + } + + int is_in(PointType const& p) const + { + if (B.is_in(p)==-1) + return P.is_in(p); + return 0; + } + + int num_of_hyperplanes() const { + return P.num_of_hyperplanes(); + } + + unsigned int dimension() const { + return P.dimension(); + } + + NT radius() const { + return B.radius(); + } + + std::pair line_intersect(PointType const& r, PointType const& v) const + { + + std::pair polypair = P.line_intersect(r, v); + std::pair ballpair = B.line_intersect(r, v); + return std::pair(std::min(polypair.first, ballpair.first), + std::max(polypair.second, ballpair.second)); + } + + std::pair line_intersect(PointType const& r, + PointType const& v, + VT &Ar, + VT &Av) const + { + std::pair polypair = P.line_intersect(r, v, Ar, Av); + std::pair ballpair = B.line_intersect(r, v); + return std::pair(std::min(polypair.first, ballpair.first), + std::max(polypair.second, ballpair.second)); + } + + std::pair line_intersect(PointType const& r, + PointType const& v, + VT &Ar, + VT &Av, + NT &lambda_prev) const + { + std::pair polypair = P.line_intersect(r, v, Ar, Av, lambda_prev); + std::pair ballpair = B.line_intersect(r, v); + return std::pair(std::min(polypair.first, ballpair.first), + std::max(polypair.second, ballpair.second)); + } + + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT &Ar, + VT &Av) + { + std::pair polypair = P.line_positive_intersect(r, v, Ar, Av); + std::pair ball_lambda = B.line_positive_intersect(r, v); + int facet = (polypair.first < ball_lambda.first) ? polypair.second : P.num_of_hyperplanes(); + + return std::pair(std::min(polypair.first, ball_lambda.first), facet); + } + + + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT &Ar, + VT &Av, + NT &lambda_prev) + { + std::pair polypair = P.line_positive_intersect(r, v, Ar, Av, lambda_prev); + std::pair ball_lambda = B.line_positive_intersect(r, v); + int facet = (polypair.first < ball_lambda.first) ? polypair.second : P.num_of_hyperplanes(); + + return std::pair(std::min(polypair.first, ball_lambda.first), facet); + } + + //---------------------accelerated billiard---------------------// + template + std::pair line_first_positive_intersect(PointType const& r, + PointType const& v, + VT& Ar, + VT& Av, + update_parameters& params) + { + std::pair polypair = P.line_first_positive_intersect(r, v, Ar, Av, params); + std::pair ball_lambda = B.line_positive_intersect(r, v); + + params.hit_ball = (polypair.first < ball_lambda.first) ? false : true; + int facet = params.hit_ball ? P.num_of_hyperplanes() : polypair.second; + params.facet_prev = polypair.second; + + return std::pair(std::min(polypair.first, ball_lambda.first), facet); + } + + template + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + MT const& AA, + update_parameters& params) + { + std::pair polypair = P.line_positive_intersect(r, v, Ar, Av, lambda_prev, AA, params); + std::pair ball_lambda = B.line_positive_intersect(r, v); + + params.hit_ball = (polypair.first < ball_lambda.first) ? false : true; + int facet = params.hit_ball ? P.num_of_hyperplanes() : polypair.second; + params.facet_prev = polypair.second; + + return std::pair(std::min(polypair.first, ball_lambda.first), facet); + } + + template + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + update_parameters& params) + { + std::pair polypair = P.line_positive_intersect(r, v, Ar, Av, lambda_prev, params); + std::pair ball_lambda = B.line_positive_intersect(r, v); + + params.hit_ball = (polypair.first < ball_lambda.first) ? false : true; + int facet = params.hit_ball ? P.num_of_hyperplanes() : polypair.second; + params.facet_prev = polypair.second; + + return std::pair(std::min(polypair.first, ball_lambda.first), facet); + } +//-------------------------------------------------------------------------// + + //First coordinate ray shooting intersecting convex body + std::pair line_intersect_coord(PointType const& r, + unsigned int const& rand_coord, + VT &lamdas) const + { + + std::pair polypair = P.line_intersect_coord(r, rand_coord, lamdas); + std::pair ballpair = B.line_intersect_coord(r, rand_coord); + return std::pair(std::min(polypair.first, ballpair.first), + std::max(polypair.second, ballpair.second)); + } + + //Not the first coordinate ray shooting intersecting convex body + std::pair line_intersect_coord(PointType const& r, + PointType const& r_prev, + unsigned int const& rand_coord, + unsigned int const& rand_coord_prev, + VT &lamdas) const + { + std::pair polypair = P.line_intersect_coord(r, r_prev, rand_coord, + rand_coord_prev, lamdas); + std::pair ballpair = B.line_intersect_coord(r, rand_coord); + return std::pair(std::min(polypair.first, ballpair.first), + std::max(polypair.second, ballpair.second)); + } + + std::pair query_dual(PointType const& p, + unsigned int const& rand_coord) + { + std::pair polypair = P.query_dual(p, rand_coord); + std::pair ballpair = B.line_intersect_coord(p, rand_coord); + return std::pair(std::min(polypair.first, ballpair.first), + std::max(polypair.second, ballpair.second)); + } + + void compute_reflection (PointType& v, PointType const& p, int &facet) + { + + if (facet == P.num_of_hyperplanes()) { + B.compute_reflection(v, p); + } else { + P.compute_reflection(v, p, facet); + } + + } + + template + void compute_reflection (PointType &v, PointType const& p, update_parameters ¶ms) + { + if (params.hit_ball) { + B.compute_reflection(v, p, params); + } else { + P.compute_reflection(v, p, params); + } + } + +}; + + +/* EXPERIMENTAL +template +class PolytopeIntersectEllipsoid { +private: + T1 P; + T2 E; + typedef typename T2::K K; +public: + PolytopeIntersectEllipsoid(T1 &Pin, T2 &Ein) : P(Pin), E(Ein) {}; + + T1 first() { return P; } + T2 second() { return E; } + + int is_in(Point p){ + //std::cout << "calling is in"< line_intersect(Point r, + Point v){ + + std::pair polypair = P.line_intersect(r,v); + std::pair returnpair; + std::pair ellpair; + bool ellinter=false; + + //check the first intersection point if it is inside ball + if(E.is_in(polypair.first)){ + returnpair.first = polypair.first; + }else{ + ellinter=true; + //compute the intersection with ball + ellpair = E.line_intersect(r,v); + returnpair.first = ellpair.first; + } + //check the second intersection point + if(E.is_in(polypair.second)){ + returnpair.second = polypair.second; + }else{ + if(ellinter) //if the intersection with ball is already computed + returnpair.second = ellpair.second; + else returnpair.second = (E.line_intersect(r,v)).second; + } + return returnpair; + } + + std::pair line_intersect_coord(Point &r, + Point &r_prev, + int rand_coord, + int rand_coord_prev, + std::vector &lamdas, + bool init + ){ + + std::pair polypair = P.line_intersect_coord(r,r_prev,rand_coord,rand_coord_prev,lamdas,init); + std::pair ellpair = E.line_intersect_coord(r,rand_coord); + return std::pair (std::min(polypair.first,ellpair.first), + std::max(polypair.second,ellpair.second)); + } + +}; + + +template +class BallPolyIntersectEll { +private: + T1 BP; + T2 E; + typedef typename T2::K K; +public: + BallPolyIntersectEll(T1 &BPin, T2 &Ein) : BP(BPin), E(Ein) {}; + + T1 first() { return BP; } + T2 second() { return E; } + + int is_in(Point p){ + //std::cout << "calling is in"< line_intersect(Point r, + Point v){ + + std::pair Bpolypair = BP.line_intersect(r,v); + std::pair returnpair; + std::pair ellpair; + bool ellinter=false; + + //check the first intersection point if it is inside ball + if(E.is_in(Bpolypair.first)){ + //std::cout<<"inside ball 1, radius:"<<_B.radius()< line_intersect_coord(Point &r, + Point &r_prev, + int rand_coord, + int rand_coord_prev, + std::vector &lamdas, + bool init + ){ + + std::pair Bpolypair = BP.line_intersect_coord(r,r_prev,rand_coord,rand_coord_prev,lamdas,init); + std::pair ellpair = E.line_intersect_coord(r,rand_coord); + return std::pair (std::min(Bpolypair.first,ellpair.first), + std::max(Bpolypair.second,ellpair.second)); + } + + + +};*/ + +#endif diff --git a/src/volesti/include/convex_bodies/barriers.h b/src/volesti/include/convex_bodies/barriers.h new file mode 100644 index 00000000..977e173f --- /dev/null +++ b/src/volesti/include/convex_bodies/barriers.h @@ -0,0 +1,52 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + + +struct LogarithmicBarrierAugmenter { + + template + struct LogarithmicBarrierObjective { + typedef HPolytope Hpolytope; + typedef typename Point::FT NT; + + func f; + Hpolytope &P; + bool enable; + + LogarithmicBarrierObjective(func f_, Hpolytope P_, bool enable_) : + f(f_), P(P_), enable(enable_) {} + + NT operator() (Point &x) { + if (enable) return f(x) + P.log_barrier(x); + else return f(x); + } + + }; + + template + struct LogarithmicBarrierGradient { + typedef HPolytope Hpolytope; + typedef typename Point::FT NT; + + func f; + Hpolytope &P; + bool enable; + + LogarithmicBarrierGradient(func f_, Hpolytope P_, bool enable_) : + f(f_), P(P_), enable(enable_) {} + + Point operator() (Point &x) { + if (enable) return f(x) + P.grad_log_barrier(x); + else return f(x); + } + + }; + +}; diff --git a/src/volesti/include/convex_bodies/convex_body.h b/src/volesti/include/convex_bodies/convex_body.h new file mode 100644 index 00000000..e70e9e66 --- /dev/null +++ b/src/volesti/include/convex_bodies/convex_body.h @@ -0,0 +1,109 @@ +#ifndef CONVEX_BODY_H +#define CONVEX_BODY_H + +#include +#include +#include +#include +#include + +/// This class represents a general convex body parameterized by a point type +/// \tparam Point Point type +template +class ConvexBody { +public: + typedef Point PointType; + typedef typename Point::FT NT; + typedef typename std::vector::iterator viterator; + //using RowMatrixXd = Eigen::Matrix; + //typedef RowMatrixXd MT; + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + typedef std::function func; + typedef std::function grad; +private: + unsigned int dim; //dimension + std::vector gs; // convex functions defining the convex body + std::vector grad_gs; // convex function gradients + unsigned int m; + NT tol = NT(1e-4); + + +public: + + ConvexBody() : m(0) {} + + ConvexBody(std::vector gs_, std::vector grad_gs_, unsigned int dim_) : + gs(gs_), grad_gs(grad_gs_), dim(dim_) + { + m = gs.size(); + } + + unsigned int dimension() { + return dim; + } + + // Compute positive line intersection (in [0, 1]) using Binary search + // x: starting point + // v: direction (ray) + std::pair line_positive_intersect(Point const& x, Point const &v) const { + NT t_min = NT(1); + int constraint = -1; + NT t; + for (unsigned int i = 0; i < m; i++) { + t = binary_search(x, v, gs[i]); + if (t < t_min) { + t_min = t; + constraint = i; + } + } + + return std::make_pair(t_min, constraint); + } + + NT binary_search(Point const &x, Point const &v, func const& f) const { + NT t_min = NT(0); + NT t_max = NT(1); + NT t; + NT value; + + while (t_max - t_min > tol) { + t = (t_max + t_min) / 2; + value = f(x + t * v); + if (value >= -tol && value <= 0) { + return t; + } else if (value < -tol) { + t_min = t; + } else { + t_max = t; + } + + } + + return t; + } + + + // Computes unit normal at point p of the boundary + Point unit_normal(Point const& p, int const& constraint) const { + Point n = grad_gs[constraint](p); + return (1 / n.length()) * n; + } + + // Computes reflection of v about point p on the boundary + void compute_reflection(Point &v, Point const& p, int const& constraint) const { + Point n = unit_normal(p, constraint); + v += -2 * v.dot(n) * n; + } + + // Check if point is in K + int is_in(Point const& p, NT tol=NT(0)) { + for (func g : gs) { + if (g(p) > NT(-tol)) return 0; + } + return -1; + } + +}; + +#endif diff --git a/src/volesti/include/convex_bodies/correlation_matrices/corre_matrix.hpp b/src/volesti/include/convex_bodies/correlation_matrices/corre_matrix.hpp new file mode 100755 index 00000000..23f4ecbe --- /dev/null +++ b/src/volesti/include/convex_bodies/correlation_matrices/corre_matrix.hpp @@ -0,0 +1,161 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +// Contributed by Huu Phuoc Le as part of Google Summer of Code 2022 program + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLESTI_CONVEX_BODIES_CORRELATION_MATRICES_CORRE_MATRIX_HPP +#define VOLESTI_CONVEX_BODIES_CORRELATION_MATRICES_CORRE_MATRIX_HPP + +/// This class handles the PointType used by CorreSpectra_MT class. +/// Every point is a correlation matrix and only the lower triangular part is stored. +/// @tparam NT Number Type +template +class CorreMatrix{ + public: + + /// The numeric/matrix/vector types we use + typedef NT FT; + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + typedef Eigen::Matrix Coeff; + + MT mat; + + CorreMatrix(){} + + CorreMatrix(unsigned int n){ + mat = MT::Identity(n,n); + } + + CorreMatrix(MT const& mat){ + this->mat = mat; + } + + CorreMatrix(VT const& coeffs){ + unsigned int n = ceil(sqrt(2*coeffs.rows())); + this->mat = MT::Identity(n,n); + int ind = 0; + for(int i = 0; i < n; ++i){ + for(int j = 0; j < i; ++j){ + this->mat(i,j) = coeffs(ind); + ++ind; + } + } + } + + int dimension() const { + int n = this->mat.rows(); + return n*(n-1)/2; + } + + void operator+= (const CorreMatrix & p){ + this->mat += p.mat; + } + + void operator-= (const CorreMatrix & p){ + this->mat -= p.mat; + } + + void operator= (const CorreMatrix & p){ + this->mat = p.mat; + } + + CorreMatrix operator+ (const CorreMatrix& p) const { + CorreMatrix temp; + temp.mat = this->mat + p.mat; + return temp; + } + + + CorreMatrix operator- () const { + CorreMatrix temp; + temp.mat = - this->mat; + return temp; + } + + void operator*= (const FT k){ + this->mat *= k; + } + + CorreMatrix operator* (const FT k) const { + MT M = this->mat; + M *= k; + return CorreMatrix(M); + } + + void operator/= (const FT k){ + this->mat /= k; + } + + NT dot(MT grad){ + int i, j, n = this->mat.rows(); + NT ret = NT(0); + for(i = 0; i < n ; ++i){ + for(j = 0; j < i; ++j){ + ret += this->mat(i,j) * grad(i,j); + } + } + return ret; + } + + NT dot(CorreMatrix c){ + int i, j, n = this->mat.rows(); + NT ret = NT(0); + for(i = 0; i < n ; ++i){ + for(j = 0; j < i; ++j){ + ret += this->mat(i,j) * c.mat(i,j); + } + } + return ret; + } + + NT squared_length() const { + int i, j, n = this->mat.rows(); + NT ret = NT(0); + for(i = 0; i < n ; ++i){ + for(j = 0; j < i; ++j){ + ret += this->mat(i,j) * this->mat(i,j); + } + } + return ret; + } + + void print() const { + int n = this->mat.rows(), i, j; + for(i = 0; i < n ; ++i){ + for(j = 0; j < i; ++j){ + std::cout<< this->mat(i,j) <<" "; + } + } + std::cout<<"\n"; + } + + VT getCoefficients() const { + int n = this->mat.rows(), ind = 0, i, j; + VT coeff(n*(n-1)/2); + for(i = 0; i < n ; ++i){ + for(j = 0; j < i; ++j){ + coeff(ind) = this->mat(i,j); + ++ind; + } + } + return coeff; + } +}; + +template +CorreMatrix operator* (const NT k, CorreMatrix p){ + return p * k; +} + +template +std::ostream& operator<<(std::ostream& os, const CorreMatrix& p){ + os << p.mat; + return os; +} + +#endif //VOLESTI_CONVEX_BODIES_CORRELATION_MATRICES_CORRE_MATRIX_HPP diff --git a/src/volesti/include/convex_bodies/correlation_matrices/correlation_spectrahedron.hpp b/src/volesti/include/convex_bodies/correlation_matrices/correlation_spectrahedron.hpp new file mode 100755 index 00000000..a615b33c --- /dev/null +++ b/src/volesti/include/convex_bodies/correlation_matrices/correlation_spectrahedron.hpp @@ -0,0 +1,266 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +// Contributed by Huu Phuoc Le as part of Google Summer of Code 2022 program + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLESTI_CONVEX_BODIES_CORRELATION_MATRICES_VOLESTI_CORRELATION_SPECTRAHEDRON_HPP +#define VOLESTI_CONVEX_BODIES_CORRELATION_MATRICES_VOLESTI_CORRELATION_SPECTRAHEDRON_HPP + +template +struct Precompute{ + + /// These flags indicate whether the corresponding matrices are computed + bool computed_A = false; + bool computed_B = false; + + /// The matrices the method positiveIntersection receives from its previous call + /// if the flag first_positive_intersection is true. + /// Matrix A is also used in coordinateIntersection + MT A, B; + + /// In method positive_intersect, the distance we are computing corresponds + /// to the minimum positive eigenvalue of a quadratic eigenvalue problem. + /// This will hold the eigenvector for that eigenvalue + VT eigenvector; + + /// Sets all flags to false + void resetFlags(){ + computed_A = computed_B = false; + } + + void set_mat_size(int const& n){ + A = -MT::Identity(n,n); + B.setZero(n, n); + eigenvector.setZero(n); + } +}; + +/// This class handles the spectrahedra of correlation matrices +/// The PointType here is stored as vector. +/// For the matrix PointType class, refer to CorrelationSpectrahedron_MT +/// @tparam Point Point Type +template +class CorrelationSpectrahedron : public Spectrahedron{ + public: + + /// The numeric/matrix/vector types we use + typedef Point PointType; + typedef typename Point::FT NT; + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + typedef Precompute PrecomputationOfValues; + + /// The size of the matrix + unsigned int n; + + PrecomputationOfValues _precomputedValues; + + /// Constructor of correlation matrix spectrahedra + + CorrelationSpectrahedron(unsigned int n){ + int i,j; + this->n = n; + this->d = n*(n-1)/2; + this->_inner_ball.first = PointType(this->d); + this->_inner_ball.second = 1/std::sqrt(this->d); + _precomputedValues.set_mat_size(n); + } + + /// \returns The size of the matrix + unsigned int matrixSize() const { + return n; + } + + std::pair getInnerBall() const { + return this->_inner_ball; + } + + /// Build a correlation matrix from a vector of entries + /// \param[in] vector of coefficients + /// \param[in] the matrix to be assigned + void buildMatrix(VT const &pvector, unsigned int const n, MT &mat) const { + NT coeff; + int i, j, ind = 0; + for(i = 0; i < n ; ++i){ + mat(i,i) = -1; + } + for(i = 0; i < n ; ++i){ + for(j = 0; j < i; ++j){ + coeff = -pvector[ind]; + mat(i,j) = mat(j,i) = coeff; + ++ind; + } + } + } + + /// Computes the reflected direction at a point on the boundary of the spectrahedron. + /// \param[in] r A point on the boundary of the spectrahedron + /// \param[in] v The direction of the trajectory as it hits the boundary + /// \param[out] reflectedDirection The reflected direction + template + void compute_reflection(PointType &v, PointType const &r, update_parameters&) const { + VT grad(this->d); + VT e = _precomputedValues.eigenvector; + int i, j, ind = 0; + NT sum_sq = NT(0); + + for(i = 0; i < n ; ++i){ + for(j = 0; j < i; ++j){ + grad(ind) = e[i]*e[j]; + sum_sq += grad(ind)*grad(ind); + ++ind; + } + } + NT dot = v.dot(grad); + dot = 2 * dot / sum_sq; + v -= dot * PointType(grad); + } + + /// Construct the generalized eigenvalue problem \[Bt - A \] for positive_intersect. + /// \param[in] p Input vector + /// \param[in] v Input vector + /// \param[in, out] _precomputedValues Holds matrices B = I - A(v), A = A(p) + void createMatricesForPositiveLinearIntersection(VT const &p, VT const &v){ + if (true) { + VT pvector = p, vvector = v; + NT coeff; + int i, j, ind =0; + for(i = 0; i < n ; ++i){ + for(j = 0; j < i; ++j){ + coeff = -pvector[ind]; + _precomputedValues.A(i,j) = _precomputedValues.A(j,i) = coeff; + coeff = -vvector[ind]; + _precomputedValues.B(i,j) = _precomputedValues.B(j,i) = coeff; + ++ind; + } + } + _precomputedValues.computed_B = true; + } + } + + NT positiveLinearIntersection(VT const &p, VT const &v){ + createMatricesForPositiveLinearIntersection(p, v); + return this->EigenvaluesProblem.minPosLinearEigenvalue_EigenSymSolver(-_precomputedValues.A, _precomputedValues.B, _precomputedValues.eigenvector); + } + + // compute intersection point of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair line_positive_intersect(PointType const &r, PointType const &v){ + NT pos_inter = positiveLinearIntersection(r.getCoefficients(), v.getCoefficients()); + return std::pair (pos_inter, -1); + } + + std::pair line_positive_intersect(PointType const &r, + PointType const &v, + VT&, + VT& , + NT const&){ + return line_positive_intersect(r, v); + } + + // compute intersection point of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair line_positive_intersect(PointType const &r, + PointType const &v, + VT&, + VT&){ + return line_positive_intersect(r, v); + } + + template + std::pair line_positive_intersect(PointType const &r, + PointType const &v, + VT&, + VT& , + NT const&, + update_parameters&){ + return line_positive_intersect(r, v); + } + + template + std::pair line_positive_intersect(PointType const &r, + PointType const &v, + VT&, + VT&, + NT const&, + MT const&, + update_parameters&){ + return line_positive_intersect(r, v); + } + + template + std::pair line_first_positive_intersect(PointType const &r, + PointType const &v, + VT&, + VT&, + update_parameters&){ + return line_positive_intersect(r, v); + } + + // compute intersection point of ray starting from r and pointing to v + std::pair line_intersect(PointType const &r, PointType const &v){ + createMatricesForPositiveLinearIntersection(r.getCoefficients(), v.getCoefficients()); + return this->EigenvaluesProblem.symGeneralizedProblem(_precomputedValues.A, _precomputedValues.B); + } + + + std::pair line_intersect(PointType const &r, + PointType const &v, + VT&, + VT&){ + return line_intersect(r, v); + } + + std::pair line_intersect(PointType const &r, + PointType const &v, + VT&, + VT&, + NT&){ + return line_intersect(r, v); + } + + /// Compute the gradient of the determinant of the LMI at p + /// \param[in] p Input parameter + /// \param[in] Input vector: the eigenvector A(p)*e = 0 + /// \param[out] ret The unit normal vector at p + void unit_normal(VT p, VT const &e, VT &ret) const { + int i, j, ind = 0; + NT sum_sqqrt_sq = NT(0); + for(i = 0; i < n ; ++i){ + for(j = 0; j < i; ++j){ + ret(ind) = e[i]*e[j]; + sum_sqqrt_sq += ret(ind)*ret(ind); + ++ind; + } + } + ret /= std::sqrt(sum_sqqrt_sq); //normalize + } + + /// Test if a point p is in the spectrahedron + /// \param p is the current point + /// \return true if position is outside the spectrahedron + int is_in(PointType const &p, NT tol=NT(0)) const { + if(isExterior(p.getCoefficients())) return 0; + return -1; + } + + bool isExterior(VT const &pos) const { + MT mat = MT(n, n); + buildMatrix(pos, n, mat); + return isExterior(mat); + } + + bool isExterior(MT const &mat) const { + return !this->EigenvaluesProblem.isPositiveSemidefinite(-mat); + } + + MT get_mat() const { + return MT::Identity(this->d, this->d); + } +}; + +#endif //VOLESTI_CONVEX_BODIES_CORRELATION_MATRICES_VOLESTI_CORRELATION_SPECTRAHEDRON_HPP \ No newline at end of file diff --git a/src/volesti/include/convex_bodies/correlation_matrices/correlation_spectrahedron_MT.hpp b/src/volesti/include/convex_bodies/correlation_matrices/correlation_spectrahedron_MT.hpp new file mode 100755 index 00000000..d3f754a5 --- /dev/null +++ b/src/volesti/include/convex_bodies/correlation_matrices/correlation_spectrahedron_MT.hpp @@ -0,0 +1,180 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +// Contributed by Huu Phuoc Le as part of Google Summer of Code 2022 program + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLESTI_CONVEX_BODIES_CORRELATION_MATRICES_VOLESTI_CORRELATION_SPECTRAHEDRON_MT_HPP +#define VOLESTI_CONVEX_BODIES_CORRELATION_MATRICES_VOLESTI_CORRELATION_SPECTRAHEDRON_MT_HPP + +#include "convex_bodies/correlation_matrices/corre_matrix.hpp" + +/// This class handles the spectrahedra of correlation matrices +/// @tparam CorreMatrix The Correlation Matrix +template +class CorrelationSpectrahedron_MT : public Spectrahedron{ + public: + + /// The numeric/matrix/vector types we use + typedef CorreMatrix PointType; + typedef typename PointType::FT NT; + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + + /// The size of the matrix + unsigned int n; + + VT eigenvector; + + /// Constructor of correlation matrix spectrahedra + + CorrelationSpectrahedron_MT(unsigned int n){ + int i,j; + this->n = n; + this->d = n*(n-1)/2; + this->_inner_ball.first = PointType(this->d); + this->_inner_ball.second = 1/std::sqrt(this->d); + this->eigenvector.setZero(n); + } + + /// \returns The size of the matrix + unsigned int matrixSize() const { + return n; + } + + std::pair getInnerBall() const { + return this->_inner_ball; + } + + /// Computes the reflected direction at a point on the boundary of the spectrahedron. + /// \param[in] r A point on the boundary of the spectrahedron + /// \param[in] v The direction of the trajectory as it hits the boundary + /// \param[out] reflectedDirection The reflected direction + template + void compute_reflection(PointType &v, PointType const &r, update_parameters&) const { + MT grad = MT::Zero(this->n, this->n); + int i, j; + NT sum_sq = NT(0), dot = NT(0); + + for(i = 0; i < n ; ++i){ + for(j = 0; j < i; ++j){ + grad(i,j) = eigenvector[i]*eigenvector[j]; + sum_sq += grad(i,j)*grad(i,j); + dot += grad(i,j) * v.mat(i,j); + } + } + dot = 2 * dot / sum_sq; + grad = dot*grad; + v -= PointType(grad); + } + + /// Computes the minimal positive t s.t. r+t*v intersects the boundary of the spectrahedron + /// \param[in] r + /// \param[in] v + /// \param[out] a NT value t + NT positiveLinearIntersection(PointType const &r, PointType const &v){ + + // minPosLinearEigenvalue_EigenSymSolver(A,B) computes the minimal positive eigenvalue of A-t*B + + return this->EigenvaluesProblem.minPosLinearEigenvalue_EigenSymSolver(r.mat, (-v).mat, eigenvector); + } + + // compute intersection point of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair line_positive_intersect(PointType const &r, + PointType const &v) { + NT pos_inter = positiveLinearIntersection(r, v); + return std::pair (pos_inter, -1); + } + + std::pair line_positive_intersect(PointType const &r, + PointType const &v, + VT&, + VT& , + NT const&){ + return line_positive_intersect(r, v); + } + + // compute intersection point of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair line_positive_intersect(PointType const &r, + PointType const &v, + VT&, + VT&){ + return line_positive_intersect(r, v); + } + + template + std::pair line_positive_intersect(PointType const &r, + PointType const &v, + VT&, + VT& , + NT const&, + update_parameters&){ + return line_positive_intersect(r, v); + } + + template + std::pair line_positive_intersect(PointType const &r, + PointType const &v, + VT&, + VT&, + NT const&, + MT const&, + update_parameters&){ + return line_positive_intersect(r, v); + } + + template + std::pair line_first_positive_intersect(PointType const &r, + PointType const &v, + VT&, + VT&, + update_parameters&){ + return line_positive_intersect(r, v); + } + + // compute intersection point of ray starting from r and pointing to v + std::pair line_intersect(PointType const &r, PointType const &v) const { + return this->EigenvaluesProblem.symGeneralizedProblem(-r.mat, -v.mat); + } + + std::pair line_intersect(PointType const &r, + PointType const &v, + VT&, + VT&) const { + return line_intersect(r, v); + } + + std::pair line_intersect(PointType const &r, + PointType const &v, + VT&, + VT&, + NT&) const { + return line_intersect(r, v); + } + + + /// Test if a point p is in the spectrahedron + /// \param p is the current point + /// \return true if position is outside the spectrahedron + int is_in(PointType const &p, NT tol=NT(0)) const { + if(this->EigenvaluesProblem.isPositiveSemidefinite(p.mat)){ + return -1; + } + return 0; + } + + bool isExterior(MT const &mat) const { + return !this->EigenvaluesProblem.isPositiveSemidefinite(mat); + } + + MT get_mat() const { + return MT::Identity(this->d, this->d); + } +}; + +#endif //VOLESTI_CONVEX_BODIES_CORRELATION_MATRICES_VOLESTI_CORRELATION_SPECTRAHEDRON_MT_HPP diff --git a/src/volesti/include/convex_bodies/ellipsoid.h b/src/volesti/include/convex_bodies/ellipsoid.h new file mode 100644 index 00000000..18c7570c --- /dev/null +++ b/src/volesti/include/convex_bodies/ellipsoid.h @@ -0,0 +1,281 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis +// Copyright (c) 2021 Vaibhav Thakkar + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. +//Contributed and/or modified by Vaibhav Thakkar, as part of Google Summer of Code 2021 program. + +// Licensed under GNU LGPL.3, see LICENCE file + + +#ifndef ELLIPSOIDS_H +#define ELLIPSOIDS_H + +#include +#include +#include "volume/math_helpers.hpp" + +/// This class represents an ellipsoid parameterized by a point type +/// \tparam Point Point type +template +class Ellipsoid{ +public: +typedef Point PointType; + typedef typename Point::FT NT; + typedef typename std::vector::iterator viterator; + typedef Eigen::Matrix VT; + typedef Eigen::Matrix MT; + +private: + // representation is x'A x <= 1, i.e center is always assumed to be origin. + MT A; + + unsigned int _dim; + MT _L_cov; // LL' = inv(A) for sampling procedures + + // eigen vectors and values + VT _eigen_values; + VT _eigen_values_inv; + VT _eigen_values_inv_sqrt; + MT _Eigen_Vectors; + +public: + + Ellipsoid() {} + + // TODO(vaithak): Add a flag for telling whether the matrix passed is already inverse + Ellipsoid(MT& Ain) : A(Ain) { + Eigen::SelfAdjointEigenSolver eigensolver(A); + if (eigensolver.info() != Eigen::Success) { + throw std::runtime_error("Eigen solver returned error!"); + } + + _eigen_values = eigensolver.eigenvalues(); + _Eigen_Vectors = eigensolver.eigenvectors(); + + _eigen_values_inv = _eigen_values.array().inverse().matrix(); + _eigen_values_inv_sqrt = _eigen_values_inv.array().sqrt().matrix(); + + _dim = A.rows(); + + Eigen::LLT lltOfA(A.inverse()); // compute the Cholesky decomposition of inv(A) + if (lltOfA.info() != Eigen::Success) { + throw std::runtime_error("Cholesky decomposition failed!"); + } + _L_cov = lltOfA.matrixL(); + } + + + // Constructor for copula ellipsoid only + Ellipsoid(std::vector >& Ain) { + _dim = Ain.size(); + A.resize(_dim, _dim); + for (unsigned int i = 0; i < Ain.size(); i++) { + for (unsigned int j = 0; j < Ain.size(); j++) { + A(i,j) = Ain[i][j]; + } + } + } + + + NT radius() const { + return _eigen_values_inv_sqrt(0); + } + + + VT eigenvalues() const { + return _eigen_values; + } + + + VT eigenvalues_inv() const { + return _eigen_values_inv; + } + + + VT eigenvalues_inv_sqrt() const { + return _eigen_values_inv_sqrt; + } + + + MT eigenvectors() const { + return _Eigen_Vectors; + } + + + unsigned int dimensions() const { + return _dim; + } + + + MT Lcov() const { + return _L_cov; + } + + + // return L_cov * x + VT mult_Lcov(VT const& x) const { + return _L_cov.template triangularView() * x; + } + + + void print() const { + std::cout << "Ellipse is in the form: x' A x <= 1, (center is assumed to be origin always) \n"; + std::cout << "A = \n" << A; + } + + + + NT mat_mult(Point const& p) const { + VT x = p.getCoefficients(); + return x.transpose() * A.template selfadjointView() * x; + } + + + VT vec_mult(VT const& b) const { + return A.template selfadjointView()*b; + } + + + NT log_volume() const { + NT ball_log_vol = (NT(_dim)/NT(2) * std::log(M_PI)) - log_gamma_function(NT(_dim) / NT(2) + 1); + NT det_factor = std::log( _eigen_values_inv_sqrt.prod() ); + + return det_factor + ball_log_vol; + } + + + void scale(NT scale_factor) { + assert (scale_factor > 0); + + NT scale_factor_sq = scale_factor * scale_factor; + NT inv_scale_factor = (NT(1.0) / scale_factor); + NT inv_scale_factor_sq = (NT(1.0) / scale_factor_sq); + + _eigen_values = inv_scale_factor_sq * _eigen_values; + _eigen_values_inv = scale_factor_sq * _eigen_values_inv; + _eigen_values_inv_sqrt = scale_factor * _eigen_values_inv_sqrt; + _L_cov = scale_factor * _L_cov; + + A = inv_scale_factor_sq * A; // as volume depends on square root of it's determinant + } + + + int is_in(Point const& p) const { + return mat_mult(p) > 1 ? 0 : -1; + } + + + // compute intersection point of ray starting from r and pointing to v + std::pair line_intersect(Point const& r, Point const& v) const { + // constants of a quadratic equation + NT a_q = mat_mult(v); + NT b_q = 2 * r.getCoefficients().dot(vec_mult(v.getCoefficients())); + NT c_q = mat_mult(r) - 1; + + NT D = std::pow(b_q, 2) - 4*a_q*c_q; + return std::pair ( (-b_q + std::sqrt(D))/(2*a_q) , (-b_q - std::sqrt(D))/(2*a_q) ); + } + + + std::pair line_intersect(Point const& r, + Point const& v, + const VT &Ar, + const VT &Av) const + { + return line_intersect(r, v); + } + + + std::pair line_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT &lambda_prev) const + { + return line_intersect(r, v); + } + + + std::pair line_positive_intersect(Point const& r, + Point const& v) const + { + NT res = line_intersect(r, v).first; + return std::pair(res, 0); + } + + + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av) const + { + return line_positive_intersect(r, v); + } + + + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev) const + { + return line_positive_intersect(r, v); + } + + + // Compute the intersection of a coordinate ray + std::pair line_intersect_coord(Point const& r, const unsigned int rand_coord) const { + NT a_q = A(rand_coord, rand_coord); + NT b_q = 2 * r.getCoefficients().dot(A.col(rand_coord)); + NT c_q = mat_mult(r) - 1; + + NT D = std::pow(b_q, 2) - 4*a_q*c_q; + return std::pair ( (-b_q + std::sqrt(D))/(2*a_q) , (-b_q - std::sqrt(D))/(2*a_q) ); + } + + + std::pair line_intersect_coord(Point const& r, + unsigned int const& rand_coord, + VT& lamdas) const + { + return line_intersect_coord(r, rand_coord); + } + + + std::pair line_intersect_coord(Point const& r, + Point const& r_prev, + unsigned int const& rand_coord, + unsigned int const& rand_coord_prev, + VT& lamdas) const + { + return line_intersect_coord(r, rand_coord); + } + + + void compute_reflection (Point& v, Point const& p) const + { + // normal vector is Ap + Point s(vec_mult(p.getCoefficients())); + s *= (1.0 / s.length()); + s *= (-2.0 * v.dot(s)); + v += s; + } + + + template + void compute_reflection (Point& v, Point const& p, update_parameters ¶ms) const + { + // normal vector is Ap + Point s(vec_mult(p.getCoefficients())); + params.ball_inner_norm = s.length(); + + params.inner_vi_ak = v.dot(s) / params.ball_inner_norm; + v += (s * (-2.0 * params.inner_vi_ak * (1.0 / params.ball_inner_norm))); + } +}; + +#endif \ No newline at end of file diff --git a/src/volesti/include/convex_bodies/hpolytope.h b/src/volesti/include/convex_bodies/hpolytope.h new file mode 100644 index 00000000..49d94282 --- /dev/null +++ b/src/volesti/include/convex_bodies/hpolytope.h @@ -0,0 +1,927 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018-19 programs. +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef HPOLYTOPE_H +#define HPOLYTOPE_H + +#include +#include +#include +#include "preprocess/max_inscribed_ball.hpp" +#include "root_finders/quadratic_polynomial_solvers.hpp" +#ifndef DISABLE_LPSOLVE + #include "lp_oracles/solve_lp.h" +#endif + + + +// check if an Eigen vector contains NaN or infinite values +template +bool is_inner_point_nan_inf(VT const& p) +{ + typedef Eigen::Array VTint; + VTint a = p.array().isNaN(); + for (int i = 0; i < p.rows(); i++) { + if (a(i) || std::isinf(p(i))){ + return true; + } + } + return false; +} + +/// This class describes a polytope in H-representation or an H-polytope +/// i.e. a polytope defined by a set of linear inequalities +/// \tparam Point Point type +template +class HPolytope { +public: + typedef Point PointType; + typedef typename Point::FT NT; + typedef typename std::vector::iterator viterator; + //using RowMatrixXd = Eigen::Matrix; + //typedef RowMatrixXd MT; + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + +private: + unsigned int _d; //dimension + MT A; //matrix A + VT b; // vector b, s.t.: Ax<=b + std::pair _inner_ball; + +public: + //TODO: the default implementation of the Big3 should be ok. Recheck. + HPolytope() {} + + HPolytope(unsigned d_, MT const& A_, VT const& b_) : + _d{d_}, A{A_}, b{b_} + { + } + + // Copy constructor + HPolytope(HPolytope const& p) : + _d{p._d}, A{p.A}, b{p.b}, _inner_ball{p._inner_ball} + { + } + + //define matrix A and vector b, s.t. Ax<=b, + // from a matrix that contains both A and b, i.e., [A | b ] + HPolytope(std::vector> const& Pin) + { + _d = Pin[0][1] - 1; + A.resize(Pin.size() - 1, _d); + b.resize(Pin.size() - 1); + for (unsigned int i = 1; i < Pin.size(); i++) { + b(i - 1) = Pin[i][0]; + for (unsigned int j = 1; j < _d + 1; j++) { + A(i - 1, j - 1) = -Pin[i][j]; + } + } + _inner_ball.second = -1; + //_inner_ball = ComputeChebychevBall(A, b); + } + + + std::pair InnerBall() const + { + return _inner_ball; + } + + void set_InnerBall(std::pair const& innerball) //const + { + _inner_ball = innerball; + } + + void set_interior_point(Point const& r) + { + _inner_ball.first = r; + } + + //Compute Chebyshev ball of H-polytope P:= Ax<=b + //Use LpSolve library + std::pair ComputeInnerBall() + { + normalize(); + #ifndef DISABLE_LPSOLVE + _inner_ball = ComputeChebychevBall(A, b); // use lpsolve library + #else + + if (_inner_ball.second <= NT(0)) { + + NT const tol = 0.00000001; + std::tuple inner_ball = max_inscribed_ball(A, b, 150, tol); + + // check if the solution is feasible + if (is_in(Point(std::get<0>(inner_ball))) == 0 || std::get<1>(inner_ball) < NT(0) || + std::isnan(std::get<1>(inner_ball)) || std::isinf(std::get<1>(inner_ball)) || + !std::get<2>(inner_ball) || is_inner_point_nan_inf(std::get<0>(inner_ball))) + { + _inner_ball.second = -1.0; + } else + { + _inner_ball.first = Point(std::get<0>(inner_ball)); + _inner_ball.second = std::get<1>(inner_ball); + } + } + #endif + + return _inner_ball; + } + + // return dimension + unsigned int dimension() const + { + return _d; + } + + + // return the number of facets + int num_of_hyperplanes() const + { + return A.rows(); + } + + int num_of_generators() const + { + return 0; + } + + + // return the matrix A + MT get_mat() const + { + return A; + } + + + MT get_AA() const { + return A * A.transpose(); + } + + // return the vector b + VT get_vec() const + { + return b; + } + + + // change the matrix A + void set_mat(MT const& A2) + { + A = A2; + } + + + // change the vector b + void set_vec(VT const& b2) + { + b = b2; + } + + Point get_mean_of_vertices() const + { + return Point(_d); + } + + NT get_max_vert_norm() const + { + return 0.0; + } + + + // print polytope in input format + void print() { + std::cout << " " << A.rows() << " " << _d << " double" << std::endl; + for (unsigned int i = 0; i < A.rows(); i++) { + for (unsigned int j = 0; j < _d; j++) { + std::cout << A(i, j) << " "; + } + std::cout << "<= " << b(i) << std::endl; + } + } + + + // Compute the reduced row echelon form + // used to transofm {Ax=b,x>=0} to {A'x'<=b'} + // e.g. Birkhoff polytopes + /* + // Todo: change the implementation in order to use eigen matrix and vector. + int rref(){ + to_reduced_row_echelon_form(_A); + std::vector zeros(_d+1,0); + std::vector ones(_d+1,0); + std::vector zerorow(_A.size(),0); + for (int i = 0; i < _A.size(); ++i) + { + for (int j = 0; j < _d+1; ++j){ + if ( _A[i][j] == double(0)){ + ++zeros[j]; + ++zerorow[i]; + } + if ( _A[i][j] == double(1)){ + ++ones[j]; + } + } + } + for(typename stdMatrix::iterator mit=_A.begin(); mit<_A.end(); ++mit){ + int j =0; + for(typename stdCoeffs::iterator lit=mit->begin(); litend() ; ){ + if(zeros[j]==_A.size()-1 && ones[j]==1) + (*mit).erase(lit); + else{ //reverse sign in all but the first column + if(lit!=mit->end()-1) *lit = (-1)*(*lit); + ++lit; + } + ++j; + } + } + //swap last and first columns + for(typename stdMatrix::iterator mit=_A.begin(); mit<_A.end(); ++mit){ + double temp=*(mit->begin()); + *(mit->begin())=*(mit->end()-1); + *(mit->end()-1)=temp; + } + //delete zero rows + for (typename stdMatrix::iterator mit=_A.begin(); mit<_A.end(); ){ + int zero=0; + for(typename stdCoeffs::iterator lit=mit->begin(); litend() ; ++lit){ + if(*lit==double(0)) ++zero; + } + if(zero==(*mit).size()) + _A.erase(mit); + else + ++mit; + } + //update _d + _d=(_A[0]).size(); + // add unit vectors + for(int i=1;i<_d;++i){ + std::vector e(_d,0); + e[i]=1; + _A.push_back(e); + } + // _d should equals the dimension + _d=_d-1; + return 1; + }*/ + + + //Check if Point p is in H-polytope P:= Ax<=b + int is_in(Point const& p, NT tol=NT(0)) const + { + int m = A.rows(); + const NT* b_data = b.data(); + + for (int i = 0; i < m; i++) { + //Check if corresponding hyperplane is violated + if (*b_data - A.row(i) * p.getCoefficients() < NT(-tol)) + return 0; + + b_data++; + } + return -1; + } + + // compute intersection point of ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair line_intersect(Point const& r, Point const& v) const + { + + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + VT sum_nom, sum_denom; + //unsigned int i, j; + unsigned int j; + int m = num_of_hyperplanes(); + + + sum_nom.noalias() = b - A * r.getCoefficients(); + sum_denom.noalias() = A * v.getCoefficients(); + + NT* sum_nom_data = sum_nom.data(); + NT* sum_denom_data = sum_denom.data(); + + for (int i = 0; i < m; i++) { + + if (*sum_denom_data == NT(0)) { + //std::cout<<"div0"< 0) min_plus = lamda; + if (lamda > max_minus && lamda < 0) max_minus = lamda; + } + + sum_nom_data++; + sum_denom_data++; + } + return std::make_pair(min_plus, max_minus); + } + + // compute intersection points of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair line_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + bool pos = false) const + { + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + VT sum_nom; + int m = num_of_hyperplanes(), facet; + + Ar.noalias() = A * r.getCoefficients(); + sum_nom = b - Ar; + Av.noalias() = A * v.getCoefficients();; + + + NT* Av_data = Av.data(); + NT* sum_nom_data = sum_nom.data(); + + for (int i = 0; i < m; i++) { + if (*Av_data == NT(0)) { + //std::cout<<"div0"< 0) { + min_plus = lamda; + if (pos) facet = i; + }else if (lamda > max_minus && lamda < 0) max_minus = lamda; + } + + Av_data++; + sum_nom_data++; + } + if (pos) return std::make_pair(min_plus, facet); + return std::make_pair(min_plus, max_minus); + } + + std::pair line_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + bool pos = false) const + { + + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + VT sum_nom; + NT mult; + //unsigned int i, j; + unsigned int j; + int m = num_of_hyperplanes(), facet; + + Ar.noalias() += lambda_prev*Av; + sum_nom = b - Ar; + Av.noalias() = A * v.getCoefficients(); + + NT* sum_nom_data = sum_nom.data(); + NT* Av_data = Av.data(); + + for (int i = 0; i < m; i++) { + if (*Av_data == NT(0)) { + //std::cout<<"div0"< 0) { + min_plus = lamda; + if (pos) facet = i; + }else if (lamda > max_minus && lamda < 0) max_minus = lamda; + } + Av_data++; + sum_nom_data++; + } + if (pos) return std::make_pair(min_plus, facet); + return std::make_pair(min_plus, max_minus); + } + + + // compute intersection point of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av) const + { + return line_intersect(r, v, Ar, Av, true); + } + + + // compute intersection point of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev) const + { + return line_intersect(r, v, Ar, Av, lambda_prev, true); + } + + + //---------------------------accelarated billiard---------------------------------- + // compute intersection point of a ray starting from r and pointing to v + // with polytope discribed by A and b + template + std::pair line_first_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + update_parameters& params) const + { + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + + NT lamda = 0; + VT sum_nom; + int m = num_of_hyperplanes(), facet; + + Ar.noalias() = A * r.getCoefficients(); + sum_nom.noalias() = b - Ar; + Av.noalias() = A * v.getCoefficients(); + + NT* Av_data = Av.data(); + NT* sum_nom_data = sum_nom.data(); + + for (int i = 0; i < m; i++) { + if (*Av_data == NT(0)) { + //std::cout<<"div0"< 0) { + min_plus = lamda; + facet = i; + params.inner_vi_ak = *Av_data; + } + } + + Av_data++; + sum_nom_data++; + } + params.facet_prev = facet; + return std::pair(min_plus, facet); + } + + + template + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + MT const& AA, + update_parameters& params) const + { + + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + + NT lamda = 0; + NT inner_prev = params.inner_vi_ak; + VT sum_nom; + int m = num_of_hyperplanes(), facet; + + Ar.noalias() += lambda_prev*Av; + if(params.hit_ball) { + Av.noalias() += (-2.0 * inner_prev) * (Ar / params.ball_inner_norm); + } else { + Av.noalias() += (-2.0 * inner_prev) * AA.col(params.facet_prev); + } + sum_nom.noalias() = b - Ar; + + NT* sum_nom_data = sum_nom.data(); + NT* Av_data = Av.data(); + + for (int i = 0; i < m; i++) { + if (*Av_data == NT(0)) { + //std::cout<<"div0"< 0) { + min_plus = lamda; + facet = i; + params.inner_vi_ak = *Av_data; + } + } + Av_data++; + sum_nom_data++; + } + params.facet_prev = facet; + return std::pair(min_plus, facet); + } + + + template + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + update_parameters& params) const + { + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + + NT lamda = 0; + VT sum_nom; + unsigned int j; + int m = num_of_hyperplanes(), facet; + + Ar.noalias() += lambda_prev*Av; + sum_nom.noalias() = b - Ar; + Av.noalias() = A * v.getCoefficients(); + + NT* sum_nom_data = sum_nom.data(); + NT* Av_data = Av.data(); + + for (int i = 0; i < m; i++) { + if (*Av_data == NT(0)) { + //std::cout<<"div0"< 0) { + min_plus = lamda; + facet = i; + params.inner_vi_ak = *Av_data; + } + } + Av_data++; + sum_nom_data++; + } + params.facet_prev = facet; + return std::pair(min_plus, facet); + } + + //-----------------------------------------------------------------------------------// + + + //First coordinate ray intersecting convex polytope + std::pair line_intersect_coord(Point const& r, + unsigned int const& rand_coord, + VT& lamdas) const + { + + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + VT sum_denom; + + int m = num_of_hyperplanes(); + + sum_denom = A.col(rand_coord); + lamdas.noalias() = b - A * r.getCoefficients(); + + NT* lamda_data = lamdas.data(); + NT* sum_denom_data = sum_denom.data(); + + for (int i = 0; i < m; i++) { + + if (*sum_denom_data == NT(0)) { + //std::cout<<"div0"< 0) min_plus = lamda; + if (lamda > max_minus && lamda < 0) max_minus = lamda; + + } + lamda_data++; + sum_denom_data++; + } + return std::make_pair(min_plus, max_minus); + } + + + //Not the first coordinate ray intersecting convex + std::pair line_intersect_coord(Point const& r, + Point const& r_prev, + unsigned int const& rand_coord, + unsigned int const& rand_coord_prev, + VT& lamdas) const + { + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + + int m = num_of_hyperplanes(); + + lamdas.noalias() += A.col(rand_coord_prev) + * (r_prev[rand_coord_prev] - r[rand_coord_prev]); + NT* data = lamdas.data(); + + for (int i = 0; i < m; i++) { + NT a = A(i, rand_coord); + + if (a == NT(0)) { + //std::cout<<"div0"< 0) min_plus = lamda; + if (lamda > max_minus && lamda < 0) max_minus = lamda; + + } + data++; + } + return std::make_pair(min_plus, max_minus); + } + + + //------------------------------oracles for exponential sampling---------------////// + + std::pair get_positive_quadratic_root(Point const& r, //current poistion + Point const& v, // current velocity + VT& Ac, // the product Ac where c is the bias vector of the exponential distribution + NT const& T, // the variance of the exponential distribution + VT& Ar, // the product Ar + VT& Av, // the product Av + int& facet_prev) const //the facet that the trajectory hit in the previous reflection + { + NT lamda = 0; + NT lamda2 =0; + NT lamda1 =0; + NT alpha; + NT min_plus = std::numeric_limits::max(); + VT sum_nom; + int m = num_of_hyperplanes(); + int facet = -1; + + sum_nom = Ar - b; + Av.noalias() = A * v.getCoefficients();; + + NT* Av_data = Av.data(); + NT* sum_nom_data = sum_nom.data(); + NT* Ac_data = Ac.data(); + + for (int i = 0; i < m; i++) + { + alpha = -((*Ac_data) / (2.0 * T)); + if (solve_quadratic_polynomial(alpha, (*Av_data), (*sum_nom_data), lamda1, lamda2)) + { + lamda = pick_first_intersection_time_with_boundary(lamda1, lamda2, i, facet_prev); + if (lamda < min_plus && lamda > 0) + { + min_plus = lamda; + facet = i; + } + } + Av_data++; + sum_nom_data++; + Ac_data++; + } + facet_prev = facet; + return std::make_pair(min_plus, facet); + } + + + // compute intersection points of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair quadratic_positive_intersect(Point const& r, //current poistion + Point const& v, // current velocity + VT& Ac, // the product Ac where c is the bias vector of the exponential distribution + NT const& T, // the variance of the exponential distribution + VT& Ar, // the product Ar + VT& Av, // the product Av + int& facet_prev) const //the facet that the trajectory hit in the previous reflection + { + Ar.noalias() = A * r.getCoefficients(); + return get_positive_quadratic_root(r, v, Ac, T, Ar, Av, facet_prev); + } + + std::pair quadratic_positive_intersect(Point const& r, //current poistion + Point const& v, // current velocity + VT& Ac, // the product Ac where c is the bias vector of the exponential distribution + NT const& T, // the variance of the exponential distribution + VT& Ar, // the product Ar + VT& Av, // the product Av + NT const& lambda_prev, // the intersection time of the previous reflection + int& facet_prev) const //the facet that the trajectory hit in the previous reflection + { + Ar.noalias() += ((lambda_prev * lambda_prev) / (-2.0*T)) * Ac + lambda_prev * Av; + return get_positive_quadratic_root(r, v, Ac, T, Ar, Av, facet_prev); + } + + NT pick_first_intersection_time_with_boundary(NT const& lamda1, NT const& lamda2, int const& current_facet, int const& previous_facet) const + { + if (lamda1 == lamda2) + { + return lamda1; + } + NT lamda; + const double tol = 1e-10; + std::pair minmax_values = std::minmax(lamda1, lamda2); + + lamda = (previous_facet == current_facet) + ? minmax_values.second < NT(tol) ? minmax_values.first : minmax_values.second + : minmax_values.second; + + if (lamda1 * lamda2 < NT(0)) + { + lamda = (previous_facet == current_facet) + ? (minmax_values.second < NT(tol)) ? minmax_values.first : minmax_values.second + : minmax_values.second; + } + else + { + lamda = (previous_facet == current_facet) + ? (minmax_values.first >= NT(0) && minmax_values.first < NT(tol)) + ? minmax_values.second : minmax_values.first + : minmax_values.first; + } + return lamda; + } + + + //------------oracle for exact hmc spherical gaussian sampling---------------// + + // compute intersection point of ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair trigonometric_positive_intersect(Point const& r, Point const& v, + NT const& omega, int &facet_prev) const + { + + NT lamda = 0, C, Phi, t1, t2, tmin; + NT min_plus = std::numeric_limits::max(), t = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + VT sum_nom, sum_denom; + unsigned int j; + int m = num_of_hyperplanes(), facet = -1; + + + sum_nom.noalias() = A * r.getCoefficients(); + sum_denom.noalias() = A * v.getCoefficients(); + + NT* sum_nom_data = sum_nom.data(); + NT* sum_denom_data = sum_denom.data(); + const NT* b_data = b.data(); + + for (int i = 0; i < m; i++) { + + C = std::sqrt((*sum_nom_data) * (*sum_nom_data) + ((*sum_denom_data) * (*sum_denom_data)) / (omega * omega)); + Phi = std::atan((-(*sum_denom_data)) / ((*sum_nom_data) * omega)); + if ((*sum_denom_data) < 0.0 && Phi < 0.0) { + Phi += M_PI; + } else if ((*sum_denom_data) > 0.0 && Phi > 0.0) { + Phi -= M_PI; + } + + if (C > (*b_data)) { + NT acos_b = std::acos((*b_data) / C); + t1 = (acos_b - Phi) / omega; + if (facet_prev == i && std::abs(t1) < 1e-10){ + t1 = (2.0 * M_PI) / omega; + } + + t2 = (-acos_b - Phi) / omega; + if (facet_prev == i && std::abs(t2) < 1e-10){ + t2 = (2.0 * M_PI) / omega; + } + + t1 += (t1 < NT(0)) ? (2.0 * M_PI) / omega : NT(0); + t2 += (t2 < NT(0)) ? (2.0 * M_PI) / omega : NT(0); + + tmin = std::min(t1, t2); + + if (tmin < t && tmin > NT(0)) { + facet = i; + t = tmin; + } + } + + sum_nom_data++; + sum_denom_data++; + b_data++; + } + facet_prev = facet; + return std::make_pair(t, facet); + } + + + // Apply linear transformation, of square matrix T^{-1}, in H-polytope P:= Ax<=b + void linear_transformIt(MT const& T) + { + A = A * T; + } + + + // shift polytope by a point c + + void shift(const VT &c) + { + b -= A*c; + } + + + // return for each facet the distance from the origin + std::vector get_dists(NT const& radius) const + { + unsigned int i=0; + std::vector dists(num_of_hyperplanes(), NT(0)); + typename std::vector::iterator disit = dists.begin(); + for ( ; disit!=dists.end(); disit++, i++) + *disit = b(i) / A.row(i).norm(); + + return dists; + } + + // no points given for the rounding, you have to sample from the polytope + template + bool get_points_for_rounding (T const& /*randPoints*/) + { + return false; + } + + MT get_T() const + { + return A; + } + + void normalize() + { + NT row_norm; + for (int i = 0; i < num_of_hyperplanes(); ++i) + { + row_norm = A.row(i).norm(); + A.row(i) = A.row(i) / row_norm; + b(i) = b(i) / row_norm; + } + } + + void compute_reflection(Point& v, Point const&, int const& facet) const + { + v += -2 * v.dot(A.row(facet)) * A.row(facet); + } + + void resetFlags() {} + + NT log_barrier(Point &x, NT t = NT(100)) const { + int m = num_of_hyperplanes(); + NT total = NT(0); + NT slack; + + for (int i = 0; i < m; i++) { + slack = b(i) - x.dot(A.row(i)); + total += log(slack); + } + + return total / t; + } + + Point grad_log_barrier(Point &x, NT t = NT(100)) { + int m = num_of_hyperplanes(); + NT slack; + + Point total(x.dimension()); + + for (int i = 0; i < m; i++) { + slack = b(i) - x.dot(A.row(i)); + total = total + (1 / slack) * A.row(i); + } + total = (1.0 / t) * total; + return total; + } + + template + void compute_reflection(Point &v, const Point &, update_parameters const& params) const { + + Point a((-2.0 * params.inner_vi_ak) * A.row(params.facet_prev)); + v += a; + } + + void update_position_internal(NT&){} + + template + std::tuple curve_intersect( + NT t_prev, + NT t0, + NT eta, + std::vector &coeffs, + bfunc phi, + bfunc grad_phi, + NonLinearOracle &intersection_oracle, + int ignore_facet=-1) + { + return intersection_oracle.apply(t_prev, t0, eta, A, b, *this, + coeffs, phi, grad_phi, ignore_facet); + } +}; + +#endif diff --git a/src/volesti/include/convex_bodies/orderpolytope.h b/src/volesti/include/convex_bodies/orderpolytope.h new file mode 100644 index 00000000..2df5c661 --- /dev/null +++ b/src/volesti/include/convex_bodies/orderpolytope.h @@ -0,0 +1,755 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2021 Vaibhav Thakkar + +// Contributed and/or modified by Vaibhav Thakkar, as part of Google Summer of Code 2021 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ORDER_POLYTOPE_H +#define ORDER_POLYTOPE_H + +#include +#include "misc/poset.h" +#include +#include "preprocess/max_inscribed_ball.hpp" +#ifndef DISABLE_LPSOLVE + #include "lp_oracles/solve_lp.h" +#endif + +/// This class represents an order polytope parameterized by a point type +/// \tparam Point Point type +template +class OrderPolytope { +public: + typedef Point PointType; + typedef typename Point::FT NT; + typedef Eigen::Matrix VT; + typedef Eigen::Matrix MT; + +private: + Poset _poset; + unsigned int _d; // dimension + + VT b; + VT _row_norms; + MT _A; // representing as Ax <= b for ComputeInnerBall and printing + + unsigned int _num_hyperplanes; + bool _normalized; + +public: + OrderPolytope(Poset const& poset) : _poset(poset) + { + _d = _poset.num_elem(); + _num_hyperplanes = 2*_d + _poset.num_relations(); // 2*d are for >=0 and <=1 constraints + b = Eigen::MatrixXd::Zero(_num_hyperplanes, 1); + _A = Eigen::MatrixXd::Zero(_num_hyperplanes, _d); + _row_norms = Eigen::MatrixXd::Constant(_num_hyperplanes, 1, 1.0); + + // first add (ai >= 0) or (-ai <= 0) rows + _A.topLeftCorner(_d, _d) = -Eigen::MatrixXd::Identity(_d, _d); + + // next add (ai <= 1) rows + _A.block(_d, 0, _d, _d) = Eigen::MatrixXd::Identity(_d, _d); + b.block(_d, 0, _d, 1) = Eigen::MatrixXd::Constant(_d, 1, 1.0); + + // next add the relations + unsigned int num_relations = _poset.num_relations(); + for(int idx=0; idx curr_relation = _poset.get_relation(idx); + _A(2*_d + idx, curr_relation.first) = 1; + _A(2*_d + idx, curr_relation.second) = -1; + } + _row_norms.block(2*_d, 0, num_relations, 1) = Eigen::MatrixXd::Constant(num_relations, 1, sqrt(2)); + + _normalized = false; + } + + + // return dimension + unsigned int dimension() const + { + return _d; + } + + + // return number of hyperplanes + unsigned int num_of_hyperplanes() const + { + return _num_hyperplanes; + } + + + // get ith column of A + VT get_col (unsigned int i) const { + return _A.col(i); + } + + + Eigen::SparseMatrix get_mat() const + { + return _A.sparseView(); + } + + + VT get_vec() const + { + return b; + } + + + // print polytope in Ax <= b format + void print() const + { + std::cout << " " << _A.rows() << " " << _d << " double" << std::endl; + for (unsigned int i = 0; i < _A.rows(); i++) { + for (unsigned int j = 0; j < _d; j++) { + std::cout << _A(i, j) << " "; + } + std::cout << "<= " << b(i) << std::endl; + } + } + + + /** multiply the sparse matrix A of the order polytope by a vector x + * if transpose = false : return Ax + * else if transpose = true : return (A^T)x + */ + VT vec_mult(VT const& x, bool transpose=false) const + { + unsigned int rows = num_of_hyperplanes(); + unsigned int i = 0; + VT res; + if (!transpose) res = Eigen::MatrixXd::Zero(rows, 1); + else res = Eigen::MatrixXd::Zero(_d, 1); + + // ------- no effect of normalize on first 2*_d rows, norm = 1 --------- + // first _d rows of >=0 constraints + for(; i < _d; ++i) { + res(i) = (-1.0) * x(i); + } + + // next _d rows of <=1 constraints + for(; i < 2*_d; ++i) { + if (!transpose) { + res(i) = (1.0) * x(i - _d); + } + else { + res(i - _d) += (1.0) * x(i); + } + } + // ----------------------------------------------------------------- + + // next rows are for order relations + for(; i < rows; ++i) { + std::pair curr_relation = _poset.get_relation(i - 2*_d); + + if (!transpose) { + if (! _normalized) + res(i) = x(curr_relation.first) - x(curr_relation.second); + else + res(i) = (x(curr_relation.first) - x(curr_relation.second)) / _row_norms(i); + } + else { + if (! _normalized) { + res(curr_relation.first) += x(i); + res(curr_relation.second) -= x(i); + } + else { + res(curr_relation.first) += x(i) / _row_norms(i); + res(curr_relation.second) -= x(i) / _row_norms(i); + } + } + } + + return res; + } + + + //Check if Point p is in the order-polytope + int is_in(Point const& p, NT tol=NT(0)) const + { + assert(p.dimension() == _d); + + VT pt_coeffs = p.getCoefficients(); + NT diff; + + for (int i = 0; i < _d; i++) { + // DON'T JUST check violation of point between 0 and 1 + // as b will change for shifted polytope. + diff = -pt_coeffs(i) - b(i); + if (diff > NT(tol)) return 0; + + diff = pt_coeffs(i) - b(i + _d); + if (diff > NT(tol)) return 0; + } + + // check violations of order relations + // again note that b can be arbitrary because of shifting + unsigned int num_relations = _poset.num_relations(); + for(int idx=0; idx curr_relation = _poset.get_relation(idx); + diff = (pt_coeffs(curr_relation.first) - pt_coeffs(curr_relation.second)); + + if (_normalized) diff /= _row_norms(idx + 2*_d); + if((diff - b(idx + 2*_d)) > NT(tol)) + return 0; + } + + return -1; + } + + + //Compute Chebyshev ball of the polytope P:= Ax<=b + //Use LpSolve library + std::pair ComputeInnerBall() + { + normalize(); + std::pair inner_ball; + #ifndef DISABLE_LPSOLVE + inner_ball = ComputeChebychevBall(_A, b); // use lpsolve library + #else + + if (inner_ball.second <= NT(0)) { + + NT const tol = 0.00000001; + std::tuple inner_ball = max_inscribed_ball(_A, b, 150, tol); + + // check if the solution is feasible + if (is_in(Point(std::get<0>(inner_ball))) == 0 || std::get<1>(inner_ball) < NT(0) || + std::isnan(std::get<1>(inner_ball)) || std::isinf(std::get<1>(inner_ball)) || + !std::get<2>(inner_ball) || is_inner_point_nan_inf(std::get<0>(inner_ball))) + { + inner_ball.second = -1.0; + } else + { + inner_ball.first = Point(std::get<0>(inner_ball)); + inner_ball.second = std::get<1>(inner_ball); + } + } + #endif + + return inner_ball; + + } + + + // TODO: This can be removed as only modified Accelerated Billiard Walk will be used with Order Polytope + // compute intersection point of ray starting from r and pointing to v + // with the order polytope + std::pair line_intersect(Point const& r, Point const& v, bool pos = false) const + { + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + VT sum_nom, sum_denom; + + int rows = num_of_hyperplanes(), facet; + + sum_nom.noalias() = b - vec_mult(r.getCoefficients()); + sum_denom.noalias() = vec_mult(v.getCoefficients()); + + NT* sum_nom_data = sum_nom.data(); + NT* sum_denom_data = sum_denom.data(); + + // iterate over all hyperplanes + for(unsigned int i = 0; i 0) { + min_plus = lamda; + if (pos) facet = i; + } + else if (lamda > max_minus && lamda < 0) { + max_minus = lamda; + } + } + + sum_nom_data++; + sum_denom_data++; + } + + if (pos) + return std::make_pair(min_plus, facet); + + return std::make_pair(min_plus, max_minus); + } + + + // TODO: This can be removed as only modified Accelerated Billiard Walk will be used with Order Polytope + // compute intersection point of ray starting from r and pointing to v + // with the order-polytope + std::pair line_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + bool pos = false) const + { + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + VT sum_nom; + + int rows = num_of_hyperplanes(), facet; + + Ar.noalias() = vec_mult(r.getCoefficients()); + Av.noalias() = vec_mult(v.getCoefficients()); + + sum_nom.noalias() = b - Ar; + + NT* sum_nom_data = sum_nom.data(); + NT* sum_denom_data = Av.data(); + + // iterate over all hyperplanes + for(unsigned int i = 0; i 0) { + min_plus = lamda; + if (pos) facet = i; + } + else if (lamda > max_minus && lamda < 0) { + max_minus = lamda; + } + } + + sum_nom_data++; + sum_denom_data++; + } + + if (pos) + return std::make_pair(min_plus, facet); + + return std::make_pair(min_plus, max_minus); + } + + + // TODO: This can be removed as only modified Accelerated Billiard Walk will be used with Order Polytope + // compute intersection point of ray starting from r and pointing to v + // with the order-polytope + std::pair line_intersect(Point const& r, + Point const& v, + VT &Ar, + VT &Av, + NT const& lambda_prev, + bool pos = false) const + { + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + VT sum_nom; + + int rows = num_of_hyperplanes(), facet; + + Ar.noalias() += lambda_prev*Av; + Av.noalias() = vec_mult(v.getCoefficients()); + + sum_nom.noalias() = b - Ar; + + NT* sum_nom_data = sum_nom.data(); + NT* sum_denom_data = Av.data(); + + // iterate over all hyperplanes + for(unsigned int i = 0; i 0) { + min_plus = lamda; + if (pos) facet = i; + } + else if (lamda > max_minus && lamda < 0) { + max_minus = lamda; + } + } + + sum_nom_data++; + sum_denom_data++; + } + + if (pos) + return std::make_pair(min_plus, facet); + + return std::make_pair(min_plus, max_minus); + } + + + // TODO: This can be removed as only modified Accelerated Billiard Walk will be used with Order Polytope + // compute intersection point of a ray starting from r and pointing to v + // with the order-polytope + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av) const + { + return line_intersect(r, v, Ar, Av, true); + } + + + // TODO: This can be removed as only modified Accelerated Billiard Walk will be used with Order Polytope + // compute intersection point of a ray starting from r and pointing to v + // with the order-polytope + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev) const + { + return line_intersect(r, v, Ar, Av, lambda_prev, true); + } + + + //-------------------------accelerated billiard--------------------------------// + // compute intersection point of a ray starting from r and pointing to v + // with the order-polytope + template + std::pair line_first_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + update_parameters ¶ms) const + { + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + VT sum_nom; + + int rows = num_of_hyperplanes(), facet; + + Ar.noalias() = vec_mult(r.getCoefficients()); + Av.noalias() = vec_mult(v.getCoefficients()); + + sum_nom.noalias() = b - Ar; + + NT* sum_nom_data = sum_nom.data(); + NT* sum_denom_data = Av.data(); + + // iterate over all hyperplanes + for(unsigned int i = 0; i 0) { + min_plus = lamda; + facet = i; + params.inner_vi_ak = *sum_denom_data; + } + } + + sum_nom_data++; + sum_denom_data++; + } + + params.facet_prev = facet; + return std::make_pair(min_plus, facet); + } + + template + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + MT const& AA, + update_parameters ¶ms) const + { + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + VT sum_nom; + + int rows = num_of_hyperplanes(), facet; + NT inner_prev = params.inner_vi_ak; + + Ar.noalias() += lambda_prev*Av; + if(params.hit_ball) { + Av.noalias() += (-2.0 * inner_prev) * (Ar / params.ball_inner_norm); + } else { + Av.noalias() += (-2.0 * inner_prev) * AA.col(params.facet_prev); + } + sum_nom.noalias() = b - Ar; + + NT* sum_nom_data = sum_nom.data(); + NT* sum_denom_data = Av.data(); + + // iterate over all hyperplanes + for(unsigned int i = 0; i 0) { + min_plus = lamda; + facet = i; + params.inner_vi_ak = *sum_denom_data; + } + } + + sum_nom_data++; + sum_denom_data++; + } + + params.facet_prev = facet; + return std::make_pair(min_plus, facet); + } + + + template + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + update_parameters ¶ms) const + { + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + VT sum_nom; + + int rows = num_of_hyperplanes(), facet; + + Ar.noalias() += lambda_prev*Av; + Av.noalias() = vec_mult(v.getCoefficients()); + + sum_nom.noalias() = b - Ar; + + NT* sum_nom_data = sum_nom.data(); + NT* sum_denom_data = Av.data(); + + // iterate over all hyperplanes + for(unsigned int i = 0; i 0) { + min_plus = lamda; + facet = i; + params.inner_vi_ak = *sum_denom_data; + } + } + + sum_nom_data++; + sum_denom_data++; + } + + params.facet_prev = facet; + return std::make_pair(min_plus, facet); + } + //------------------------------------------------------------------------------// + + + // TODO: This can be removed as only modified Accelerated Billiard Walk will be used with Order Polytope + // Compute the intersection of a coordinate ray + // with the order polytope + std::pair line_intersect_coord(Point const& r, + unsigned int const& rand_coord, + VT& lamdas) const + { + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + VT sum_denom; + + int rows = num_of_hyperplanes(); + + sum_denom = get_col(rand_coord); + lamdas = b - vec_mult(r.getCoefficients()); + + NT* sum_nom_data = lamdas.data(); + NT* sum_denom_data = sum_denom.data(); + + // iterate over all hyperplanes + for(unsigned int i = 0; i 0) { + min_plus = lamda; + } + else if (lamda > max_minus && lamda < 0) { + max_minus = lamda; + } + } + + sum_nom_data++; + sum_denom_data++; + } + + return std::make_pair(min_plus, max_minus); + } + + + // TODO: This can be removed as only modified Accelerated Billiard Walk will be used with Order Polytope + std::pair line_intersect_coord(Point const& r, + Point const& r_prev, + unsigned int const& rand_coord, + unsigned int const& rand_coord_prev, + VT& lamdas) const + { + NT lamda = 0; + NT min_plus = std::numeric_limits::max(); + NT max_minus = std::numeric_limits::lowest(); + + int rows = num_of_hyperplanes(); + + lamdas.noalias() += get_col(rand_coord_prev) + * (r_prev[rand_coord_prev] - r[rand_coord_prev]); + NT* sum_nom_data = lamdas.data(); + + // iterate over all hyperplanes + for(unsigned int i = 0; i 0) { + min_plus = lamda; + } + else if (lamda > max_minus && lamda < 0) { + max_minus = lamda; + } + } + + sum_nom_data++; + } + + return std::make_pair(min_plus, max_minus); + } + + + // no points given for the rounding, you have to sample from the polytope + template + bool get_points_for_rounding (T const& /*randPoints*/) + { + return false; + } + + + // shift polytope by a point c + void shift(VT const& c) + { + b -= vec_mult(c); + } + + + // get a point inside the order polytope, + // NOTE: the current implementation only works for non shifted order polytope + VT inner_point() + { + // get topologically sorted list of indices + std::vector sorted_list = _poset.topologically_sorted_list(); + + // vector to hold n linearly spaced values between 0-1 + std::vector lin_space_values(_d); + NT start = 0.05, end = 0.95; + NT h = (end - start)/static_cast(_d-1); + NT val = start; + for(auto x=lin_space_values.begin(); x!=lin_space_values.end(); ++x) { + *x = val; + val += h; + } + + // final result vector + VT res(_d); + for(int i=0; i<_d; ++i) { + unsigned int curr_idx = sorted_list[i]; + res(curr_idx) = lin_space_values[i]; + } + + return res; + } + + + void normalize() + { + if (_normalized == true) + return; + + // for b and _A, first 2*_d rows are already normalized, for + _normalized = true; // -> will be used to make normalization idempotent + for (unsigned int i = 0; i < _num_hyperplanes; ++i) + { + _A.row(i) /= _row_norms(i); + b(i) /= _row_norms(i); + } + } + + + // return for each facet the distance from the origin + std::vector get_dists(NT const& radius) const + { + std::vector dists(_num_hyperplanes, NT(0)); + + for (unsigned int i = 0; i < _num_hyperplanes; ++i) { + if (_normalized) + dists[i] = b(i); + else + dists[i] = b(i) / _row_norms(i); + } + + return dists; + } + + + // compute reflection given dot product and facet + void compute_reflection(Point& v, NT dot_prod, unsigned int facet) const + { + // calculating -> v += -2 * dot_prod * A.row(facet); + if (facet < _d) { + v.set_coord(facet, v[facet] - 2 * dot_prod * (-1.0)); + } + else if (facet < 2*_d) { + v.set_coord(facet-_d, v[facet-_d] - 2 * dot_prod * (1.0)); + } + else { + std::pair curr_relation = _poset.get_relation(facet - 2*_d); + v.set_coord(curr_relation.first, v[curr_relation.first] - 2 * dot_prod * (1.0 / _row_norms(facet))); + v.set_coord(curr_relation.second, v[curr_relation.second] - 2 * dot_prod * (-1.0 / _row_norms(facet))); + } + } + + + // compute reflection in O(1) time for order polytope + void compute_reflection(Point& v, Point const&, unsigned int facet) const + { + NT dot_prod; + if (facet < _d) { + dot_prod = -v[facet]; + } + else if (facet < 2*_d) { + dot_prod = v[facet - _d]; + } + else { + std::pair curr_relation = _poset.get_relation(facet - 2*_d); + dot_prod = v[curr_relation.first] - v[curr_relation.second]; + dot_prod = dot_prod / _row_norms(facet); + } + + compute_reflection(v, dot_prod, facet); + } + + + template + void compute_reflection(Point &v, Point const&, update_parameters const& params) const + { + NT dot_prod = params.inner_vi_ak; + unsigned int facet = params.facet_prev; + compute_reflection(v, dot_prod, facet); + } +}; + +#endif diff --git a/src/volesti/include/convex_bodies/spectrahedra/LMI.h b/src/volesti/include/convex_bodies/spectrahedra/LMI.h new file mode 100644 index 00000000..5ad879fc --- /dev/null +++ b/src/volesti/include/convex_bodies/spectrahedra/LMI.h @@ -0,0 +1,251 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLESTI_LMI_H +#define VOLESTI_LMI_H + +#include "matrix_operations/EigenvaluesProblems.h" + + +template +struct evaluate_lmi { + +}; + +template +struct evaluate_lmi > { +public: + typedef Eigen::Matrix MT; + /// The type for Eigen vector + typedef Eigen::Matrix VT; + + MT vectorMatrix; + + int _m, _d; + + /// Create the vectorMatrix, which has at each column the distinct elements of each A_i, i=1,...,d + void setVectorMatrix(int const& m, int const& d, std::vector &matrices) + { + _m = m; + _d = d; + int newM = m * (m + 1) / 2; + + // allocate memory + vectorMatrix.setZero(newM, d); + //a.setZero(m); + + // initialze iterator and skip A_0 + typename std::vector::iterator iter = matrices.begin(); + iter++; + + // copy elements + int atMatrix = 0; + + for (; iter != matrices.end(); iter++, atMatrix++) { + int i = 0; + + for (int at_row = 0; at_row < m; at_row++) + for (int at_col = at_row; at_col < m; at_col++) { + vectorMatrix(i++, atMatrix) = (*iter)(at_row, at_col); + } + + } + } + + /// Compute \[x_1*A_1 + ... + x_n A_n] + /// \param[in] x Input vector + /// \param[out] res Output matrix + void evaluateWithoutA0(const VT &x, MT& res, bool complete_mat = false) const { + //#define EVALUATE_WITHOUT_A0_NAIVE + #if defined(EVALUATE_WITHOUT_A0_NAIVE) + res.setZero(_m, _m); + typename std::vector::iterator it; + + int i = 0; + it = matrices.begin(); + ++it; // skip A0 + for (; it != matrices.end(); it++, i++) + res.noalias() += x(i) * (*it); + #else + + VT a = vectorMatrix * x; + + double *data = res.data(); + double *v = a.data(); + + int at = 0; + + // copy lower triangular + for (int at_col = 0; at_col < _m; at_col++) { + int col_offset = at_col * _m; + double *target = data + col_offset + at_col; + + for (int at_row = at_col; at_row < _m; at_row++) { + *(target++) = *(v++); + } + } + + if(complete_mat) { + v = a.data(); + + // copy upper triangular + for (int at_row = 0; at_row < _m; at_row++) { + double *target = data + at_row + at_row * _m; + + for (int at_col = at_row; at_col < _m; at_col++) { + *target = *(v++); + target = target + _m; + } + } + } + #endif + //return true; + } + +}; + + +/// This class handles a linear matrix inequality of the form \[A_0 + \sum x_i A_i\] +/// A template specialization for dense Eigen matrices and vectors +/// @tparam NT Numeric Type +/// @tparam MT Matrix Type +/// @tparam VT Vector Type +template +class LMI { + public: + + evaluate_lmi lmi_evaluator; + + /// The matrices A_0, A_i + std::vector matrices; + + /// The dimension of the vector x + unsigned int d; + + /// The size of the matrices A_i + unsigned int m; + + /// At each column keep the m*(m+1)/2 distinct elements of each matrix A_i, i=1,...,d + MT vectorMatrix; + + LMI(){} + + /// Creates A LMI object + /// \param[in] matrices The matrices A_0, A_i + LMI(std::vector& matrices) { + typename std::vector::iterator it = matrices.begin(); + + while (it!=matrices.end()) { + this->matrices.push_back(*it); + it++; + } + + d = matrices.size() - 1; + m = matrices[0].rows(); + + lmi_evaluator.setVectorMatrix(m, d, matrices); + } + + /// \returns The dimension of vector x + unsigned int dimension() const { + return d; + } + + /// \return The matrices A0, A1, ..., Ad + std::vector getMatrices() const { + return matrices; + } + + /// \returns The size of the matrices + unsigned int sizeOfMatrices() const { + return m; + } + + /// Evaluate A_0 + \[A_0 + \sum x_i A_i \] + /// \param[in] x The input vector + /// \param[out] ret The output matrix + void evaluate(VT const & x, MT& ret, bool complete_mat = false) const { + lmi_evaluator.evaluateWithoutA0(x, ret, complete_mat); + + // add A0 + ret += matrices[0]; + } + + /// Compute \[x_1*A_1 + ... + x_n A_n] + /// \param[in] x Input vector + /// \param[out] res Output matrix + void evaluateWithoutA0(const VT& x, MT& res, bool complete_mat = false) const { + lmi_evaluator.evaluateWithoutA0(x, res, complete_mat); + } + + /// Compute the gradient of the determinant of the LMI at p + /// \param[in] p Input parameter + /// \param[in] Input vector: lmi(p)*e = 0, e != 0 + /// \param[out] ret The normalized gradient of the determinant of the LMI at p + void normalizedDeterminantGradient(VT r, VT const& e, VT &ret) const { + NT* ret_data = ret.data(); + NT sum_sqqrt_sq = NT(0); + for (int i = 0; i < d; i++) { + // todo, use iterators + *ret_data = e.dot(matrices[i+1].template selfadjointView< Eigen::Lower >() * e); + sum_sqqrt_sq += (*ret_data) * (*ret_data); + ret_data++; + } + + //normalize + ret /= std::sqrt(sum_sqqrt_sq); + } + + /// \param i An indicator to a matrix + /// \return Pointer to A_i + MT* const getMatrix(const int i) { + return &(matrices[i]); + } + + MT get_A0() { + return matrices[0]; + } + + void set_A0(MT const& A0) { + matrices[0] = A0; + } + + /// Prints the matrices A0, ..., An + void print() const { + int i = 0; + + for (auto iter = matrices.begin(); iter != matrices.end(); iter++, i++) { + std::cout << "A" << i << "\n"; + std::cout << *iter << "\n\n"; + } + } + + /// check if the matrix is negative semidefinite + /// \param matrix a matrix + /// \return Pointer to A_i + bool isNegativeSemidefinite(MT const & matrix ) const { + EigenvaluesProblems eigs; + NT eival = eigs.findSymEigenvalue(matrix); + return eival <= 0; + } + + /// evaluate LMI(pos) and check if its negative semidefinite + /// \param pos a vector of our current position + /// \return true is LMI(pos) is negative semidefinite + bool isNegativeSemidefinite(VT const & pos) const { + MT mat; + mat.setZero(m, m); + + evaluate(pos, mat, true); + return isNegativeSemidefinite(mat); + } + +}; + +#endif //VOLESTI_LMI_H diff --git a/src/volesti/include/convex_bodies/spectrahedra/spectrahedron.h b/src/volesti/include/convex_bodies/spectrahedra/spectrahedron.h new file mode 100644 index 00000000..e8f34138 --- /dev/null +++ b/src/volesti/include/convex_bodies/spectrahedra/spectrahedron.h @@ -0,0 +1,492 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +// Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. +// Modified by Huu Phuoc Le as part of Google Summer of Code 2022 program + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLESTI_SPECTRAHEDRON_H +#define VOLESTI_SPECTRAHEDRON_H + +#include "LMI.h" +#include "chrono" + + +/// Among successive calls of this class methods, we may need to pass data +/// from one call to the next, to avoid repeating computations, or to efficiently update values +/// Warning: this struct assists in many methods; perhaps for different methods use different instances +template +struct PrecomputationOfValues { + + /// These flags indicate whether the corresponding matrices are computed + /// if yes, we can use them and not compute them fro scratch + // TODO: avoid the use of flags + bool computed_A = false; + bool computed_C = false; + bool computed_XY = false; + + /// The matrices the method positiveIntersection receives from its previous call + /// if the flag first_positive_intersection is true. + /// Matrix A is also used in coordinateIntersection + MT A, B, C, X, Y; + + /// In method positive_intersect, the distance we are computing corresponds + /// to the minimum positive eigenvalue of a quadratic eigenvalue problem. + /// This will hold the eigenvector for that eigenvalue + VT eigenvector; + + /// Sets all flags to false + void resetFlags() { + computed_XY = computed_C = computed_A = false; + } + + void set_mat_size(int const& m) + { + A.setZero(m, m); + B.setZero(m, m); + C.setZero(m, m); + + X.setZero(2*m, 2*m); + Y.setZero(2*m, 2*m); + + eigenvector.setZero(m); + } +}; + + +/// This class manipulates a spectrahedron, described by a Linear Matrix Inequality i.e. LMI +/// \tparam Point Point Type +template +class Spectrahedron { +public: + + /// The numeric/matrix/vector types we use + typedef Point PointType; + typedef typename Point::FT NT; + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + + double maxDouble = std::numeric_limits::max(); + + /// The type of a pair of NT + typedef std::pair pairNT; + + typedef PrecomputationOfValues _PrecomputationOfValues; + + _PrecomputationOfValues precomputedValues; + + EigenvaluesProblems EigenvaluesProblem; + + /// The dimension of the spectrahedron + unsigned int d; + VT grad; + std::pair _inner_ball; + + /// The linear matrix inequality that describes the spectrahedron + LMI lmi; + + Spectrahedron() {} + + /// Creates a spectrahedron + /// \param[in] lmi The linear matrix inequality that describes the spectrahedron + Spectrahedron(const LMI& lmi) : lmi(lmi) { + d = lmi.dimension(); + //grad.setZero(d); + precomputedValues.resetFlags(); + precomputedValues.set_mat_size(lmi.sizeOfMatrices()); + } + + void set_interior_point(PointType const& r) + { + _inner_ball.first = r; + } + + std::pair ComputeInnerBall() { + NT radius = maxDouble; + + for (unsigned int i = 0; i < dimension(); ++i) { + + std::pair min_max = coordinateIntersection(_inner_ball.first.getCoefficients(), i+1); + + if (min_max.first < radius) radius = min_max.first; + if (-min_max.second < radius) radius = -min_max.second; + } + + radius = radius / std::sqrt(NT(dimension())); + _inner_ball.second = radius; + + return std::pair(_inner_ball.first, radius); + } + + std::pair InnerBall() const + { + return _inner_ball; + } + + /// Construct the quadratic eigenvalue problem \[At^2 + Bt + C \] for positive_intersect. + /// A = lmi(c) - A0, B = lmi(b) - A0 and C = lmi(c). + /// \param[in] a Input vector + /// \param[in] b Input vector + /// \param[in] c Input vector + /// \param[in, out] precomputedValues Holds matrices A, C + void createMatricesForPositiveQuadIntersection(const VT& a, const VT& b, const VT& c) { + + // check if matrices A, C are ready + // if not compute them + if (!precomputedValues.computed_A) { + lmi.evaluateWithoutA0(a, precomputedValues.A, true); + } + + if (!precomputedValues.computed_C) { + lmi.evaluate(c, precomputedValues.C, true); + } + + // compute Matrix B + lmi.evaluateWithoutA0(b, precomputedValues.B, true); + } + + /// Construct the generalized eigenvalue problem \[Bt + C \] for positive_intersect. + /// \param[in] p Input vector + /// \param[in] v Input vector + /// \param[in, out] precomputedValues Holds matrices A, C + void createMatricesForPositiveLinearIntersection(const VT& p, const VT& v) { + // check if matrices A, C are ready + // if not compute them + if (!precomputedValues.computed_C) { + lmi.evaluate(p, precomputedValues.C); + } + + // compute Matrix B + lmi.evaluateWithoutA0(v, precomputedValues.B); + } + + + void createMatricesForPositiveIntersection(const VT& p, const VT& v) { + + // check if matrices B, C are ready if not compute them + if (!precomputedValues.computed_C) + { + lmi.evaluate(p, precomputedValues.C); + } + + lmi.evaluateWithoutA0(v, precomputedValues.B); + } + + /// Computes the distance d we must travel on the parametrized polynomial curve \[at^2 + bt + c \], + /// assuming we start at t=0, and we start increasing t. + /// We construct the quadratic eigenvalue problem \[At^2 + Bt + C \], + /// where A = lmi(c) - A0, B = lmi(b) - A0 and C = lmi(c). + /// Then we do a linearization and transform it to the generalized problem X+lY, + /// which we pass to an external library. + /// \param[in] a Input vector, the coefficient of t \[t^2\] + /// \param[in] b Input vector, the coefficient of t + /// \param[in] c Input Vector, the constant term + /// \returns The distance d + NT positiveQuadIntersection(VT const & a, VT const & b, VT const & c) { + unsigned int matrixDim = lmi.sizeOfMatrices(); + + // create matrices A, B, C + createMatricesForPositiveQuadIntersection(a, b, c); + + // get the minimum positive eigenvalue of At^2 + Bt + C + NT distance = EigenvaluesProblem.minPosQuadraticEigenvalue(precomputedValues.A, precomputedValues.B, + precomputedValues.C, precomputedValues.X, + precomputedValues.Y, + precomputedValues.eigenvector, + precomputedValues.computed_XY); + return distance; + } + + + NT positiveLinearIntersection(VT const & p, VT const & v) + { + createMatricesForPositiveLinearIntersection(p, v); + NT distance = EigenvaluesProblem.minPosLinearEigenvalue(precomputedValues.C, precomputedValues.B, + precomputedValues.eigenvector); + return distance; + } + + /// Computes the distance d one must travel on the line a + tb, + /// assuming we start at t=0 and that b has zero everywhere and 1 in its i-th coordinate. + /// We must solve the generalized eigenvalue problem A+tB, where A = lmi(a) and B=(lmi) - A0 = A_i + /// If the flag precomputedValues,computed_A is true, the matrix A is not computed. + /// \param[in] a Input vector + /// \param[in] coordinate Indicator of the i-th coordinate, 1 <= coordinate <= dimension + /// \return The pair (positive t, negative t) for which we reach the boundary + pairNT coordinateIntersection(VT const & a, int const coordinate) { + + // prepare the generalized eigenvalue problem A+lB + // we may not have to compute A! + if (!precomputedValues.computed_A) + lmi.evaluate(a, precomputedValues.A); + + return EigenvaluesProblem.symGeneralizedProblem(precomputedValues.A, *(lmi.getMatrix(coordinate))); + } + + //First coordinate ray intersecting convex polytope + std::pair line_intersect_coord(Point &r, + unsigned int const& rand_coord, + VT&) + { + return coordinateIntersection(r.getCoefficients(), rand_coord); + } + + //Not the first coordinate ray intersecting convex + std::pair line_intersect_coord(PointType &r, + PointType&, + unsigned int const& rand_coord, + unsigned int&, + VT&) + { + return coordinateIntersection(r.getCoefficients(), rand_coord); + } + + // compute intersection point of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair line_positive_intersect(PointType const& r, + PointType const& v) + { + NT pos_inter = positiveLinearIntersection(r.getCoefficients(), v.getCoefficients()); + return std::pair (pos_inter, -1); + } + + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT&, + VT& , + NT const&) { + return line_positive_intersect(r, v); + } + + template + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT&, + VT& , + NT const&, + update_parameters&) + { + return line_positive_intersect(r, v); + } + + template + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT&, + VT&, + NT const&, + MT const&, + update_parameters& ) + { + return line_positive_intersect(r, v); + } + + template + std::pair line_first_positive_intersect(PointType const& r, + PointType const& v, + VT&, + VT&, + update_parameters&) + { + return line_positive_intersect(r, v); + } + + // compute intersection point of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT&, + VT&) + { + return line_positive_intersect(r, v); + } + + // compute intersection point of ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair line_intersect(PointType const& r, PointType const& v) + { + NT pos_inter = positiveLinearIntersection(r.getCoefficients(), v.getCoefficients()); + NT neg_inter = -positiveLinearIntersection(r.getCoefficients(), NT(-1)*v.getCoefficients()); + + return std::make_pair(pos_inter, neg_inter); + } + + + std::pair line_intersect(PointType const& r, + PointType const& v, + VT&, + VT&) + { + return line_intersect(r, v); + } + + std::pair line_intersect(PointType const& r, + PointType const& v, + VT&, + VT&, + NT&) + { + return line_intersect(r, v); + } + + void update_position_internal(NT &t){ + precomputedValues.C += t * precomputedValues.B; + precomputedValues.computed_C = true; + } + + MT get_mat() const + { + return MT::Identity(lmi.dimension(), lmi.dimension()); + } + + void resetFlags() + { + precomputedValues.resetFlags(); + } + + void set_flags(bool bool_flag) + { + precomputedValues.computed_A = bool_flag; + precomputedValues.computed_C = bool_flag; + precomputedValues.computed_XY = bool_flag; + } + + MT get_C() const + { + return precomputedValues.C; + } + + void update_C(NT const& lambda) + { + precomputedValues.C += (lambda * lambda) * precomputedValues.A + lambda * precomputedValues.B; + } + + // return the number of facets + int num_of_hyperplanes() const + { + return 0; + } + + void shift(VT e) { + MT A0 = getLMI().get_A0(); + std::vector matrices = getLMI().getMatrices(); + + int d = matrices.size(); + + for (int i = 1; i < d; ++i) { + A0 = A0 + e(i-1)*matrices[i]; + } + + lmi.set_A0(A0); + + _inner_ball.first = PointType(dimension()); + } + + /// Computes the reflected direction at a point on the boundary of the spectrahedron. + /// \param[in] r A point on the boundary of the spectrahedron + /// \param[in] v The direction of the trajectory as it hits the boundary + /// \param[out] reflectedDirection The reflected direction + template + void compute_reflection(PointType &v, PointType const& r, update_parameters& ) const + { + VT grad(d); + lmi.normalizedDeterminantGradient(r.getCoefficients(), precomputedValues.eigenvector, grad); + + // compute reflected direction + // if v is original direction and s the surface normal, + // reflected direction = v - 2 *s + + NT dot = 2 * v.dot(grad); + v += -dot * PointType(grad); + } + + + /// \return The dimension of the spectrahedron + unsigned int dimension() const { + return d; + } + + /// \return The LMI describing this spectrahedron + LMI getLMI() const { + return lmi; + } + + /// Estimates the diameter of the spectrahedron. It samples points uniformly with coordinate directions + /// hit and run, and returns the maximum distance between them. + /// \tparam Point + /// \param[in] numPoints The number of points to sample for the estimation + /// \return An estimation of the diameter of the spectrahedron + template + NT estimateDiameter(int const numPoints, PointType const & interiorPoint, RNGType &rng) { + + std::list randPoints; + + precomputedValues.computed_A = false; + VT p = interiorPoint.getCoefficients(); + + // sample points with walk length set to 1 + for (int samplingNo=0 ; samplingNo distances = this->coordinateIntersection(p, coordinate); + + // uniformly set the new point on the segment + // defined by the intersection points + NT lambda = rng.sample_urdist(); + NT diff = distances.first + lambda * (distances.second - distances.first); + + p(coordinate - 1) = p(coordinate - 1) + diff; + + // update the precomputedValues, so we can skip + // computations in the next call + precomputedValues.computed_A = true; + precomputedValues.A += diff * *(this->getLMI().getMatrix(coordinate)); + randPoints.push_back(Point(p)); + } + + // find maximum distance among points; + NT maxDistance = 0; + typename std::list::iterator itInner, itOuter = randPoints.begin(); + + for (; itOuter!=randPoints.end() ; ++itOuter) + for (itInner=itOuter ; itInner!=randPoints.end() ; ++itInner) { + NT current = itOuter->distance(*itInner); + if (current > maxDistance) + maxDistance = current; + } + + return maxDistance; + } + + int is_in(PointType const& p, NT tol=NT(0)) const + { + if (isExterior(p.getCoefficients())) { + return 0; + } + return -1; + } + + /// Find out is lmi(current position) = mat is in the exterior of the spectrahedron + /// \param mat a matrix where mat = lmi(current position) + /// \return true if position is outside the spectrahedron + bool isExterior(MT const & mat) const { + return !lmi.isNegativeSemidefinite(mat); + } + + /// Find out is pos is in the exterior of the spectrahedron + /// \param pos a vector + /// \return true if pos is outside the spectrahedron + bool isExterior(VT const & pos) const { + return !lmi.isNegativeSemidefinite(pos); + } +}; + +#endif //VOLESTI_SPECTRAHEDRON_H diff --git a/src/volesti/include/convex_bodies/vpolyintersectvpoly.h b/src/volesti/include/convex_bodies/vpolyintersectvpoly.h new file mode 100644 index 00000000..a6dd97ca --- /dev/null +++ b/src/volesti/include/convex_bodies/vpolyintersectvpoly.h @@ -0,0 +1,429 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018-19 programs. +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VPOLYINTERSECTVPOLY_H +#define VPOLYINTERSECTVPOLY_H + +#include +#include +#include +#include "sampling/sphere.hpp" + +/// This class represents the intersection of two V-polytopes +/// \tparam VPolytope VPolytope Type +/// \tparam RNGType RNGType Type +template +class IntersectionOfVpoly { +public: + typedef typename VPolytope::NT NT; + typedef typename VPolytope::PointType PointType; + typedef PointType Point; + typedef typename VPolytope::MT MT; + typedef typename VPolytope::VT VT; + unsigned seed; + std::pair _inner_ball; + NT rad; + VPolytope P1; + VPolytope P2; + + IntersectionOfVpoly(): P1(), P2() {} + + IntersectionOfVpoly(VPolytope P, VPolytope Q) : P1(P), P2(Q) { + seed = std::chrono::system_clock::now().time_since_epoch().count(); + } + + IntersectionOfVpoly(VPolytope P, VPolytope Q, unsigned _seed) : P1(P), P2(Q) { + seed = _seed; + } + + VPolytope first() { return P1; } + VPolytope second() { return P2; } + + std::pair InnerBall() const + { + return _inner_ball; + } + + int is_in(const Point &p, NT tol=NT(0)) const { + if(P1.is_in(p)==-1) + return P2.is_in(p); + return 0; + } + + + int num_of_hyperplanes() const { + return 0; + } + + unsigned int dimension() const { + return P1.dimension(); + } + + int num_of_vertices() const { + return P1.num_of_vertices() + P2.num_of_vertices(); + } + + unsigned int upper_bound_of_hyperplanes() const { + return dimension() + 1; + //return 4; + } + + int num_of_generators() const { + return 0; + } + + NT getRad() const { + return rad; + } + + MT get_mat() const { + return P1.get_mat(); + } + + VT get_vec() const { + return P1.get_vec(); + } + + MT get_T() const { + return P1.get_mat(); + } + + MT get_mat2() const { + return P2.get_mat(); + } + + Point get_mean_of_vertices() const { + return Point(P1.dimension()); + } + + + NT get_max_vert_norm() const { + return 0.0; + } + + void print() { + P1.print(); + P2.print(); + } + + bool is_feasible() { + bool empty; + int k = P1.get_mat().rows() + P2.get_mat().rows(); + RNGType rng(k); + rng.set_seed(seed); + PointInIntersection(P1.get_mat(), P2.get_mat(), + GetDirection::apply(k, rng), empty); + return !empty; + } + + std::pair ComputeInnerBall() { + + unsigned int num = 0, d = P1.dimension(); + MT V1 = P1.get_mat(), V2 = P2.get_mat(); + int k1 = V1.rows(), k2 = V2.rows(); + int k = k1 + k2; + RNGType rng(k); + rng.set_seed(seed); + Point direction(k), p(d); + std::vector vertices; + typename std::vector::iterator rvert; + bool same; + + while(num::apply(k, rng); + p = PointInIntersection(V1, V2, direction, same); + + same = false; + rvert = vertices.begin(); + for ( ; rvert!=vertices.end(); ++rvert) { + if (p==(*rvert)) { + same = true; + break; + } + } + if (same) continue; + vertices.push_back(p); + num++; + + } + + _inner_ball = P1.get_center_radius_inscribed_simplex(vertices.begin(), vertices.end()); + return _inner_ball; + + } + + void set_InnerBall(std::pair const& innerball) const + { + _inner_ball = innerball; + } + + void set_interior_point(Point const& r) + { + _inner_ball.first = r; + } + +/* + unsigned int num_of_v = 0; + unsigned int d = dimension(); + MT V(0, d); + MT V1 = P1.get_mat(); + MT V2 = P2.get_mat(); + VT itervec(d); + std::vector temp_p(d, 0.0); + typename std::vector::iterator pit; + int j; + for (int i = 0; i < V1.rows(); ++i) { + pit = temp_p.begin(); + j = 0; + for (; pit!=temp_p.end(); ++pit, ++j) { + *pit = V1(i,j); + itervec(j) = V1(i,j); + } + Point p(d, temp_p.begin(), temp_p.end()); + if (P2.is_in(p) == -1) { + V.conservativeResize(V.rows() + 1, V.cols()); + V.row(V.rows()-1) = itervec; + num_of_v++; + } + } + + for (int i = 0; i < V2.rows(); ++i) { + pit = temp_p.begin(); + j = 0; + for (; pit!=temp_p.end(); ++pit, ++j) { + *pit = V2(i,j); + itervec(j) = V2(i,j); + } + Point p(d, temp_p.begin(), temp_p.end()); + if (P1.is_in(p) == -1) { + V.conservativeResize(V.rows() + 1, V.cols()); + V.row(V.rows()-1) = itervec; + num_of_v++; + } + } + if (num_of_v <= d) { + std::cout<<"no simplex"< res; + res.second = -1.0; + return res; + } + + VPolytope Q; + Q.init(d, V, itervec); + return Q.ComputeInnerBall(); + }*/ + + // compute intersection point of ray starting from r and pointing to v + // with the V-polytope + std::pair line_intersect(const Point &r, const Point &v) const { + + std::pair P1pair = P1.line_intersect(r, v); + std::pair P2pair = P2.line_intersect(r, v); + return std::pair(std::min(P1pair.first, P2pair.first), + std::max(P1pair.second, P2pair.second)); + + } + + // compute intersection point of ray starting from r and pointing to v + // with the V-polytope + std::pair line_intersect(const Point &r, const Point &v, const VT &Ar, + const VT &Av) const { + return line_intersect(r, v); + } + + + // compute intersection point of ray starting from r and pointing to v + // with the V-polytope + std::pair line_intersect(const Point &r, const Point &v, const VT &Ar, + const VT &Av, const NT &lambda) const { + return line_intersect(r, v); + } + + std::pair line_positive_intersect(const Point &r, const Point &v) const { + + std::pair P1pair = P1.line_positive_intersect(r, v); + std::pair P2pair = P2.line_positive_intersect(r, v); + + if(P1pair.first < P2pair.first) { + return std::pair(P1pair.first, 1); + } + return std::pair(P2pair.first, 2); + } + + std::pair line_positive_intersect(const Point &r, const Point &v, const VT &Ar, + const VT &Av) const { + return line_positive_intersect(r, v); + } + + + std::pair line_positive_intersect(const Point &r, const Point &v, const VT &Ar, + const VT &Av, const NT &lambda_prev) const { + return line_positive_intersect(r, v);//, Ar, Av); + } + + //------------------------------accelarated billiard------------------------------// + template + std::pair line_first_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + update_parameters& params) const + { + return line_positive_intersect(r, v); + } + + template + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + MT const& AA, + update_parameters& params) const + { + return line_positive_intersect(r, v); + } + + template + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + update_parameters& params) const + { + return line_positive_intersect(r, v); + } + //------------------------------------------------------------------------------// + + + // Compute the intersection of a coordinate ray + // with the V-polytope + std::pair line_intersect_coord(const Point &r, + const unsigned int &rand_coord, + const VT &lamdas) const { + std::pair P1pair = P1.line_intersect_coord(r, rand_coord, lamdas); + std::pair P2pair = P2.line_intersect_coord(r, rand_coord, lamdas); + return std::pair(std::min(P1pair.first, P2pair.first), + std::max(P1pair.second, P2pair.second)); + } + + + // Compute the intersection of a coordinate ray + // with the V-polytope + std::pair line_intersect_coord(const Point &r, + const Point &r_prev, + const unsigned int &rand_coord, + const unsigned int &rand_coord_prev, + const VT &lamdas) const { + return line_intersect_coord(r, rand_coord, lamdas); + } + + + //------------------------------oracles for exponential sampling---------------////// + + // compute intersection points of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair quadratic_positive_intersect(Point const& r, + Point const& v, + VT const& Ac, + NT const& T, + VT& Ar, + VT& Av, + int& facet_prev) const + { + throw std::runtime_error("Quadratic polynomial trajectories are supported only for H-polytopes"); + } + + std::pair quadratic_positive_intersect(Point const& r, + Point const& v, + VT const& Ac, + NT const& T, + VT& Ar, + VT& Av, + NT const& lambda_prev, + int& facet_prev) const + { + throw std::runtime_error("Quadratic polynomial trajectories are supported only for H-polytopes"); + } + + //------------oracle for exact hmc spherical gaussian sampling---------------// + std::pair trigonometric_positive_intersect(Point const& r, Point const& v, + NT const& omega, int &facet_prev) const + { + return std::make_pair(0, 0); + } + + // shift polytope by a point c + void shift(const VT &c) { + P1.shift(c); + P2.shift(c); + } + + + // apply linear transformation, of square matrix T, to the V-Polytope + void linear_transformIt(const MT &T) { + P1.linear_transformIt(T); + P2.linear_transformIt(T); + } + + std::vector get_dists(const NT &radius) const { + std::vector res(upper_bound_of_hyperplanes(), radius); + return res; + } + + template + bool get_points_for_rounding (PointList &randPoints) { + if (num_of_vertices()>40*dimension()) { + return false; + } + if(!P1.get_points_for_rounding(randPoints)) { + return false; + } + if(!P2.get_points_for_rounding(randPoints)) { + return false; + } + + return true; + } + + void normalize() {} + + void resetFlags() {} + + void update_position_internal(NT&){} + + void compute_reflection (Point &v, const Point &p, const int &facet) const { + + if (facet == 1) { + P1.compute_reflection (v, p, facet); + } else { + P1.compute_reflection (v, p, facet); + } + + } + + template + void compute_reflection (Point &v, const Point &p, update_parameters const& params) const { + + if (params.facet_prev == 1) { + P1.compute_reflection (v, p, params); + } else { + P2.compute_reflection (v, p, params); + } + + } + +}; + + +#endif diff --git a/src/volesti/include/convex_bodies/vpolytope.h b/src/volesti/include/convex_bodies/vpolytope.h new file mode 100644 index 00000000..82ca6689 --- /dev/null +++ b/src/volesti/include/convex_bodies/vpolytope.h @@ -0,0 +1,648 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018-19 programs. +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VPOLYTOPE_H +#define VPOLYTOPE_H + +#include +#include +#include + +#include "lp_oracles/vpolyoracles.h" +#include + + +/// This class describes a polytope in V-representation or an V-polytope +/// i.e. a polytope defined as a convex combination of points +/// \tparam Point Point type +template +class VPolytope { +public: + typedef Point PointType; + typedef typename Point::FT NT; + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + +private: + unsigned int _d; //dimension + MT V; //matrix V. Each row contains a vertex + VT b; // vector b that contains first column of ine file + std::pair _inner_ball; + + // TODO: Why don't we use std::vector and std::vector for these pointers? + REAL *conv_comb, *conv_comb2, *conv_mem, *row; + int *colno, *colno_mem; + +public: + VPolytope() {} + + VPolytope(const unsigned int &dim, const MT &_V, const VT &_b): + _d{dim}, V{_V}, b{_b}, + conv_comb{new REAL[V.rows() + 1]}, + conv_comb2{new REAL[V.rows() + 1]}, + conv_mem{new REAL[V.rows()]}, + row{new REAL[V.rows() + 1]}, + colno{new int[V.rows() + 1]}, + colno_mem{new int[V.rows()]} + { + } + + // Construct matrix V which contains the vertices row-wise + // TODO: change rows; + VPolytope(std::vector> const& Pin) + { + _d = Pin[0][1] - 1; + V.resize(Pin.size() - 1, _d); + b.resize(Pin.size() - 1); + for (unsigned int i = 1; i < Pin.size(); i++) { + b(i - 1) = Pin[i][0]; + unsigned int j; + for (j = 1; j < _d + 1; j++) { + V(i - 1, j - 1) = Pin[i][j]; + } + } + conv_comb = new REAL[Pin.size()]; + conv_comb2 = new REAL[Pin.size()]; + conv_mem = new REAL[V.rows()]; + row = new REAL[V.rows() + 1]; + colno = new int[V.rows() + 1]; + colno_mem = new int[V.rows()]; + } + + template + void copy_array(T* source, T* result, size_t count) + { + T* tarray; + tarray = new T[count]; + std::copy_n(source, count, tarray); + delete [] result; + result = tarray; + } + + VPolytope& operator=(const VPolytope& other) + { + if (this != &other) { // protect against invalid self-assignment + _d = other._d; + V = other.V; + b = other.b; + + copy_array(other.conv_comb, conv_comb, V.rows() + 1); + copy_array(other.conv_comb2, conv_comb2, V.rows() + 1); + copy_array(other.conv_mem, conv_mem, V.rows()); + copy_array(other.row, row, V.rows() + 1); + copy_array(other.colno, colno, V.rows() + 1); + copy_array(other.colno_mem, colno_mem, V.rows()); + } + return *this; + } + + VPolytope& operator=(VPolytope&& other) + { + if (this != &other) { // protect against invalid self-assignment + _d = other._d; + V = other.V; + b = other.b; + + conv_comb = other.conv_comb; other.conv_comb = nullptr; + conv_comb2 = other.conv_comb2; other.conv_comb2 = nullptr; + conv_mem = other.conv_mem; other.conv_mem = nullptr; + row = other.row; other.row = nullptr; + colno = other.colno; colno = nullptr; + colno_mem = other.colno_mem; colno_mem = nullptr; + } + return *this; + } + + + VPolytope(const VPolytope& other) : + _d{other._d}, V{other.V}, b{other.b}, + conv_comb{new REAL[V.rows() + 1]}, + conv_comb2{new REAL[V.rows() + 1]}, + conv_mem{new REAL[V.rows()]}, + row{new REAL[V.rows() + 1]}, + colno{new int[V.rows() + 1]}, + colno_mem{new int[V.rows()]} + { + std::copy_n(other.conv_comb, V.rows() + 1, conv_comb); + std::copy_n(other.conv_comb2, V.rows() + 1, conv_comb2); + std::copy_n(other.conv_mem, V.rows(), conv_mem); + std::copy_n(other.row, V.rows() + 1, row); + std::copy_n(other.colno, V.rows() + 1, colno); + std::copy_n(other.colno_mem, V.rows(), colno_mem); + } + + VPolytope(VPolytope&& other) : + _d{other._d}, V{other.V}, b{other.b}, + conv_comb{nullptr}, conv_comb2{nullptr}, conv_mem{nullptr}, row{nullptr}, + colno{nullptr}, colno_mem{nullptr} + { + conv_comb = other.conv_comb; other.conv_comb = nullptr; + conv_comb2 = other.conv_comb2; other.conv_comb2 = nullptr; + conv_mem = other.conv_mem; other.conv_mem = nullptr; + row = other.row; other.row = nullptr; + colno = other.colno; colno = nullptr; + colno_mem = other.colno_mem; colno_mem = nullptr; + } + + ~VPolytope() { + delete [] conv_comb; + delete [] conv_comb2; + delete [] colno; + delete [] colno_mem; + delete [] row; + delete [] conv_mem; + } + + std::pair InnerBall() const + { + return _inner_ball; + } + + void set_InnerBall(std::pair const& innerball) //const + { + _inner_ball = innerball; + } + + void set_interior_point(Point const& r) + { + _inner_ball.first = r; + } + + // return dimension + unsigned int dimension() const { + return _d; + } + + + // this function returns 0. The main sampler requests this function to set the length of lambdas vector + int num_of_hyperplanes() const { + return 0; + } + + int num_of_generators() const { + return 0; + } + + // compute the number of facets of the cyclic polytope in dimension _d with the same number of vertices + // this is an upper bound for the number of the facets from McMullen's Upper Bound Theorem + unsigned int upper_bound_of_hyperplanes() const { + return 2 * _d; + } + + + // return the number of vertices + int num_of_vertices() const { + return V.rows(); + } + + // return the matrix V + MT get_mat() const { + return V; + } + + // return the vector b + VT get_vec() const { + return b; + } + + // change the matrix V + void set_mat(const MT &V2) { + V = V2; + } + + // change the vector b + void set_vec(const VT &b2) { + b = b2; + } + + MT get_T() const { + return V; + } + + + // print polytope in input format + void print() { + std::cout << " " << V.rows() << " " << _d << " float" << std::endl; + for (unsigned int i = 0; i < V.rows(); i++) { + for (unsigned int j = 0; j < _d; j++) { + std::cout << V(i, j) << " "; + } + std::cout<<"\n"; + } + } + + Point get_mean_of_vertices() { + Point xc(_d); + for (int i = 0; i < num_of_vertices(); ++i) { + xc.add(V.row(i)); + } + xc *= (1.0/NT(num_of_vertices())); + + return xc; + } + + + NT get_max_vert_norm() { + NT rad =0.0; + NT rad_iter; + for (int i = 0; i < num_of_vertices(); ++i) { + rad_iter = V.row(i).norm(); + if(rad_iter>rad)rad = rad_iter; + } + return rad; + } + + void normalize() {} + + // take d+1 points as input and compute the chebychev ball of the defined simplex + // done is true when the simplex is full dimensional and false if it is not + std::pair get_center_radius_inscribed_simplex(const typename std::vector::iterator it_beg, + const typename std::vector::iterator it_end) { + + Point p0 = *it_beg,p1,c; + unsigned int dim = p0.dimension(), i, j; + std::vector temp_p; + NT radius = 0.0, gi, sum = 0.0; + MT B(dim,dim), Bg(dim,dim), e(1,dim); + VT row(dim), g(dim); + std::pair result; + + for (j=1; jgetCoefficients() - p0.getCoefficients(); + } + + Bg = B; + B = B.inverse(); + for (i=0; i ComputeInnerBall() { + + std::vector verts(_d+1); + std::vector vecp(_d); + unsigned int vert_rand, pointer=0; + unsigned int i,j; + int m = num_of_vertices(); + std::vector x_vec(_d); + bool done=false; + unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); + RNGType rng(seed); + boost::random::uniform_int_distribution<> uidist(1, m); + + std::pair res; + // while d+1 points do not define a full dimensional simplex repeat + while(!done){ + pointer=0; + x_vec.assign(_d+1,0); + while(pointer!=_d+1) { + vert_rand = uidist(rng); + // Check if this vertex is selected first time + if (std::find(x_vec.begin(), x_vec.begin() + pointer, vert_rand) == x_vec.begin() + pointer) { + x_vec[pointer] = vert_rand; + pointer++; + } + } + + for(i=0; i<(_d+1); i++){ + for (j=0; j<_d; j++) { + vecp[j] = V(x_vec[i] - 1, j); + } + verts[i] = Point(_d,vecp.begin(),vecp.end()); + } + res=get_center_radius_inscribed_simplex(verts.begin(), verts.end(), done); + } + return res; + }*/ + + + std::pair ComputeInnerBall() { + + NT radius = std::numeric_limits::max(), min_plus; + Point center(_d); + + std::list randPoints; + if (!get_points_for_rounding(randPoints)) { + center = get_mean_of_vertices(); + } else { + + MT Ap(_d,randPoints.size()); + typename std::list::iterator rpit=randPoints.begin(); + + unsigned int i, j = 0; + for ( ; rpit!=randPoints.end(); rpit++, j++) { + const NT* point_data = rpit->getCoefficients().data(); + + for ( i=0; i < rpit->dimension(); i++){ + Ap(i,j)=double(*point_data); + point_data++; + } + } + MT Q(_d, _d); + VT c2(_d); + size_t w=1000; + + KhachiyanAlgo(Ap,0.01,w,Q,c2); // call Khachiyan algorithm + + //Get ellipsoid matrix and center as Eigen objects + for(unsigned int i=0; i<_d; i++) center.set_coord(i, NT(c2(i))); + } + + std::pair res; + Point v(_d); + for (unsigned int i = 0; i < _d; ++i) { + v.set_to_origin(); + v.set_coord(i, 1.0); + res = intersect_double_line_Vpoly(V, center, v, row, colno); + min_plus = std::min(res.first, -1.0*res.second); + if (min_plus < radius) radius = min_plus; + } + + radius = radius / std::sqrt(NT(_d)); + _inner_ball = std::pair (center, radius); + return std::pair (center, radius); + } + + + // check if point p belongs to the convex hull of V-Polytope P + int is_in(const Point &p, NT tol=NT(0)) const { + if (memLP_Vpoly(V, p, conv_mem, colno_mem)){ + return -1; + } + return 0; + } + + + // compute intersection point of ray starting from r and pointing to v + // with the V-polytope + std::pair line_intersect(const Point &r, const Point &v) const { + + return intersect_double_line_Vpoly(V, r, v, row, colno); + } + + + // compute intersection point of ray starting from r and pointing to v + // with the V-polytope + std::pair line_intersect(const Point &r, const Point &v, const VT &Ar, + const VT &Av) const { + return intersect_double_line_Vpoly(V, r, v, row, colno); + } + + // compute intersection point of ray starting from r and pointing to v + // with the V-polytope + std::pair line_intersect(const Point &r, const Point &v, const VT &Ar, + const VT &Av, const NT &lambda_prev) const { + + return intersect_double_line_Vpoly(V, r, v, row, colno); + } + + + std::pair line_positive_intersect(const Point &r, const Point &v) const { + return std::pair (intersect_line_Vpoly(V, r, v, conv_comb, row, colno, false, false), 1); + } + + std::pair line_positive_intersect(const Point &r, const Point &v, const VT &Ar, + const VT &Av) const { + return line_positive_intersect(r, v); + } + + + std::pair line_positive_intersect(const Point &r, const Point &v, const VT &Ar, + const VT &Av, const NT &lambda_prev) const { + return line_positive_intersect(r, v); + } + + //-------------------------accelarated billiard--------------------------------// + template + std::pair line_first_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + update_parameters ¶ms) const + { + return line_positive_intersect(r, v); + } + + template + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + MT const& AA, + update_parameters ¶ms) const + { + return line_positive_intersect(r, v); + } + + template + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + update_parameters ¶ms) const + { + return line_positive_intersect(r, v); + } + //------------------------------------------------------------------------------// + + + // Compute the intersection of a coordinate ray + // with the V-polytope + std::pair line_intersect_coord(const Point &r, + const unsigned int rand_coord, + const VT &lamdas) const { + Point v(_d); + v.set_coord(rand_coord, 1.0); + return intersect_double_line_Vpoly(V, r, v, row, colno); + } + + + // Compute the intersection of a coordinate ray + // with the V-polytope + std::pair line_intersect_coord(const Point &r, + const Point &r_prev, + const unsigned int rand_coord, + const unsigned int rand_coord_prev, + const VT &lamdas) const { + return line_intersect_coord(r, rand_coord, lamdas); + } + + + //------------------------------oracles for exponential sampling---------------////// + + // compute intersection points of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair quadratic_positive_intersect(Point const& r, + Point const& v, + VT const& Ac, + NT const& T, + VT& Ar, + VT& Av, + int& facet_prev) const + { + throw std::runtime_error("Quadratic polynomial trajectories are supported only for H-polytopes"); + } + + std::pair quadratic_positive_intersect(Point const& r, + Point const& v, + VT const& Ac, + NT const& T, + VT& Ar, + VT& Av, + NT const& lambda_prev, + int& facet_prev) const + { + throw std::runtime_error("Quadratic polynomial trajectories are supported only for H-polytopes"); + } + + + //------------oracle for exact hmc spherical gaussian sampling---------------// + std::pair trigonometric_positive_intersect(Point const& r, Point const& v, + NT const& omega, int &facet_prev) const + { + return std::make_pair(0, 0); + } + + + // shift polytope by a point c + void shift(const VT &c) { + MT V2 = V.transpose().colwise() - c; + V = V2.transpose(); + } + + + // apply linear transformation, of square matrix T, to the V-Polytope + void linear_transformIt(const MT &T) { + MT V2 = T.inverse() * V.transpose(); + V = V2.transpose(); + } + + + // consider an upper bound for the number of facets of a V-polytope + // for each facet consider a lower bound for the distance from the origin + // useful for CV algorithm to get the first gaussian + std::vector get_dists(const NT &radius) const { + std::vector res(upper_bound_of_hyperplanes(), radius); + return res; + } + + + // in number_of_vertices<=20*dimension use the vertices for the rounding + // otherwise you have to sample from the V-polytope + template + bool get_points_for_rounding (PointList &randPoints) { + if (num_of_vertices()>20*_d) { + return false; + } + unsigned int j; + + typename std::vector::iterator pointIt; + for (int i=0; i 0.0) { + Fmat2.row(count) = V.row(j); + count++; + } else { + outvert = j; + } + } + + VT a = Fmat2.colPivHouseholderQr().solve(VT::Ones(_d)); + if (a.dot(V.row(outvert)) > 1.0) a = -a; + a /= a.norm(); + + // compute reflection + a *= (-2.0 * v.dot(a)); + v += a; + } + + template + void compute_reflection(Point &v, const Point &p, update_parameters const& params) const { + + int count = 0, outvert; + MT Fmat2(_d,_d); + for (int j = 0; j < num_of_vertices(); ++j) { + if (*(conv_comb + j) > 0.0) { + Fmat2.row(count) = V.row(j); + count++; + } else { + outvert = j; + } + } + + VT a = Fmat2.colPivHouseholderQr().solve(VT::Ones(_d)); + if (a.dot(V.row(outvert)) > 1.0) a *= -1.0; + a /= a.norm(); + + // compute reflection + a *= (-2.0 * v.dot(a)); + v += a; + } + + void resetFlags() {} + + void update_position_internal(NT&){} + + template + std::tuple curve_intersect( + NT t_prev, + NT t0, + NT eta, + std::vector &coeffs, + bfunc phi, + bfunc grad_phi, + NonLinearOracle &intersection_oracle, + int ignore_facet=-1) + { + return intersection_oracle.apply(t_prev, t0, eta, V, *this, + coeffs, phi, grad_phi, ignore_facet); + } + + +}; + +#endif diff --git a/src/volesti/include/convex_bodies/zonoIntersecthpoly.h b/src/volesti/include/convex_bodies/zonoIntersecthpoly.h new file mode 100644 index 00000000..bbad43a6 --- /dev/null +++ b/src/volesti/include/convex_bodies/zonoIntersecthpoly.h @@ -0,0 +1,272 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018-19 programs. +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +#ifndef ZONOINTERSECTHPOLY_H +#define ZONOINTERSECTHPOLY_H + +/// This class represents the intersection of a Zonotope with an H-polytope +/// \tparam Zonotope Zonotope Type +/// \tparam HPolytope HPolytope Type +template +class ZonoIntersectHPoly { +private: + Zonotope Z; + HPolytope HP; +public: + typedef typename HPolytope::NT NT; + typedef typename HPolytope::VT VT; + typedef typename HPolytope::MT MT; + typedef typename Zonotope::PointType PointType; + + ZonoIntersectHPoly() + {} + + ZonoIntersectHPoly(Zonotope &Z1, HPolytope &HP1) + : Z(Z1) + , HP(HP1) + {} + + Zonotope first() const { return Z; } + HPolytope second() const { return HP; } + + int is_in(const PointType &p) const { + if(HP.is_in(p)==-1) + return Z.is_in(p); + return 0; + } + + int num_of_hyperplanes() const { + return HP.num_of_hyperplanes(); + } + + unsigned int dimension() const { + return HP.dimension(); + } + + unsigned int num_of_generators() const { + return Z.num_of_generators(); + } + + MT get_mat() const { + return HP.get_mat(); + } + + MT get_T() const { + return Z.get_mat(); + } + + MT get_vec() const { + return HP.get_vec(); + } + + std::pair InnerBall() const + { + return Z.InnerBall(); + } + + std::pair line_intersect(PointType const& r, + PointType const& v) const + { + + std::pair polypair = HP.line_intersect(r, v); + std::pair zonopair = Z.line_intersect(r, v); + return std::pair(std::min(polypair.first, zonopair.first), + std::max(polypair.second, zonopair.second)); + } + + std::pair line_intersect(PointType const& r, + PointType const& v, + VT& Ar, + VT& Av) const + { + std::pair polypair = HP.line_intersect(r, v, Ar, Av); + std::pair zonopair = Z.line_intersect(r, v); + return std::pair(std::min(polypair.first, zonopair.first), + std::max(polypair.second, zonopair.second)); + } + + std::pair line_intersect(PointType const& r, + PointType const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev) const + { + std::pair polypair = HP.line_intersect(r, v, Ar, Av, lambda_prev); + std::pair zonopair = Z.line_intersect(r, v); + return std::pair(std::min(polypair.first, zonopair.first), + std::max(polypair.second, zonopair.second)); + } + + //First coordinate ray shooting intersecting convex body + std::pair line_intersect_coord(PointType const& r, + unsigned int const& rand_coord, + VT& lamdas) const + { + std::pair polypair = HP.line_intersect_coord(r, rand_coord, lamdas); + std::pair zonopair = Z.line_intersect_coord(r, rand_coord, lamdas); + return std::pair(std::min(polypair.first, zonopair.first), + std::max(polypair.second, zonopair.second)); + } + + + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT& Ar, + VT& Av) const + { + std::pair polypair = HP.line_positive_intersect(r, v, Ar, Av); + std::pair zonopair = Z.line_positive_intersect(r, v, Ar, Av); + int facet = HP.num_of_hyperplanes()+1; + + if (polypair.first < zonopair.first ) facet = polypair.second; + + return std::pair(std::min(polypair.first, zonopair.first), facet); + } + + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev) const + { + std::pair polypair = HP.line_positive_intersect(r, v, Ar, Av, + lambda_prev); + std::pair zonopair = Z.line_positive_intersect(r, v, Ar, Av); + int facet = HP.num_of_hyperplanes()+1; + + if (polypair.first < zonopair.first ) facet = polypair.second; + + return std::pair(std::min(polypair.first, zonopair.first), + facet); + } + + //---------------------accelarated billiard---------------------// + template + std::pair line_first_positive_intersect(PointType const& r, + PointType const& v, + VT& Ar, + VT& Av, + update_parameters& params) const + { + std::pair polypair = HP.line_first_positive_intersect(r, v, Ar, Av, params); + std::pair zonopair = Z.line_positive_intersect(r, v, Ar, Av); + int facet = HP.num_of_hyperplanes(); + params.facet_prev = polypair.second; + + if (polypair.first < zonopair.first ) { + facet = polypair.second; + params.hit_ball = false; + } else { + params.hit_ball = true; + } + + return std::pair(std::min(polypair.first, zonopair.first), facet); + } + + template + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + MT const& AA, + update_parameters& params) const + { + std::pair polypair = HP.line_positive_intersect(r, v, Ar, Av, lambda_prev, params); + std::pair zonopair = Z.line_positive_intersect(r, v, Ar, Av); + int facet = HP.num_of_hyperplanes(); + params.facet_prev = polypair.second; + + if (polypair.first < zonopair.first ) { + facet = polypair.second; + params.hit_ball = false; + } else { + params.hit_ball = true; + } + + return std::pair(std::min(polypair.first, zonopair.first), facet); + } + + template + std::pair line_positive_intersect(PointType const& r, + PointType const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + update_parameters& params) const + { + std::pair polypair = HP.line_positive_intersect(r, v, Ar, Av, lambda_prev, params); + std::pair zonopair = Z.line_positive_intersect(r, v, Ar, Av); + int facet = HP.num_of_hyperplanes(); + params.facet_prev = polypair.second; + + if (polypair.first < zonopair.first ) { + facet = polypair.second; + params.hit_ball = false; + } else { + params.hit_ball = true; + } + + return std::pair(std::min(polypair.first, zonopair.first), facet); + } +//-------------------------------------------------------------------------// + + //Not the first coordinate ray shooting intersecting convex body + std::pair line_intersect_coord(PointType const& r, + PointType const& r_prev, + unsigned int const& rand_coord, + unsigned int const& rand_coord_prev, + VT& lamdas) const + { + + std::pair polypair = HP.line_intersect_coord(r, r_prev, + rand_coord, + rand_coord_prev, + lamdas); + std::pair zonopair = Z.line_intersect_coord(r, rand_coord, + lamdas); + return std::pair(std::min(polypair.first, zonopair.first), + std::max(polypair.second, zonopair.second)); + } + + std::pair query_dual(PointType const& p, + unsigned int const& rand_coord) const + { + std::pair polypair = HP.query_dual(p, rand_coord); + std::pair zonopair = Z.line_intersect_coord(p, rand_coord); + return std::pair(std::min(polypair.first, zonopair.first), + std::max(polypair.second, zonopair.second)); + } + + void compute_reflection (PointType &v, + PointType const& p, + int const& facet) const + { + if (facet == (HP.num_of_hyperplanes()+1)) { + Z.compute_reflection(v, p, facet); + } else { + HP.compute_reflection(v, p, facet); + } + + } + + template + void compute_reflection (PointType &v, const PointType &p, update_parameters const& params) const { + + if (params.hit_ball) { + Z.compute_reflection(v, p, params); + } else { + HP.compute_reflection(v, p, params.facet_prev); + } + + } + +}; + +#endif diff --git a/src/volesti/include/convex_bodies/zpolytope.h b/src/volesti/include/convex_bodies/zpolytope.h new file mode 100644 index 00000000..1ad8868e --- /dev/null +++ b/src/volesti/include/convex_bodies/zpolytope.h @@ -0,0 +1,619 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018-19 programs. +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ZPOLYTOPE_H +#define ZPOLYTOPE_H + +#include + +#include +#include +#include "lp_oracles/vpolyoracles.h" +#include "lp_oracles/zpolyoracles.h" + +/// This class describes a zonotope i.e. the Minkowski sum of a set of line segments +/// \tparam Point Point type +template +class Zonotope { +public: + typedef Point PointType; + typedef typename Point::FT NT; + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + +private: + unsigned int _d; //dimension + MT V; //matrix V. Each row contains a vertex + VT b; // vector b that contains first column of ine file + MT T; + std::pair _inner_ball; + NT maxNT = std::numeric_limits::max(); + NT minNT = std::numeric_limits::lowest(); + + REAL *conv_comb, *row_mem, *row; + int *colno, *colno_mem; + MT sigma; + MT Q0; + + +public: + + Zonotope() {} + + Zonotope(const unsigned int &dim, const MT &_V, const VT &_b): + _d{dim}, V{_V}, b{_b}, + conv_comb{new REAL[V.rows() + 1]}, + row_mem{new REAL[V.rows()]}, + row{new REAL[V.rows() + 1]}, + colno{new int[V.rows() + 1]}, + colno_mem{new int[V.rows()]} + { + compute_eigenvectors(V.transpose()); + } + + /*Zonotope(unsigned int const dim, MT const& _V, VT const& _b) + { + _d = dim; + V = _V; + b = _b; + + conv_comb = new REAL[V.rows()+1]; + row_mem = new REAL[V.rows()]; + row = new REAL[V.rows() + 1]; + colno = new int[V.rows() + 1]; + colno_mem = new int[V.rows()]; + + //conv_comb = (REAL *) malloc((V.rows()+1) * sizeof(*conv_comb)); + //colno = (int *) malloc((V.rows()+1) * sizeof(*colno)); + //row = (REAL *) malloc((V.rows()+1) * sizeof(*row)); + //colno_mem = (int *) malloc((V.rows()) * sizeof(*colno_mem)); + //row_mem = (REAL *) malloc((V.rows()) * sizeof(*row_mem)); + + compute_eigenvectors(V.transpose()); + }*/ + + Zonotope(std::vector > const& Pin) + { + _d = Pin[0][1] - 1; + V.resize(Pin.size() - 1, _d); + b.resize(Pin.size() - 1); + for (unsigned int i = 1; i < Pin.size(); i++) + { + b(i - 1) = Pin[i][0]; + for (unsigned int j = 1; j < _d + 1; j++) + { + V(i - 1, j - 1) = Pin[i][j]; + } + } + + conv_comb = new REAL[Pin.size()]; + row_mem = new REAL[V.rows()]; + row = new REAL[V.rows() + 1]; + colno = new int[V.rows() + 1]; + colno_mem = new int[V.rows()]; + + compute_eigenvectors(V.transpose()); + } + + template + void copy_array(T* source, T* result, size_t count) + { + T* tarray; + tarray = new T[count]; + std::copy_n(source, count, tarray); + delete [] result; + result = tarray; + } + + Zonotope& operator=(const Zonotope& other) + { + if (this != &other) { // protect against invalid self-assignment + _d = other._d; + V = other.V; + b = other.b; + T = other.T; + + copy_array(other.conv_comb, conv_comb, V.rows() + 1); + copy_array(other.row_mem, row_mem, V.rows()); + copy_array(other.row, row, V.rows() + 1); + copy_array(other.colno, colno, V.rows() + 1); + copy_array(other.colno_mem, colno_mem, V.rows()); + } + return *this; + } + + Zonotope& operator=(Zonotope&& other) + { + if (this != &other) { // protect against invalid self-assignment + _d = other._d; + V = other.V; + b = other.b; + T = other.T; + + conv_comb = other.conv_comb; other.conv_comb = nullptr; + row_mem = other.row_mem; other.row_mem = nullptr; + row = other.row; other.row = nullptr; + colno = other.colno; colno = nullptr; + colno_mem = other.colno_mem; colno_mem = nullptr; + } + return *this; + } + + + Zonotope(const Zonotope& other) : + _d{other._d}, V{other.V}, b{other.b}, T{other.T}, + conv_comb{new REAL[V.rows() + 1]}, + row_mem{new REAL[V.rows()]}, + row{new REAL[V.rows() + 1]}, + colno{new int[V.rows() + 1]}, + colno_mem{new int[V.rows()]} + { + std::copy_n(other.conv_comb, V.rows() + 1, conv_comb); + std::copy_n(other.row_mem, V.rows(), row_mem); + std::copy_n(other.row, V.rows() + 1, row); + std::copy_n(other.colno, V.rows() + 1, colno); + std::copy_n(other.colno_mem, V.rows(), colno_mem); + } + + Zonotope(Zonotope&& other) : + _d{other._d}, V{other.V}, b{other.b}, T{other.T}, + conv_comb{nullptr}, row_mem{nullptr}, row{nullptr}, + colno{nullptr}, colno_mem{nullptr} + { + conv_comb = other.conv_comb; other.conv_comb = nullptr; + row_mem = other.row_mem; other.row_mem = nullptr; + row = other.row; other.row = nullptr; + colno = other.colno; colno = nullptr; + colno_mem = other.colno_mem; colno_mem = nullptr; + } + + ~Zonotope() { + delete [] conv_comb; + delete [] colno; + delete [] colno_mem; + delete [] row; + delete [] row_mem; + } + + void set_interior_point(Point const& r) + { + _inner_ball.first = r; + } + + // return the dimension + unsigned int dimension() const + { + return _d; + } + + // this function returns 0. The main sampler requests this function to set the length of lambdas vector + int num_of_hyperplanes() const + { + return 0; + } + + + // return the number of parallelopipeds. Used in get_dists fnction. + unsigned int upper_bound_of_hyperplanes() const + { + return 2*_d; + } + + void compute_eigenvectors(MT const& G) + { + + int k = G.cols(); + MT ps = G; + sigma.resize(k,k); + sigma = ps.transpose()*ps; + sigma = (sigma + sigma.transpose()) / 2; + Eigen::SelfAdjointEigenSolver es(sigma); + + MT D = es.eigenvalues().asDiagonal(); + MT Q2 = es.eigenvectors(); + + Q0.resize(k,k-_d); + int count=0; + for (int i = 0; i < k; ++i) + { + if (es.eigenvalues()[i]<0.0000001) + { + for (int j = 0; j < k; ++j) + { + Q0(j, count) = Q2(j, i); + } + count++; + } + } + Eigen::JacobiSVD svd(Q0, Eigen::ComputeFullU | Eigen::ComputeFullV); + MT T2 = svd.matrixU().transpose(); + T.resize(_d,k); + for (int i = k-_d; i < k; ++i) + { + for (int j = 0; j < k; ++j) + { + T(i-k+_d,j) = T2(i,j); + } + } + + for (int i1 = 0; i1 < k; ++i1) + { + sigma(i1,i1) = sigma(i1,i1) + 0.00000001; + } + } + + MT get_T() const + { + return T; + } + + MT get_Q0() const + { + return Q0; + } + + MT get_sigma() const + { + return sigma; + } + + // return the number of vertices + int num_of_vertices() const + { + return V.rows(); + } + + std::pair InnerBall() const + { + return _inner_ball; + } + + void set_InnerBall(std::pair const& innerball) //const + { + _inner_ball = innerball; + } + + // return the number of generators + int num_of_generators() const + { + return V.rows(); + } + + // return the matrix V + MT get_mat() const + { + return V; + } + + // return the vector b + VT get_vec() const + { + return b; + } + + // change the matrix V + void set_mat(MT const& V2) + { + V = V2; + } + + // change the vector b + void set_vec(VT const& b2) + { + b = b2; + } + + Point get_mean_of_vertices() const + { + return Point(_d); + } + + + NT get_max_vert_norm() const + { + return 0.0; + } + + + // print polytope in input format + void print() + { +#ifdef VOLESTI_DEBUG + std::cout << " " << V.rows() << " " << _d << " float" << std::endl; +#endif + for (unsigned int i = 0; i < V.rows(); i++) { + for (unsigned int j = 0; j < _d; j++) { +#ifdef VOLESTI_DEBUG + std::cout << V(i, j) << " "; +#endif + } +#ifdef VOLESTI_DEBUG + std::cout<<"\n"; +#endif + } + } + + + // check if point p belongs to the convex hull of V-Polytope P + int is_in(Point const& p, NT tol=NT(0)) const + { + if(memLP_Zonotope(V, p, row_mem, colno_mem)) + { + return -1; + } + return 0; + } + + + // Compute an inner ball of the zonotope + std::pair ComputeInnerBall() + { + std::vector temp(_d,0); + NT radius = maxNT, min_plus; + Point center(_d); + + for (unsigned int i = 0; i < _d; ++i) { + temp.assign(_d,0); + temp[i] = 1.0; + Point v(_d,temp.begin(), temp.end()); + min_plus = intersect_line_Vpoly(V, center, v, conv_comb, + row, colno, false, true); + if (min_plus < radius) radius = min_plus; + } + + radius = radius / std::sqrt(NT(_d)); + _inner_ball = std::pair (center, radius); + return _inner_ball; + } + + // compute intersection point of ray starting from r and pointing to v + // with the Zonotope + std::pair line_intersect(Point const& r, Point const& v) const + { + return intersect_line_zono(V, r, v, conv_comb, colno); + } + + + // compute intersection point of ray starting from r and pointing to v + // with the Zonotope + std::pair line_intersect(Point const& r, + Point const& v, + VT const& Ar, + VT const& Av) const + { + return intersect_line_zono(V, r, v, conv_comb, colno); + } + + // compute intersection point of ray starting from r and pointing to v + // with the Zonotope + std::pair line_intersect(Point const& r, + Point const& v, + VT const& Ar, + VT const& Av, + NT const& lambda_prev) const + { + return intersect_line_zono(V, r, v, conv_comb, colno); + } + + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT const& Ar, + VT const& Av) const + { + return std::pair (intersect_line_Vpoly(V, r, v, conv_comb, + row, colno, + false, true), 1); + } + + + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT const& Ar, + VT const& Av, + NT const& lambda_prev) const + { + return line_positive_intersect(r, v, Ar, Av); + } + + //---------------------------accelarated billiard-----------------------------// + template + std::pair line_first_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + update_parameters& params) const + { + return line_positive_intersect(r, v, Ar, Av); + } + + template + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + MT const& AA, + update_parameters& params) const + { + return line_positive_intersect(r, v, Ar, Av); + } + + template + std::pair line_positive_intersect(Point const& r, + Point const& v, + VT& Ar, + VT& Av, + NT const& lambda_prev, + update_parameters& params) const + { + return line_positive_intersect(r, v, Ar, Av); + } + //------------------------------------------------------------------------------// + + // Compute the intersection of a coordinate ray + // with the Zonotope + std::pair line_intersect_coord(Point const& r, + const unsigned int rand_coord, + VT const& lamdas) const + { + + std::vector temp(_d,0); + temp[rand_coord]=1.0; + Point v(_d,temp.begin(), temp.end()); + + return intersect_line_zono(V, r, v, conv_comb, colno); + + } + + + // Compute the intersection of a coordinate ray + // with the Zonotope + std::pair line_intersect_coord(Point const& r, + Point const& r_prev, + const unsigned int rand_coord, + const unsigned int rand_coord_prev, + VT const& lamdas) const + { + return line_intersect_coord(r, rand_coord, lamdas); + } + + + //------------------------------oracles for exponential sampling---------------////// + + // compute intersection points of a ray starting from r and pointing to v + // with polytope discribed by A and b + std::pair quadratic_positive_intersect(Point const& r, + Point const& v, + VT const& Ac, + NT const& T, + VT& Ar, + VT& Av, + int& facet_prev) const + { + throw std::runtime_error("Quadratic polynomial trajectories are supported only for H-polytopes"); + } + + std::pair quadratic_positive_intersect(Point const& r, + Point const& v, + VT const& Ac, + NT const& T, + VT& Ar, + VT& Av, + NT const& lambda_prev, + int& facet_prev) const + { + throw std::runtime_error("Quadratic polynomial trajectories are supported only for H-polytopes"); + } + + + //------------oracle for exact hmc spherical gaussian sampling---------------// + std::pair trigonometric_positive_intersect(Point const& r, Point const& v, + NT const& omega, int &facet_prev) const + { + return std::make_pair(0, 0); + } + + // shift polytope by a point c + // vector c has to be always the zero vector + void shift(VT const& c) + { + return; + } + + + // get number of parallelopipeds + // for each parallelopiped consider a lower bound for the distance from the origin + // useful for CG algorithm to get the first gaussian + std::vector get_dists(NT const& radius) const + { + std::vector res(upper_bound_of_hyperplanes(), radius); + return res; + } + + // apply linear transformation, of square matrix T, to thr Zonotope + void linear_transformIt(MT const& T) + { + MT V2 = T.inverse() * V.transpose(); + V = V2.transpose(); + } + + // return false to the rounding function + // no points are given so they have o be sampled + template + bool get_points_for_rounding (T const& randPoints) + { + return false; + } + + void normalize() {} + + void compute_reflection(Point &v, Point const& p, int const& facet) const + { + //compute_reflection(v, p, 0.0); + + int count = 0; + MT Fmat(_d-1,_d); + const NT e = 0.0000000001; + for (int j = 0; j < num_of_generators(); ++j) + { + if (((1.0 - *(conv_comb + j) ) > e || (1.0 - *(conv_comb + j) ) + > e*std::abs(*(conv_comb + j))) && + ((1.0 + *(conv_comb + j) ) > e || (1.0 + *(conv_comb + j) ) + > e*std::abs(*(conv_comb + j)))) + { + Fmat.row(count) = V.row(j); + count++; + } + } + + VT a = Fmat.fullPivLu().kernel(); + + if(p.getCoefficients().dot(a) < 0.0) a *= -1.0; + + a = a/a.norm(); + + // compute reflection + a *= (-2.0 * v.dot(a)); + v += a; + } + + void resetFlags() {} + + void update_position_internal(NT&){} + + template + void compute_reflection(Point &v, const Point &p, update_parameters const& params) const { + + int count = 0; + MT Fmat(_d-1,_d); + const NT e = 0.0000000001; + for (int j = 0; j < num_of_generators(); ++j) { + if (((1.0 - *(conv_comb + j) ) > e || (1.0 - *(conv_comb + j) ) > e*std::abs(*(conv_comb + j))) && + ((1.0 + *(conv_comb + j) ) > e || (1.0 + *(conv_comb + j) ) > e*std::abs(*(conv_comb + j)))) { + Fmat.row(count) = V.row(j); + count++; + } + } + + VT a = Fmat.fullPivLu().kernel(); + + if(p.getCoefficients().dot(a) < 0.0) a *= -1.0; + + a = a/a.norm(); + + // compute reflection + a *= (-2.0 * v.dot(a)); + v += a; + } + +}; + +#endif diff --git a/src/volesti/include/diagnostics/diagnostics.hpp b/src/volesti/include/diagnostics/diagnostics.hpp new file mode 100644 index 00000000..64905875 --- /dev/null +++ b/src/volesti/include/diagnostics/diagnostics.hpp @@ -0,0 +1,24 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef DIAGNOSTICS_DIAGNOSTICS_HPP +#define DIAGNOSTICS_DIAGNOSTICS_HPP + +#include "misc/print_table.hpp" +#include "diagnostics/multivariate_psrf.hpp" +#include "diagnostics/univariate_psrf.hpp" +#include "diagnostics/interval_psrf.hpp" +#include "diagnostics/geweke.hpp" +#include "diagnostics/raftery.hpp" +#include "diagnostics/effective_sample_size.hpp" +#include "diagnostics/thin_samples.hpp" +#include "diagnostics/print_diagnostics.hpp" + +#endif diff --git a/src/volesti/include/diagnostics/effective_sample_size.hpp b/src/volesti/include/diagnostics/effective_sample_size.hpp new file mode 100644 index 00000000..c372eb69 --- /dev/null +++ b/src/volesti/include/diagnostics/effective_sample_size.hpp @@ -0,0 +1,123 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#include + +#ifndef DIAGNOSTICS_EFFECTIVE_SAMPLE_SIZE_HPP +#define DIAGNOSTICS_EFFECTIVE_SAMPLE_SIZE_HPP + +template +void cummulative_minimum(std::vector &v) { + unsigned int N = v.size(); + for (unsigned int i = 1; i < N; i++) { + if (v[i] > v[i - 1]) v[i] = v[i - 1]; + } +} + +template +VT effective_sample_size(MT const& samples, unsigned int &min_ess) { + typedef Eigen::FFT EigenFFT; + typedef std::complex CNT; + EigenFFT fft; + + // Sample matrix is provided as d x n_samples + unsigned int d = samples.rows(); + unsigned int N = samples.cols(); + unsigned int N_even = N - N % 2; + + // Calculate effective sample size per dimension + VT ess; + ess.resize(d); + + // Autocorrelation vector + std::vector autocorrelation(N_even, NT(0)); + std::vector min_auto_correlation(N_even / 2, NT(0)); + + + // Z-normalized samples + std::vector normalized_sample_row(2 * N); + + // FFT vector + std::vector fft_vec(2 * N); + std::vector fft_inv_vec(2 * N); + std::vector psd(2 * N); + + // Helper variables + NT row_mean; + NT variance; + NT gap; + + for (unsigned int i = 0; i < d; i++) { + + // Z-normalization + row_mean = samples.row(i).mean(); + + for (int j = 0; j < N; j++) { + normalized_sample_row[j] = samples(i, j) - row_mean; + // Zero-padding + normalized_sample_row[j + N] = NT(0); + } + + variance = NT(0); + + for (int j = 0; j < N; j++) { + variance += std::pow(normalized_sample_row[j], 2); + } + + variance *= (1.0 / N); + + for (int j = 0; j < N; j++) { + normalized_sample_row[j] /= NT(1e-16 + sqrt(variance)); + } + + // Perform FFT on 2N points + fft.fwd(fft_vec, normalized_sample_row); + + // Calculate PSD which is the norm squared of the FFT of the sequence + for (int j = 0; j < 2 * N; j++) { + psd[j].real(std::norm(fft_vec[j])); + psd[j].imag(NT(0)); + } + + // Invert fft to get autocorrelation function + fft.inv(fft_inv_vec, psd); + + for (unsigned int j = 0; j < N_even; j++) { + autocorrelation[j] = std::real(fft_inv_vec[j]) / N; + } + + // Calculate minimum autocorrelation + for (int j = 0; j < N_even; j += 2) { + min_auto_correlation[j/2] = autocorrelation[j] + autocorrelation[j + 1]; + } + + gap = NT(0); + cummulative_minimum(min_auto_correlation); + + for (int j = 0; j < N_even / 2; j++) { + if (min_auto_correlation[j] > 0) gap += min_auto_correlation[j]; + } + + gap = 2 * gap - NT(1); + if (gap < NT(1)) gap = NT(1); + + ess(i) = (1.0 * N) / gap; + + // Store minimum effective sample size as integer (in general ess is not int) + // for the thinning process + if (i == 0) min_ess = (unsigned int) ceil(ess(i)); + else if ((unsigned int)ceil(ess(i)) < min_ess) min_ess = (unsigned int)ceil(ess(i)); + + } + + return ess; +} + +#endif diff --git a/src/volesti/include/diagnostics/ess_updater_autocovariance.hpp b/src/volesti/include/diagnostics/ess_updater_autocovariance.hpp new file mode 100644 index 00000000..4023a51c --- /dev/null +++ b/src/volesti/include/diagnostics/ess_updater_autocovariance.hpp @@ -0,0 +1,69 @@ +// VolEsti (volume computation and sampling library) +// Copyright (c) 2021 Vissarion Fisikopoulos +// Copyright (c) 2021 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef DIAGNOSTICS_ESS_UPDATER_AUTOCOVARIANCE_HPP +#define DIAGNOSTICS_ESS_UPDATER_AUTOCOVARIANCE_HPP + +#include +#include +#include +#include +#include +#include + +/** + Compute autocovariance estimates for every lag for the input sequence + using the Geyer's stable estimator given in + Charles J. Geyer, Practical Markov Chain Monte Carlo, Statistical Science 1992. + + * @tparam NT number type + * @tparam VT vector type + * @param samples the sequence of correlated samples + * @param auto_cov the autocovariance estimates +*/ +template +void compute_autocovariance(VT const& samples, VT &auto_cov) +{ + const NT eps = 1e-16; + typedef Eigen::FFT EigenFFT; + typedef Eigen::Matrix, Eigen::Dynamic, 1> CVT; + EigenFFT fft; + + unsigned int N = samples.size(); + NT samples_mean = samples.mean(); + auto_cov.setZero(N); + + // compute normalized samples + VT normalized_samples(2 * N); + normalized_samples.setZero(); + normalized_samples.head(N) = samples.array() - samples_mean; + + NT variance = (normalized_samples.cwiseProduct(normalized_samples)).sum(); + variance *= (1.0 / N); + variance += eps * (samples_mean*samples_mean); + normalized_samples.head(N) = normalized_samples.head(N).array() / sqrt(variance); + + // Perform FFT on 2N points + CVT frequency(2 * N); + fft.fwd(frequency, normalized_samples); + + // Invert fft to get autocorrelation function + CVT auto_cov_tmp(2 * N); + frequency = frequency.cwiseAbs2(); + fft.inv(auto_cov_tmp, frequency); + + auto_cov = auto_cov_tmp.head(N).real().array() / N; + + boost::accumulators::accumulator_set> accumulator; + for (int i = 0; i < samples.size(); ++i) + { + accumulator(samples.coeff(i)); + } + + auto_cov = auto_cov.array() * boost::accumulators::variance(accumulator); +} + +#endif diff --git a/src/volesti/include/diagnostics/ess_window_updater.hpp b/src/volesti/include/diagnostics/ess_window_updater.hpp new file mode 100644 index 00000000..99edb5aa --- /dev/null +++ b/src/volesti/include/diagnostics/ess_window_updater.hpp @@ -0,0 +1,154 @@ +// VolEsti (volume computation and sampling library) +// Copyright (c) 2021 Vissarion Fisikopoulos +// Copyright (c) 2021 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef DIAGNOSTICS_ESS_UPDATER_HPP +#define DIAGNOSTICS_ESS_UPDATER_HPP + +#include "ess_updater_autocovariance.hpp" + + +/** + This is a class that updates the effective sample size (ess) of a sample given a new chain + using Welford's algorithm to update the average values and the variance estimates where needed. + The chains has to be of the same length. The ess estimation exploits Geyer's stable estimator + for the autocovariance and the Geyer's conversion to a monotone sequence, given in, + + Charles J. Geyer, Practical Markov Chain Monte Carlo, Statistical Science 1992. + + * @tparam NT number type + * @tparam VT vector type + * @tparam MT matrix type +*/ +template +class ESSestimator { + +private: + unsigned int num_draws, max_s, s, d, num_chains, jj; + VT cm_mean, cm_var, cv_mean, draws, var_plus, ess, auto_cov; + NT oldM, rho_hat_odd, rho_hat_even, mean_var, M2, delta, new_elem; + MT acov_s_mean, rho_hat_s; + +public: + ESSestimator() {} + + ESSestimator(unsigned int const& _ndraws, unsigned int const& _dim) + { + num_draws = _ndraws; + d = _dim; + num_chains = 0; + + cm_mean.setZero(d); + cm_var.setZero(d); + cv_mean.setZero(d); + var_plus.setZero(d); + ess.setZero(d); + draws.setZero(num_draws); + acov_s_mean.setZero(num_draws-3, d); + rho_hat_s.setZero(num_draws, d); + } + + void update_estimator(MT const& samples) + { + num_chains++; + + for (int i = 0; i < d; i++) + { + draws = samples.row(i).transpose(); + compute_autocovariance(draws, auto_cov); + + new_elem = draws.mean(); + delta = new_elem - cm_mean.coeff(i); + cm_mean(i) += delta / NT(num_chains); + cm_var(i) += delta * (new_elem - cm_mean(i)); + + new_elem = auto_cov.coeff(0) * NT(num_draws) / (NT(num_draws) - 1.0); + delta = new_elem - cv_mean.coeff(i); + cv_mean(i) += delta / NT(num_chains); + + new_elem = auto_cov.coeff(1); + delta = new_elem - acov_s_mean.coeff(0, i); + acov_s_mean(0, i) += delta / NT(num_chains); + jj = 1; + while (jj < num_draws-4) + { + new_elem = auto_cov.coeff(jj+1); + delta = new_elem - acov_s_mean.coeff(jj, i); + acov_s_mean(jj, i) += delta / NT(num_chains); + + new_elem = auto_cov.coeff(jj+2); + delta = new_elem - acov_s_mean.coeff(jj+1, i); + acov_s_mean(jj+1, i) += delta / NT(num_chains); + + jj += 2; + } + } + } + + + void estimate_effective_sample_size() + { + rho_hat_s.setZero(num_draws, d); + + var_plus = cv_mean * (NT(num_draws) - 1.0) / NT(num_draws); + if (num_chains > 1) + { + VT cm_var_temp = cm_var * (1.0 / (NT(num_chains)-1.0)); + var_plus += cm_var_temp; + } + + for (int i = 0; i < d; i++) + { + rho_hat_even = 1.0; + rho_hat_s(0, i) = rho_hat_even; + rho_hat_odd = 1 - (cv_mean.coeff(i) - acov_s_mean.coeff(0, i)) / var_plus.coeff(i); + rho_hat_s(1, i) = rho_hat_odd; + + s = 1; + while (s < (num_draws - 4) && (rho_hat_even + rho_hat_odd) > 0) + { + rho_hat_even = 1.0 - (cv_mean.coeff(i) - acov_s_mean.coeff(s, i)) / var_plus.coeff(i); + rho_hat_odd = 1.0 - (cv_mean.coeff(i) - acov_s_mean.coeff(s+1, i)) / var_plus.coeff(i); + if ((rho_hat_even + rho_hat_odd) >= 0) + { + rho_hat_s(s + 1, i) = rho_hat_even; + rho_hat_s(s + 2, i) = rho_hat_odd; + } + s += 2; + } + + max_s = s; + // this is used in the improved estimate + if (rho_hat_even > 0) + { + rho_hat_s(max_s + 1, i) = rho_hat_even; + } + + // Convert Geyer's positive sequence into a monotone sequence + for (jj = 1; jj <= max_s - 3; jj += 2) + { + if (rho_hat_s(jj + 1, i) + rho_hat_s.coeff(jj + 2, i) > rho_hat_s.coeff(jj - 1, i) + rho_hat_s.coeff(jj, i)) + { + rho_hat_s(jj + 1, i) = (rho_hat_s.coeff(jj - 1, i) + rho_hat_s.coeff(jj, i)) / 2.0; + rho_hat_s(jj + 2, i) = rho_hat_s.coeff(jj + 1, i); + } + } + NT num_total_draws = NT(num_chains) * NT(num_draws); + NT tau_hat = -1.0 + 2.0 * rho_hat_s.col(i).head(max_s).sum() + rho_hat_s.coeff(max_s + 1, i); + ess(i) = num_total_draws / tau_hat; + } + } + + + VT get_effective_sample_size() + { + return ess; + } + +}; + + +#endif + diff --git a/src/volesti/include/diagnostics/geweke.hpp b/src/volesti/include/diagnostics/geweke.hpp new file mode 100644 index 00000000..7bfd291a --- /dev/null +++ b/src/volesti/include/diagnostics/geweke.hpp @@ -0,0 +1,79 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +//Licensed under GNU LGPL.3, see LICENCE file + +/* + This function implements a multivariate version of the Geweke diagnostic. + It is reduced to Hotelling's Two Sample test, which is a multivariate + extension of the common two sample Student's t-test. The null hypothesis + is that there is no difference between sample means. + + It is based on "Evaluating the accuracy of sampling-based approaches + to the calculation of posterior moments, 1992" by J. Geweke + + Inputs: samples, a matrix that contains sample points column-wise + frac_first, the portion of the first in order points in matrix samples + frac_last, the portion of the last in order points in matrix samples + alpha, the confidence level for the statistical test + + Output: A boolean to denote the result of Geweke diagnostic: + (i) false if the null hypothesis is rejected + (ii) true if the null hypothesis is not rejected +*/ + + +#ifndef DIAGNOSTICS_GEWEKE_HPP +#define DIAGNOSTICS_GEWEKE_HPP + +#include + +template +bool perform_geweke(MT const& samples, + NT frac_first = 0.1, + NT frac_last = 0.5, + NT alpha = 0.05) +{ + unsigned int d = samples.rows(), N = samples.cols(); + unsigned int N1 = N * frac_first; + unsigned int N2 = N * frac_last; + + // Compute sample means and covariances + VT mean1 = samples.block(0, 0, d, N1).rowwise().mean(); + VT mean2 = samples.block(0, N - N2, d, N2).rowwise().mean(); + + MT norm_chain1 = samples.block(0, 0, d, N1).colwise() - mean1; + MT norm_chain2 = samples.block(0, N - N2, d, N2).colwise() - mean2; + + MT sigma1 = (norm_chain1 * norm_chain1.transpose()) / (NT(N1) - 1.0); + MT sigma2 = (norm_chain2 * norm_chain2.transpose()) / (NT(N2) - 1.0); + + // Compute the pooled covariance matrix + MT S_pl = ((NT(N1) - NT(1)) * sigma1 + (NT(N2) - 1.0) * sigma2) / (NT(N1) + NT(N2) - NT(2)); + + // T2 follows Hotelling's T-squared distribution under the assumption of + // equal covariances and when the null hypothesis is true + NT T2 = (mean1 - mean2).transpose() * S_pl.inverse() * (mean1 - mean2); + T2 = ((NT(N1) * NT(N2)) / (NT(N1) + NT(N2))) * T2; + + // U follows Fischer distribution + // We use this transformation to check the null hypothesis more easily + NT U = ((NT(N1) + NT(N2) - NT(d) - 1.0) / ((NT(N1) + NT(N2) - 2.0) * NT(d))) * T2; + + boost::math::fisher_f dist(d, int(N1) + int(N2) - d - 1); + + NT F1 = boost::math::quantile(dist, alpha / 2.0); + NT F2 = boost::math::quantile(boost::math::complement(dist, alpha / 2.0)); + + if (U <= F1 || U > F2) { // reject null hypothesis + return false; + } + return true; // do not reject null hypothesis + +} + +#endif diff --git a/src/volesti/include/diagnostics/interval_psrf.hpp b/src/volesti/include/diagnostics/interval_psrf.hpp new file mode 100644 index 00000000..cbcdfd10 --- /dev/null +++ b/src/volesti/include/diagnostics/interval_psrf.hpp @@ -0,0 +1,79 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +//Licensed under GNU LGPL.3, see LICENCE file + +/* + This function implements the interval diagnostic of Brooks & Gelman. + It is based on "General Methods for Monitoring Convergence of Iterative Simulations, 1998" by S. Brooks and A. Gelman + + For each coordinate the sample is splitted into two parts. + Then the psrf of S. Brooks and A. Gelman is computed for each coordinate. + + Inputs: samples, a matrix that contains sample points column-wise + + Output: The value of interval PSRF of S. Brooks and A. Gelman for each coordinate +*/ + +#ifndef DIAGNOSTICS_INTERVAL_PSRF_HPP +#define DIAGNOSTICS_INTERVAL_PSRF_HPP + +template +VT interval_psrf(MT const& samples, NT alpha = 0.05) +{ + MT runs = samples.transpose(); + unsigned int N = samples.cols(), d = samples.rows(); + unsigned int N1 = N / 2; + unsigned int N2 = N - N1; + VT sorted_samples(N), marginal_samples(N), sorted_subsamples1(N1), sorted_subsamples2(N2), results(d); + std::vector temp_col(N); + + for (int i = 0; i < d; i++) + { + sorted_samples = runs.col(i); + marginal_samples = runs.col(i); + + temp_col.resize(N); + temp_col = std::vector(&sorted_samples[0], sorted_samples.data() + sorted_samples.cols() * + sorted_samples.rows()); + std::sort(temp_col.begin(), temp_col.end()); + sorted_samples = Eigen::Map(&temp_col[0], temp_col.size()); + + int n1 = N * (alpha / NT(2)), n2 = N - N * (alpha / NT(2)); + + NT len_total_sequence_interval = sorted_samples(n2) - sorted_samples(n1); + + sorted_subsamples1 = marginal_samples.block(0,0,N1,1); + temp_col.resize(N1); + temp_col = std::vector(&sorted_subsamples1[0], sorted_subsamples1.data() + + sorted_subsamples1.cols() * sorted_subsamples1.rows()); + std::sort(temp_col.begin(), temp_col.end()); + sorted_subsamples1 = Eigen::Map(&temp_col[0], temp_col.size()); + + sorted_subsamples2 = marginal_samples.block(N1,0,N2,1); + temp_col.resize(N2); + temp_col = std::vector(&sorted_subsamples2[0], sorted_subsamples2.data() + + sorted_subsamples2.cols() * sorted_subsamples2.rows()); + std::sort(temp_col.begin(), temp_col.end()); + sorted_subsamples2 = Eigen::Map(&temp_col[0], temp_col.size()); + + n1 = N1 * (alpha / NT(2)), n2 = N1 - N1 * (alpha / NT(2)); + NT len_sequence_interval1 = sorted_subsamples1(n2) - sorted_subsamples1(n1); + + n1 = N2 * (alpha / NT(2)), n2 = N2 - N2 * (alpha / NT(2)); + NT len_sequence_interval2 = sorted_subsamples2(n2) - sorted_subsamples2(n1); + + NT R = (len_total_sequence_interval) / + ((len_sequence_interval1 + len_sequence_interval2) / NT(2)); + + results(i) = std::abs(1.0 - R) + NT(1); + } + return results; +} + + +#endif diff --git a/src/volesti/include/diagnostics/multivariate_psrf.hpp b/src/volesti/include/diagnostics/multivariate_psrf.hpp new file mode 100644 index 00000000..883b28d6 --- /dev/null +++ b/src/volesti/include/diagnostics/multivariate_psrf.hpp @@ -0,0 +1,55 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +//Licensed under GNU LGPL.3, see LICENCE file + +/* + This function implements a multivariate version of the Rubin & Gelman diagnostic. + It is based on "Inference from iterative simulation using multiple sequences, 1992" by D. B. Rubin and A. Gelman + and "General Methods for Monitoring Convergence of Iterative Simulations, 1998" by S. Brooks and A. Gelman + + The sample is splitted into two parts. Then a multivariate psrf is computed as proposed by S. Brooks and A. Gelman + + Inputs: samples, a matrix that contains sample points column-wise + + Output: The value of multivariate PSRF by S. Brooks and A. Gelman +*/ + +#ifndef DIAGNOSTICS_PSRF_HPP +#define DIAGNOSTICS_PSRF_HPP + +template +NT multivariate_psrf(MT const& samples) +{ + unsigned int N = samples.cols(), d = samples.rows(); + unsigned int N1 = N / 2; + unsigned int N2 = N - N1; + + VT mean1 = samples.block(0, 0, d, N1).rowwise().mean(); + VT mean2 = samples.block(0, N1, d, N - N1).rowwise().mean(); + + MT norm_chain1 = samples.block(0, 0, d, N1).colwise() - mean1; + MT norm_chain2 = samples.block(0, N1, d, N - N1).colwise() - mean2; + + MT W = ((norm_chain1 * norm_chain1.transpose()) / (NT(N1) - 1.0) + + (norm_chain2 * norm_chain2.transpose()) / (NT(N2) - 1.0)) / NT(2); + + VT mean00 = (mean1 + mean2) / 2.0; + + MT B = (mean1 - mean00) * (mean1 - mean00).transpose() + + (mean2 - mean00) * (mean2 - mean00).transpose(); + + MT WB = W.inverse() * B; + Eigen::SelfAdjointEigenSolver eigensolver(WB); + NT l_max = eigensolver.eigenvalues().maxCoeff(); + + NT R = (NT(N1) - NT(1))/NT(N1) + 1.5 * l_max; + return R; +} + + +#endif diff --git a/src/volesti/include/diagnostics/print_diagnostics.hpp b/src/volesti/include/diagnostics/print_diagnostics.hpp new file mode 100644 index 00000000..b009d6d0 --- /dev/null +++ b/src/volesti/include/diagnostics/print_diagnostics.hpp @@ -0,0 +1,56 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef DIAGNOSTICS_PRINT_DIAGNOSTICS_HPP +#define DIAGNOSTICS_PRINT_DIAGNOSTICS_HPP + +template +void print_diagnostics(MT const& samples, unsigned int &min_ess, StreamType &stream) { + + unsigned int d = samples.rows(); + unsigned int N = samples.cols(); + + VariadicTable vt( + {"Dimension", + "Average", + "Standard Deviation", + "Effective Sample Size", + "Interval PSRF (50%)" + }); + + VT ess = effective_sample_size(samples, min_ess); + VT intv_psrf = interval_psrf(samples); + + NT row_mean, row_std; + + vt.setColumnPrecision({1, 3, 3, 3, 3}); + + vt.setColumnFormat({VariadicTableColumnFormat::AUTO, + VariadicTableColumnFormat::SCIENTIFIC, + VariadicTableColumnFormat::SCIENTIFIC, + VariadicTableColumnFormat::SCIENTIFIC, + VariadicTableColumnFormat::SCIENTIFIC}); + + for (unsigned int i = 0; i < d; i++) { + row_mean = samples.row(i).mean(); + row_std = NT(0); + for (int j = 0; j < N; j++) { + row_std += std::pow(samples(i, j) - row_mean, 2); + } + row_std = sqrt(row_std / N); + vt.addRow(i + 1, row_mean, row_std, ess(i), intv_psrf(i)); + } + + vt.print(stream); + std::cout << "interval_psrf = " << intv_psrf.maxCoeff() << "us" << std::endl; +} + + +#endif diff --git a/src/volesti/include/diagnostics/raftery.hpp b/src/volesti/include/diagnostics/raftery.hpp new file mode 100644 index 00000000..1fa087a5 --- /dev/null +++ b/src/volesti/include/diagnostics/raftery.hpp @@ -0,0 +1,142 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +//Licensed under GNU LGPL.3, see LICENCE file + +/* + This function implements a multivariate version of the raftery & Lewis diagnostic. + It is based on Matlab version of coda package in http://www.spatial-econometrics.com/gibbs/ + and "How many iterations in the Gibbs sampler?, 1992" by A. Raftery and S. Lewis + + Inputs: samples, a matrix that contains sample points column-wise + q, the quantile of the quantity of interest. The default value is 0.025. + r, the level of precision desired. The default value is 0.01. + s, the probability associated with r. The default value is 0.95. + + Outputs: (i) The number of draws required for burn-in + (ii) The skip parameter for 1st-order Markov chain + (iii) The skip parameter sufficient to get independence chain + (iv) The number of draws required to achieve r precision + (v) The number of draws if the chain is white noise + (vi) The I-statistic from Raftery and Lewis (1992) +*/ + +#ifndef DIAGNOSTICS_RAFTERY_HPP +#define DIAGNOSTICS_RAFTERY_HPP + +template +NT round_to_zero(NT x) +{ + return (x > 0.0) ? std::floor(x) : std::ceil(x); +} + +#include "raftery_subroutines/empquant.hpp" +#include "raftery_subroutines/indtest.hpp" +#include "raftery_subroutines/mctest.hpp" +#include "raftery_subroutines/mcest.hpp" +#include "raftery_subroutines/thin.hpp" +#include "raftery_subroutines/ppnd.hpp" + + +template +MT perform_raftery(MT const& samples, NT const& q, NT const& r, NT const& s) +{ + MT runs = samples.transpose(); + + typedef Eigen::Matrix MTint; + typedef Eigen::Matrix VTint; + + unsigned int n = runs.rows(), d = runs.cols(), kthin, kmind; + MT results(d, 6); + MTint work = MTint::Zero(n, d); + VTint tmp = VTint::Zero(n); + std::pair xy; + std::pair g2bic; + + NT cutpt, alpha, beta, g2, bic, epss; + int tcnt; + + MT sorted_samples(n, d); + VT a(n); + std::vector temp_col(n); + + for (int i = 0; i < d; i++) + { + a = runs.col(i); + temp_col = std::vector(&a[0], a.data() + a.cols() * a.rows()); + std::sort(temp_col.begin(), temp_col.end()); + sorted_samples.col(i) = Eigen::Map(&temp_col[0], temp_col.size()); + } + + for (int i = 0; i < d; i++) + { + cutpt = empquant(sorted_samples.col(i), q); + for (int j = 0; j < n; j++) + { + if (runs(j, i) <= cutpt) work(j, i) = 1; + } + kthin = 1; bic = 1.0; epss = 0.001; + + while(bic > 0.0) + { + xy = thin(work.col(i), n, kthin); + tcnt = xy.first; + tmp = xy.second; + g2bic = mctest(tmp, tcnt); + g2 = g2bic.first; + bic = g2bic.second; + kthin++; + if (kthin > n / 2) { + break; + } + } + + kthin--; + g2bic = mcest(tmp, tcnt); + alpha = g2bic.first; + beta = g2bic.second; + kmind = kthin; + g2bic = indtest(tmp, tcnt); + g2 = g2bic.first; + bic = g2bic.second; + + while (bic > 0.0) + { + xy = thin(work.col(i), n, kmind); + tcnt = xy.first; + tmp = xy.second; + g2bic = indtest(tmp, tcnt); + g2 = g2bic.first; + bic = g2bic.second; + kmind++; + if (kmind > n) { + break; + } + } + + NT psum = alpha + beta; + NT tmp1 = std::log(psum * epss / std::max(alpha, beta)) / std::log(std::abs(1.0 - psum)); + NT nburn = round_to_zero((tmp1 + 1.0) * NT(kthin)); + NT phi = ppnd((s + 1.0) / 2.0); + NT tmp2 = (2.0 - psum) * alpha * beta * (phi * phi) / (psum * psum * psum * (r * r)); + NT nprec = round_to_zero(tmp2 + 1.0) * kthin; + NT nmin = round_to_zero(((1.0 - q) * q * (phi * phi) / (r * r)) + 1.0); + NT irl = (nburn + nprec) / nmin; + NT kind = std::max(round_to_zero(irl + 1.0), NT(kmind)); + + results(i, 0) = NT(kthin); + results(i, 1) = NT(nburn); + results(i, 2) = kind; + results(i, 3) = NT(nburn) + nprec; + results(i, 4) = nmin; + results(i, 5) = irl; + } + return results; +} + + +#endif diff --git a/src/volesti/include/diagnostics/raftery_subroutines/empquant.hpp b/src/volesti/include/diagnostics/raftery_subroutines/empquant.hpp new file mode 100644 index 00000000..6604f0ad --- /dev/null +++ b/src/volesti/include/diagnostics/raftery_subroutines/empquant.hpp @@ -0,0 +1,32 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +//Based on Matlab version of coda package in http://www.spatial-econometrics.com/gibbs/ + +#ifndef EMPQUANT_HPP +#define EMPQUANT_HPP + + +template +NT empquant(VT const& sorted_samples, NT const& q) +{ + unsigned int n = sorted_samples.rows(); + + NT order = (n - 1) * q + 1.0; + NT fract = order - NT(int(order)); + int low = std::max(round_to_zero(order), 1.0); + int high = std::min(low + 1.0, NT(n)); + + NT y = (1.0 - fract) * sorted_samples(low - 1) + fract * sorted_samples(high - 1); + + return y; +} + + +#endif diff --git a/src/volesti/include/diagnostics/raftery_subroutines/indtest.hpp b/src/volesti/include/diagnostics/raftery_subroutines/indtest.hpp new file mode 100644 index 00000000..8af7a329 --- /dev/null +++ b/src/volesti/include/diagnostics/raftery_subroutines/indtest.hpp @@ -0,0 +1,43 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +//Based on Matlab version of coda package in http://www.spatial-econometrics.com/gibbs/ + +#ifndef INDTEST_HPP +#define INDTEST_HPP + + +template +std::pair indtest(VT const& d, int const& n) +{ + MT t = MT::Zero(2, 2); + int t1, t2; + NT fitted, focus; + for (int i1 = 1; i1 < n; i1++){ + t(d(i1 - 1), d(i1))=t(d(i1 - 1), d(i1)) + 1; + } + NT dcm1 = NT(n) - 1.0, g2 = 0.0; + for (int i1 = 0; i1 < 2; i1++){ + for (int i2 = 0; i2 < 2; i2++){ + if (t(i1, i2) != 0){ + t1 = t(i1, 0) + t(i1, 1); + t2 = t(0, i2) + t(1, i2); + fitted = (NT(t1) * NT(t2)) / dcm1; + focus = NT(t(i1, i2)); + g2 = g2 + std::log(focus / fitted) * focus; + } + } + } + g2 = g2 * 2.0; + NT bic = g2 - std::log(dcm1); + return std::pair(g2, bic); +} + + +#endif diff --git a/src/volesti/include/diagnostics/raftery_subroutines/mcest.hpp b/src/volesti/include/diagnostics/raftery_subroutines/mcest.hpp new file mode 100644 index 00000000..8f29021b --- /dev/null +++ b/src/volesti/include/diagnostics/raftery_subroutines/mcest.hpp @@ -0,0 +1,28 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +//Based on Matlab version of coda package in http://www.spatial-econometrics.com/gibbs/ + +#ifndef MCEST_HPP +#define MCEST_HPP + +template +std::pair mcest(VT const& d, int const& n) +{ + MT t = MT::Zero(2, 2); + for (int i1 = 1; i1 < n; i1++){ + t(d(i1 - 1), d(i1)) = t(d(i1 - 1), d(i1)) + 1; + } + NT alpha = NT(t(0, 1)) / NT((t(0, 0) + t(0, 1))), beta = NT(t(1, 0)) / NT((t(1, 0)+t(1, 1))); + + return std::pair(alpha, beta); +} + + +#endif diff --git a/src/volesti/include/diagnostics/raftery_subroutines/mctest.hpp b/src/volesti/include/diagnostics/raftery_subroutines/mctest.hpp new file mode 100644 index 00000000..f5108db8 --- /dev/null +++ b/src/volesti/include/diagnostics/raftery_subroutines/mctest.hpp @@ -0,0 +1,63 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +//Based on Matlab version of coda package in http://www.spatial-econometrics.com/gibbs/ + +#ifndef MCTEST_HPP +#define MCTEST_HPP + + +template +std::pair mctest(VT const& d, int const& n) +{ + MT m1 = MT::Zero(2,2), m2 = MT::Zero(2,2); + NT g2 = 0.0, bic = 0.0, fitted; + int i1, i2, i3, t1, t2, t3, t4, focus; + + for (int i=2; i(g2, bic); +} + + +#endif diff --git a/src/volesti/include/diagnostics/raftery_subroutines/ppnd.hpp b/src/volesti/include/diagnostics/raftery_subroutines/ppnd.hpp new file mode 100644 index 00000000..c2c8b630 --- /dev/null +++ b/src/volesti/include/diagnostics/raftery_subroutines/ppnd.hpp @@ -0,0 +1,56 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +//Based on Matlab version of coda package in http://www.spatial-econometrics.com/gibbs/ + +#ifndef PPND_HPP +#define PPND_HPP + +template +NT ppnd(NT const& p) +{ + NT split1 = 0.425, split2 = 5.0, const1 = 0.180625, const2 = 1.6, a0=3.3871327179e+00, + a1 = 5.0434271938e+01, a2 = 1.5929113202e+02, a3 = 5.9109374720e+01, b1 = 1.7895169469e+01, + b2 = 7.8757757664e+01, b3 = 6.7187563600e+01, c0 = 1.4234372777e+00, c1 = 2.7568153900e+00, + c2 = 1.3067284816e+00, c3 = 1.7023821103e-01, d1 = 7.3700164250e-01, d2 = 1.2021132975e-01, + e0 = 6.6579051150e+00, e1 = 3.0812263860e+00, e2 = 4.2868294337e-01, e3 = 1.7337203997e-02, + f1 = 2.4197894225e-01, f2 = 1.2258202635e-02; + + NT q = p - 0.5, r, y; + + if (std::abs(q) <= split1){ + r = const1 - q * q; + y = q * (((a3 * r + a2) * r + a1) * r + a0) / (((b3 * r + b2) * r + b1) * r + 1.0); + return y; + } else if (q < 0.0){ + r = p; + } else{ + r = 1 - p; + } + if (r <= 0.0){ + return 0.0; + } + + r = std::sqrt(-1.0 * std::log(r)); + + if (r <= split2){ + r = r - const2; + y = (((c3 * r + c2) * r + c1) * r + c0) / ((d2 * r + d1) * r + 1.0); + } else{ + r = r - split2; + y = (((e3 * r + e2) * r + e1) * r + e0)/((f2 * r + f1) * r + 1.0); + } + + if (q < 0.0) return -y; + + return y; +} + +#endif + diff --git a/src/volesti/include/diagnostics/raftery_subroutines/thin.hpp b/src/volesti/include/diagnostics/raftery_subroutines/thin.hpp new file mode 100644 index 00000000..c9b5ec10 --- /dev/null +++ b/src/volesti/include/diagnostics/raftery_subroutines/thin.hpp @@ -0,0 +1,32 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +//Based on Matlab version of coda package in http://www.spatial-econometrics.com/gibbs/ + +#ifndef THIN_HPP +#define THIN_HPP + +template +std::pair thin(VT const& work, unsigned int const& n, unsigned int const& kthin) +{ + VT y((n-1) / kthin + 1); + + int i = 0, j = 0; + while (i < n) + { + y(j) = work(i); + j++; + i += kthin; + } + + return std::pair((n-1) / kthin + 1, y); +} + + +#endif diff --git a/src/volesti/include/diagnostics/thin_samples.hpp b/src/volesti/include/diagnostics/thin_samples.hpp new file mode 100644 index 00000000..8e41fe2f --- /dev/null +++ b/src/volesti/include/diagnostics/thin_samples.hpp @@ -0,0 +1,37 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef DIAGNOSTICS_THIN_SAMPLES_HPP +#define DIAGNOSTICS_THIN_SAMPLES_HPP + +template +MT thin_samples(MT const& samples, NT const& min_ess) { + + // Sample matrix is provided as d x n_samples + unsigned int d = samples.rows(); + unsigned int N = samples.cols(); + unsigned int gap; + unsigned int N_gap; + + // Thin samples are the initial samples which are N / min_ess apart + gap = N / min_ess; + N_gap = N - N % gap; + + MT thin_samples; + thin_samples.resize(d, N_gap / gap); + + for (int i = 0; i < N_gap; i += gap) { + thin_samples.col(i / gap) = samples.col(i); + } + + return thin_samples; +} + +#endif diff --git a/src/volesti/include/diagnostics/univariate_psrf.hpp b/src/volesti/include/diagnostics/univariate_psrf.hpp new file mode 100644 index 00000000..3a863908 --- /dev/null +++ b/src/volesti/include/diagnostics/univariate_psrf.hpp @@ -0,0 +1,67 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +//Licensed under GNU LGPL.3, see LICENCE file + +/* + This function implements the Rubin & Gelman diagnostic. + It is based on "Inference from iterative simulation using multiple sequences, 1992" by D. B. Rubin and A. Gelman + + For each coordinate the sample is splitted into two parts. + Then the psrf of D.B. Rubin and A. Gelman is computed for each coordinate + + Inputs: samples, a matrix that contains sample points column-wise + + Output: The value of PSRF of D.B. Rubin and A. Gelman for each coordinate +*/ + +#ifndef DIAGNOSTICS_MARGINAL_PSRF_HPP +#define DIAGNOSTICS_MARGINAL_PSRF_HPP + +template +VT univariate_psrf(MT const& samples) +{ + MT runs = samples.transpose(); + unsigned int N = samples.cols(), d = samples.rows(); + unsigned int N1 = N / 2; + unsigned int N2 = N - N1; + VT coord_samples(N), results(d); + NT mean1, mean2, mean00, sum, R, W, B, sigma; + + for (int i = 0; i < d; i++) + { + coord_samples = runs.col(i); + mean1 = coord_samples.block(0,0,N1,1).mean(); + mean2 = coord_samples.block(N1,0,N2,1).mean(); + + sum = NT(0); + for (int j = 0; j < N1; j++) + { + sum += (coord_samples(j) - mean1) * (coord_samples(j) - mean1); + } + W = sum / (NT(N1) - NT(1)); + + sum = NT(0); + for (int j = N1; j < N; j++) + { + sum += (coord_samples(j) - mean2) * (coord_samples(j) - mean2); + } + W += (sum / (NT(N2) - NT(1))); + W = W / NT(2); + + mean00 = coord_samples.mean(); + + B = (mean1 - mean00) * (mean1 - mean00) + (mean2 - mean00) * (mean2 - mean00); + sigma = ((NT(N1) - NT(1)) / NT(N1)) * W + B; + R = std::sqrt(sigma / W); + + results(i) = R; + } + return results; +} + +#endif diff --git a/src/volesti/include/generators/boost_random_number_generator.hpp b/src/volesti/include/generators/boost_random_number_generator.hpp new file mode 100644 index 00000000..d15fee15 --- /dev/null +++ b/src/volesti/include/generators/boost_random_number_generator.hpp @@ -0,0 +1,95 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2020 Vissarion Fisikopoulos + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef GENERATORS_BOOST_RANDOM_NUMBER_GENERATOR_HPP +#define GENERATORS_BOOST_RANDOM_NUMBER_GENERATOR_HPP + +#include +#include + +/////////////////// Random numbers generator +/// +/// \tparam RNGType +/// \tparam NT +/// \tparam Ts + +template +struct BoostRandomNumberGenerator; + +template +struct BoostRandomNumberGenerator +{ + BoostRandomNumberGenerator(int d) + : _rng(std::chrono::system_clock::now().time_since_epoch().count()) + , _urdist(0, 1) + , _uidist(0, d-1) + , _ndist(0, 1) + {} + + NT sample_urdist() + { + return _urdist(_rng); + } + + NT sample_uidist() + { + return _uidist(_rng); + } + + NT sample_ndist() + { + return _ndist(_rng); + } + + void set_seed(unsigned rng_seed){ + _rng.seed(rng_seed); + } + +private : + RNGType _rng; + boost::random::uniform_real_distribution _urdist; + boost::random::uniform_int_distribution<> _uidist; + boost::random::normal_distribution _ndist; +}; + + +template +struct BoostRandomNumberGenerator +{ + BoostRandomNumberGenerator(int d=1) + : _rng(Seed) + , _urdist(0, 1) + , _uidist(0, d-1) + , _ndist(0, 1) + {} + + NT sample_urdist() + { + return _urdist(_rng); + } + + NT sample_uidist() + { + return _uidist(_rng); + } + + NT sample_ndist() + { + return _ndist(_rng); + } + + void set_seed(unsigned rng_seed){ + _rng.seed(rng_seed); + } + +private : + RNGType _rng; + boost::random::uniform_real_distribution _urdist; + boost::random::uniform_int_distribution<> _uidist; + boost::random::normal_distribution _ndist; +}; + +#endif // GENERATORS_BOOST_RANDOM_NUMBER_GENERATOR_HPP diff --git a/src/volesti/include/generators/convex_bodies_generator.h b/src/volesti/include/generators/convex_bodies_generator.h new file mode 100644 index 00000000..f80b98d1 --- /dev/null +++ b/src/volesti/include/generators/convex_bodies_generator.h @@ -0,0 +1,124 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef CONVEX_BODIES_GEN_H +#define CONVEX_BODIES_GEN_H + +#include + +#include "convex_bodies/convex_body.h" + +#ifndef isnan + using std::isnan; +#endif + +/// This function generates a unit ball of given dimension +/// @tparam ConvexBody Type of returned Convex Body +template +ConvexBody generate_unit_ball(unsigned int dim) { + + typedef typename ConvexBody::MT MT; + typedef typename ConvexBody::VT VT; + typedef typename ConvexBody::NT NT; + typedef typename ConvexBody::PointType Point; + typedef std::function func; + typedef std::function grad; + + func unit_ball_func = [](const Point &x) { + return x.dot(x) - 1; + }; + + grad unit_ball_grad = [](const Point &x) { + return 2 * x; + }; + + std::vector unit_ball_funcs{unit_ball_func}; + std::vector unit_ball_grads{unit_ball_grad}; + + return ConvexBody(unit_ball_funcs, unit_ball_grads, dim); +} + +/// This function generates a unit ball of given dimension intersected by a hyperplane +/// @tparam ConvexBody Type of returned Convex Body +template +ConvexBody generate_unit_ball_intersect_hyperplane(unsigned int dim) { + + typedef typename ConvexBody::MT MT; + typedef typename ConvexBody::VT VT; + typedef typename ConvexBody::NT NT; + typedef typename ConvexBody::PointType Point; + typedef std::function func; + typedef std::function grad; + + func unit_ball_func = [](const Point &x) { + return x.dot(x) - 1; + }; + + func hyperplane_func = [](const Point &x) { + return x[0] - 0.5; + }; + + grad unit_ball_grad = [](const Point &x) { + return 2 * x; + }; + + grad hyperplane_grad = [](const Point &x) { + Point v = Point(x.dimension()); + v.set_coord(0, 1); + return v; + }; + + std::vector unit_ball_funcs{unit_ball_func, hyperplane_func}; + std::vector unit_ball_grads{unit_ball_grad, hyperplane_grad}; + + return ConvexBody(unit_ball_funcs, unit_ball_grads, dim); +} + +/// This function generates a unit ball of given dimension intersected by a logsum exponential function +/// @tparam ConvexBody Type of returned Convex Body +template +ConvexBody generate_unit_ball_intersect_logsumexp(unsigned int dim) { + + typedef typename ConvexBody::MT MT; + typedef typename ConvexBody::VT VT; + typedef typename ConvexBody::NT NT; + typedef typename ConvexBody::PointType Point; + typedef std::function func; + typedef std::function grad; + + func unit_ball_func = [](const Point &x) { + return x.dot(x) - pow(x.dimension(), 2); + }; + + func logsumexp_func = [](const Point &x) { + typedef typename Point::FT NT; + NT s = 0; + for (unsigned int i = 0; i < x.dimension(); i++) { + s += exp(x[i]); + } + return log(s); + }; + + grad unit_ball_grad = [](const Point &x) { + return 2 * x; + }; + + grad logsumexp_grad = [](const Point &x) { + Point z(x.dimension()); + for (unsigned int i = 0; i < x.dimension(); i++) { + z.set_coord(i, exp(x[i])); + } + return (1 / z.sum()) * z; + }; + + std::vector unit_ball_funcs{unit_ball_func, logsumexp_func}; + std::vector unit_ball_grads{unit_ball_grad, logsumexp_grad}; + + return ConvexBody(unit_ball_funcs, unit_ball_grads, dim); +} + +#endif diff --git a/src/volesti/include/generators/h_polytopes_generator.h b/src/volesti/include/generators/h_polytopes_generator.h new file mode 100644 index 00000000..dae44bdc --- /dev/null +++ b/src/volesti/include/generators/h_polytopes_generator.h @@ -0,0 +1,61 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef H_POLYTOPES_GEN_H +#define H_POLYTOPES_GEN_H + +#include + + +#ifndef isnan + using std::isnan; +#endif + +/// This function generates a random H-polytope of given dimension and number of hyperplanes $m$ +/// @tparam Polytope Type of returned polytope +/// @tparam RNGType RNGType Type +template +Polytope random_hpoly(unsigned int dim, unsigned int m, double seed = std::numeric_limits::signaling_NaN()) { + + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Polytope::NT NT; + typedef typename Polytope::PointType Point; + + unsigned rng_seed = std::chrono::system_clock::now().time_since_epoch().count(); + RNGType rng(rng_seed); + if (!isnan(seed)) { + unsigned rng_seed = seed; + rng.seed(rng_seed); + } + + MT A(m, dim); + VT b(m); + Point p(dim); + + for (int i = 0; i < m; ++i) { + boost::normal_distribution<> rdist(0, 1); + NT normal = NT(0); + NT *data = p.pointerToData(); + + //RNGType rng2 = var.rng; + for (unsigned int i = 0; i < dim; ++i) { + *data = rdist(rng); + normal += *data * *data; + data++; + } + + normal = 1.0 / std::sqrt(normal); + p *= normal; + A.row(i) = p.getCoefficients(); + b(i) = 10.0; + } + + return Polytope(dim, A, b); +} + +#endif diff --git a/src/volesti/include/generators/known_polytope_generators.h b/src/volesti/include/generators/known_polytope_generators.h new file mode 100644 index 00000000..050918ad --- /dev/null +++ b/src/volesti/include/generators/known_polytope_generators.h @@ -0,0 +1,361 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef KNOWN_POLYTOPE_GENERATORS_H +#define KNOWN_POLYTOPE_GENERATORS_H + +#include + +#include "convex_bodies/hpolytope.h" +#include "convex_bodies/vpolytope.h" + +/// This function generates a hypercube of given dimension +/// The result can be either in V-representation (Vpoly=true) or in H-representation (V-poly-false) +/// @tparam Polytope Type of returned polytope +template +Polytope generate_cube(const unsigned int& dim, const bool& Vpoly) { + + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + MT A; + VT b; + unsigned int m; + + if (!Vpoly) { + + A.resize(2 * dim, dim); + b.resize(2 * dim); + for (unsigned int i = 0; i < dim; ++i) { + b(i) = 1.0; + for (unsigned int j = 0; j < dim; ++j) { + if (i == j) { + A(i, j) = 1.0; + } else { + A(i, j) = 0.0; + } + } + } + for (unsigned int i = 0; i < dim; ++i) { + b(i + dim) = 1.0; + for (unsigned int j = 0; j < dim; ++j) { + if (i == j) { + A(i + dim, j) = -1.0; + } else { + A(i + dim, j) = 0.0; + } + } + } + } else { + + m = 2 << (dim - 1); + A.resize(m, dim); + b.resize(m); + for(unsigned int i=0; i> 1; + ++j; + } + for(; j +Polytope generate_cross(const unsigned int &dim, const bool &Vpoly) { + + unsigned int m; + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + + MT A; + VT b; + if (!Vpoly) { + + m = 2 << (dim - 1); + A.resize(m, dim); + b.resize(m); + for(unsigned int i=0; i> 1; + ++j; + } + for(; j +Polytope generate_simplex(const unsigned int &dim, const bool &Vpoly){ + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + + MT A; + A.resize(dim+1, dim); + VT b; + b.resize(dim+1); + + for(unsigned int i=0; i +Polytope generate_prod_simplex(const unsigned int &dim, bool Vpoly = false){ + + Polytope Perr; + try + { + if(Vpoly) throw false; + } + catch (bool e) { + #ifdef VOLESTI_DEBUG + std::cout<<"Only prod simplices in H-representation can be generated.."< +Polytope generate_skinny_cube(const unsigned int &dim, bool Vpoly = false) { + + Polytope Perr; + try + { + if(Vpoly) throw false; + } + catch (bool e) { + #ifdef VOLESTI_DEBUG + std::cout<<"Only prod simplices in H-representation can be generated.."< +Polytope generate_birkhoff(unsigned int const& n) { + + unsigned int m = n * n; + unsigned int d = n * n - 2 * n + 1; + + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + + MT A = MT::Zero(m, d); + VT b(m); + + b(d) = -1.0 * int(n - 2); + + for (int i = 0; i < d; ++i) { + A(d, i) = -1; + } + + for (int i = 0; i < d; ++i) { + b(i) = 0; + A(i, i) = -1; + } + + for (int i = d+1; i < d+1+n-1; ++i) { + b(i) = 1; + for (int counter = 0; counter < n-1; ++counter) { + A(i, counter * (n-1) + (i-d-1)) = 1; + } + } + + for (int i = d+n; i < m; ++i) { + b(i) = 1; + for (int counter = 0; counter < n-1; ++counter) { + A(i, counter + (i-d-n) * (n-1)) = 1; + } + } + + Polytope P(d, A, b); + + return P; +} + +#endif diff --git a/src/volesti/include/generators/order_polytope_generator.h b/src/volesti/include/generators/order_polytope_generator.h new file mode 100644 index 00000000..bfece556 --- /dev/null +++ b/src/volesti/include/generators/order_polytope_generator.h @@ -0,0 +1,63 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2021 Vissarion Fisikopoulos +// Copyright (c) 2018-2021 Apostolos Chalkis +// Copyright (c) 2021 Vaibhav Thakkar + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ORDER_POLYTOPES_GEN_H +#define ORDER_POLYTOPES_GEN_H + +#include +#include +#include "misc.h" +#include "misc/poset.h" + + +// Instances taken from: https://github.com/ttalvitie/le-counting-practice +static const std::unordered_map instances = +{ + {"bipartite_0.5_008_0", R"(0 0 0 0 1 0 1 0 + 0 0 0 0 1 0 0 0 + 0 0 0 0 1 1 0 1 + 0 0 0 0 1 0 1 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0)"}, + + {"bayesiannetwork_andes_008_0", R"(0 0 0 0 0 0 0 0 + 1 0 0 0 0 0 0 0 + 0 1 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 1 1 0 0 0 0 + 0 0 0 0 1 0 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 1 1 1 0)"}, + +}; + +// generates an Order Polytope from an instance name +// Instances taken from: https://github.com/ttalvitie/le-counting-practice +/// @tparam Polytope Type of returned polytope +template +Polytope generate_orderpoly(std::string& instance_name) { + std::stringstream in_ss(instances.at(instance_name)); + Poset poset = read_poset_from_file_adj_matrix(in_ss).second; + return Polytope(poset); +} + +// Generates a cube as an Order Polytope +/// @tparam Polytope Type of returned polytope +template +Polytope generate_cube_orderpoly(unsigned int dim) { + typedef typename Poset::RV RV; + + RV order_relations; + Poset poset(dim, order_relations); + Polytope OP(poset); + return OP; +} + +#endif diff --git a/src/volesti/include/generators/sdp_generator.h b/src/volesti/include/generators/sdp_generator.h new file mode 100644 index 00000000..c2b3a154 --- /dev/null +++ b/src/volesti/include/generators/sdp_generator.h @@ -0,0 +1,151 @@ +// +// Created by panagiotis on 9/7/2019. +// + +#ifndef VOLESTI_SDP_GENERATOR_H +#define VOLESTI_SDP_GENERATOR_H + +#include /* srand, rand */ +#include /* time */ + +typedef boost::mt19937 RNGType; + +/// Generates a random matrix +/// @tparam NT Number type +template +void randomMatrixGOE(Eigen::Matrix& M) { + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + + unsigned m = M.rows(); + boost::normal_distribution<> rdist(0,1); + unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();//4 if fixed for debug + RNGType rng(seed); + + for (unsigned int i=0; i +Spectrahedron, Eigen::Matrix > generateSDP(int n, int m) { + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + + MT ones = MT::Ones(m, m); + MT M = 2* Eigen::MatrixXd::Random(m,m) - ones; + + MT I = Eigen::MatrixXd::Identity(m, m); + std::vector matrices(n + 1); + matrices[0] = -(M * M.transpose()) - I; + + std::cout<<"A0 = "< lmi(matrices); + Spectrahedron spectrahedron(lmi); + return spectrahedron; + + //return optimization::sdp_problem(spectrahedron, obj); +} + +/// Generates a random spectahedron S(n, m) +/// @tparam NT Number type +template +Spectrahedron, Eigen::Matrix > generateSDP2(int n, int m) { + + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + + MT ones = MT::Ones(m, m); + MT M = 2* Eigen::MatrixXd::Random(m,m) - ones; + + MT I = Eigen::MatrixXd::Identity(m, m); + std::vector matrices(n + 1); + matrices[0] = -(M * M.transpose()) - I; + + //std::cout<<"A0 = "< lmi(matrices); + Spectrahedron spectrahedron(lmi); + return spectrahedron; + + //return optimization::sdp_problem(spectrahedron, obj); +} + + +#endif //VOLESTI_SDP_GENERATOR_H diff --git a/src/volesti/include/generators/v_polytopes_generators.h b/src/volesti/include/generators/v_polytopes_generators.h new file mode 100644 index 00000000..3977e948 --- /dev/null +++ b/src/volesti/include/generators/v_polytopes_generators.h @@ -0,0 +1,175 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef V_POLYTOPES_GEN_H +#define V_POLYTOPES_GEN_H + +#include + +#ifndef isnan + using std::isnan; +#endif + +template +void removeRow(MT &matrix, unsigned int rowToRemove) +{ + unsigned int numRows = matrix.rows()-1; + unsigned int numCols = matrix.cols(); + + if( rowToRemove < numRows ) + matrix.block(rowToRemove,0,numRows-rowToRemove,numCols) = matrix.bottomRows(numRows-rowToRemove); + + matrix.conservativeResize(numRows,numCols); +} + +/// Generates a random V-polytope +/// @tparam Polytope polytope type +/// @tparam RNGType RNGType type +template +Polytope random_vpoly(unsigned int dim, unsigned int k, double seed = std::numeric_limits::signaling_NaN()) { + + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Polytope::NT NT; + typedef typename Polytope::PointType PointType; + typedef PointType Point; + + unsigned rng_seed = std::chrono::system_clock::now().time_since_epoch().count(); + RNGType rng(rng_seed); + if (!isnan(seed)) { + unsigned rng_seed = seed; + rng.seed(rng_seed); + } + boost::normal_distribution<> rdist(0,1); + + typename std::vector::iterator pit; + MT V(k, dim); + unsigned int j; + + + std::vector Xs(dim,0); + NT normal = NT(0); + + for (unsigned int i = 0; i < k; ++i) { + + normal = NT(0); + for (unsigned int i=0; i +Polytope random_vpoly_incube(unsigned int d, unsigned int k, double seed = std::numeric_limits::signaling_NaN()) { + + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Polytope::NT NT; + typedef typename Polytope::PointType PointType; + typedef PointType Point; + + REAL *conv_mem; + int *colno_mem; + + conv_mem = (REAL *) malloc(k * sizeof(*conv_mem)); + colno_mem = (int *) malloc(k * sizeof(*colno_mem)); + + unsigned rng_seed = std::chrono::system_clock::now().time_since_epoch().count(); + RNGType rng(rng_seed); + if (!isnan(seed)) { + unsigned rng_seed = seed; + rng.seed(rng_seed); + } + boost::random::uniform_real_distribution<> urdist1(-1, 1); + + Point p(d); + typename std::vector::iterator pit; + MT V(k, d); + unsigned int j, count_row,it=0; + std::vector indices; + + VT b = VT::Ones(k); + + for (unsigned int i = 0; i < k; ++i) { + for (int j = 0; j < d; ++j) { + V(i, j) = urdist1(rng); + } + } + if(k==d+1){ + return Polytope(d, V, b); + } + + MT V2(k,d); + V2 = V; + indices.clear(); + while(it<20) { + V.resize(V2.rows(), d); + V = V2; + for (int i = 0; i < indices.size(); ++i) { + V.conservativeResize(V.rows()+1, d); + for (int j = 0; j < d; ++j) { + V(V.rows()-1, j) = urdist1(rng); + } + } + indices.clear(); + V2.resize(k, d); + V2 = V; + + for (int i = 0; i < k; ++i) { + for (int j = 0; j < d; ++j) { + p.set_coord(j, V(i, j)); + } + removeRow(V2, i); + if (memLP_Vpoly(V2, p, conv_mem, colno_mem)){ + indices.push_back(i); + } + V2.resize(k, d); + V2 = V; + } + if (indices.size()==0) { + return Polytope(d, V, b); + } + V2.resize(k - indices.size(), d); + count_row =0; + for (int i = 0; i < k; ++i) { + if(std::find(indices.begin(), indices.end(), i) != indices.end()) { + continue; + } else { + for (int j = 0; j < d; ++j) V2(count_row, j) = V(i,j); + count_row++; + } + } + it++; + } + + + free(colno_mem); + free(conv_mem); + + return Polytope(d, V2, VT::Ones(V2.rows())); +// return VP; + +} + +#endif diff --git a/src/volesti/include/generators/z_polytopes_generators.h b/src/volesti/include/generators/z_polytopes_generators.h new file mode 100644 index 00000000..8afbc6d1 --- /dev/null +++ b/src/volesti/include/generators/z_polytopes_generators.h @@ -0,0 +1,145 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos +// Copyright (c) 2018 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef Z_POLYTOPES_GEN_H +#define Z_POLYTOPES_GEN_H + +#include + +#ifndef isnan + using std::isnan; +#endif + +/// Generates a random Zonotope with generators draw from Gaussian distribution +/// @tparam Polytope polytope type +/// @tparam RNGType RNGType type +template +Polytope gen_zonotope_gaussian(int dim, int m, double seed = std::numeric_limits::signaling_NaN()) { + + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Polytope::NT NT; + + unsigned rng_seed = std::chrono::system_clock::now().time_since_epoch().count(); + RNGType rng(rng_seed); + if (!isnan(seed)) { + unsigned rng_seed = seed; + rng.seed(rng_seed); + } + boost::normal_distribution<> rdist(0, 1); + boost::normal_distribution<> rdist2(50, 33.3); + + MT A; + VT b; + A.resize(m, dim); + b.resize(m); + NT rand_gaus; + + for (unsigned int i = 0; i < m; ++i) { + b(i) = 1.0; + for (unsigned int j = 0; j < dim; ++j) { + A(i,j) = rdist(rng); + } + A.row(i)=A.row(i)/A.row(i).norm(); + while(true){ + rand_gaus = rdist2(rng); + if (rand_gaus > 0.0 && rand_gaus<100.0){ + A.row(i) = A.row(i) * rand_gaus; + break; + } + } + } + + Polytope P(dim, A, b); + return P; +} + + +/// Generates a random Zonotope with generators draw from uniform distribution +/// @tparam Polytope polytope type +/// @tparam RNGType RNGType type +template +Polytope gen_zonotope_uniform(int dim, int m, double seed = std::numeric_limits::signaling_NaN()) { + + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Polytope::NT NT; + + unsigned rng_seed = std::chrono::system_clock::now().time_since_epoch().count(); + RNGType rng(rng_seed); + if (!isnan(seed)) { + unsigned rng_seed = seed; + rng.seed(rng_seed); + } + boost::normal_distribution<> rdist(0, 1); + boost::random::uniform_real_distribution<> urdist1(0, 100); + + MT A; + VT b; + A.resize(m, dim); + b.resize(m); + + for (unsigned int i = 0; i < m; ++i) { + b(i) = 1.0; + for (unsigned int j = 0; j < dim; ++j) { + A(i,j) = rdist(rng); + } + A.row(i)=A.row(i)/A.row(i).norm(); + A.row(i) = A.row(i) * urdist1(rng); + } + + Polytope P(dim, A, b); + return P; + +} + + +/// Generates a random Zonotope with generators draw from exponential distribution +/// @tparam Polytope polytope type +/// @tparam RNGType RNGType type +template +Polytope gen_zonotope_exponential(int dim, int m, double seed = std::numeric_limits::signaling_NaN()) { + + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Polytope::NT NT; + + unsigned rng_seed = std::chrono::system_clock::now().time_since_epoch().count(); + RNGType rng(rng_seed); + if (!isnan(seed)) { + unsigned rng_seed = seed; + rng.seed(rng_seed); + } + boost::normal_distribution<> rdist(0, 1); + boost::normal_distribution<> expdist(1.0/30.0); + + MT A; + VT b; + A.resize(m, dim); + b.resize(m); + NT rand_exp; + + for (unsigned int i = 0; i < m; ++i) { + b(i) = 1.0; + for (unsigned int j = 0; j < dim; ++j) { + A(i,j) = rdist(rng); + } + A.row(i)=A.row(i)/A.row(i).norm(); + while(true){ + rand_exp = expdist(rng); + if (rand_exp > 0.0 && rand_exp<100.0){ + A.row(i) = A.row(i) * rand_exp; + break; + } + } + } + + Polytope P(dim, A, b); + return P; +} + +#endif diff --git a/src/volesti/include/integration/simple_MC_integration.hpp b/src/volesti/include/integration/simple_MC_integration.hpp new file mode 100644 index 00000000..58739650 --- /dev/null +++ b/src/volesti/include/integration/simple_MC_integration.hpp @@ -0,0 +1,276 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2021 Vissarion Fisikopoulos +// Copyright (c) 2018-2021 Apostolos Chalkis + +// Contributed and/or modified by Suraj Choubey, as part of Google Summer of Code 2021 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// Monte Carlo Integration algorithm used here : https://en.wikipedia.org/wiki/Monte_Carlo_integration#Overview + +#ifndef SIMPLE_MC_INTEGRATION_HPP +#define SIMPLE_MC_INTEGRATION_HPP + +#include +#include +#include +#include +#include "convex_bodies/hpolytope.h" +#include "Eigen/Eigen" +#include "generators/known_polytope_generators.h" +#include "boost_random_number_generator.hpp" +#include "cartesian_geom/cartesian_kernel.h" +#include "random_walks/random_walks.hpp" +#include "volume/volume_sequence_of_balls.hpp" +#include "volume/volume_cooling_gaussians.hpp" +#include "volume/volume_cooling_balls.hpp" +#include "misc.h" + +typedef double NT; +typedef Cartesian Kernel; +typedef typename Kernel::Point Point; +typedef HPolytope Hpolytope; +typedef boost::mt19937 RNGType; +typedef BoostRandomNumberGenerator RandomNumberGenerator; +typedef typename HPolytope::MT MT; +typedef typename HPolytope::VT VT; + +enum volumetype {CB ,CG ,SOB}; // Volume type for polytope +typedef typename std::vector Limit; // Standard way for user to use limits +// E.g. Limits LL{0.5, 1.5, 2.5} ; Limits UL{1.2, 1.8 , 2.8 } ; + +const Limit lt{0}; // To initialize non-initialized limits +const Point pt{0}; // To initialize non-initialized points + +// To check if two n-dimensional points ensure valid limits in integration +template +< + typename Point = Point, + typename NT = NT +> +bool valid_limits(Point LL, Point UL) { + if (UL.dimension() == LL.dimension()) { + for (int i = 0; i < LL.dimension(); i++) { + if (LL[i] > UL[i]) { + std::cerr << "Invalid integration limits\n"; + return false; + } + } + return true; + } else { + std::cerr << "Invalid integration limits\n"; + return false; + } +} + +// Initialize Limit Point +template +< + typename Point = Point, + typename NT = NT +> +Point init_limit(Limit L, int dim) { + Point pt(dim); + for (int i = 0; i < dim; i++) { + pt.set_coord(i, L[i]); + } + return pt; +} + +//Initialize to [-1,1]^n +template +< + typename Point = Point, + typename NT = NT +> +void initiate_unit_limits(Point& LL, Point& UL, int dim) { + LL.set_dimension(dim); + UL.set_dimension(dim); + LL.set_to_origin(); + UL.set_to_origin(); + + for (int i = 0 ; i < dim ; i++) { + LL.set_coord(i, -1); + UL.set_coord(i, 1); + } +} + +// Simple MC Integration Over Polytopes +template +< + typename WalkType = BallWalk, + typename Polytope = Hpolytope, + typename VolumeWalkType = BallWalk, + typename RNG = RandomNumberGenerator, + typename NT = NT, + typename Functor +> +NT simple_mc_polytope_integrate(Functor Fx, + Polytope &P, + RNG &rng, + int N = 10000, + volumetype voltype = SOB, + int walk_length = 1, + NT e = 0.1, + Point Origin = pt) +{ + + int dim = P.dimension(); + // P.print(); + + // Check if ShiftPoint is shifted with accurate dimensions + if (Origin.dimension() == 0 && dim > 0) { + Origin.set_dimension(dim); + Origin.set_to_origin(); + } else if (Origin.dimension() != dim && dim > 0) { + std::cerr << "Polytope Dimension != Shiftpoint Dimension" << std::endl; + return -1; + } + + // std::cout << "Origin.dimension() = " << Origin.dimension() << std::endl; + // std::cout << "P.dimension() = " << P.dimension() << std::endl; + + // Volume calculation for HPolytope + NT volume; + + switch (voltype) { + case CB: + volume = volume_cooling_balls (P, rng, e, walk_length).second; + break; + case CG: + volume = volume_cooling_gaussians (P, rng, e, walk_length); + break; + case SOB: + volume = volume_sequence_of_balls (P, rng, e, walk_length); + break; + default: + std::cerr << "Error in volume type: CB / SOB / CG" << std::endl; + return -1; + } + + // std::cout << "Volume of the convex body = " << volume << std::endl; + + // For implementing Uniform Walks + std::pair inner_ball = P.ComputeInnerBall(); + Point x0 = inner_ball.first; + typename WalkType::template Walk walk(P, x0, rng); + + NT sum = 0; + + // Applying and walking through Uniform Walks + Storing Points in Vector + for (int i = 0; i < N; i++) { + walk.apply(P, x0, walk_length, rng); + sum += Fx(x0 + Origin); + + // (x0 + Origin).print(); + } + + // Integration Value + NT integration_value = volume * sum / N ; + return integration_value; +} + +template +< + typename WalkType = BallWalk, + typename Polytope = Hpolytope, + typename VolumeWalkType = BallWalk, + typename RNG = RandomNumberGenerator, + typename NT = NT, + typename Functor +> +NT simple_mc_polytope_integrate(Functor Fx, + Polytope &P, + int N = 10000, + volumetype voltype = SOB, + int walk_length = 1, + NT e = 0.1, + Point Origin = pt) +{ + RNG rng(P.dimension()); + return simple_mc_polytope_integrate(Fx, P, rng, N, voltype, + walk_length, e, Origin); +} + +// Simple MC Integration over Hyper-Rectangles +template +< + typename WalkType = BallWalk, + typename RNG = RandomNumberGenerator, + typename NT = NT, + typename Functor +> +NT simple_mc_integrate(Functor Fx, + int dim, + RNG &rng, + int N = 10000, + volumetype voltype = SOB, + Limit LowLimit = lt, + Limit UpLimit = lt, + int walk_length = 10, + NT e = 0.1) +{ + + // Setting up integration limits + Point LL, UL; + if (LowLimit.size() == 1 && UpLimit.size() == 1 && LowLimit[0] == 0 && UpLimit[0] == 0) { + initiate_unit_limits(LL, UL, dim); + } else if (LowLimit.size() == UpLimit.size() && LowLimit.size() == dim) { + LL = init_limit (LowLimit, dim); + UL = init_limit (UpLimit, dim); + } else { + std::cerr << "Invalid limits entered"; + return -1; + } + + NT sum = 0; + + if (valid_limits(LL, UL)) { + + // Creating an MT & VT for HPolytope(Hyper-Rectangle) for integration limits using LL & UL + MT mt(dim*2, dim); + mt.setZero(); + VT vt(dim*2); + vt.setZero(); + + for (int i=0; i (Fx, P, rng, N, voltype, walk_length, e); + return integration_value; + + } else { + std::cerr << "Invalid integration limits" << std::endl; + return -1; + } +} + +template +< + typename WalkType = BallWalk, + typename RNG = RandomNumberGenerator, + typename NT = NT, + typename Functor +> +NT simple_mc_integrate(Functor Fx, + int dim, + int N = 10000, + volumetype voltype = SOB, + Limit LowLimit = lt, + Limit UpLimit = lt, + int walk_length = 10, + NT e = 0.1) +{ + RNG rng(dim); + return simple_mc_integrate(Fx, dim, rng, N, voltype, LowLimit, UpLimit, walk_length, e); +} +#endif diff --git a/src/volesti/include/lp_oracles/misc_lp.h b/src/volesti/include/lp_oracles/misc_lp.h new file mode 100644 index 00000000..9e8e2528 --- /dev/null +++ b/src/volesti/include/lp_oracles/misc_lp.h @@ -0,0 +1,145 @@ +#ifndef MISC_LP_H +#define MISC_LP_H + + +#include +#include +#include +#undef Realloc +#undef Free +#include "lp_lib.h" + + +// Computes the Chebychev ball of an H-polytope described by a dxd matrix A and d-dimensional vector b, s.t.: Ax<=b +/// @tparam NT Number type +/// @tparam Point Point type +/// @tparam MT Matrix type +/// @tparam VT Vector type +template +std::pair ComputeChebychevBall(MT &A, VT &b){ + + lprec *lp; + int d = A.cols(); + int Ncol=d+1, j, m=A.rows(), i; + int *colno = NULL; + + REAL *row = NULL; + std::pair exception_pair(Point(1),-1.0); + + try + { + lp = make_lp(m, Ncol); + if(lp == NULL) throw false; + } + catch (bool e) { +#ifdef VOLESTI_DEBUG + std::cout<<"Could not construct Linear Program for chebychev center "< res; + + std::vector temp_p(d,0); + get_variables(lp, row); + for(j = 0; j < d; j++){ + temp_p[j]=NT(row[j]); + } + Point xc( d , temp_p.begin() , temp_p.end() ); + NT r=NT(get_objective(lp)); + res = std::pair (xc,r); + delete_lp(lp); + free(row); + free(colno); + + return res; +} + + + +#endif diff --git a/src/volesti/include/lp_oracles/solve_lp.h b/src/volesti/include/lp_oracles/solve_lp.h new file mode 100644 index 00000000..f7013e99 --- /dev/null +++ b/src/volesti/include/lp_oracles/solve_lp.h @@ -0,0 +1,296 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2018 Vissarion Fisikopoulos, Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. + +// VolEsti is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// VolEsti is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// See the file COPYING.LESSER for the text of the GNU Lesser General +// Public License. If you did not receive this file along with HeaDDaCHe, +// see . + + +#ifndef SOLVE_LP_H +#define SOLVE_LP_H + + +#include +#include +#include +#undef Realloc +#undef Free +#include "lp_lib.h" + + +// compute the chebychev ball of an H-polytope described by a dxd matrix A and d-dimensional vector b, s.t.: Ax<=b +template +std::pair ComputeChebychevBall(const MT &A, const VT &b){ + + lprec *lp; + int d = A.cols(); + int Ncol=d+1, j, m=A.rows(), i; + int *colno = NULL; + + REAL *row = NULL; + std::pair exception_pair(Point(1),-1.0); + + try + { + lp = make_lp(m, Ncol); + if(lp == NULL) throw false; + } + catch (bool e) { + #ifdef VOLESTI_DEBUG + std::cout<<"Could not construct Linear Program for chebychev center "< res; + + std::vector temp_p(d,0); + get_variables(lp, row); + for(j = 0; j < d; j++){ + temp_p[j]=NT(row[j]); + } + Point xc( d , temp_p.begin() , temp_p.end() ); + NT r = NT(get_objective(lp)); + res = std::pair (xc,r); + delete_lp(lp); + + return res; +} + + +template +Point PointInIntersection(MT V1, MT V2, Point direction, bool &empty) { + + typedef typename Point::FT NT; + unsigned int d = V1.cols(); + unsigned int k1 = V1.rows(); + unsigned int k2 = V2.rows(); + unsigned int k = k1 + k2; + VT cb(k1); + lprec *lp; + int Ncol=k, *colno = NULL, j, i; + REAL *row = NULL; + Point p(d); + + try + { + lp = make_lp(d+2, Ncol); + if(lp == NULL) throw false; + } + catch (bool e) { +#ifdef VOLESTI_DEBUG + std::cout<<"Could not construct Linear Program for membership "<. + + +#ifndef VPOLYORACLES_H +#define VPOLYORACLES_H + + +#include +#include +#include +#undef Realloc +#undef Free +#include "lp_lib.h" + + +// return true if q belongs to the convex hull of the V-polytope described by matrix V +// otherwise return false +template +bool memLP_Vpoly(const MT &V, const Point &q, NT *row, int *colno){ + + //typedef typename Point::FT NT; + int d=q.dimension(); + lprec *lp; + int Ncol=d+1, j, i, m=V.rows(); + m++; + + try + { + lp = make_lp(m, Ncol); + if(lp == NULL) throw false; + } + catch (bool e) { +#ifdef VOLESTI_DEBUG + std::cout<<"Could not construct Linear Program for membership "<0.0){ + return false; + } + return true; +} + + + +// compute the intersection of a ray with a V-polytope +// if maxi is true compute positive lambda, when the ray is p + lambda \cdot v +// otherwise compute the negative lambda +template +NT intersect_line_Vpoly(const MT &V, const Point &p, const Point &v, + NT *conv_comb, NT *row, int *colno, bool maxi, bool zonotope){ + + int d=v.dimension(), i; + lprec *lp; + int m=V.rows(); + m++; + int Ncol=m, j, Nrows; + NT res; + if(!zonotope) { + Nrows = d+1; + } else { + Nrows = d; + } + + try + { + lp = make_lp(Nrows, Ncol); + if(lp == NULL) throw false; + } + catch (bool e) { +#ifdef VOLESTI_DEBUG + std::cout<<"Could not construct Linear Program for ray-shooting "<. + + +#ifndef ZPOLYORACLES_H +#define ZPOLYORACLES_H + + +#include +#include +#include +#undef Realloc +#undef Free +#include "lp_lib.h" + + +template +bool memLP_Zonotope(const MT &V, const Point &q, NT *row, int *colno){ + + //typedef typename Point::FT NT; + int d=q.dimension(); + lprec *lp; + int Ncol=V.rows(), j, i; + + try + { + lp = make_lp(d, Ncol); + if(lp == NULL) throw false; + } + catch (bool e) { +#ifdef VOLESTI_DEBUG + std::cout<<"Could not construct Linear Program for membership "< +std::pair intersect_line_zono(const MT &V, const Point &p, const Point &v, NT *row, int *colno){ + + std::pair pair_res; + int d=v.dimension(), i; + lprec *lp;//, *lp2; + int m=V.rows(); + m++; + int Ncol=m, j, Nrows; + NT res; + Nrows = d; + + try + { + lp = make_lp(Nrows, Ncol); + //lp2 = make_lp(Nrows, Ncol); + if(lp == NULL) throw false; + } + catch (bool e) { +#ifdef VOLESTI_DEBUG + std::cout<<"Could not construct Linear Program for ray-shooting "< +class DenseProductMatrix { +public: + /// Eigen matrix type + typedef Eigen::Matrix MT; + /// Eigen vector type + typedef Eigen::Matrix VT; + + /// The number of rows + int _rows; + /// The number of cols + int _cols; + + /// Pointer to matrix A + MT const *A; + /// Pointer to matrix B + MT const *B; + + /// The decomposition we will use + /// If PARTIAL_LU_DECOMPOSITION is defined, use the Eigen partial LU decomposition, + /// otherwise full. The partial is faster but assumes that the matrix has full rank. +#if defined(PARTIAL_LU_DECOMPOSITION) + typedef Eigen::PartialPivLU Decomposition; +#else + typedef Eigen::FullPivLU Decomposition; +#endif + + /// The LU decomposition of B + Decomposition Blu; + + /// Constructs an object of this class and computes the LU decomposition of B. + /// + /// \param[in] A The matrix A + /// \param[in] B The matrix B + DenseProductMatrix(MT const *A, MT const *B) : A(A), B(B) { + Blu = Decomposition(*B); + _rows = A->rows(); + _cols = B->cols(); + } + + ///Required by Spectra + /// \return The number of rows + int rows() { + return _rows; + } + + ///Required by Spectra + /// \return The number of columns + int cols() { + return _cols; + } + + /// Required by Spectra. + /// Computes the product Cx = y, i.e. @f[ (B^-1 A)v = y@$]. But B = LU, so Ax = LUy. + /// Let Ax = v, then LUy = v. Then Lw = v and finally Uy = w to get y; + /// \param[in] x_in + /// \param[out] y_out + void perform_op(NT const * x_in, NT* y_out) { + + // Declaring the vectors like this, we don't copy the values of x_in to v + // and next of y to y_out + Eigen::Map const x(const_cast(x_in), _rows); + VT const v = *A * x; + + Eigen::Map y(y_out, _rows); + y = Blu.solve(v); + } + + /// Required by arpack. + /// Computes the product Cx = y, i.e. @f[ (B^-1 A)v = y@$]. But B = LU, so Ax = LUy. + /// Let Ax = v, then LUy = v. Then Lw = v and finally Uy = w to get y; + /// \param[in] x_in + /// \param[out] y_out + void MultMv(NT * x_in, NT* y_out) { + + // Declaring the vectors like this, we don't copy the values of x_in to v + // and next of y to y_out + Eigen::Map const x(const_cast(x_in), _rows); + VT const v = *A * x; + + Eigen::Map y(y_out, _rows); + y = Blu.solve(v); + } +}; +#endif //VOLESTI_DENSEPRODUCTMATRIX_H diff --git a/src/volesti/include/matrix_operations/EigenDenseMatrix.h b/src/volesti/include/matrix_operations/EigenDenseMatrix.h new file mode 100644 index 00000000..2a3582e3 --- /dev/null +++ b/src/volesti/include/matrix_operations/EigenDenseMatrix.h @@ -0,0 +1,76 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLESTI_EIGENDENSEMATRIX_H +#define VOLESTI_EIGENDENSEMATRIX_H + +/// A wrap class to use Eigen dense matrices when solving Eigenvalue problems with ARPACK++ +/// \tparam NT Numeric Type +template +class EigenDenseMatrix { +public: + + /// Eigen matrix type + typedef Eigen::Matrix MT; + /// Eigen vector type + typedef Eigen::Matrix VT; + + /// The matrix + MT const * M; + + /// number of columns + int n; + /// number of rows + int m; + + /// \return Number of rows + int nrows() { return m;} + + /// \return Number of columns + int ncols() { return n;} + + /// \return Number of rows + int rows() { return m;} + + /// \return Number of columns + int cols() { return n;} + + /// Required by ARPACK++ : Multiplies the matrix with vector v + /// \param[in] v The input vector, for example double* + /// \param[out] w The result of M*v + void MultMv(NT* v, NT* w) { + // Declaring the vectors like this, we don't copy the values of v and after to w + Eigen::Map _v(v, m); + Eigen::Map _w(w, m); + + _w = *M * _v; + } + + /// Required by ARPACK++ : Multiplies the matrix with vector v + /// \param[in] v The input vector, for example double* + /// \param[out] w The result of M*v + void perform_op(NT* v, NT* w) { + // Declaring the vectors like this, we don't copy the values of v and after to w + Eigen::Map _v(v, m); + Eigen::Map _w(w, m); + + _w = *M * _v; + } + + + /// Constructs an object + /// \param[in] M An Eigen Matrix + EigenDenseMatrix(MT const * M) { + this->M = M; + n = M->cols(); + m = M->rows(); + } + +}; +#endif //VOLESTI_EIGENDENSEMATRIX_H diff --git a/src/volesti/include/matrix_operations/EigenvaluesProblems.h b/src/volesti/include/matrix_operations/EigenvaluesProblems.h new file mode 100644 index 00000000..4012177f --- /dev/null +++ b/src/volesti/include/matrix_operations/EigenvaluesProblems.h @@ -0,0 +1,450 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. +// Contributed and modified by Huu Phuoc Le as part of Google Summer of Code 2022 program + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLESTI_EIGENVALUESPROBLEMS_H +#define VOLESTI_EIGENVALUESPROBLEMS_H + +/// Uncomment the solver the function minPosGeneralizedEigenvalue uses +/// Eigen solver for generalized eigenvalue problem +//#define EIGEN_EIGENVALUES_SOLVER +/// Spectra standard eigenvalue problem +#define SPECTRA_EIGENVALUES_SOLVER +/// ARPACK++ standard eigenvalues solver +//#define ARPACK_EIGENVALUES_SOLVER + +#include +#include "DenseProductMatrix.h" +#include "EigenDenseMatrix.h" + +#include "Spectra/include/Spectra/SymGEigsSolver.h" +#include "Spectra/include/Spectra/GenEigsSolver.h" + +/// Solve eigenvalues problems +/// \tparam NT Numeric Type +/// \tparam MT Matrix Type +/// \tparam VT Vector Type +template +class EigenvaluesProblems { + +}; + + +/// A specialization of the template class EigenvaluesProblems for dense Eigen matrices and vectors. +/// \tparam NT Numer Type +template +class EigenvaluesProblems, Eigen::Matrix > { +public: + /// The type for Eigen Matrix + typedef Eigen::Matrix MT; + /// The type for Eigen vector + typedef Eigen::Matrix VT; + /// The type of a complex Eigen Vector for handling eigenvectors +#if defined(EIGEN_EIGENVALUES_SOLVER) || defined (SPECTRA_EIGENVALUES_SOLVER) + typedef typename Eigen::GeneralizedEigenSolver::ComplexVectorType CVT; +#elif defined(ARPACK_EIGENVALUES_SOLVER) + typedef Eigen::Matrix CVT; +#endif + + /// The type of a pair of NT + typedef std::pair NTpair; + + + /// Find the smallest eigenvalue of M + /// \param M a symmetric matrix + /// \return smallest eigenvalue + NT findSymEigenvalue(MT const & M) { + EigenDenseMatrix _M(&M); + +//#define NOT_WORKING +#ifdef NOT_WORKING + // Creating an eigenvalue problem and defining what we need: + // the smallest eigenvalue of M. + ARNonSymStdEig > + dprob(M.cols(), 1, &_M, &EigenDenseMatrix::MultMv, std::string ("LR"), 8, 0.0, 100*15); + + // compute + if (dprob.FindEigenvectors() == 0) { + std::cout << "Failed in findSymEigenvalue\n"; + // if failed with default (and fast) parameters, try with stable (and slow) + dprob.ChangeNcv(M.cols()/10); + if (dprob.FindEigenvectors() == 0) { + std::cout << "\tFailed Again\n"; + return NT(0); + } + } + + if (!dprob.EigenvaluesFound()) { + // if failed to find eigenvalues + return NT(0); + } + + // retrieve eigenvalue of the original system + return dprob.EigenvalueReal(0); +#elif defined(SPECTRA) + // This parameter is for Spectra. It must be larger than #(requested eigenvalues) + 2 + // and smaller than the size of matrix; + int ncv = M.cols()/10 + 5; + if (ncv > M.cols()) ncv = M.cols(); + + Spectra::SymEigsSolver > eigs(&_M, 1, ncv); + // compute + eigs.init(); + eigs.compute(50000); + if(eigs.info() == Spectra::SUCCESSFUL) { + return eigs.eigenvalues()(0); + } + else { + std::cout << "Spectra failed\n"; + return NT(0); + } +#else + Eigen::SelfAdjointEigenSolver solver; + solver.compute(M, Eigen::EigenvaluesOnly); +// typename Eigen::GeneralizedEigenSolver::ComplexVectorType eivals = solver.eigenvalues(); +// NT max = eivals(0).real(); +// +// for (int i = 1; i < eivals.rows(); i++) +// if (eivals(i).real() > max) +// max = eivals(i).real(); + + return solver.eigenvalues().maxCoeff(); +#endif + } + + /// Find the minimum positive and maximum negative eigenvalues of the generalized eigenvalue + /// problem A + lB, where A, B symmetric and A negative definite. + /// \param[in] A Input matrix + /// \param[in] B Input matrix + /// \return The pair (minimum positive, maximum negative) of eigenvalues + NTpair symGeneralizedProblem(MT const & A, MT const & B) const { + + int matrixDim = A.rows(); + + // Spectra solves Xv=lYv, where Y positive definite + // Set X = B, Y=-A. Then, the eigenvalues we want are the minimum negative + // and maximum positive eigenvalues of Xv=lYv. + + // Construct matrix operation object using the wrapper classes provided by Spectra + Spectra::DenseSymMatProd op(B); + Spectra::DenseCholesky Bop(-A); + + // Construct generalized eigen solver object + // requesting the minmum negative and largest positive eigenvalues + Spectra::SymGEigsSolver, Spectra::DenseCholesky, Spectra::GEIGS_CHOLESKY> + geigs(&op, &Bop, 2, 5 < matrixDim ? 5 : matrixDim); + + // Initialize and compute + geigs.init(); + int nconv = geigs.compute(); + + // Retrieve results + if (geigs.info() != Spectra::SUCCESSFUL) + return {NT(0), NT(0)}; + + Eigen::VectorXd evalues; + double lambdaMinPositive, lambdaMaxNegative; + + evalues = geigs.eigenvalues(); + + // get the eigenvalues of the original problem + lambdaMinPositive = 1 / evalues(0); + lambdaMaxNegative = 1 / evalues(1); + + return {lambdaMinPositive, lambdaMaxNegative}; + } + + NT minPosLinearEigenvalue(MT const & A, MT const & B, VT &eigvec) { + int matrixDim = A.rows(); + double lambdaMinPositive; + + Spectra::DenseSymMatProd op(B); + Spectra::DenseCholesky Bop(-A); + + // Construct generalized eigen solver object, requesting the largest three generalized eigenvalues + Spectra::SymGEigsSolver, Spectra::DenseCholesky, Spectra::GEIGS_CHOLESKY> + geigs(&op, &Bop, 1, 15 < matrixDim ? 15 : matrixDim); + + // Initialize and compute + geigs.init(); + int nconv = geigs.compute(); + + VT evalues; + if (geigs.info() == Spectra::SUCCESSFUL) { + evalues = geigs.eigenvalues(); + eigvec = geigs.eigenvectors().col(0); + } + + lambdaMinPositive = 1 / evalues(0); + + return lambdaMinPositive; + } + + /// Finds the minimum positive real eigenvalue of the generalized eigenvalue problem A + lB and + /// the corresponding eigenvector. + /// If the macro EIGEN_EIGENVALUES_SOLVER is defined, the Generalized Solver of Eigen is used. + /// Otherwise, we transform the generalized to a standard eigenvalue problem and use Spectra. + /// Warning: With Spectra we might get a value smaller than the minimum positive real eigenvalue (the real part + /// of a complex eigenvalue). + /// No restriction on the matrices! + /// \param[in] A Input matrix + /// \param[in] B Input matrix + /// \param[out] eigenvector The eigenvector corresponding to the minimum positive eigenvalue + /// \return The minimum positive eigenvalue + NT minPosGeneralizedEigenvalue(MT const & A, MT const & B, CVT& eigenvector) { + NT lambdaMinPositive = std::numeric_limits::max(); + +#if defined(EIGEN_EIGENVALUES_SOLVER) + // use the Generalized eigenvalue solver of Eigen + + // compute generalized eigenvalues with Eigen solver + Eigen::GeneralizedEigenSolver ges(A, -B); + + // retrieve minimum positive eigenvalue + typename Eigen::GeneralizedEigenSolver::ComplexVectorType alphas = ges.alphas(); + VT betas = ges.betas(); + int index = 0; + + for (int i = 0; i < alphas.rows(); i++) { + + if (betas(i) == 0 || alphas(i).imag() != 0) + continue; + + double lambda = alphas(i).real() / betas(i); + if (lambda > 0 && lambda < lambdaMinPositive) { + lambdaMinPositive = lambda; + index = i; + } + } + + // retrieve corresponding eigenvector + eigenvector = ges.eigenvectors().col(index); +#elif defined(SPECTRA_EIGENVALUES_SOLVER) + // Transform the problem to a standard eigenvalue problem and use the general eigenvalue solver of Spectra + + // This makes the transformation to standard eigenvalue problem. See class for more info. + // We have the generalized problem A + lB, or Av = -lBv + // This class computes the matrix product vector Mv, where M = -B * A^[-1] + MT _B = -1 * B; // TODO avoid this allocation + DenseProductMatrix M(&_B, &A); + + // This parameter is for Spectra. It must be larger than #(requested eigenvalues) + 2 + // and smaller than the size of matrix; + int ncv = 3; + + // Prepare to solve Mx = (1/l)x + // we want the smallest positive eigenvalue in the original problem, + // so in this the largest positive eigenvalue; + Spectra::GenEigsSolver > eigs(&M, 1, ncv); + + // compute + eigs.init(); + eigs.compute(); + + //retrieve result and invert to get required eigenvalue of the original problem + if (eigs.info() != Spectra::SUCCESSFUL) { + eigenvector.setZero(A.rows()); + return NT(0); + } + + lambdaMinPositive = 1/((eigs.eigenvalues())(0).real()); + + // retrieve corresponding eigenvector + int matrixDim = A.rows(); + eigenvector.resize(matrixDim); + for (int i = 0; i < matrixDim; i++) + eigenvector(i) = (eigs.eigenvectors()).col(0)(i); + +#elif defined(ARPACK_EIGENVALUES_SOLVER) + // Transform the problem to a standard eigenvalue problem and use the general eigenvalue solver of ARPACK++ + + // This makes the transformation to standard eigenvalue problem. See class for more info. + // We have the generalized problem A + lB, or Av = -lBv + // This class computes the matrix product vector Mv, where M = -B * A^[-1] + MT _B = -1 * B; // TODO avoid this allocation + DenseProductMatrix M(&_B, &A); + + // Creating an eigenvalue problem and defining what we need: + // the eigenvector of A with largest real. + ARNonSymStdEig > + + dprob(A.cols(), 1, &M, &DenseProductMatrix::MultMv, std::string ("LR"), 8 op(B); + Spectra::DenseCholesky Bop(-A); + + // Construct generalized eigen solver object, requesting the largest three generalized eigenvalues + Spectra::SymGEigsSolver, Spectra::DenseCholesky, Spectra::GEIGS_CHOLESKY> + geigs(&op, &Bop, 1, 15 < matrixDim ? 15 : matrixDim); + + // Initialize and compute + geigs.init(); + int nconv = geigs.compute(); + + // Retrieve results + VT evalues; + + if (geigs.info() == Spectra::SUCCESSFUL) { + evalues = geigs.eigenvalues(); + eigvec = geigs.eigenvectors().col(0); + } + + lambdaMinPositive = 1 / evalues(0); + + return lambdaMinPositive; + } + + /// Transform the quadratic eigenvalue problem \[At^2 + Bt + c\] to + /// the generalized eigenvalue problem X+lY. + /// If the updateOnly flag is false, compute matrices X,Y from scratch; + /// otherwise update them. + /// \param[in] A + /// \param[in] B + /// \param[in] C + /// \param[in, out] X + /// \param[in, out] Y + /// \param[in, out] updateOnly True if X,Y were previously computed and only B,C changed + void linearization(const MT &A, const MT &B, const MT &C, MT &X, MT &Y, bool &updateOnly) { + unsigned int matrixDim = A.rows(); + + // check if the matrices X,Y are computed. + //if yes, update them; otherwise compute them from scratch + if (!updateOnly) { + X.resize(2 * matrixDim, 2 * matrixDim); + Y.resize(2 * matrixDim, 2 * matrixDim); + + Y.block(matrixDim, matrixDim, matrixDim, matrixDim) = -1 * C; + Y.block(0, matrixDim, matrixDim, matrixDim) = MT::Zero(matrixDim, matrixDim); + Y.block(matrixDim, 0, matrixDim, matrixDim) = MT::Zero(matrixDim, matrixDim); + Y.block(0, 0, matrixDim, matrixDim) = A; + + X.block(0, matrixDim, matrixDim, matrixDim) = C; + X.block(0, 0, matrixDim, matrixDim) = B; + X.block(matrixDim, 0, matrixDim, matrixDim) = C; + X.block(matrixDim, matrixDim, matrixDim, matrixDim) = MT::Zero(matrixDim, matrixDim); + } else { + Y.block(matrixDim, matrixDim, matrixDim, matrixDim) = -1 * C; + + X.block(0, matrixDim, matrixDim, matrixDim) = C; + X.block(0, 0, matrixDim, matrixDim) = B; + X.block(matrixDim, 0, matrixDim, matrixDim) = C; + } + } + + /// Find the minimum positive real eigenvalue of the quadratic eigenvalue problem \[At^2 + Bt + c\]. + /// First transform it to the generalized eigenvalue problem X+lY. + /// If the updateOnly flag is false, compute matrices X,Y from scratch; + /// otherwise only update them. + /// \param[in] A Input matrix + /// \param[in] B Input matrix + /// \param[in] C Input matrix + /// \param[in, out] X + /// \param[in, out] Y + /// \param[out] eigenvector The eigenvector corresponding to the minimum positive eigenvalue + /// \param[in, out] updateOnly True if X,Y were previously computed and only B,C changed + /// \return Minimum positive eigenvalue + NT minPosQuadraticEigenvalue(MT const & A, MT const &B, MT const &C, MT &X, MT &Y, VT &eigenvector, bool &updateOnly) { + // perform linearization and create generalized eigenvalue problem X+lY + linearization(A, B, C, X, Y, updateOnly); + + // solve generalized problem + CVT eivector; + NT lambdaMinPositive = minPosGeneralizedEigenvalue(X, Y, eivector); + + if (lambdaMinPositive == 0) + return 0; + + int matrixDim = A.rows(); + + // the eivector has dimension 2*matrixDim + // while the eigenvector of the original problem has dimension matrixDim + // retrieve the eigenvector by keeping only #matrixDim coordinates. + eigenvector.resize(matrixDim); + +#if defined(EIGEN_EIGENVALUES_SOLVER) || defined (SPECTRA_EIGENVALUES_SOLVER) + for (int i = 0; i < matrixDim; i++) + eigenvector(i) = eivector(matrixDim + i).real(); +#elif defined(ARPACK_EIGENVALUES_SOLVER) + for (int i = 0; i < matrixDim; i++) + eigenvector(i) = eivector(matrixDim + i); +#endif + + return lambdaMinPositive; + } + + // Using LDLT decomposition to check membership + // Faster than computing the largest eigenvalue with Spectra + // more numerically stable for singular matrices + bool isPositiveSemidefinite(MT const &A) const { + Eigen::LDLT A_ldlt(A); + if (A_ldlt.info() != Eigen::NumericalIssue && A_ldlt.isPositive()) + return true; + return false; + } + + /// Minimum positive eigenvalue of the generalized eigenvalue problem A - lB + /// Use Eigen::GeneralizedSelfAdjointEigenSolver ges(B,A) (faster) + /// \param[in] A: symmetric positive definite matrix + /// \param[in] B: symmetric matrix + /// \return The minimum positive eigenvalue and the corresponding eigenvector + NT minPosLinearEigenvalue_EigenSymSolver(MT const & A, MT const & B, VT &eigvec) const { + NT lambdaMinPositive = NT(0); + Eigen::GeneralizedSelfAdjointEigenSolver ges(B,A); + lambdaMinPositive = 1/ges.eigenvalues().reverse()[0]; + eigvec = ges.eigenvectors().reverse().col(0).reverse(); + return lambdaMinPositive; + } +}; + +#endif //VOLESTI_EIGENVALUESPROBLEMS_H diff --git a/src/volesti/include/misc/linear_extensions.h b/src/volesti/include/misc/linear_extensions.h new file mode 100644 index 00000000..13866816 --- /dev/null +++ b/src/volesti/include/misc/linear_extensions.h @@ -0,0 +1,92 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2018 Vissarion Fisikopoulos + +// Licensed under GNU LGPL.3, see LICENCE file + +inline void linear_extensions_to_order_polytope(std::istream &is, + std::ostream &os){ + + std::string ns; + //read n: the number of elements in the poset + std::getline(is, ns, ' '); + std::istringstream buffer(ns); + int n; + buffer >> n; + //read m: the number of edges/relations in the poset + std::getline(is, ns, '\n'); + std::istringstream bufferm(ns); + int m; + bufferm >> m; + + os << "order_"< ipoint; + point.erase( std::remove( point.begin(), point.end(), ' ' ), + point.end() ); + point.erase( std::remove( point.begin(), point.end(), '[' ), + point.end() ); + std::string coord; + if (point[0]==',') + point.erase(0,1); + std::stringstream stream(point); + if (!point.empty()){ + int i=1; + os<<" 0 "; + //read point 1 + std::getline(stream, coord, ','); + std::istringstream buffer(coord); + int temp; + buffer >> temp; + ipoint.push_back(temp); + //os << temp << " "; + //write inequality 1 + for(;i> temp2; + ipoint.push_back(temp2); + //os << temp2 << "\n"; + //write inequality 2 + for(;i +#include +#include +#include +#include "poset.h" + +//function to print rounding to double coordinates +template +void round_print(T p) { + std::cout<<"test version.."< Vpolys; + +int Minkowski_sum_naive(V_polytope &P1, V_polytope &P2, V_polytope &Msum){ + std::cout<<(!P1.empty() && !P2.empty())< ep(P1[0].dimension()); + ep.insert(Msum_all.begin(),Msum_all.end()); + //std::vector extreme_points; + ep.get_extreme_points(std::back_inserter(Msum)); + return Msum.size(); + } + return -1; +} +*/ + +// polymake file to compute exact volume +template +void print_polymake_volfile(T &P, + std::ostream& os){ + std::cout<<"test version.."<;\n"; + os << "$p->POINTS=<<'.';\n"; + for (typename T::iterator vit = P.begin(); vit != P.end(); vit++){ + os << "1 "; + for (Point::Cartesian_const_iterator cit=vit->cartesian_begin(); + cit != vit->cartesian_end(); + cit++){ + os << *cit; + if (cit - vit->cartesian_begin() != vit->dimension()-1) + os << " "; + } + //os << "|" << vit->point().index(); + os << "\n"; + } + os << ".\n"; + os << "print ' ';\n"; + os << "print $p->N_POINTS;\n"; + os << "print ' ';\n"; + os << "print $p->N_VERTICES;\n"; + os << "print ' ';\n"; + os << "print $p->DIM;\n"; + os << "print ' ';\n"; + os << "my $t0 = [gettimeofday];\n"; + os << "my $f=$p->VOLUME;\n"; + os << "print $f;\n"; + os << "print ' ';\n"; + os << "print tv_interval($t0,[gettimeofday]);\n"; + os << "print \"\n\";\n"; + os << std::endl; +}*/ + +// polymake file to compute exact volume +template +void print_polymake_volfile2(T &P, + std::ostream& os){ + std::cout<<"test version.."<INEQUALITIES=<<'.';\n"; + //os << "my $p=new Polytope;\n"; + //os << "$p->POINTS=<<'.';\n"; + for (typename T::iterator vit = P.begin(); vit != P.end(); vit++){ + Hyperplane::Coefficient_const_iterator cit_end = vit->coefficients_end(); + os << *(--cit_end)<<" "; + //os << "0 "; + Hyperplane::Coefficient_const_iterator cit = vit->coefficients_begin(); + //++cit; + for (; cit != cit_end; cit++){ + //std::cout<<*cit<<" "; + os <<(*cit)<<" "; + if (cit - vit->coefficients_begin() != vit->dimension()-1) + os << " "; + } + //os << "|" << vit->point().index(); + os << "\n"; + } + os << ".\n"; + //$p=new Polytope(INEQUALITIES=>$inequalities); + + os << "print ' ';\n"; + os << "print $p->N_POINTS;\n"; + os << "print ' ';\n"; + os << "print $p->N_VERTICES;\n"; + os << "print ' ';\n"; + os << "print $p->DIM;\n"; + os << "print ' ';\n"; + os << "my $t0 = [gettimeofday];\n"; + os << "my $f=$p->VOLUME;\n"; + os << "print $f;\n"; + os << "print ' ';\n"; + os << "print tv_interval($t0,[gettimeofday]);\n"; + os << "print \"\n\";\n"; + os << std::endl; +}*/ +template +void read_pointset(std::istream &is, + std::vector > &Input){ + + std::string point; + + while(!std::getline(is, point, '\n').eof()) { + //std::cout< input; + while (found2!=std::string::npos || point[found]=='-') + { + //std::cout<<"*"<<(point[found]!='-')<<"*"< +void read_objective(std::istream &is, std::vector &obj) { + NT element; + while (is >> element) obj.push_back(element); + +} + +template +std::pair read_inner_ball(std::istream &is) { + std::vector obj; + read_objective(is, obj); + unsigned int dim = obj.size() - 1; + Point center(dim); + NT radius = obj[dim]; + for (unsigned int i = 0; i < dim; i++) { + center.set_coord(i, obj[i]); + } + + return std::make_pair(center, radius); +} + +/* read a poset given in the following format: + - First line contains a single positive integer 'n' - number of elements + - Next `m` lines follow containing a pair 'i j' in each line to signify A_i <= A_j + i.e i_th element is less than or equal to the j_th element +*/ +Poset read_poset_from_file(std::istream &data_file) { + typedef typename Poset::RT RT; + typedef typename Poset::RV RV; + + // read number of elements + unsigned int n; + data_file >> n; + + // read relations line by line + RT curr_relation; + RV relations; + while(data_file >> curr_relation.first >> curr_relation.second) + relations.push_back(curr_relation); + + return Poset(n, relations); +} + + +// read a poset given as an adjacency matrix +std::pair read_poset_from_file_adj_matrix(std::istream &in) { + typedef typename Poset::RV RV; + + RV edges; + unsigned int x, n = 0; + + // read a single line + std::string line; + std::getline(in, line); + std::stringstream line_ss(line); + while(line_ss >> x) { + if(x) { + edges.emplace_back(0, n); + } + ++n; + } + + // read rest of the lines + for(unsigned int a = 1; a < n; ++a) { + for(unsigned int b = 0; b < n; ++b) { + if(!(in >> x)) { + std::cerr << "Invalid adjacency matrix"; + return std::pair(false, Poset()); + } + + if(x) { + edges.emplace_back(a, b); + } + } + } + + return std::pair(true, Poset(n, edges)); +} + +#endif //MISC_H diff --git a/src/volesti/include/misc/poset.h b/src/volesti/include/misc/poset.h new file mode 100644 index 00000000..69ba02ec --- /dev/null +++ b/src/volesti/include/misc/poset.h @@ -0,0 +1,131 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2021 Vaibhav Thakkar + +// Contributed and/or modified by Vaibhav Thakkar, as part of Google Summer of Code 2021 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef POSET_H +#define POSET_H + +#include +#include +#include + +/// A class to represent a partial order set aka Poset +class Poset { +public: + typedef std::pair RT; // relation type + typedef std::vector RV; // relations vector + +private: + unsigned int n; // elements will be from 0 to n-1 + RV order_relations; // pairs of form a <= b + +public: + Poset() {} + + Poset(unsigned int _n, RV& _order_relations) : + n(_n), order_relations(verify(_order_relations, n)) + {} + + + // verify if the relations are valid + static RV verify(const RV& relations, unsigned int n) + { + for (int i = 0; i < relations.size(); i++) { + if (relations[i].first < 0 || relations[i].first >= n) + throw "invalid elements in order relations"; + + if (relations[i].second < 0 || relations[i].second >= n) + throw "invalid elements in order relations"; + } + + // TODO: Check if corresponding DAG is actually acyclic + + return relations; + } + + + void print() + { + std::cout << "Number of elements: " << num_elem() << std::endl; + + unsigned int count = num_relations(); + for(int i=0; i + bool is_in(const std::vector& pt_coeffs, NT tol=NT(0)) const + { + for (int i = 0; i < order_relations.size(); i++) { + unsigned int a = order_relations[i].first; + unsigned int b = order_relations[i].second; + + if (! (pt_coeffs[a] - pt_coeffs[b] <= tol) ) { + return false; + } + } + + return true; + } + + + std::vector topologically_sorted_list() const + { + std::vector > adjList(n); + std::vector indegree(n, 0); + + for(auto x: order_relations) { + adjList[x.first].push_back(x.second); + indegree[x.second] += 1; + } + + std::queue q; + for(unsigned int i=0; i res; + while(!q.empty()) { + unsigned int curr = q.front(); + res.push_back(curr); + q.pop(); + + for(unsigned int i=0; i +#include +#include +#include +#include +#include +#include +#include + +/** + * Used to specify the column format + */ +enum class VariadicTableColumnFormat +{ + AUTO, + SCIENTIFIC, + FIXED, + PERCENT +}; + +/** + * A class for "pretty printing" a table of data. + * + * Requries C++11 (and nothing more) + * + * It's templated on the types that will be in each column + * (all values in a column must have the same type) + * + * For instance, to use it with data that looks like: "Fred", 193.4, 35, "Sam" + * with header names: "Name", "Weight", "Age", "Brother" + * + * You would invoke the table like so: + * VariadicTable vt("Name", "Weight", "Age", "Brother"); + * + * Then add the data to the table: + * vt.addRow("Fred", 193.4, 35, "Sam"); + * + * And finally print it: + * vt.print(); + */ +template +class VariadicTable +{ +public: + /// The type stored for each row + typedef std::tuple DataTuple; + + /** + * Construct the table with headers + * + * @param headers The names of the columns + * @param static_column_size The size of columns that can't be found automatically + */ + VariadicTable(std::vector headers, + unsigned int static_column_size = 0, + unsigned int cell_padding = 1) + : _headers(headers), + _num_columns(std::tuple_size::value), + _static_column_size(static_column_size), + _cell_padding(cell_padding) + { + assert(headers.size() == _num_columns); + } + + /** + * Add a row of data + * + * Easiest to use like: + * table.addRow({data1, data2, data3}); + * + * @param data A Tuple of data to add + */ + void addRow(Ts... entries) { _data.emplace_back(std::make_tuple(entries...)); } + + /** + * Pretty print the table of data + */ + template + void print(StreamType & stream) + { + size_columns(); + + // Start computing the total width + // First - we will have _num_columns + 1 "|" characters + unsigned int total_width = _num_columns + 1; + + // Now add in the size of each colum + for (auto & col_size : _column_sizes) + total_width += col_size + (2 * _cell_padding); + + // Print out the top line + stream << std::string(total_width, '-') << "\n"; + + // Print out the headers + stream << "|"; + for (unsigned int i = 0; i < _num_columns; i++) + { + // Must find the center of the column + auto half = _column_sizes[i] / 2; + half -= _headers[i].size() / 2; + + stream << std::string(_cell_padding, ' ') << std::setw(_column_sizes[i]) << std::left + << std::string(half, ' ') + _headers[i] << std::string(_cell_padding, ' ') << "|"; + } + + stream << "\n"; + + // Print out the line below the header + stream << std::string(total_width, '-') << "\n"; + + // Now print the rows of the table + for (auto & row : _data) + { + stream << "|"; + print_each(row, stream); + stream << "\n"; + } + + // Print out the line below the header + stream << std::string(total_width, '-') << "\n"; + } + + /** + * Set how to format numbers for each column + * + * Note: this is ignored for std::string columns + * + * @column_format The format for each column: MUST be the same length as the number of columns. + */ + void setColumnFormat(const std::vector & column_format) + { + assert(column_format.size() == std::tuple_size::value); + + _column_format = column_format; + } + + /** + * Set how many digits of precision to show for floating point numbers + * + * Note: this is ignored for std::string columns + * + * @column_format The precision for each column: MUST be the same length as the number of columns. + */ + void setColumnPrecision(const std::vector & precision) + { + assert(precision.size() == std::tuple_size::value); + _precision = precision; + } + +protected: + // Just some handy typedefs for the following two functions + typedef decltype(&std::right) right_type; + typedef decltype(&std::left) left_type; + + // Attempts to figure out the correct justification for the data + // If it's a floating point value + template ::type>::value>::type> + static right_type justify(int /*firstchoice*/) + { + return std::right; + } + + // Otherwise + template + static left_type justify(long /*secondchoice*/) + { + return std::left; + } + + /** + * These three functions print out each item in a Tuple into the table + * + * Original Idea From From https://stackoverflow.com/a/26908596 + * + * BTW: This would all be a lot easier with generic lambdas + * there would only need to be one of this sequence and then + * you could pass in a generic lambda. Unfortunately, that's C++14 + */ + + /** + * This ends the recursion + */ + template + void print_each(TupleType &&, + StreamType & /*stream*/, + std::integral_constant< + size_t, + std::tuple_size::type>::value>) + { + } + + /** + * This gets called on each item + */ + template ::type>::value>::type> + void print_each(TupleType && t, StreamType & stream, std::integral_constant) + { + auto & val = std::get(t); + + // Set the precision + if (!_precision.empty()) + { + assert(_precision.size() == + std::tuple_size::type>::value); + + stream << std::setprecision(_precision[I]); + } + + // Set the format + if (!_column_format.empty()) + { + assert(_column_format.size() == + std::tuple_size::type>::value); + + if (_column_format[I] == VariadicTableColumnFormat::SCIENTIFIC) + stream << std::scientific; + + if (_column_format[I] == VariadicTableColumnFormat::FIXED) + stream << std::fixed; + + if (_column_format[I] == VariadicTableColumnFormat::PERCENT) + stream << std::fixed << std::setprecision(2); + } + + stream << std::string(_cell_padding, ' ') << std::setw(_column_sizes[I]) + << justify(0) << val << std::string(_cell_padding, ' ') << "|"; + + // Unset the format + if (!_column_format.empty()) + { + // Because "stream << std::defaultfloat;" won't compile with old GCC or Clang + stream.unsetf(std::ios_base::floatfield); + } + + // Recursive call to print the next item + print_each(std::forward(t), stream, std::integral_constant()); + } + + /** + * his is what gets called first + */ + template + void print_each(TupleType && t, StreamType & stream) + { + print_each(std::forward(t), stream, std::integral_constant()); + } + + /** + * Try to find the size the column will take up + * + * If the datatype has a size() member... let's call it + */ + template + size_t sizeOfData(const T & data, decltype(((T *)nullptr)->size()) * /*dummy*/ = nullptr) + { + return data.size(); + } + + /** + * Try to find the size the column will take up + * + * If the datatype is an integer - let's get it's length + */ + template + size_t sizeOfData(const T & data, + typename std::enable_if::value>::type * /*dummy*/ = nullptr) + { + if (data == 0) + return 1; + + return std::log10(data) + 1; + } + + /** + * If it doesn't... let's just use a statically set size + */ + size_t sizeOfData(...) { return _static_column_size; } + + /** + * These three functions iterate over the Tuple, find the printed size of each element and set it + * in a vector + */ + + /** + * End the recursion + */ + template + void size_each(TupleType &&, + std::vector & /*sizes*/, + std::integral_constant< + size_t, + std::tuple_size::type>::value>) + { + } + + /** + * Recursively called for each element + */ + template ::type>::value>::type> + void + size_each(TupleType && t, std::vector & sizes, std::integral_constant) + { + sizes[I] = sizeOfData(std::get(t)); + + // Override for Percent + if (!_column_format.empty()) + if (_column_format[I] == VariadicTableColumnFormat::PERCENT) + sizes[I] = 6; // 100.00 + + // Continue the recursion + size_each(std::forward(t), sizes, std::integral_constant()); + } + + /** + * The function that is actually called that starts the recursion + */ + template + void size_each(TupleType && t, std::vector & sizes) + { + size_each(std::forward(t), sizes, std::integral_constant()); + } + + /** + * Finds the size each column should be and set it in _column_sizes + */ + void size_columns() + { + _column_sizes.resize(_num_columns); + + // Temporary for querying each row + std::vector column_sizes(_num_columns); + + // Start with the size of the headers + for (unsigned int i = 0; i < _num_columns; i++) + _column_sizes[i] = _headers[i].size(); + + // Grab the size of each entry of each row and see if it's bigger + for (auto & row : _data) + { + size_each(row, column_sizes); + + for (unsigned int i = 0; i < _num_columns; i++) + _column_sizes[i] = std::max(_column_sizes[i], column_sizes[i]); + } + } + + /// The column headers + std::vector _headers; + + /// Number of columns in the table + unsigned int _num_columns; + + /// Size of columns that we can't get the size of + unsigned int _static_column_size; + + /// Size of the cell padding + unsigned int _cell_padding; + + /// The actual data + std::vector _data; + + /// Holds the printable width of each column + std::vector _column_sizes; + + /// Column Format + std::vector _column_format; + + /// Precision For each column + std::vector _precision; +}; diff --git a/src/volesti/include/nlp_oracles/nlp_hpolyoracles.hpp b/src/volesti/include/nlp_oracles/nlp_hpolyoracles.hpp new file mode 100644 index 00000000..d7394ca2 --- /dev/null +++ b/src/volesti/include/nlp_oracles/nlp_hpolyoracles.hpp @@ -0,0 +1,572 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +/* This file contains the implementation of boundary oracles between an + d-dimensional H-polytope P: Ax <= b with a curve p(t) = sum c_j phi_j(t) + where c_j are d-dimensional coefficients and phi_j(t) are basis functions + (e.g. polynomials, rational functions, splines). The boundary oracle + returns one root (in general multiple roots exist) assuming that for t >= t_p + the curve p(t) penetrates the H-polytope. The problem reduces to the following + non-linear optimization problem for finding the maximum t such that p(t) penetrates + the polytope where t lies inside [t0, t0 + eta] for some eta > 0 + + max t + + subject to + t >= t0 + t <= t0 + eta + A p(t) <= b + + The second constraint can be rewritten as (A*C) * Phi <= b which is eventually + the optimization problem we solve where the vector Phi contains all the basis + functions and C has c_j's as columns. + + The second optimization problem that can be solved is the following + + min_i min t_i + + subject to + + t0 <= t_i <= t0 + eta + A_i^T p(t_i) = b_i + A_j^T p(t_i) <= b_i j neq i + + + + We use interior-point methods to solve the non-linear optimization program + using COIN-OR ipopt and the ifopt interface for Eigen + ipopt. + +*/ + +#ifndef NLP_HPOLYORACLES_HPP +#define NLP_HPOLYORACLES_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "root_finders/root_finders.hpp" +#define MAX_NR_TRIES 10000 + +#define POLYTOL 1e-7 + +#ifndef isnan + using std::isnan; +#endif + +#ifndef isinf + using std::isinf; +#endif + + +using namespace ifopt; + +/// Define the variable t we use in the optimization +/// \tparam VT Vector Type +/// \tparam NT Numeric Type +template +class HPolyOracleVariables : public VariableSet { +public: + NT t, tb, eta; + + HPolyOracleVariables(NT t_prev, NT tb_, NT eta_=-1): + VariableSet(1, "t"), t(t_prev), tb(tb_), eta(eta_) {}; + + void SetVariables(const VT& T) override { + t = T(0); + } + + VectorXd GetValues() const override { + VectorXd T(1); + T(0) = t; + return T; + } + + VecBound GetBounds() const override { + VecBound bounds(GetRows()); + NT tu = eta > 0 ? NT(tb + eta) : NT(inf); + + bounds.at(0) = Bounds(tb, tu); + return bounds; + }; + +}; + +/// Define the cost function f(t) = t (ipopt takes minimization so it is -t) +/// \tparam VT Vector Type +/// \tparam NT Numeric Type +template +class HPolyOracleCost : public CostTerm { +public: + std::string method; + + HPolyOracleCost(std::string method_) : + CostTerm("h_poly_cost"), method(method_) {}; + + NT GetCost() const override { + VectorXd T = GetVariables()->GetComponent("t")->GetValues(); + if (method == "max_pos") return - T(0); + else if (method == "min_pos") return T(0); + } + + void FillJacobianBlock (std::string var_set, Jacobian& jac) const override { + if (var_set == "t") { + if (method == "max_pos") jac.coeffRef(0, 0) = (NT) (-1.0); + else if (method == "min_pos") jac.coeffRef(0, 0) = (NT) (1.0); + } + } + +}; + +/// Define the feasibility constraint A p(t) <= b which we translate +/// to (A * C) * Phi <= b +/// \tparam MT Matrix Type +/// \tparam VT Vector Type +/// \tparam NT Numeric Type +/// \tparam bfunc feasibility constraint type +template +class HPolyOracleFeasibility : public ConstraintSet { +public: + MT &C; + VT &b; + bfunc phi, grad_phi; + NT t0; + int m, M; + std::string method; + int index; + int ignore_facet; + + HPolyOracleFeasibility( + MT &C_, + VT &b_, + NT t0_, + bfunc basis, + bfunc basis_grad, + std::string method_, + int i, + int ignore_facet_=-1) : + C(C_), b(b_), t0(t0_), phi(basis), grad_phi(basis_grad), + ConstraintSet(C_.rows(), "h_poly_feasibility"), + method(method_), index(i), ignore_facet(ignore_facet_) { + m = C_.rows(); + M = C_.cols(); + }; + + // Define bounds for feasibility + VecBound GetBounds() const override { + VecBound bounds(GetRows()); + for (int i = 0; i < m; i++) { + if (method == "min_pos" && i == index) { + bounds.at(i) = Bounds(b(i), b(i)); + } + else { + bounds.at(i) = Bounds((NT) (-inf), b(i)); + } + + if (i == ignore_facet) { + bounds.at(i) = Bounds((NT) (-inf), (NT) (inf)); + } + + } + return bounds; + } + + VectorXd GetValues() const override { + VectorXd T = GetVariables()->GetComponent("t")->GetValues(); + NT t = T(0); + VectorXd phis; + phis.resize(M); + + for (int i = 0; i < M; i++) { + phis(i) = (NT) (phi(t, t0, i, M)); + } + return C * phis; + } + + // Calculate jacobian matrix of constraints + void FillJacobianBlock (std::string var_set, Jacobian& jac_block) const override { + + if (var_set == "t") { + VectorXd T = GetVariables()->GetComponent("t")->GetValues(); + NT t = T(0); + NT temp; + + for (int i = 0; i < m; i++) { + jac_block.coeffRef(i, 0) = NT(0); + for (int j = 0; j < M; j++) { + temp = grad_phi(t, t0, j, M); + if (isinf(temp)) continue; + else jac_block.coeffRef(i, 0) += temp; + } + } + } + } + + +}; + +/// Oracle that uses the COIN-OR ipopt solver +/// \tparam Polytope Polytope Type +/// \tparam bfunc feasibility constraint type +template +struct IpoptHPolyoracle { + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Polytope::NT NT; + typedef typename Polytope::PointType Point; + + + std::tuple apply( + NT t_prev, + NT t0, + NT eta, + MT &A, + VT &b, + Polytope &P, + std::vector &coeffs, + bfunc phi, + bfunc grad_phi, + int ignore_facet=-1, + std::string solution="max_pos") + { + + MT C, C_tmp; + C_tmp.resize(coeffs[0].dimension(), coeffs.size()); + + + for (int i = 0; i < coeffs.size(); i++) { + C_tmp.col(i) = coeffs[i].getCoefficients(); + } + + // C_tmp: dimension x num_coeffs + // A: constraints x dimension + // C: constraints x num_coeffs + C = A * C_tmp; + + // Initialize COIN-OR ipopt solver + IpoptSolver ipopt; + ipopt.SetOption("linear_solver", "mumps"); + ipopt.SetOption("jacobian_approximation", "exact"); + ipopt.SetOption("tol", POLYTOL); + ipopt.SetOption("acceptable_tol", 100 * POLYTOL); + ipopt.SetOption("max_iter", 1000000); + + ipopt.SetOption("print_level", 0); + ipopt.SetOption("sb", "yes"); + + NT t, t_tmp; + + if (solution == "max_pos") { + + Problem nlp; + std::shared_ptr> + hpolyoraclevariables (new HPolyOracleVariables(t_prev, t0, eta)); + std::shared_ptr> + hpolyoraclecost (new HPolyOracleCost(solution)); + std::shared_ptr> + hpolyoraclefeasibility (new HPolyOracleFeasibility + (C, b, t0, phi, grad_phi, "max_pos", 0, ignore_facet)); + + nlp.AddVariableSet (hpolyoraclevariables); + nlp.AddCostSet (hpolyoraclecost); + nlp.AddConstraintSet(hpolyoraclefeasibility); + + ipopt.Solve(nlp); + + t = nlp.GetOptVariables()->GetValues()(0); + + } + + if (solution == "min_pos") { + int m = A.rows(); + + t = eta > 0 ? t0 + eta : std::numeric_limits::max(); + + for (int i = 0; i < m; i++) { + + if (i == ignore_facet) continue; + + Problem nlp; + std::shared_ptr> + hpolyoraclevariables (new HPolyOracleVariables(t_prev, t0, eta)); + std::shared_ptr> + hpolyoraclecost (new HPolyOracleCost(solution)); + std::shared_ptr> + hpolyoraclefeasibility (new HPolyOracleFeasibility + (C, b, t0, phi, grad_phi, "min_pos", i)); + + nlp.AddVariableSet (hpolyoraclevariables); + nlp.AddCostSet (hpolyoraclecost); + nlp.AddConstraintSet(hpolyoraclefeasibility); + + t_tmp = nlp.GetOptVariables()->GetValues()(0); + + std::cout << "t is " << t_tmp << std::endl; + + if (t_tmp < t && t_tmp > t0) t = t_tmp; + + + } + } + + Point p(coeffs[0].dimension()); + + for (unsigned int i = 0; i < coeffs.size(); i++) { + p += phi(t, t0, i, coeffs.size()) * coeffs[i]; + } + + const NT* b_data = b.data(); + + int f_min = -1; + NT dist_min = std::numeric_limits::max(); + + for (int i = 0; i < A.rows(); i++) { + if (*b_data == 0 && std::abs(*b_data - A.row(i) * p.getCoefficients()) < dist_min) { + f_min = i; + dist_min = std::abs(*b_data - A.row(i) * p.getCoefficients()); + } + else if (*b_data != 0 && std::abs(*b_data - A.row(i) * p.getCoefficients()) / *b_data < dist_min) { + f_min = i; + dist_min = std::abs(*b_data - A.row(i) * p.getCoefficients()) / *b_data; + } + b_data++; + } + + return std::make_tuple(t, p, f_min); + }; + +}; + +/// Compute intersection of H-polytope P := Ax <= b +/// with polynomial curve p(t) = sum a_j (t - t0)^j +/// Uses the MPsolve library +/// \tparam Polytope Polytope Type +/// \tparam bfunc feasibility constraint type +template +struct MPSolveHPolyoracle { + + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Polytope::NT NT; + typedef typename Polytope::PointType Point; + + std::tuple apply( + NT t_prev, + NT t0, + NT eta, + MT &A, + VT &b, + Polytope &P, + std::vector &coeffs, + bfunc phi, + bfunc grad_phi, + int ignore_facet=-1, + bool positive_real=true) + { + NT maxNT = std::numeric_limits::max(); + NT minNT = std::numeric_limits::lowest(); + + NT tu = eta > 0 ? t0 + eta : NT(maxNT); + NT t = tu; + Point dummy(coeffs[0].dimension()); + + for (unsigned int j = 0; j < coeffs.size(); j++) { + dummy = dummy + pow(tu - t0, NT(j)) * coeffs[j]; + } + + std::tuple result = std::make_tuple(tu, dummy, -1); + + int m = A.rows(); + + // Keeps constants A_i^T C_j + std::vector Z(coeffs.size(), NT(0)); + + // std::vector> solutions; + + // Iterate over all hyperplanes + for (int i = 0; i < m; i++) { + + if (i == ignore_facet) continue; + + for (unsigned int j = 0; j < coeffs.size(); j++) { + Z[j] = A.row(i) * coeffs[j].getCoefficients(); + + #ifdef VOLESTI_DEBUG + std::cout << "Z [ " << j << " ] = " << Z[j] << std::endl; + #endif + } + + // Find point projection on m-th hyperplane + Z[0] -= b(i); + + std::vector> solutions = mpsolve(Z, positive_real); + + for(std::pair sol: solutions) { + + #ifdef VOLESTI_DEBUG + std::cout << "Facet: " << i << " Candidate root is " << sol.first + t0 << std::endl; + #endif + + // Check if solution is in the desired range [t0, t0 + eta] and if it is the current minimum + if (t0 + sol.first <= tu && t0 + sol.first < std::get<0>(result)) { + t = t0 + sol.first; + + // Calculate point from this root + Point p = Point(coeffs[0].dimension()); + + for (unsigned int j = 0; j < coeffs.size(); j++) { + p += pow(t - t0, NT(j)) * coeffs[j] ; + } + + #ifdef VOLESTI_DEBUG + std::cout << "Calculcated point is " << std::endl << p.getCoefficients() << std::endl; + #endif + + // Check if point satisfies Ax <= b up to some tolerance and change current solution + if (P.is_in(p, 1e-6)) { + result = std::make_tuple(t, p, i); + } + } + } + + } + + return result; + }; + +}; + +/// Compute intersection of H-polytope P := Ax <= b +/// with curve p(t) = sum a_j phi_j(t) where phi_j are basis +/// functions (e.g. polynomials) +/// Uses Newton-Raphson to solve the transcendental equation +/// \tparam Polytope Polytope Type +/// \tparam bfunc feasibility constraint type +template +struct NewtonRaphsonHPolyoracle { + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Polytope::NT NT; + typedef typename Polytope::PointType Point; + + std::tuple apply( + NT t_prev, + NT t0, + NT eta, + MT &A, + VT &b, + Polytope &P, + std::vector &coeffs, + bfunc phi, + bfunc grad_phi, + int ignore_facet=-1) + { + + // Keep results in a vector (in case of multiple roots) + // The problem has O(m * len(coeffs)) solutions if phi's are polys + // due to the Fundamental Theorem of Algebra + // Some roots may be common for more than one hyperplanes + NT maxNT = std::numeric_limits::max(); + NT minNT = std::numeric_limits::lowest(); + + // Root + NT t = t_prev; + NT tu = eta > 0 ? t0 + eta : NT(maxNT); + + Point dummy(coeffs[0].dimension()); + + for (unsigned int j = 0; j < coeffs.size(); j++) { + dummy += coeffs[j] * phi(tu, t0, j, coeffs.size()); + } + + + std::tuple result = std::make_tuple(tu, dummy, -1); + + // Helper variables for Newton-Raphson + NT dot_u, num, den, den_tmp; + + // Regularization for NR (e.g. at critical points where grad = 0) + NT reg = (NT) 1e-7; + VT Z; + int m = A.rows(); + + // Keeps constants A_i^T C_j + Z.resize(coeffs.size()); + + // Iterate over all hyperplanes + for (int i = 0; i < m; i++) { + + if (i == ignore_facet) continue; + + // Calculate constants + start_iter: t_prev = t0 + reg; + + + for (unsigned int j = 0; j < coeffs.size(); j++) { + Z(j) = A.row(i) * coeffs[j].getCoefficients(); + } + + + for (int tries = 0; tries < MAX_NR_TRIES; tries++) { + + num = - b(i); + den = (NT) 0; + + // Calculate numerator f(t) and denominator f'(t) + for (int j = 0; j < coeffs.size(); j++) { + num += Z(j) * phi(t_prev, t0, j, coeffs.size()); + + // Avoid ill-posed derivative (e.g. 0^{-1}) + if (j > 0) den += Z(j) * grad_phi(t_prev, t0, j, coeffs.size()); + } + + // Regularize denominator if near 0 + if (std::abs(den) < 10 * reg) den += reg; + + // Newton-Raphson Iteration t = t_old - f(t) / f'(t) + t = t_prev - num / den; + + if (t < 0 && t_prev < 0) continue; + + if (std::abs(t - t_prev) < 1e-6 && t > t0) { + // Add root (as t) and facet + + Point p = Point(coeffs[0].dimension()); + + for (unsigned int j = 0; j < coeffs.size(); j++) { + p += coeffs[j] * phi(t, t0, j, coeffs.size()); + } + + if (P.is_in(p) && t < std::get<0>(result)) + result = std::make_tuple(t, p, i); + + } + + t_prev = t; + + } + + } + + return result; + + }; + +}; + + +#endif diff --git a/src/volesti/include/nlp_oracles/nlp_oracles.hpp b/src/volesti/include/nlp_oracles/nlp_oracles.hpp new file mode 100644 index 00000000..11f978e8 --- /dev/null +++ b/src/volesti/include/nlp_oracles/nlp_oracles.hpp @@ -0,0 +1,22 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#include +#include +#include +#include + +#include "nlp_hpolyoracles.hpp" +#include "nlp_vpolyoracles.hpp" + +#ifndef NLP_POLYORACLES_HPP +#define NLP_POLYORACLES_HPP + +#endif diff --git a/src/volesti/include/nlp_oracles/nlp_vpolyoracles.hpp b/src/volesti/include/nlp_oracles/nlp_vpolyoracles.hpp new file mode 100644 index 00000000..9e921d9c --- /dev/null +++ b/src/volesti/include/nlp_oracles/nlp_vpolyoracles.hpp @@ -0,0 +1,314 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +/* This file contains the implementation of boundary oracles between an + d-dimensional V-polytope given by V with a curve p(t) = sum c_j phi_j(t) + where c_j are d-dimensional coefficients and phi_j(t) are basis functions + (e.g. polynomials, rational functions, splines). The boundary oracle + returns one root (in general multiple roots exist) assuming that for t >= t_p + the curve p(t) penetrates the V-polytope. The problem reduces to the following + non-linear optimization problem + + max t + + subject to + t >= 0 + lambda_i >= 0 i \in [V] + \sum_{i = 1}^m lambda_i = 1 + \sum_{i = 1}^m lambda_i v_i - \sum_{i = 1}^M c_j phi_j(t) = 0 + + We use interior-point methods to solve the non-linear optimization program + using COIN-OR ipopt and the ifopt interface for Eigen + ipopt. + +*/ + +#ifndef NLP_VPOLYORACLES_HPP +#define NLP_VPOLYORACLES_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ifopt; + +/// Define the variable t we use in the optimization +/// \tparam VT Vector Type +/// \tparam MT Matrix Type +template +class VPolyOracleVariableT : public VariableSet { +public: + NT t, tb, eta; + + VPolyOracleVariableT(NT t_prev, NT tb_, NT eta_=-1): VariableSet(1, "t"), t(t_prev), tb(tb_), eta(eta_) {}; + + void SetVariables(const VT& T) override { + t = T(0); + } + + VectorXd GetValues() const override { + VectorXd T(1); + T(0) = t; + return T; + } + + VecBound GetBounds() const override { + VecBound bounds(GetRows()); + NT tu = eta > 0 ? NT(tb + eta) : NT(inf); + + bounds.at(0) = Bounds(tb, tu); + return bounds; + }; + +}; + + +/// Define the variable t we use in the optimization +/// \tparam VT Vector Type +/// \tparam MT Matrix Type +template +class VPolyOracleVariableLambdas : public VariableSet { +public: + VectorXd lambdas; + int m; + + VPolyOracleVariableLambdas(int m_): m(m_), VariableSet(m_, "lambdas") { + lambdas.resize(m_); + for (int i = 0; i < m_; i++) lambdas(i) = NT(0); + }; + + void SetVariables(const VectorXd& lambdas_) override { + // for (int i = 0; i < m; i++) lambdas(i) = lambdas_(i); + lambdas = lambdas_; + } + + VectorXd GetValues() const override { + return lambdas; + } + + VecBound GetBounds() const override { + VecBound bounds(GetRows()); + for (int i = 0; i < m; i++) bounds.at(i) = Bounds(NT(0), NT(inf)); + return bounds; + }; + +}; + +/// Define the cost function f(t) = t (ipopt takes minimization so it is -t) +/// \tparam VT Vector Type +/// \tparam MT Matrix Type +template +class VPolyOracleCost : public CostTerm { +public: + int m; + + VPolyOracleCost(int m_) : CostTerm("v_poly_cost"), m(m_) {}; + + NT GetCost() const override { + VectorXd T = GetVariables()->GetComponent("t")->GetValues(); + return - T(0); + } + + void FillJacobianBlock (std::string var_set, Jacobian& jac) const override { + if (var_set == "t") jac.coeffRef(0, 0) = NT(-1.0); + if (var_set == "lambdas") { + for (int i = 0; i < m; i++) { + jac.coeffRef(0, i) = NT(0.0); + } + } + } + + +}; + +/// Define the feasibility lambdas +/// \tparam VT Vector Type +/// \tparam MT Matrix Type +template +class VPolyoracleFeasibilityLambdas : public ConstraintSet { +public: + int m; + + VPolyoracleFeasibilityLambdas(int m_) : ConstraintSet(1, "lambdas_simplex"), m(m_) {}; + + + VectorXd GetValues() const override { + VectorXd lambdas = GetVariables()->GetComponent("lambdas")->GetValues(); + VectorXd S(1); + S(0) = NT(0); + for (int i = 0; i < m; i++) S(0) += lambdas(i); + return S; + } + + VecBound GetBounds() const override { + VecBound bounds(GetRows()); + bounds.at(0) = Bounds(NT(1.0), NT(1.0)); + return bounds; + } + + void FillJacobianBlock (std::string var_set, Jacobian& jac) const override { + if (var_set == "lambdas") + for (int i = 0; i < m; i++) { + jac.coeffRef(0, i) = NT(1.0); + } + if (var_set == "t") jac.coeffRef(0, 0) = NT(0); + } + + +}; + +template +class VPolyOracleFeasibilityCurve : public ConstraintSet { +public: + int m; // number of lambdas + int M; // number of coefficients + int d_; // dimension + std::vector &coeffs; + MT &V; + NT t0; + + bfunc phi, grad_phi; + +// V, coeffs, t0, phi, grad_phi + + VPolyOracleFeasibilityCurve(MT &V_, std::vector &coeffs_, NT t0_, bfunc basis, bfunc basis_grad) : + V(V_), coeffs(coeffs_), t0(t0_), phi(basis), grad_phi(basis_grad), ConstraintSet(V_.cols() ,"curve_feasibility") { + m = V.rows(); + M = (int) (coeffs.size()); + d_ = V.cols(); + } + + VecBound GetBounds() const override { + VecBound bounds(GetRows()); + for (int i = 0; i < d_; i++) bounds.at(i) = Bounds(NT(0), NT(0)); + return bounds; + } + + VectorXd GetValues() const override { + VectorXd values_(d_); + NT t = GetVariables()->GetComponent("t")->GetValues()(0); + VectorXd lambdas = GetVariables()->GetComponent("lambdas")->GetValues(); + + for (int i = 0; i < d_; i++) { + values_(i) = NT(0); + + for (int j = 0; j < m; j++) { + values_(i) += lambdas(j) * V(j, i); + } + + for (int j = 0; j < M; j++) { + values_(i) -= phi(t, t0, j, M) * coeffs[j][i]; + } + + } + + return values_; + + } + + void FillJacobianBlock (std::string var_set, Jacobian& jac) const override { + if (var_set == "t") { + NT t = GetVariables()->GetComponent("lambdas")->GetValues()(0); + + for (int i = 0; i < d_; i++) { + for (int j = 0; j < M; j++) { + jac.coeffRef(i, 0) -= grad_phi(t, t0, j, M) * coeffs[j][i]; + } + } + } + + if (var_set == "lambdas") { + for (int i = 0; i < d_; i++) { + for (int j = 0; j < m; j++) { + jac.coeffRef(i, j) = V(j, i); + } + } + } + + } + +}; + +/// Oracle for V-polytopes +/// \tparam Polytope Polytope Type +/// \tparam bfunc feasibility constraint type +template +struct IpoptVPolyoracle { + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Polytope::NT NT; + typedef typename Polytope::PointType Point; + + std::tuple apply( + NT t_prev, + NT t0, + NT eta, + MT &V, + Polytope &P, + std::vector &coeffs, + bfunc phi, + bfunc grad_phi, + int ignore_facet=-1) { // ignore facet not supported + + Problem nlp; + + int m = V.rows(); + + std::shared_ptr> vpolyoraclevariablet (new VPolyOracleVariableT(t_prev, t0, eta)); + std::shared_ptr> vpolyoraclevariable_lambdas (new VPolyOracleVariableLambdas(m)); + + nlp.AddVariableSet(vpolyoraclevariablet); + nlp.AddVariableSet(vpolyoraclevariable_lambdas); + + std::shared_ptr> vpolyoraclecost (new VPolyOracleCost(m)); + + nlp.AddCostSet(vpolyoraclecost); + + std::shared_ptr> vpolyoraclefeasibility_lambdas (new VPolyoracleFeasibilityLambdas(m)); + std::shared_ptr> vpolyoraclefeasibility_curve (new VPolyOracleFeasibilityCurve(V, coeffs, t0, phi, grad_phi)); + + nlp.AddConstraintSet(vpolyoraclefeasibility_lambdas); + nlp.AddConstraintSet(vpolyoraclefeasibility_curve); + + nlp.PrintCurrent(); + + IpoptSolver ipopt; + ipopt.SetOption("linear_solver", "mumps"); + + // TODO fix exact jacobian + ipopt.SetOption("jacobian_approximation", "finite-difference-values"); + ipopt.SetOption("tol", 1e-7); + ipopt.SetOption("print_level", 0); + ipopt.SetOption("sb", "yes"); + + ipopt.Solve(nlp); + + NT t = nlp.GetOptVariables()->GetValues()(0); + + Point p(coeffs[0].dimension()); + + for (unsigned int i = 0; i < coeffs.size(); i++) { + p += phi(t, t0, i, coeffs.size()) * coeffs[i]; + } + + return std::make_tuple(t, p, NT(-1)); + + } + + + +}; + +#endif diff --git a/src/volesti/include/ode_solvers/basis.hpp b/src/volesti/include/ode_solvers/basis.hpp new file mode 100644 index 00000000..f51e92f7 --- /dev/null +++ b/src/volesti/include/ode_solvers/basis.hpp @@ -0,0 +1,186 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ODE_SOLVERS_BASIS_HPP +#define ODE_SOLVERS_BASIS_HPP + +enum BasisType { + DERIVATIVE = 0, + FUNCTION = 1, + INTEGRAL = 2 +}; + + +template +struct PolynomialBasis { + BasisType basis_type; + + PolynomialBasis(BasisType basis_type_) : basis_type(basis_type_) {} + + NT operator() (NT t, const NT t0, unsigned int j, const unsigned int ord) const { + switch (basis_type) { + case FUNCTION: + return pow(t - t0, NT(j)); + case DERIVATIVE: + return NT(j) * pow(t - t0, NT(j - 1)); + case INTEGRAL: + return pow(t - t0, NT(j + 1)) / NT(j + 1); + } + } + +}; + +template +struct Polynomial { + + BasisType basis_type; + PolynomialBasis basis; + std::vector coeffs; + NT result; + unsigned int ord; + + Polynomial(std::vector coeffs_, BasisType basis_type_) : + basis_type(basis_type_), coeffs(coeffs_), basis(basis_type_) { + ord = coeffs.size(); + } + + NT operator() (NT t, NT t0, unsigned int j=-1, unsigned int ord=-1) { + result = NT(0); + + for (unsigned int i = 0; i < ord; i++) { + result += coeffs[i] * basis(t, t0, i, ord); + } + + return result; + } + + static std::vector convolve(std::vector const& p, std::vector const& q) { + // Performs direct convolution of p and q (less round-off error than FFT) + unsigned int n = p.size(); + unsigned int m = q.size(); + unsigned int r = n + m - 1; + + std::vector result(r, NT(0)); + + unsigned int j, k; + + for (unsigned int i = 0; i < r; i++) { + j = (i >= m - 1)? i - (m - 1) : 0; + k = (i < n - 1)? i : n - 1; + for (unsigned int z = j; z <= k; z++) result[i] += (p[z] * q[i - z]); + } + + return result; + } + + static std::vector multi_convolve(std::vector> &seq) { + + std::vector result; + result = seq[0]; + + for (unsigned int i = 1; i < seq.size(); i++) { + result = convolve(result, seq[i]); + } + + return result; + + } + + +}; + +template +struct RationalFunctionBasis { + bfunc p, q; + bfunc grad_p, grad_q; + NT reg = 1e-6; + BasisType basis_type; + NT num, den, grad_num, grad_den; + + RationalFunctionBasis(bfunc num, bfunc grad_num, bfunc den, bfunc grad_den, BasisType basis_type_) : + p(num), grad_p(grad_num), q(den), grad_q(grad_den), basis_type(basis_type_) {}; + + NT operator()(NT t, NT t0, unsigned int j, unsigned int ord) { + + switch (basis_type) { + case FUNCTION: + num = p(t, t0, j, ord); + den = q(t, t0, j, ord); + if (std::abs(den) < reg) den += reg; + return num / den; + case DERIVATIVE: + num = p(t, t0, j, ord); + grad_num = grad_p(t, t0, j, ord); + den = q(t, t0, j, ord); + grad_den = grad_q(t, t0, j, ord); + if (std::abs(den * den) < reg) den += reg; + return (grad_num / den) - (grad_den * num) / den; + case INTEGRAL: + throw true; + } + } + +}; + +template +struct LagrangePolynomial { + + VT coeffs; + VT nodes; + int basis = -1; + + LagrangePolynomial() {} + + int order() const { + return nodes.rows(); + } + + void set_nodes(VT nodes_) { + nodes = nodes_; + } + + void set_basis(int basis_) { + basis = basis_; + } + + void set_coeffs(VT coeffs_) { + coeffs = coeffs_; + } + + NT operator() (NT t) const { + + int j = (int) round((order() * acos(t)) / M_PI - 0.5); + NT temp = cos((j+0.5) * M_PI / order()); + + if (abs(temp - nodes(j)) < 1e-6) { + if (basis == -1) return coeffs(j); + else return NT(1); + } else { + throw true; + return NT(-1); + } + + } +}; + +template +void degree_doubling_chebyshev(std::vector &coeffs, + std::vector &result) { + unsigned int N = coeffs.size() - 1; + + for (int i = 0; i <= 2 * N; i++) { + if (i > N) result[i] = coeffs[i - N]; + else if (i == N) result[i] = 2 * coeffs[0]; + else result[i] = coeffs[N - i]; + } + +} + +#endif diff --git a/src/volesti/include/ode_solvers/collocation.hpp b/src/volesti/include/ode_solvers/collocation.hpp new file mode 100644 index 00000000..3b025289 --- /dev/null +++ b/src/volesti/include/ode_solvers/collocation.hpp @@ -0,0 +1,298 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ODE_SOLVERS_COLLOCATION_HPP +#define ODE_SOLVERS_COLLOCATION_HPP + +#include "nlp_oracles/nlp_hpolyoracles.hpp" +#include "nlp_oracles/nlp_vpolyoracles.hpp" + +template < + typename Point, + typename NT, + typename Polytope, + typename bfunc, + typename func, + typename NontLinearOracle=MPSolveHPolyoracle< + Polytope, + bfunc + > +> +struct CollocationODESolver { + + + // Vectors of points + typedef std::vector pts; + typedef std::vector ptsv; + + // typedef from existing templates + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef std::vector MTs; + typedef std::vector bounds; + typedef std::vector coeffs; + + unsigned int dim; + + NT eta; + NT t, t_prev, dt, t_temp; + const NT tol = 1e-3; + + // If set to true the solver assumes linearity of the field + // Otherwise it approximates the constant vector with Euler method + const bool exact = false; + + // If set to true it enables precomputation (does not recompute A and b at every step) + const bool precompute = true; + + bool precompute_flag = false; + + // Function oracles x'(t) = F(x, t) + func F; + + // Basis functions + bfunc phi, grad_phi; + + bounds Ks; + + // Contains the sub-states + pts xs, xs_prev, zs; + Point y; + + NT temp_grad; + NT temp_func; + + // Basis coefficients + ptsv as; + + // Temporal coefficients + coeffs cs; + + // Matrices for collocation methods + MTs As, Bs; + + // Keeps the solution to Ax = b temporarily + MTs temps; + + VT Ar, Av; + + NontLinearOracle oracle; + + int prev_facet = -1; + Point prev_point; + + CollocationODESolver(NT initial_time, NT step, pts initial_state, func oracle, + bounds boundaries, coeffs c_coeffs, bfunc basis, bfunc grad_basis) : + t(initial_time), xs(initial_state), F(oracle), eta(step), Ks(boundaries), + cs(c_coeffs), phi(basis), grad_phi(grad_basis) { + dim = xs[0].dimension(); + initialize_matrices(); + }; + + unsigned int order() const { + return cs.size(); + } + + void initialize_matrices() { + As = MTs(xs.size()); + Bs = MTs(xs.size()); + temps = MTs(xs.size()); + as = ptsv(xs.size(), pts(order(), Point(xs[0].dimension()))); + for (unsigned int i = 0; i < xs.size(); i++) { + // Gradient matrix is of size (order - 1) x (order - 1) + As[i].resize(order()-1, order()-1); + // Constants matrix is of size (order - 1) x dim + Bs[i].resize(order()-1, xs[0].dimension()); + temps[i].resize(order()-1, xs[0].dimension()); + } + + zs = pts{Point(1)}; + } + + void step() { + t_prev = t; + xs_prev = xs; + + for (unsigned int ord = 0; ord < order(); ord++) { + // Calculate t_ord + t = t_prev + cs[ord] * eta; + + for (int i = 0; i < xs.size(); i++) { + // a0 = F(x0, t0) + y = F(i,xs, t); + + if (ord == 0) { + as[i][0] = xs_prev[i]; + + if (exact) { + for (unsigned int j = 0; j < order() - 1; j++) { + Bs[i].row(j) = y.getCoefficients(); + } + } + + } + // Construct matrix A + else { + if (exact) { + + if (!precompute || (precompute && !precompute_flag)) { + for (unsigned int j = 0; j < order() - 1; j++) { + temp_grad = grad_phi(t, t_prev, order() - j - 1, order()); + zs[0].set_coord(0, phi(t, t_prev, order() - j - 1, order())); + temp_func = F(i,zs, t)[0]; + As[i](ord-1, j) = temp_grad - temp_func; + } + } + } + else { + + // Compute new derivative (inter-point) + dt = (cs[ord] - cs[ord-1]) * eta; + y = dt * y; + + // Do not take into account reflections + xs[i] += y; + + // Keep grads for matrix B + Bs[i].row(ord-1) = y.getCoefficients().transpose(); + + // Construct matrix A that contains the gradients of the basis functions + if (!precompute || (precompute && !precompute_flag)) { + for (unsigned int j = 0; j < order() - 1; j++) { + As[i](ord-1, j) = grad_phi(t, t_prev, order() - j - 1, order()); + } + } + } + } + + } + } + + // Solve linear systems + for (int i = 0; i < xs.size(); i++) { + // temp contains solution in decreasing order of bases + temps[i] = As[i].colPivHouseholderQr().solve(Bs[i]); + + for (int j = 0; j < order() - 1; j++) { + // TODO Add vectorized implementation + // as[i][order() - j - 1] += temp(j); + for (int k = 0; k < xs[0].dimension(); k++) { + as[i][order() - j - 1].set_coord(k, temps[i](j, k)); + } + } + } + + if (!exact) { + for (int r = 0; r < (int) (eta / tol); r++) { + for (unsigned int i = 0; i < xs.size(); i++) { + xs[i] = Point(xs[i].dimension()); + for (unsigned int ord = 0; ord < order(); ord++) { + xs[i] += as[i][ord] * phi(t_prev + eta, t_prev, ord, order()); + } + } + + for (unsigned int i = 0; i < xs.size(); i++) { + for (int ord = 1; ord < order(); ord++) { + t_temp = cs[ord] * eta; + y = F(i,xs, t_temp); + Bs[i].row(ord-1) = y.getCoefficients().transpose(); + } + } + + + // Solve linear systems + for (int i = 0; i < xs.size(); i++) { + // temp contains solution in decreasing order of bases + temps[i] = As[i].colPivHouseholderQr().solve(Bs[i]); + + for (int j = 0; j < order() - 1; j++) { + // TODO Add vectorized implementation + // as[i][order() - j - 1] += temp(j); + for (int k = 0; k < xs[0].dimension(); k++) { + as[i][order() - j - 1].set_coord(k, temps[i](j, k)); + } + } + } + + } + + + } + + + // Compute next point + for (unsigned int i = 0; i < xs.size(); i++) { + if (Ks[i] == NULL) { + // xs[i] = Point(xs[i].dimension()); + // for (unsigned int ord = 0; ord < order(); ord++) { + // xs[i] += as[i][ord] * phi(t_prev + eta, t_prev, ord, order()); + // } + + if (prev_facet != -1 && i > 0) Ks[i-1]->compute_reflection(xs[i], prev_point, prev_facet); + prev_facet = -1; + + + } else { + std::tuple result = Ks[i]->curve_intersect(t_prev, t_prev, + eta, as[i], phi, grad_phi, oracle); + + // Point is inside polytope + if (std::get<2>(result) == -1 && Ks[i]->is_in(std::get<1>(result))) { + // std::cout << "Inside" << std::endl; + xs[i] = Point(xs[i].dimension()); + for (unsigned int ord = 0; ord < order(); ord++) { + xs[i] += as[i][ord] * phi(t_prev + eta, t_prev, ord, order()); + } + + prev_facet = -1; + + } + else { + // std::cout << "outside" << std::endl; + // Stick to the boundary + xs[i] = 0.99 * std::get<1>(result); + prev_point = xs[i]; + prev_facet = std::get<2>(result); + + + } + } + } + + t += eta; + + precompute_flag = true; + + } + + + void print_state() { + for (int j = 0; j < xs.size(); j++) { + for (unsigned int i = 0; i < xs[j].dimension(); i++) { + std::cout << xs[j][i] << " "; + } + } + std::cout << std::endl; + } + + void steps(int num_steps) { + for (int i = 0; i < num_steps; i++) step(); + } + + Point get_state(int index) { + return xs[index]; + } + + void set_state(int index, Point p) { + xs[index] = p; + } +}; + +#endif diff --git a/src/volesti/include/ode_solvers/euler.hpp b/src/volesti/include/ode_solvers/euler.hpp new file mode 100644 index 00000000..2e240742 --- /dev/null +++ b/src/volesti/include/ode_solvers/euler.hpp @@ -0,0 +1,130 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ODE_SOLVERS_EULER_HPP +#define ODE_SOLVERS_EULER_HPP + +template < + typename Point, + typename NT, + typename Polytope, + typename func +> +struct EulerODESolver { + + typedef std::vector pts; + typedef std::vector bounds; + typedef typename Polytope::VT VT; + + unsigned int dim; + + NT eta; + NT t; + VT Ar, Av; + + func F; + bounds Ks; + + // Contains the sub-states + pts xs; + pts xs_prev; + + // Previous state boundary point + Point x_prev_bound; + + // Previous state boundary facet + int prev_facet = -1; + + EulerODESolver(NT initial_time, NT step, pts initial_state, func oracle, + bounds boundaries) : + eta(step), t(initial_time), F(oracle), Ks(boundaries), xs(initial_state) { + dim = xs[0].dimension(); + }; + + + void step(int k, bool accepted) { + xs_prev = xs; + t += eta; + for (unsigned int i = 0; i < xs.size(); i++) { + Point y = F(i, xs_prev, t); + y = eta * y; + + if (Ks[i] == NULL) { + xs[i] = xs_prev[i] + y; + if (prev_facet != -1 && i > 0) { + Ks[i-1]->compute_reflection(xs[i], x_prev_bound, prev_facet); + } + + } + else { + + // Find intersection (assuming a line trajectory) between x and y + do { + // Find line intersection between xs[i] (new position) and y + std::pair pbpair = Ks[i]->line_positive_intersect(xs_prev[i], y, Ar, Av); + + if (pbpair.first >= 0 && pbpair.first <= 1) { + // Advance to point on the boundary + xs_prev[i] += (pbpair.first * 0.95) * y; + + // Update facet for reflection of derivative + prev_facet = pbpair.second; + x_prev_bound = xs_prev[i]; + + // Reflect ray y on the boundary point y now is the reflected ray + Ks[i]->compute_reflection(y, xs_prev[i], pbpair.second); + // Add it to the existing (boundary) point and repeat + xs[i] = xs_prev[i] + y; + + } + else { + prev_facet = -1; + xs[i] = xs_prev[i] + y; + } + } while (!Ks[i]->is_in(xs[i])); + + } + + } + + } + + void print_state() { + for (int j = 0; j < xs.size(); j++) { + for (unsigned int i = 0; i < xs[j].dimension(); i++) { + std::cout << xs[j][i] << " "; + } + } + std::cout << std::endl; + } + + void steps(int num_steps, bool accepted) { + for (int i = 0; i < num_steps; i++) step(i, accepted); + } + + Point get_state(int index) { + return xs[index]; + } + + void set_state(int index, Point p) { + xs[index] = p; + } + + void disable_adaptive() { + // TODO Implement + } + + void enable_adaptive() { + // TODO Implement + } + +}; + +#endif diff --git a/src/volesti/include/ode_solvers/generalized_leapfrog.hpp b/src/volesti/include/ode_solvers/generalized_leapfrog.hpp new file mode 100644 index 00000000..098ea035 --- /dev/null +++ b/src/volesti/include/ode_solvers/generalized_leapfrog.hpp @@ -0,0 +1,172 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ODE_SOLVERS_GENERALIZED_LEAPFROG_HPP +#define ODE_SOLVERS_GENERALIZED_LEAPFROG_HPP + +template < +typename Point, +typename NT, +typename ConvexBody, +typename func +> +struct GeneralizedLeapfrogODESolver { + + typedef std::vector pts; + + typedef Eigen::Matrix MT; + + typedef std::vector bounds; + typedef typename ConvexBody::VT VT; + + unsigned int dim; + std::vector lambda_prev; + + + NT eta; + NT eta0; + NT t; + NT dl = 0.995; + + func F; + bounds Ks; + + // Contains the sub-states + pts xs; + pts xs_prev; + + MT _AA; + + std::pair pbpair; + + unsigned long long num_reflections = 0; + unsigned long long num_steps = 0; + + bool adaptive = true; + + GeneralizedLeapfrogODESolver(NT initial_time, NT step, pts initial_state, func oracle, bounds boundaries, bool adaptive_=true) : + eta(step), eta0(step), t(initial_time), F(oracle), Ks(boundaries), xs(initial_state), adaptive(adaptive_) { + dim = xs[0].dimension(); + initialize(); + }; + + void initialize() { + for (unsigned int i = 0; i < xs.size(); i++) { + lambda_prev.push_back(NT(0)); + } + } + + void disable_adaptive() { + adaptive = false; + } + + void enable_adaptive() { + adaptive = true; + } + + void step(int k, bool accepted) { + num_steps++; + + if (adaptive) eta = (eta0 * num_steps) / (num_steps + num_reflections); + + xs_prev = xs; + unsigned int x_index, v_index, it; + t += eta; + for (unsigned int i = 1; i < xs.size(); i += 2) { + //pbpair.second = -1; + x_index = i - 1; + v_index = i; + + // v' <- v + eta / 2 F(x) + Point z = F(v_index, xs_prev, t); + z = (eta / 2) * z; + xs[v_index] = xs[v_index] + z; + + // x <- x + eta v' + Point y = xs[v_index]; + + if (Ks[x_index] == NULL) { + xs[x_index] = xs_prev[x_index] + eta*y; + } + else { + // Find intersection (assuming a line trajectory) between x and y + bool step_completed = false; + NT T = eta; + if (k == 0 && !accepted) { + lambda_prev[x_index] = 0.0; + } + + pbpair = Ks[x_index]->line_positive_intersect(xs_prev[x_index], y); + if (T <= pbpair.first) { + xs[x_index] = xs_prev[x_index] + T * y; + xs[v_index] = y; + lambda_prev[x_index] = T; + step_completed = true; + } + + if (!step_completed) { + lambda_prev[x_index] = dl * pbpair.first; + xs_prev[x_index] = xs_prev[x_index] + lambda_prev[x_index] * y; + T -= lambda_prev[x_index]; + Ks[x_index]->compute_reflection(y, xs_prev[x_index], pbpair.second); + num_reflections++; + + while (true) + { + pbpair = Ks[x_index]->line_positive_intersect(xs_prev[x_index], y); + if (T <= pbpair.first) { + xs[x_index] = xs_prev[x_index] + T * y; + xs[v_index] = y; + lambda_prev[x_index] = T; + break; + } + lambda_prev[x_index] = dl * pbpair.first; + xs_prev[x_index] = xs_prev[x_index] + lambda_prev[x_index] * y; + T -= lambda_prev[x_index]; + Ks[x_index]->compute_reflection(y, xs_prev[x_index], pbpair.second); + num_reflections++; + } + } + } + + // tilde v <- v + eta / 2 F(tilde x) + z = F(v_index, xs, t); + z = (eta / 2) * z; + xs[v_index] = xs[v_index] + z; + + } + + } + + void print_state() { + for (int j = 0; j < xs.size(); j ++) { + for (unsigned int i = 0; i < xs[j].dimension(); i++) { + std::cout << xs[j][i] << " "; + } + } + std::cout << std::endl; + } + + void steps(int num_steps, bool accepted) { + for (int i = 0; i < num_steps; i++) step(i, accepted); + } + + Point get_state(int index) { + return xs[index]; + } + + void set_state(int index, Point p) { + xs[index] = p; + } + +}; + + +#endif diff --git a/src/volesti/include/ode_solvers/implicit_midpoint.hpp b/src/volesti/include/ode_solvers/implicit_midpoint.hpp new file mode 100644 index 00000000..786734d8 --- /dev/null +++ b/src/volesti/include/ode_solvers/implicit_midpoint.hpp @@ -0,0 +1,177 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" + +#ifndef IMPLICIT_MIDPOINT_HPP +#define IMPLICIT_MIDPOINT_HPP +#include "preprocess/crhmc/opts.h" +#include "random_walks/crhmc/hamiltonian.hpp" +#ifdef TIME_KEEPING +#include +#endif + +template +inline std::vector operator+(const std::vector &v1, + const std::vector &v2) +{ + std::vector result(v1.size()); + for (int i = 0; i < v1.size(); i++) { + result[i] = v1[i] + v2[i]; + } + return result; +} +template +inline std::vector operator*(const std::vector &v, const Type alpha) +{ + std::vector result(v.size()); + for (int i = 0; i < v.size(); i++) { + result[i] = v[i] * alpha; + } + return result; +} +template +inline std::vector operator/(const std::vector &v, const Type alpha) +{ + return v * (1 / alpha); +} +template +inline std::vector operator-(const std::vector &v1, + const std::vector &v2) +{ + + return v1 + v2 * (-1.0); +} +template +struct ImplicitMidpointODESolver { + using VT = typename Polytope::VT; + using MT = typename Polytope::MT; + using pts = std::vector; + using hamiltonian = Hamiltonian; + using Opts = opts; + + unsigned int dim; + NT eta; + int num_steps = 0; + NT t; + + // Contains the sub-states + pts xs; + pts xs_prev; + + // Function oracle + func F; + Polytope &P; + Opts &options; + MT nu; + int num_runs = 0; + hamiltonian ham; + bool done; +#ifdef TIME_KEEPING + std::chrono::time_point start, end; + std::chrono::duration DU_duration = std::chrono::duration::zero(); + std::chrono::duration approxDK_duration = std::chrono::duration::zero(); +#endif + ImplicitMidpointODESolver(NT initial_time, + NT step, + pts initial_state, + func oracle, + Polytope &boundaries, + Opts &user_options) : + eta(step), + t(initial_time), + xs(initial_state), + F(oracle), + P(boundaries), + options(user_options), + ham(hamiltonian(boundaries)) + { + dim = xs[0].rows(); + }; + + void step(int k, bool accepted) { + num_runs++; + pts partialDerivatives; +#ifdef TIME_KEEPING + start = std::chrono::system_clock::now(); +#endif + partialDerivatives = ham.DU(xs); +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + DU_duration += end - start; +#endif + xs = xs + partialDerivatives * (eta / 2); + xs_prev = xs; + done = false; + nu = MT::Zero(P.equations(), simdLen); + for (int i = 0; i < options.maxODEStep; i++) { + pts xs_old = xs; + pts xmid = (xs_prev + xs) / 2.0; +#ifdef TIME_KEEPING + start = std::chrono::system_clock::now(); +#endif + partialDerivatives = ham.approxDK(xmid, nu); + +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + approxDK_duration += end - start; +#endif + xs = xs_prev + partialDerivatives * (eta); + VT dist = ham.x_norm(xmid, xs - xs_old) / eta; + NT maxdist = dist.maxCoeff(); + //If the estimate does not change terminate + if (maxdist < options.implicitTol) { + done = true; + num_steps = i; + break; + //If the estimate is very bad, sample another velocity + } else if (maxdist > options.convergence_bound) { + xs = xs * std::nan("1"); + done = true; + num_steps = i; + break; + } + } +#ifdef TIME_KEEPING + start = std::chrono::system_clock::now(); +#endif + partialDerivatives = ham.DU(xs); +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + DU_duration += end - start; +#endif + xs = xs + partialDerivatives * (eta / 2); + ham.project(xs); + } + + void steps(int num_steps, bool accepted) { + for (int i = 0; i < num_steps; i++) { + step(i, accepted); + } + } + + MT get_state(int index) { return xs[index]; } + + void set_state(int index, MT p) { xs[index] = p; } + template + void print_state(StreamType &stream) { + for (int j = 0; j < xs.size(); j++) { + stream << "state " << j << ": \n"; + stream << xs[j]; + stream << '\n'; + } + } +}; + +#endif diff --git a/src/volesti/include/ode_solvers/integral_collocation.hpp b/src/volesti/include/ode_solvers/integral_collocation.hpp new file mode 100644 index 00000000..10db9be9 --- /dev/null +++ b/src/volesti/include/ode_solvers/integral_collocation.hpp @@ -0,0 +1,289 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// Refers to the integral collocation method with Lagrange Polynomials +// from Lee, Yin Tat, Zhao Song, and Santosh S. Vempala. +//"Algorithmic theory of ODEs and sampling from well-conditioned +// logconcave densities." arXiv preprint arXiv:1812.06243 (2018). + + +#ifndef ODE_SOLVERS_INTEGRAL_COLLOCATION_HPP +#define ODE_SOLVERS_INTEGRAL_COLLOCATION_HPP + +#include "nlp_oracles/nlp_hpolyoracles.hpp" +#include "nlp_oracles/nlp_vpolyoracles.hpp" +#include "boost/numeric/ublas/vector.hpp" +#include "boost/numeric/ublas/io.hpp" +#include "boost/math/special_functions/chebyshev.hpp" +#include "boost/math/special_functions/chebyshev_transform.hpp" + +template < + typename Point, + typename NT, + typename Polytope, + typename func +> +struct IntegralCollocationODESolver { + + + // Vectors of points + typedef std::vector pts; + typedef std::vector ptsv; + + // typedef from existing templates + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef std::vector MTs; + + typedef std::vector bounds; + typedef std::vector coeffs; + typedef boost::numeric::ublas::vector boost_vector; + typedef boost::math::chebyshev_transform chebyshev_transform_boost; + + unsigned int dim; + + NT eta; + NT t, t_prev, dt, temp_node, a, b; + const NT tol = 1e-6; + + // Function oracles x'(t) = F(x, t) + func F; + bounds Ks; + + // Contains the sub-states + pts xs, xs_prev, X_temp; + Point y; + + // Temporal coefficients + coeffs cs; + + VT Ar, Av, X_op, nodes; + + MT A_phi, X0, X, X_prev, F_op; + + unsigned int _order; + + LagrangePolynomial lagrange_poly; + + int prev_facet = -1; + Point prev_point; + + IntegralCollocationODESolver(NT initial_time, NT step, pts initial_state, + func oracle, bounds boundaries, unsigned int order_=4) : + t(initial_time), xs(initial_state), X_temp(initial_state), F(oracle), eta(step), Ks(boundaries), + _order(order_) { + dim = xs[0].dimension(); + initialize_matrices(); + }; + + unsigned int order() const { + return _order; + } + + void initialize_matrices() { + + A_phi.resize(order(), order()); + nodes.resize(order()); + + std::vector temp; + + for (unsigned int j = 0; j < order(); j++) { + nodes(j) = cos((j+0.5) * M_PI / order()); + } + + lagrange_poly.set_nodes(nodes); + + // Calculate integrals of basis functions based on the Discrete Chebyshev Transform + for (unsigned int i = 0; i < order(); i++) { + + lagrange_poly.set_basis((int) i); + + for (unsigned int j = 0; j <= i; j++) { + if (nodes(j) < NT(0)) { + a = nodes(j); + b = NT(0); + } else { + a = NT(0); + b = nodes(j); + } + + chebyshev_transform_boost transform(lagrange_poly, a, b); + A_phi(i, j) = NT(transform.integrate()); + A_phi(j, i) = A_phi(i, j); + } + } + + #ifdef VOLESTI_DEBUG + std::cout << "A_phi" << std::endl; + std::cout << A_phi << std::endl; + #endif + + X.resize(xs.size() * dim, order()); + X0.resize(xs.size() * dim, order()); + X_prev.resize(xs.size() * dim, order()); + X_op.resize(xs.size() * dim); + + F_op.resize(xs.size() * dim, order()); + + lagrange_poly.set_basis(-1); + } + + void initialize_fixed_point() { + for (unsigned int ord = 0; ord < order(); ord++) { + for (unsigned int i = 0; i < xs.size(); i++) { + for (unsigned int j = i * dim; j < (i + 1) * dim; j++) { + X0(j, ord) = xs_prev[i][j % dim]; + } + } + } + } + + void step() { + xs_prev = xs; + initialize_fixed_point(); + + std::vector transforms; + + X = X0; + X_prev = 100 * X0; + NT err; + + do { + for (unsigned int ord = 0; ord < order(); ord++) { + for (unsigned int i = 0; i < xs.size(); i++) { + for (unsigned int j = i * dim; j < (i + 1) * dim; j++) { + X_temp[i].set_coord(j % dim, X(j, ord)); + } + } + + for (unsigned int i = 0; i < xs.size(); i++) { + // std::cout << "pre y" << std::endl; + temp_node = nodes(ord) * eta; + y = F(i, X_temp, temp_node); + + for (int j = i * dim; j < (i + 1) * dim; j++) { + F_op(j, ord) = y[j % dim]; + } + + } + } + + X = X0 + F_op * A_phi; + + X_prev = X; + + err = sqrt((X - X_prev).squaredNorm()); + + } while (err > 1e-10); + + X_op = X0.col(0); + + unsigned int max_transform_coeffs_length = 0; + + + for (unsigned int i = 0; i < xs.size(); i++) { + for (unsigned int j = i * dim; j < (i + 1) * dim; j++) { + lagrange_poly.set_coeffs(F_op.row(j).transpose()); + chebyshev_transform_boost transform(lagrange_poly, 0, eta, 1e-5, 5); + transforms.push_back(transform); + // Keep max transform length for zero-padding + if (max_transform_coeffs_length < transform.coefficients().size()) { + max_transform_coeffs_length = transform.coefficients().size(); + } + + X_op(j) += NT(transform.integrate()); + } + } + + + for (unsigned int i = 0; i < xs.size(); i++) { + if (Ks[i] == NULL) { + for (unsigned int j = i * dim; j < (i + 1) * dim; j++) { + xs[i].set_coord(j % dim, X_op(j)); + } + } + else { + throw true; + // TODO Implement oracle. It requires doing chebyshev transforms + // with the same #of points at each dimension + // 1. Temporarily store coefficients as points + // 2. Initialize everything to Points at origin (for zero-padding) + // pts temp_pts(max_transform_coeffs_length, Point(dim)); + + // 3. Store transformation to polynomial of twice the degree + // pts transformed_pts(2 * max_transform_coeffs_length, Point(dim)); + // std::vector temp_coeffs; + + // 4. Invoke chebyshev transform coefficients + // Precomputed from the final step of the fixed point iterator + // for (unsigned int j = i * dim; j < (i + 1) * dim; j++) { + // temp_coeffs = transforms[j].coefficients(); + // for (unsigned int k = 0; k < temp_coeffs.size(); k++) { + // temp_pts[k].set_coord(j % dim, temp_coeffs[k]); + // } + // } + + // 5. Apply degree-doubling transformation + // The transformation takes the n Chebyshev transform coefficients c[i] + // and creates a complex polynomial of order 2n with coefficients a[i] + // such that a[n] = 2 * c[0], a[i] = c[i - n] for i > n and a[i] = c[n - i] for i < n + // This polynomial h(z) is defined on the complex plane. + // Its roots are related to the chebyshev transform as: z is a root of + // the degree-doubled polynomial h(z) then Re(z) is a + // root for the chebyshev transfrom. This transformation is domain_name + // in order to be able to use MPSolve to compute the boundary oracle + // degree_doubling_chebyshev(temp_pts, transformed_pts); + + // 6. Integrate coefficients + // We want the integral of the function instead of the function itself. + // Integrating the polynomial is fairly easy. + // transformed_pts.insert(transformed_pts.begin(), Point(dim)); + + // for (unsigned int k = 1; k < transformed_pts.size(); k++) { + // transformed_pts[k] = (1.0 / k) * transformed_pts[k]; + // } + + // 7. Find roots using MPSolve + // Project the computed coefficients to each of the polytopes normals and + // calculate the complex roots of the resulting equations. + // Then keep the smallest positive rational part. + } + + } + + + + } + + + void print_state() { + for (int j = 0; j < xs.size(); j++) { + for (unsigned int i = 0; i < xs[j].dimension(); i++) { + std::cout << xs[j][i] << " "; + } + } + std::cout << std::endl; + } + + void steps(int num_steps) { + for (int i = 0; i < num_steps; i++) step(); + } + + Point get_state(int index) { + return xs[index]; + } + + void set_state(int index, Point p) { + xs[index] = p; + } +}; + + +#endif diff --git a/src/volesti/include/ode_solvers/leapfrog.hpp b/src/volesti/include/ode_solvers/leapfrog.hpp new file mode 100644 index 00000000..b7f2724b --- /dev/null +++ b/src/volesti/include/ode_solvers/leapfrog.hpp @@ -0,0 +1,221 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ODE_SOLVERS_LEAPFROG_HPP +#define ODE_SOLVERS_LEAPFROG_HPP + +template < +typename Point, +typename NT, +typename Polytope, +typename func +> +struct LeapfrogODESolver { + + struct update_parameters +{ + update_parameters() + : facet_prev(0), hit_ball(false), inner_vi_ak(0.0), ball_inner_norm(0.0) + {} + int facet_prev; + bool hit_ball; + double inner_vi_ak; + double ball_inner_norm; +}; + + update_parameters _update_parameters; + + typedef std::vector pts; + + typedef Eigen::Matrix MT; + + typedef std::vector bounds; + typedef typename Polytope::VT VT; + + unsigned int dim; + + std::vector Ar, Av; + std::vector lambda_prev; + + NT eta; + NT eta0; + NT t; + NT dl = 0.995; + + func F; + bounds Ks; + + // Contains the sub-states + pts xs; + pts xs_prev; + + Point grad_x; + + MT _AA; + + std::pair pbpair; + + unsigned long long num_reflections = 0; + unsigned long long num_steps = 0; + + bool adaptive = true; + + LeapfrogODESolver(NT initial_time, NT step, pts initial_state, func oracle, bounds boundaries, bool adaptive_=true) : + eta(step), eta0(step), t(initial_time), F(oracle), Ks(boundaries), xs(initial_state), adaptive(adaptive_) { + dim = xs[0].dimension(); + _update_parameters = update_parameters(); + grad_x.set_dimension(dim); + initialize(); + }; + + + void initialize() { + for (unsigned int i = 0; i < xs.size(); i++) { + VT ar, av; + if (Ks[i] != NULL) { + ar.setZero(Ks[i]->num_of_hyperplanes()); + ar = Ks[i]->get_mat() * xs[i].getCoefficients(); + av.setZero(Ks[i]->num_of_hyperplanes()); + _AA.noalias() = Ks[i]->get_mat() * Ks[i]->get_mat().transpose(); + Ks[i]->resetFlags(); + } + Ar.push_back(ar); + Av.push_back(av); + lambda_prev.push_back(NT(0)); + } + } + + void disable_adaptive() { + adaptive = false; + } + + void enable_adaptive() { + adaptive = true; + } + + void step(int k, bool accepted) { + num_steps++; + if (adaptive) eta = (eta0 * num_steps) / (num_steps + num_reflections); + + xs_prev = xs; + unsigned int x_index, v_index, it; + t += eta; + Point y; + for (unsigned int i = 1; i < xs.size(); i += 2) { + + x_index = i - 1; + v_index = i; + + // v' <- v + eta / 2 F(x) + if (k == 0 && !accepted) { + grad_x = F(v_index, xs_prev, t); + } + xs[v_index] += (eta / 2) * grad_x; + + // x <- x + eta v' + y = xs[v_index]; + + if (Ks[x_index] == NULL) { + xs[x_index] += eta*y; + } + else { + // Find intersection (assuming a line trajectory) between x and y + bool step_completed = false; + NT T = eta; + if (k == 0 && !accepted) { + Ar[x_index] = Ks[x_index]->get_mat() * xs_prev[x_index].getCoefficients(); + lambda_prev[x_index] = 0.0; + Ks[x_index]->resetFlags(); + } + + pbpair = Ks[x_index]->line_positive_intersect(xs_prev[x_index], y, Ar[x_index], Av[x_index], + lambda_prev[x_index], _update_parameters); + if (T <= pbpair.first) { + xs[x_index] = xs_prev[x_index] + T * y; + xs[v_index] = y; + lambda_prev[x_index] = T; + Ks[x_index]->update_position_internal(T); + step_completed = true; + } + + if (!step_completed) { + lambda_prev[x_index] = dl * pbpair.first; + xs_prev[x_index] = xs_prev[x_index] + lambda_prev[x_index] * y; + T -= lambda_prev[x_index]; + Ks[x_index]->update_position_internal(lambda_prev[x_index]); + Ks[x_index]->compute_reflection(y, xs_prev[x_index], _update_parameters); + num_reflections++; + + while (true) + { + pbpair = Ks[x_index]->line_positive_intersect(xs_prev[x_index], y, Ar[x_index], Av[x_index], + lambda_prev[x_index], _AA, _update_parameters); + if (T <= pbpair.first) { + xs[x_index] = xs_prev[x_index] + T * y; + xs[v_index] = y; + lambda_prev[x_index] = T; + Ks[x_index]->update_position_internal(T); + break; + } + lambda_prev[x_index] = dl * pbpair.first; + xs_prev[x_index] = xs_prev[x_index] + lambda_prev[x_index] * y; + T -= lambda_prev[x_index]; + Ks[x_index]->update_position_internal(lambda_prev[x_index]); + Ks[x_index]->compute_reflection(y, xs_prev[x_index], _update_parameters); + num_reflections++; + } + } + } + + // tilde v <- v + eta / 2 F(tilde x) + grad_x = F(v_index, xs, t); + xs[v_index] += (eta / 2) * grad_x; + } + + } + + void print_state() { + for (int j = 0; j < xs.size(); j ++) { + for (unsigned int i = 0; i < xs[j].dimension(); i++) { + std::cout << xs[j][i] << " "; + } + } + std::cout << std::endl; + } + + void steps(int num_steps, bool accepted) { + for (int i = 0; i < num_steps; i++) step(i, accepted); + } + + Point get_state(int index) { + return xs[index]; + } + + void set_state(int index, Point p) { + xs[index] = p; + } + + NT get_eta() { + return eta; + } + + void set_eta(NT &eta_) { + eta = eta_; + eta0 = eta_; + } + + bounds get_bounds() { + return Ks; + } + +}; + + +#endif diff --git a/src/volesti/include/ode_solvers/ode_solvers.hpp b/src/volesti/include/ode_solvers/ode_solvers.hpp new file mode 100644 index 00000000..68e730fc --- /dev/null +++ b/src/volesti/include/ode_solvers/ode_solvers.hpp @@ -0,0 +1,64 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +/* + Each solver solves an ODE of the form d^k x/dt^k = F(x, t) + The vector x can be written as x = (x_1, ...., x_n) where each + x_i is conditioned on a convex polytope K_i with bounary reflections + on the boundary of K_i for each 1 <= i <= n. Each sub-state is determined + by the oracle function F_i(x, t) which has range over x = (x_1, ..., x_n). + + Some general parameter notations for the solvers + + Templates + 1. Polytope: The polytope type (H-Polytope, V-Polytope, Z-Polytope) K_i + 2. NT: Number type (double, float) + 3. Point: Point type + 4. func: Function type for the oracle collection K_i + 5. bfunc: Basis function type (e.g. polynomial) for non-linear trajectory + methods (such as collocation) + + Variables + 1. Ks: A vector of domains K_i for 1 <= i <= n + 2. xs: A vector of substates x_i for 1 <= i <= n + 3. xs_prev: The previous state of the solver + 4. F: A functor of oracles F_i for 1 <= i <= n + 5. eta: Step size + 6. t: Temporal variable + + TODO: + 1. Change datastructure of boundaries (std::vector) + +*/ + +#include +#include +#include +#include + +#include "ode_solvers/euler.hpp" +#include "ode_solvers/implicit_midpoint.hpp" +#include "ode_solvers/runge_kutta.hpp" +#include "ode_solvers/leapfrog.hpp" +#include "ode_solvers/richardson_extrapolation.hpp" +#include "ode_solvers/oracle_functors.hpp" +#include "ode_solvers/randomized_midpoint.hpp" +#include "ode_solvers/generalized_leapfrog.hpp" + +#ifndef DISABLE_NLP_ORACLES +#include "ode_solvers/collocation.hpp" +#include "ode_solvers/basis.hpp" +#include "ode_solvers/integral_collocation.hpp" +#endif + +#ifndef ODE_SOLVERS_ODE_SOLVERS_HPP +#define ODE_SOLVERS_ODE_SOLVERS_HPP + +#endif diff --git a/src/volesti/include/ode_solvers/oracle_functors.hpp b/src/volesti/include/ode_solvers/oracle_functors.hpp new file mode 100644 index 00000000..35fc8887 --- /dev/null +++ b/src/volesti/include/ode_solvers/oracle_functors.hpp @@ -0,0 +1,397 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ODE_SOLVERS_ORACLE_FUNCTORS_HPP +#define ODE_SOLVERS_ORACLE_FUNCTORS_HPP + +struct OptimizationFunctor { + template < + typename NT, + typename Functor, + typename GradFunctor + > + struct parameters { + NT T; // Temperature + unsigned int dim; // Dimension + Functor f; + GradFunctor neg_grad_f; + NT L; + NT m; + NT kappa; + unsigned int order; + + parameters( + NT T_, + unsigned int dim_, + Functor f_, + GradFunctor neg_grad_f_) : + T(T_), + dim(dim_), + f(f_), + neg_grad_f(neg_grad_f_), + L(1), + m(1), + kappa(1), + order(2) + {}; + + void update_temperature(NT k, NT l) { + T = T * pow(1.0 + 1.0 / pow(dim, k), l); + } + }; + + template + < + typename Point, + typename Functor, + typename GradFunctor + > + struct GradientFunctor { + typedef typename Point::FT NT; + typedef std::vector pts; + + parameters ¶ms; + + GradientFunctor(parameters ¶ms_) : params(params_) {}; + + // The index i represents the state vector index + Point operator() (unsigned int const& i, pts const& xs, NT const& t) const { + if (i == params.order - 1) { + return params.neg_grad_f(i, xs, t) * params.T; // returns - a*x + } else { + return xs[i + 1]; // returns derivative + } + } + }; + + template + < + typename Point, + typename Functor, + typename GradFunctor + > + struct FunctionFunctor { + typedef typename Point::FT NT; + parameters ¶ms; + + FunctionFunctor(parameters ¶ms_) : params(params_) {}; + + NT operator() (Point const& x) const { + return params.f(x) * params.T; + } + }; +}; + +struct IsotropicQuadraticFunctor { + + // Holds function oracle and gradient oracle for the function 1/2 a ||x||^2 + template < + typename NT + > + struct parameters { + NT alpha; + unsigned int order; + NT L; // Lipschitz constant of gradient + NT m; // Strong-convexity parameter + NT kappa; // Condition number + + parameters() : + alpha(NT(1)), + order(2), + L(NT(1)), + m(NT(1)), + kappa(1) + {}; + + parameters( + NT alpha_, + unsigned int order_) : + alpha(alpha_), + order(order_), + L(alpha_), + m(alpha_), + kappa(1) + {}; + }; + + + template + < + typename Point + > + struct GradientFunctor { + typedef typename Point::FT NT; + typedef std::vector pts; + + parameters ¶ms; + + GradientFunctor(parameters ¶ms_) : params(params_) {}; + + // The index i represents the state vector index + Point operator() (unsigned int const& i, pts const& xs, NT const& t) const { + if (i == params.order - 1) { + return (-params.alpha) * xs[0]; // returns - a*x + } else { + return xs[i + 1]; // returns derivative + } + } + + Point operator()(Point const &x){ + Point y = (-params.alpha) * x; + return y; + } + }; + + + template + < + typename Point + > + struct FunctionFunctor { + typedef typename Point::FT NT; + + parameters ¶ms; + + FunctionFunctor(parameters ¶ms_) : params(params_) {}; + + NT operator() (Point const& x) const { + return 0.5 * params.alpha * x.dot(x); + } + }; + +}; + +struct IsotropicLinearFunctor { + + // Exponential Density + template < + typename NT + > + struct parameters { + NT alpha; + unsigned int order; + NT L; // Lipschitz constant of gradient + NT m; // Strong-convexity constant + NT kappa; // Condition number + + parameters() : + alpha(NT(1)), + order(1), + L(0), + m(0), + kappa(1) + {}; + + parameters(NT alpha_, unsigned int order_) : + alpha(alpha_), + order(order), + L(0), + m(0), + kappa(1) + {} + }; + + template + < + typename Point + > + struct GradientFunctor { + typedef typename Point::FT NT; + typedef std::vector pts; + + parameters ¶ms; + + GradientFunctor(parameters ¶ms_) : params(params_) {}; + + // The index i represents the state vector index + Point operator() (unsigned int const& i, pts const& xs, NT const& t) const { + if (i == params.order - 1) { + Point y = Point::all_ones(xs[0].dimension()); + y = (- params.alpha) * y; + return y; + } else { + return xs[i + 1]; // returns derivative + } + } + + }; + + template + < + typename Point + > + struct FunctionFunctor { + typedef typename Point::FT NT; + + parameters ¶ms; + + FunctionFunctor(parameters ¶ms_) : params(params_) {}; + + NT operator() (Point const& x) const { + return params.alpha * x.sum(); + } + + }; + +}; + + +struct ExponentialFunctor { + + // Sample from linear program c^T x (exponential density) + template < + typename NT, + typename Point + > + struct parameters { + unsigned int order; + NT L; // Lipschitz constant for gradient + NT m; // Strong convexity constant + NT kappa; // Condition number + Point c; // Coefficients of LP objective + NT a; // Inverse variance + + parameters(Point c_) : order(2), L(1), m(1), kappa(1), c(c_), a(1.0) {}; + parameters(Point c_, NT a_) : order(2), L(1), m(1), kappa(1), c(c_), a(a_) {}; + + }; + + template + < + typename Point + > + struct GradientFunctor { + typedef typename Point::FT NT; + typedef std::vector pts; + + parameters ¶ms; + + GradientFunctor(parameters ¶ms_) : params(params_) {}; + + // The index i represents the state vector index + Point operator() (unsigned int const& i, pts const& xs, NT const& t) const { + if (i == params.order - 1) { + Point y(params.c); + return (-params.a) * y; + } else { + return xs[i + 1]; // returns derivative + } + } + + }; + + template + < + typename Point + > + struct FunctionFunctor { + typedef typename Point::FT NT; + + parameters ¶ms; + + FunctionFunctor(parameters ¶ms_) : params(params_) {}; + + // The index i represents the state vector index + NT operator() (Point const& x) const { + return params.a * x.dot(params.c); + } + + }; + +}; + + +struct GaussianFunctor { + + template < + typename NT, + typename Point + > + struct parameters { + Point x0; + NT a; + NT eta; + unsigned int order; + NT L; // Lipschitz constant for gradient + NT m; // Strong convexity constant + NT kappa; // Condition number + + parameters(Point x0_, NT a_, NT eta_) : + x0(x0_), a(a_), eta(eta_), order(2), L(2 * a_), m(2 * a_), kappa(1) {}; + + }; + + template + < + typename Point + > + struct GradientFunctor { + typedef typename Point::FT NT; + typedef std::vector pts; + + parameters ¶ms; + + GradientFunctor(parameters ¶ms_) : params(params_) {}; + + // The index i represents the state vector index + Point operator() (unsigned int const& i, pts const& xs, NT const& t) const { + if (i == params.order - 1) { + Point y = (-2.0 * params.a) * (xs[0] - params.x0); + return y; + } else { + return xs[i + 1]; // returns derivative + } + } + Point operator()(Point const&x){ + Point y = (-2.0 * params.a) * (x - params.x0); + return y; + } + }; + + template + < + typename Point + > + struct FunctionFunctor { + typedef typename Point::FT NT; + + parameters ¶ms; + + FunctionFunctor(parameters ¶ms_) : params(params_) {}; + + // The index i represents the state vector index + NT operator() (Point const& x) const { + Point y = x - params.x0; + return params.a * y.dot(y); + } + + }; + + template +< + typename Point +> +struct HessianFunctor { + typedef typename Point::FT NT; + + parameters ¶ms; + + HessianFunctor(parameters ¶ms_) : params(params_) {}; + + // The index i represents the state vector index + Point operator() (Point const& x) const { + return (2.0 * params.a) * Point::all_ones(x.dimension()); + } + +}; + +}; + +#endif diff --git a/src/volesti/include/ode_solvers/oracle_functors_rcpp.hpp b/src/volesti/include/ode_solvers/oracle_functors_rcpp.hpp new file mode 100644 index 00000000..853dcb3c --- /dev/null +++ b/src/volesti/include/ode_solvers/oracle_functors_rcpp.hpp @@ -0,0 +1,159 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ODE_SOLVERS_ORACLE_FUNCTORS_RCPP_HPP +#define ODE_SOLVERS_ORACLE_FUNCTORS_RCPP_HPP + +enum ode_solvers { + no_solver, + leapfrog, + euler, + runge_kutta, + richardson, + collocation +}; + +// Holds Oracle Functor that wraps an R function via RCpp +// The R function is provided as an Rcpp::Function object +// The functor uses Rcpp::as and Rcpp::wrap to do the conversion, +// call the oracle, and convert the results back to C++ +struct RcppFunctor { + + template < + typename NT + > + struct parameters { + NT L; // Lipschitz constant of gradient + NT m; // Strong-convexity parameter + NT eta; // Step-size (if defined by user) + NT kappa; // Condition number + unsigned int order; // Order of ODE functor + + parameters( + NT L_, + NT m_, + NT eta_, + unsigned int order_=2 + ) : + L(L_), + m(m_), + eta(eta_), + kappa(L_ / m_), + order(order_) + {} + }; + + // Log-probability gradient functor + template + < + typename Point + > + struct GradientFunctor { + typedef typename Point::FT NT; + typedef typename Point::Coeff VT; + typedef std::vector pts; + + parameters params; + Rcpp::Function neg_grad_f; // Negative gradient as an Rcpp::Function + bool negate; + + GradientFunctor( + parameters params_, + Rcpp::Function neg_grad_f_, + bool negate_=true): + params(params_), + neg_grad_f(neg_grad_f_), + negate(negate_) + {}; + + // The index i represents the state vector index + Point operator() (unsigned int const& i, pts const& xs, NT const& t) const { + if (i == params.order - 1) { + // Convert point to Rcpp::NumericMatrix + + VT y = Rcpp::as(neg_grad_f(Rcpp::wrap(xs[0].getCoefficients()))); + + Point z(y); + + if (negate) z = (-1.0) * z; + + // Return result as Point + return z; + } else { + return xs[i + 1]; // returns derivative + } + } + + Point operator() (Point const& x) const { + VT y = Rcpp::as(neg_grad_f(Rcpp::wrap(x.getCoefficients()))); + + Point z(y); + + if (negate) z = (-1.0) * z; + + // Return result as Point + return z; + } + + }; + + // Negative log-probability functor + template + < + typename Point + > + struct FunctionFunctor { + typedef typename Point::FT NT; + typedef typename Point::Coeff VT; + + parameters params; + Rcpp::Function negative_logprob; + + FunctionFunctor( + parameters params_, + Rcpp::Function negative_logprob_) : + params(params_), + negative_logprob(negative_logprob_) + {}; + + NT operator() (Point const& x) const { + return Rcpp::as(negative_logprob(Rcpp::wrap(x.getCoefficients()))); + } + + }; + + // Log-probability hessian functor + template + < + typename Point + > + struct HessianFunctor { + typedef typename Point::FT NT; + typedef typename Point::Coeff VT; + + parameters params; + Rcpp::Function hessian; // Negative hessian as an Rcpp::Function + + HessianFunctor( + parameters params_, + Rcpp::Function hessian_) : + params(params_), + hessian(hessian_) + {}; + + Point operator() (Point const& x) const { + VT y= Rcpp::as(hessian(Rcpp::wrap(x.getCoefficients()))); + return Point(y); + } + + }; +}; + +#endif diff --git a/src/volesti/include/ode_solvers/randomized_midpoint.hpp b/src/volesti/include/ode_solvers/randomized_midpoint.hpp new file mode 100644 index 00000000..d2a58e3c --- /dev/null +++ b/src/volesti/include/ode_solvers/randomized_midpoint.hpp @@ -0,0 +1,226 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// Based on https://papers.nips.cc/paper/8483-the-randomized-midpoint-method-for-log-concave-sampling.pdf + +#ifndef ODE_SOLVERS_RANDOMIZED_MIDPOINT_HPP +#define ODE_SOLVERS_RANDOMIZED_MIDPOINT_HPP + +template < +typename Point, +typename NT, +typename Polytope, +typename func, +typename RandomNumberGenerator +> +struct RandomizedMipointSDESolver { + + typedef std::vector pts; + + typedef std::vector bounds; + typedef typename Polytope::VT VT; + typedef typename Polytope::MT MT; + + unsigned int dim; + + VT Ar, Av; + + NT eta; + NT t; + NT u; + + func F; + bounds Ks; + + // Contains the sub-states + pts xs; + pts xs_prev; + Point y, w, z; + + Point W1, W2, W3; + VT Y, Z; + MT C, L, D, M; + + std::pair pbpair; + + RandomizedMipointSDESolver(NT initial_time, NT step, pts initial_state, func oracle, bounds boundaries, NT u_=NT(1)) : + eta(step), t(initial_time), u(u_), F(oracle), Ks(boundaries), xs(initial_state) { + dim = xs[0].dimension(); + Y.resize(2 * dim); + Z.resize(2 * dim); + C.resize(2 * dim, 2 * dim); + L.resize(2 * dim, 2 * dim); + D.resize(2 * dim, 2 * dim); + M.resize(2 * dim, 2 * dim); + W1 = Point(dim); + W2 = Point(dim); + W3 = Point(dim); + + for (unsigned int i = 0; i < 2 * dim; i++) { + Y(i) = NT(0); + Z(i) = NT(0); + for (unsigned int j = 0; j < 2 * dim; j++) { + C(i, j) = NT(0); + D(i, j) = NT(0); + } + } + }; + + void step(RandomNumberGenerator &rng) { + xs_prev = xs; + unsigned int x_index, v_index, it; + NT a; + t += eta; + for (unsigned int i = 1; i < xs.size(); i += 2) { + + a = rng.sample_urdist(); + + x_index = i - 1; + v_index = i; + + calculate_Ws(a, rng); + + z = xs_prev[x_index]; + z = z + (0.5 * (1 - exp(- 2 * a * eta))) * xs_prev[v_index]; + z = z - (0.5 * u * (a * eta - 0.5 * (1 - exp(-2 * a * eta)))) * F(v_index, xs_prev, t); + z = z + sqrt(u) * W1; + + w = xs_prev[x_index]; + xs[x_index] = xs_prev[x_index]; + y = (-1.0) * xs_prev[x_index]; + xs_prev[x_index] = z; + xs[x_index] = xs[x_index] + (0.5 * (1 - exp(-2 * eta))) * xs[v_index]; + xs[x_index] = xs[x_index] + (0.5 * u * eta * (1 - exp(-2 * (eta - a * eta)))) * F(v_index, xs_prev, t); + xs[x_index] = xs[x_index] + sqrt(u) * W2; + + xs[v_index] = exp(- 2 * eta) * xs_prev[v_index]; + xs[v_index] = xs[v_index] + u * eta * exp(-2 * (eta - a * eta)) * F(v_index, xs_prev, t); + xs[v_index] = xs[v_index] + (2 * sqrt(u)) * W3; + + xs_prev[x_index] = w; + + y = y + xs[x_index]; + + if (Ks[x_index] == NULL) { + xs[x_index] = xs_prev[x_index] + y; + } + else { + // Find intersection (assuming a line trajectory) between x and y + do { + + pbpair = Ks[x_index]->line_positive_intersect(xs_prev[x_index], y, Ar, Av); + + if (pbpair.first >= 0 && pbpair.first <= 1) { + xs_prev[x_index] += (pbpair.first * 0.95) * y; + Ks[x_index]->compute_reflection(y, xs_prev[x_index], pbpair.second); + xs[x_index] = xs_prev[x_index] + y; + + // Reflect velocity + Ks[x_index]->compute_reflection(xs[v_index], xs[x_index], pbpair.second); + } + else { + xs[x_index] = xs_prev[x_index] + y; + } + } while (!Ks[x_index]->is_in(xs[x_index])); + } + + } + } + + void print_state() { + for (int j = 0; j < xs.size(); j ++) { + for (unsigned int i = 0; i < xs[j].dimension(); i++) { + std::cout << xs[j][i] << " "; + } + } + std::cout << std::endl; + } + + void steps(int num_steps, RandomNumberGenerator &rng) { + for (int i = 0; i < num_steps; i++) step(rng); + } + + Point get_state(int index) { + return xs[index]; + } + + void set_state(int index, Point p) { + xs[index] = p; + } + + void calculate_Ws(NT &a, RandomNumberGenerator &rng) { + // Initialize matrices to zero + Y = 0 * Y; + Z = 0 * Z; + C = 0 * C; + D = 0 * D; + + // Helper variables + NT temp_y, temp_z, h1, h2, g1, g2; + + // Variance of variable G1, G2 + temp_y = 0.25 * (exp(4 * a * eta) - 1); + temp_z = 0.25 * (exp(4 * eta) - exp(4 * a * eta)); + for (unsigned int i = 0; i < dim; i++) { + C(i, i) = temp_y; + D(i, i) = temp_z; + } + + // Variance of H1, H2 + temp_y = a * eta; + temp_z = (eta - a * eta); + for (unsigned int i = dim; i < 2 * dim; i++) { + C(i, i) = temp_y; + D(i, i) = temp_z; + } + + // Covariances of Hi, Gi + temp_y = 0.5 * (exp(2 * a * eta) - 1); + temp_z = 0.5 * (exp(2 * eta) - exp(2 * a * eta)); + for (unsigned int i = 0; i < dim; i++) { + C(i, i + dim) = temp_y; + C(i + dim, i) = temp_y; + D(i, i + dim) = temp_z; + D(i + dim, i) = temp_z; + } + + // Cholesky Decomposition + Eigen::LLT lltofC(C); + L = lltofC.matrixL(); + Eigen::LLT lltofD(D); + M = lltofD.matrixL(); + + // Normal Vectors + for (unsigned int i = 0; i < 2 * dim; i++) { + Y(i) = rng.sample_ndist(); + Z(i) = rng.sample_ndist(); + } + + // Transformed vectors + Y = L * Y; + Z = M * Z; + + // Calculate Brownian Integrals W1, W2, W3 (Appendix A) + for (int i = 0; i < dim; i++) { + h1 = Y(i + dim); + g1 = Y(i); + h2 = Z(i + dim); + g2 = Z(i); + W1.set_coord(i, h1 - exp(- 2 * a * eta) * g1); + W2.set_coord(i, h1 + h2 - exp(- 2 * a * eta) * (g1 + g2)); + W3.set_coord(i, exp(- 2 * eta) * (g1 + g2)); + } + + } + +}; + + +#endif diff --git a/src/volesti/include/ode_solvers/richardson_extrapolation.hpp b/src/volesti/include/ode_solvers/richardson_extrapolation.hpp new file mode 100644 index 00000000..5d6eb887 --- /dev/null +++ b/src/volesti/include/ode_solvers/richardson_extrapolation.hpp @@ -0,0 +1,188 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ODE_SOLVERS_RICHARDSON_EXTRAPOLATION_HPP +#define ODE_SOLVERS_RICHARDSON_EXTRAPOLATION_HPP + + +template < + typename Point, + typename NT, + typename Polytope, + typename func +> +struct RichardsonExtrapolationODESolver { + + typedef std::vector pts; + + typedef std::vector bounds; + typedef std::vector coeffs; + typedef std::vector scoeffs; + typedef std::vector ptsv; + typedef std::vector ptsm; + + typedef typename Polytope::VT VT; + + unsigned int dim; + const unsigned int MAX_TRIES = 5; + + NT eta, eta_temp; + NT t, t_prev; + NT tol = 1e-7; + NT error = NT(-1); + NT den; + Point num, y; + VT Ar, Av; + + RKODESolver *solver; + + func F; + bounds Ks; + + // Contains the sub-states + pts xs; + pts xs_prev; + + ptsm A; + bool flag; + + // Previous state boundary point + Point x_prev_bound; + + // Previous state boundary facet + int prev_facet = -1; + + RichardsonExtrapolationODESolver(NT initial_time, NT step, pts initial_state, + func oracle, bounds boundaries) : + eta(step), t(initial_time), F(oracle), Ks(boundaries), xs(initial_state) { + dim = xs[0].dimension(); + A = ptsm(MAX_TRIES+1, ptsv(MAX_TRIES+1, pts(xs.size()))); + initialize_solver(); + }; + + void initialize_solver() { + solver = new RKODESolver(t, eta, xs, F, bounds{NULL}); + } + + void step(int k, bool accepted) { + xs_prev = xs; + eta_temp = eta; + flag = true; + + // Use RK4 solver + solver->xs = xs_prev; + solver->t = t; + solver->eta = eta_temp; + solver->steps(1, false); + A[1][1] = solver->xs; + + + for (unsigned int j = 1; j <= MAX_TRIES-1; j++) { + // Reduce step size by two + eta_temp /= 2; + + // Find solution with half stepsize and twice the num of steps + solver->xs = xs_prev; + solver->t = t; + solver->eta = eta_temp; + solver->steps(2*j, false); + A[j+1][1] = solver->xs; + + // Perform Richardson extrapolation + for (unsigned int k = 1; k <= j; k++) { + den = 1.0 * ((4 << k) - 1); + for (unsigned int i = 0; i < xs.size(); i++) { + num = (1.0 * (4 << k)) * A[j+1][k][i]; + num = num - A[j][k][i]; + A[j+1][k+1][i] = (1 / den) * num; + } + } + + error = NT(-1); + + for (unsigned int i = 0; i < xs.size(); i++) { + y = A[j+1][j+1][i] - A[j][j][i]; + if (sqrt(y.dot(y)) > error) error = sqrt(y.dot(y)); + } + + if (error < tol || j == MAX_TRIES - 1) { + for (unsigned int i = 0; i < xs.size(); i++) { + y = A[j+1][j+1][i] - xs[i]; + + if (Ks[i] == NULL) { + xs[i] = xs_prev[i] + y; + if (prev_facet != -1 && i > 0) { + Ks[i-1]->compute_reflection(xs[i], x_prev_bound, prev_facet); + } + prev_facet = -1; + } + else { + + // Find intersection (assuming a line trajectory) between x and y + do { + // Find line intersection between xs[i] (new position) and y + std::pair pbpair = Ks[i]->line_positive_intersect(xs_prev[i], y, Ar, Av); + // If point is outside it would yield a negative param + if (pbpair.first >= 0 && pbpair.first <= 1) { + + xs_prev[i] += (pbpair.first * 0.95) * y; + + // Update facet for reflection of derivative + prev_facet = pbpair.second; + x_prev_bound = xs_prev[i]; + + // Reflect ray y on the boundary point y now is the reflected ray + Ks[i]->compute_reflection(y, xs_prev[i], pbpair.second); + // Add it to the existing (boundary) point and repeat + xs[i] = xs_prev[i] + y; + + } + else { + prev_facet = -1; + xs[i] = xs_prev[i] + y; + } + } while (!Ks[i]->is_in(xs[i])); + + } + + } + break; + } + } + + t += eta; + + } + + void print_state() { + for (int j = 0; j < xs.size(); j++) { + for (unsigned int i = 0; i < xs[j].dimension(); i++) { + std::cout << xs[j][i] << " "; + } + } + std::cout << std::endl; + } + + void steps(int num_steps, bool accepted) { + for (int i = 0; i < num_steps; i++) step(i, accepted); + } + + Point get_state(int index) { + return xs[index]; + } + + void set_state(int index, Point p) { + xs[index] = p; + } + +}; + + +#endif diff --git a/src/volesti/include/ode_solvers/runge_kutta.hpp b/src/volesti/include/ode_solvers/runge_kutta.hpp new file mode 100644 index 00000000..5d8793ca --- /dev/null +++ b/src/volesti/include/ode_solvers/runge_kutta.hpp @@ -0,0 +1,172 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ODE_SOLVERS_RUNGE_KUTTA_H +#define ODE_SOLVERS_RUNGE_KUTTA_H + + +template < + typename Point, + typename NT, + typename Polytope, + typename func +> +struct RKODESolver { + + typedef std::vector pts; + typedef std::vector ptsv; + + typedef std::vector bounds; + typedef std::vector coeffs; + typedef std::vector scoeffs; + + typedef typename Polytope::VT VT; + + unsigned int dim; + + NT eta; + NT t, t_prev; + + VT Ar, Av; + + func F; + bounds Ks; + + // Contains the sub-states + pts xs; + ptsv ks; + Point y; + + // Previous state boundary point + Point x_prev_bound; + + // Previous state boundary facet + int prev_facet = -1; + + scoeffs as; + coeffs cs, bs; + + + RKODESolver(NT initial_time, NT step, pts initial_state, func oracle, + bounds boundaries) : + eta(step), t(initial_time), F(oracle), Ks(boundaries), xs(initial_state) { + dim = xs[0].dimension(); + // If no coefficients are given the RK4 method is assumed + cs = coeffs{0, 0.5, 0.5, 1}; + bs = coeffs{1.0/6, 1.0/3, 1.0/3, 1.0/6}; + as = scoeffs{ + coeffs{}, + coeffs{0.5}, + coeffs{0, 0.5}, + coeffs{0, 0, 1.0} + }; + }; + + + // RKODESolver(NT initial_time, NT step, pts initial_state, func oracle, + // bounds boundaries, scoeffs a_coeffs, coeffs b_coeffs, coeffs c_coeffs) : + // t(initial_time), xs(initial_state), F(oracle), eta(step), Ks(boundaries), + // as(a_coeffs), bs(b_coeffs), cs(c_coeffs) { + // dim = xs[0].dimension(); + // }; + + unsigned int order() { + return bs.size(); + } + + void step(int k, bool accepted) { + ks = ptsv(order(), xs); + t_prev = t; + + for (unsigned int ord = 0; ord < order(); ord++) { + // Initialize t to previous + t = t_prev + cs[ord] * eta; + + // Initialize ks to previous solution we use + // Initialize argument + for (int j = 0; j < ord; j++) { + for (int r = 0; r < xs.size(); r++) { + y = ks[j][r]; + y = (eta * as[ord][j]) * y; + ks[ord][r] = ks[ord][r] + y; + } + } + + for (unsigned int i = 0; i < xs.size(); i++) { + // Calculate k_i s + y = F(i,ks[ord], t); + ks[ord][i] = y; + y = (eta * bs[i]) * y; + + if (Ks[i] == NULL) { + xs[i] = xs[i] + y; + if (prev_facet != -1 && i > 0) + Ks[i-1]->compute_reflection(xs[i], x_prev_bound, prev_facet); + prev_facet = -1; + } + else { + + // Find intersection (assuming a line trajectory) between x and y + do { + // Find line intersection between xs[i] (new position) and y + std::pair pbpair = Ks[i]->line_positive_intersect(xs[i], y, Ar, Av); + // If point is outside it would yield a negative param + if (pbpair.first >= 0 && pbpair.first <= 1) { + // Advance to point on the boundary + xs[i] += (pbpair.first * 0.99) * y; + + // Update facet for reflection of derivative + prev_facet = pbpair.second; + x_prev_bound = xs[i]; + + // Reflect ray y on the boundary point y now is the reflected ray + Ks[i]->compute_reflection(y, xs[i], pbpair.second); + // Add it to the existing (boundary) point and repeat + xs[i] += y; + + } + else { + prev_facet = -1; + xs[i] += y; + } + } while (!Ks[i]->is_in(xs[i])); + + } + + } + } + + } + + void print_state() { + for (int j = 0; j < xs.size(); j++) { + for (unsigned int i = 0; i < xs[j].dimension(); i++) { + std::cout << xs[j][i] << " "; + } + } + std::cout << std::endl; + } + + void steps(int num_steps, bool accepted) { + for (int i = 0; i < num_steps; i++) step(i, accepted); + } + + Point get_state(int index) { + return xs[index]; + } + + void set_state(int index, Point p) { + xs[index] = p; + } + +}; + + +#endif diff --git a/src/volesti/include/optimization/simulated_annealing.hpp b/src/volesti/include/optimization/simulated_annealing.hpp new file mode 100644 index 00000000..77e15b12 --- /dev/null +++ b/src/volesti/include/optimization/simulated_annealing.hpp @@ -0,0 +1,152 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLESTI_SIMULATED_ANNEALING_HPP +#define VOLESTI_SIMULATED_ANNEALING_HPP + +#include + +#include "optimization/sliding_window.hpp" +#include "random_walks/boltzmann_hmc_walk.hpp" + + +/// A magic number! +/// when estimating the diameter of the spectrahedron, +/// sample 20 + sqrt(dimension) points to estimate it +#define CONSTANT_1 20 + +/// Holds parameters of the algorithm +/// \tparam Point Point Type +template +struct SimulatedAnnealingSettings { + /// The numeric type + typedef typename Point::FT NT; + + /// Desired accuracy (relative error) + NT error; + /// The walk length of the HMC random walk + int walkLength; + /// A bound to the number of steps; if negative it is unbounded + int maxNumSteps; + + /// Starting from an initial temperature, at each step it will decrease by a factor of + /// \[ 1 - 1 / (dimension^k) \]. Default is 0.5 + NT k; + + SimulatedAnnealingSettings(NT const error, int const walkLength = 1, int const maxNumSteps = -1, NT const k = 0.5) : error(error), + walkLength(walkLength), maxNumSteps(maxNumSteps), k(k) {} +}; + + +/// Simulated Annealing algorithm for a semidefinite program +/// Minimize \[ c^T x \], s.t. LMI(x) <= 0 +/// \param[in] spectrahedron A spectrahedron described by a linear matrix inequality +/// \param[in] objectiveFunction The function we minimize +/// \param[in] settings Parameters of the algorithm +/// \param[in] interiorPoint An initial feasible solution to start the algorithm +/// \param[out] solution The vector minimizing the objective function +/// \param[in] verbose True to print messages. Default is false +/// \return The best approximation to the optimal solution +template +double solve_sdp(_Spectrahedron & spectrahedron, Point const & objectiveFunction, _Settings const & settings, + Point const & interiorPoint, Point& solution, bool verbose = false) { + + // fetch the data types we will use + typedef typename _Spectrahedron::NT NT; + typedef typename _Spectrahedron::MT MT; + typedef typename _Spectrahedron::VT VT; + typedef BoostRandomNumberGenerator RNGType; + typedef BoltzmannHMCWalk::Walk<_Spectrahedron, RNGType > HMC; + + // the algorithm requires the objective function to be normalized + // we will need to remember the norm + VT _objectiveFunctionNormed = objectiveFunction.getCoefficients(); + NT objectiveFunctionNorm = _objectiveFunctionNormed.norm(); + _objectiveFunctionNormed.normalize(); + Point objectiveFunctionNormed = Point(_objectiveFunctionNormed); + + RNGType rng(spectrahedron.dimension()); + + // Estimate the diameter of the spectrahedron + // needed for the random walk and for the simulated annealing algorithm + NT diameter = spectrahedron.estimateDiameter(CONSTANT_1 + std::sqrt(spectrahedron.dimension()), interiorPoint, rng); + + /******** initialization *********/ + solution = interiorPoint; + // the minimum till last iteration + NT currentMin = objectiveFunction.dot(solution); + int stepsCount = 0; + // initial temperature must be the diameter of the body + NT temperature = diameter; + // after each iteration, temperature = temperature * tempDecreaseFactor + NT tempDecreaseFactor = 1.0 - static_cast(1.0 / std::pow(spectrahedron.dimension(), settings.k)); + + // initialize random walk; + typename HMC::Settings hmc_settings = typename HMC::Settings(settings.walkLength, rng, objectiveFunction, temperature, diameter); + HMC hmcRandomWalk = HMC(hmc_settings); + NT previous_min = objectiveFunction.dot(solution); + + /******** solve *********/ + // if settings.maxNumSteps is negative there is no + // bound to the number of steps - stop + // when desired relative error is achieved + while (stepsCount < settings.maxNumSteps || settings.maxNumSteps < 0) { + + // sample one point with current temperature + std::list randPoints; + + // get a sample under the Boltzmann distribution + // using the HMC random walk + while (1) { + hmcRandomWalk.apply(spectrahedron, solution, settings.walkLength, randPoints); + + // if the sampled point is not inside the spectrahedron (error in boundary oracle), + // get a new one + if (spectrahedron.isExterior(spectrahedron.get_C())) { + if (verbose) std::cout << "Sampled point outside the spectrahedron.\n"; + randPoints.clear(); + spectrahedron.resetFlags(); + } + else { + // update values; + solution = randPoints.back(); + randPoints.clear(); + break; + } + } + + // update current value + currentMin = objectiveFunction.dot(solution); + ++stepsCount; + + // compute relative error + NT relError = relativeError(previous_min, currentMin); + previous_min = currentMin; + + if (verbose) + std::cout << "Step: " << stepsCount << ", Temperature: " << temperature << ", Min: " << currentMin + << ", Relative error: " << relError << "\n"; + + // check if we reached desired accuracy + if (relError < settings.error) + break; + + // decrease the temperature + temperature *= tempDecreaseFactor; + hmcRandomWalk.setTemperature(temperature); + + } /* while (stepsCount < settings.maxNumSteps || settings.maxNumSteps < 0) { */ + + // return the minimum w.r.t. the original objective function + return currentMin*objectiveFunctionNorm; +} + + + +#endif //VOLESTI_SIMULATED_ANNEALING_HPP diff --git a/src/volesti/include/optimization/sliding_window.hpp b/src/volesti/include/optimization/sliding_window.hpp new file mode 100644 index 00000000..daab137a --- /dev/null +++ b/src/volesti/include/optimization/sliding_window.hpp @@ -0,0 +1,66 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLESTI_SLIDING_WINDOW_HPP +#define VOLESTI_SLIDING_WINDOW_HPP + + +/// Computes the relative error +/// \tparam NT Numeric type +/// \param[in] approx The approximated value +/// \param[in] exact The exact value +/// \return The relative error +template +NT relativeError(NT approx, NT exact) { + return std::fabs((exact - approx) / exact); +} + + +/// A sliding window, which allows to get the relative error of approximations +/// \tparam NT Numeric type +template +class SlidingWindow { +public: + + /// The stored approximations + std::list approximations; + /// The size of the window + int windowSize; + /// The number of entries in list approximations + int numEntries; + + /// Constructor + /// \param[in] windowSize The size of the window + SlidingWindow(int windowSize) : windowSize(windowSize) { + numEntries = 0; + } + + /// Adds an approximation in the window + /// \param[in] approximation The new approximation + void push(NT approximation) { + // if window is full, remove the oldest value + if (numEntries >= windowSize) { + approximations.pop_back(); + } + else + numEntries++; + + approximation.push_front(approximation); + } + + /// \return The relative error between the youngest and oldest approximations + double getRelativeError() { + if (numEntries < windowSize) + return 1; + + return relativeError(approximations.back(), approximations.front()); + } +}; + +#endif //VOLESTI_SLIDING_WINDOW_HPP diff --git a/src/volesti/include/preprocess/crhmc/analytic_center.h b/src/volesti/include/preprocess/crhmc/analytic_center.h new file mode 100644 index 00000000..7124626b --- /dev/null +++ b/src/volesti/include/preprocess/crhmc/analytic_center.h @@ -0,0 +1,173 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" +#ifndef ANALYTIC_CENTER_H +#define ANALYTIC_CENTER_H +#include "Eigen/Eigen" +#include "PackedCSparse/PackedChol.h" +#include "preprocess/crhmc/crhmc_utils.h" +#include "preprocess/crhmc/opts.h" +#include +#include +#include +#ifndef SIMD_LEN +#define SIMD_LEN 0 +#endif +const size_t chol_k2 = (SIMD_LEN == 0) ? 1 : SIMD_LEN; + +/*This function computes the analytic center of the polytope*/ +//And detects additional constraint that need to be added +// x - It outputs the minimizer of min f(x) subjects to {Ax=b} +// C - detected constraint matrix +// If the domain ({Ax=b} intersect dom(f)) is not full dimensional in {Ax=b} +// because of the dom(f), the algorithm will detect the collapsed dimension +// and output the detected constraint C x = d +// d - detected constraint vector +template +std::tuple analytic_center(SpMat const &A, VT const &b, Polytope &f, Opts const &options, VT x = VT::Zero(0, 1)) +{ + using CholObj = typename Polytope::CholObj; + using Triple = typename Polytope::Triple; + using Tx = typename Polytope::Tx; + // initial conditions + int n = A.cols(); + int m = A.rows(); + if (x.rows() == 0 || !f.barrier.feasible(x)) + { + x = f.barrier.center; + } + + VT lambda = VT::Zero(n, 1); + int fullStep = 0; + NT tConst = 0; + NT primalErr = std::numeric_limits::max(); + NT dualErr = std::numeric_limits::max(); + NT primalErrMin = std::numeric_limits::max(); + NT primalFactor = 1; + NT dualFactor = 1 + b.norm(); + std::vector idx; + + CholObj solver = CholObj(transform_format(A)); + solver.accuracyThreshold = 0; + for (int iter = 0; iter < options.ipmMaxIter; iter++) + { + std::pair pair_analytic_oracle = f.analytic_center_oracle(x); + VT grad = pair_analytic_oracle.first; + VT hess = pair_analytic_oracle.second; + + // compute the residual + VT rx = lambda - grad; + VT rs = b - A * x; + + // check stagnation + primalErrMin = std::min(primalErr, primalErrMin); + primalErr = rx.norm() / primalFactor; + NT dualErrLast = dualErr; + dualErr = rs.norm() / dualFactor; + bool feasible = f.barrier.feasible(x); + //Compare the dual and primal error to the last and minimum so far + if ((dualErr > (1 - 0.9 * tConst) * dualErrLast) || + (primalErr > 10 * primalErrMin) || !feasible) + { + VT dist = f.barrier.boundary_distance(x); + NT th = options.ipmDistanceTol; + visit_lambda(dist, [&idx, th](double v, int i, int j) + { + if (v < th) + idx.push_back(i); }); + if (idx.size() > 0) + { + break; + } + } + + // compute the step direction + VT Hinv = hess.cwiseInverse(); + solver.decompose((Tx *)Hinv.data()); + VT out(m, 1); + solver.solve((Tx *)rs.data(), (Tx *)out.data()); + VT dr1 = A.transpose() * out; + VT in = A * Hinv.cwiseProduct(rx); + solver.solve((Tx *)in.data(), (Tx *)out.data()); + + VT dr2 = A.transpose() * out; + VT dx1 = Hinv.cwiseProduct(dr1); + VT dx2 = Hinv.cwiseProduct(rx - dr2); + + // compute the step size + VT dx = dx1 + dx2; + NT tGrad = std::min(f.barrier.step_size(x, dx), 1.0); + dx = dx1 + tGrad * dx2; + NT tConst = std::min(0.99 * f.barrier.step_size(x, dx), 1.0); + tGrad = tGrad * tConst; + + // make the step + x = x + tConst * dx; + lambda = lambda - dr2; + + if (!f.barrier.feasible(x)) + { + break; + } + //If we have have converged + if (tGrad == 1) + { + //do some more fullStep + fullStep = fullStep + 1; + if (fullStep > log(dualErr / options.ipmDualTol) && fullStep > options.min_convergence_steps) + { + break; + } + } + else + { + fullStep = 0; + } + } + SpMat C; + VT d; + if (idx.size() == 0) + { + VT dist = f.barrier.boundary_distance(x); + NT th = options.ipmDistanceTol; + visit_lambda(dist, [&idx, th](double v, int i, int j) + { + if (v < th) + idx.push_back(i); }); + } + + if (idx.size() > 0) + { + C.resize(idx.size(), n); + std::pair pboundary = f.barrier.boundary(x); + VT A_ = pboundary.first; + VT b_ = pboundary.second; + std::vector sparseIdx; + for (int i = 0; i < idx.size(); i++) + { + sparseIdx.push_back(Triple(i, idx[i], A_(idx[i]))); + } + C.setFromTriplets(sparseIdx.begin(), sparseIdx.end()); + d.resize(idx.size(), 1); + copy_indicies(d, b_, idx); + } + else + { + C = MT::Zero(0, n).sparseView(); + d = VT::Zero(0, 1); + } + return std::make_tuple(x, C, d); +} +#endif diff --git a/src/volesti/include/preprocess/crhmc/constraint_problem.h b/src/volesti/include/preprocess/crhmc/constraint_problem.h new file mode 100644 index 00000000..82931505 --- /dev/null +++ b/src/volesti/include/preprocess/crhmc/constraint_problem.h @@ -0,0 +1,81 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef CONSTRAINT_PROBLEM_H +#define CONSTRAINT_PROBLEM_H +#include "Eigen/Eigen" +/*Input structure: With this the user can define a polytope sampling problem*/ +template +class constraint_problem { +public: + using Type = typename Point::FT; + using PointType = Point; + using VT = Eigen::Matrix; + using MT = MatrixType; +private: + unsigned int num_vars; // num_vars of the original problem + MatrixType Aeq; // Matrix of coefficients for the equality constraints + VT beq; // Right hand side of the equality constraints + MatrixType Aineq; // Matrix of coefficients for the inequality constraints + VT bineq; // Right hand side of the inequality constraints + VT lb; // lb on the output coordinates preset to -1e9 + VT ub; // ub on the output coordinates preset to +1e9 + Type inf = 1e9; +public: + /*Constructors for different input instances*/ + constraint_problem(const int dim, MT const &Aeq_, VT const &beq_, MT const &Aineq_, + VT const &bineq_, VT const &lb_, VT const &ub_) + : num_vars(dim), Aeq(Aeq_), beq(beq_), Aineq(Aineq_), bineq(bineq_), + lb(lb_), ub(ub_) { + } + + constraint_problem(const int dim) { + num_vars = dim; + init(num_vars); + } + + + void init(int num_vars) { + Aineq.resize(0, num_vars); + Aeq.resize(0, num_vars); + bineq.resize(0, 1); + beq.resize(0, 1); + lb = -VT::Ones(num_vars) * inf; + ub = VT::Ones(num_vars) * inf; + } + void set_equality_constraints(MT const &Aeq_, VT const &beq_){ + Aeq = Aeq_; + beq = beq_; + } + void set_inequality_constraints(MT const &Aineq_, VT const &bineq_){ + Aineq = Aineq_; + bineq = bineq_; + } + void set_bounds(VT const &lb_, VT const &ub_){ + lb = lb_; + ub = ub_; + } + std::pair get_equations(){ + return std::make_pair(Aeq,beq); + } + std::pair get_inequalities(){ + return std::make_pair(Aineq,bineq); + } + std::pair get_bounds(){ + return std::make_pair(lb,ub); + } + unsigned int dimension(){ + return num_vars; + } + +}; + +#endif diff --git a/src/volesti/include/preprocess/crhmc/crhmc_input.h b/src/volesti/include/preprocess/crhmc/crhmc_input.h new file mode 100644 index 00000000..05f61c4b --- /dev/null +++ b/src/volesti/include/preprocess/crhmc/crhmc_input.h @@ -0,0 +1,172 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" +#ifndef CRHMC_INPUT_H +#define CRHMC_INPUT_H +#include "Eigen/Eigen" +#include "opts.h" +#include "convex_bodies/hpolytope.h" +#include "preprocess/crhmc/constraint_problem.h" +/*0 funciton handles are given as a reference in case the user gives no +function. Then the uniform function is implied*/ +template +struct ZeroFunctor +{ + using Type = typename Point::FT; + Point operator()(Point const &x) const { return Point(x.dimension()); } + struct parameters { + Type L=1; + Type eta=1; + }; + struct parameters params; +}; +template +struct ZeroScalarFunctor +{ + using Type = typename Point::FT; + Type operator()(Point const &x) const { return 0; } +}; + +/// +/// Input structure: With this the user can define the input for a crhmc polytope sampling problem +template , + typename grad = ZeroFunctor, + typename hess = ZeroFunctor> +class crhmc_input +{ + using Type = typename Point::FT; + using VT = Eigen::Matrix; + ZeroFunctor zerof; + ZeroScalarFunctor zerosf; + +public: + using MT = MatrixType; + using Func = func; + using Grad = grad; + using Hess = hess; + using point= Point; + MatrixType Aineq; // Matrix of coefficients for the inequality constraints + VT bineq; // Right hand side of the inequality constraints + MatrixType Aeq; // Matrix of coefficients for the equality constraints + VT beq; // Right hand side of the equality constraints + opts options; // structure of the parameters of the problem + VT lb; // lb on the output coordinates preset to -1e7 + VT ub; // ub on the output coordinates preset to +1e7 + func &f; // Negative log density function handle + grad &df; // Negative log density gradient function handle + hess &ddf; // Negative log density hessian function handle + bool fZero; // whether f is completely zero + bool fHandle; // whether f is handle or not + bool dfHandle; // whether df is handle or not + bool ddfHandle; // whether ddf is handle or not + unsigned int dimension; // dimension of the original problem + const Type inf = options.max_coord + 1; // helper for barrier handling + /*Constructors for different input instances*/ + crhmc_input(int dim, func &function, grad &g, hess &h) + : f(function), df(g), ddf(h) + { dimension=dim; + fZero = false; + fHandle = true; + dfHandle = true; + ddfHandle = true; + init(dimension); + } + crhmc_input(int dim, func &function) + : f(function), df(zerof), ddf(zerof) + { dimension=dim; + fZero = false; + fHandle = true; + dfHandle = false; + ddfHandle = false; + init(dimension); + } + crhmc_input(int dim, func &function, grad &g) + : f(function), df(g), ddf(zerof) + { dimension=dim; + fZero = false; + fHandle = true; + dfHandle = true; + ddfHandle = false; + init(dimension); + } + crhmc_input(int dim) : f(zerosf), df(zerof), ddf(zerof) + { dimension=dim; + fZero = true; + fHandle = false; + dfHandle = false; + ddfHandle = false; + init(dimension); + } + + void init(int dimension) + { + Aineq.resize(0, dimension); + Aeq.resize(0, dimension); + bineq.resize(0, 1); + beq.resize(0, 1); + lb = -VT::Ones(dimension) * inf; + ub = VT::Ones(dimension) * inf; + } +}; +#include + +template < + typename Input, typename Polytope, typename Func, typename Grad, typename Hess, + typename std::enable_if>::value>::type * = nullptr> +inline Input convert2crhmc_input(Polytope &P, Func &f, Grad &g, Hess &h) { + int dimension = P.dimension(); + Input input = Input(dimension, f, g, h); + if (std::is_same< + Hess, ZeroFunctor>::value){ + input.ddfHandle=false; + } + input.Aineq = P.get_mat(); + input.bineq = P.get_vec(); + return input; +} + +template >::value>::type + * = nullptr> +inline Input convert2crhmc_input(Polytope &P, Func &f, Grad &g, Hess &h) { + int dimension = P.dimension(); + Input input = Input(dimension, f, g, h); + if (std::is_same< + Hess, ZeroFunctor>::value){ + input.ddfHandle=false; + } + std::tie(input.Aineq, input.bineq) = P.get_inequalities(); + std::tie(input.Aeq, input.beq) = P.get_equations(); + std::tie(input.lb, input.ub) = P.get_bounds(); + return input; +} +template >::value && + !std::is_same>::value>:: + type * = nullptr> +inline Input convert2crhmc_input(Polytope &P, Func &f, Grad &g, Hess &h) { + /*CRHMC works only for H-polytopes and constraint_problems for now*/ + int dimension = 0; + Input input = Input(dimension, f, g, h); + return input; +} +#endif diff --git a/src/volesti/include/preprocess/crhmc/crhmc_problem.h b/src/volesti/include/preprocess/crhmc/crhmc_problem.h new file mode 100644 index 00000000..c501bce6 --- /dev/null +++ b/src/volesti/include/preprocess/crhmc/crhmc_problem.h @@ -0,0 +1,703 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" +#ifndef CRHMC_PROBLEM_H +#define CRHMC_PROBLEM_H +#include "Eigen/Eigen" +#include "PackedCSparse/PackedChol.h" +#include "cartesian_geom/cartesian_kernel.h" +#include "convex_bodies/hpolytope.h" +#include "preprocess/crhmc/analytic_center.h" +#include "preprocess/crhmc/crhmc_input.h" +#include "preprocess/crhmc/crhmc_utils.h" +#include "preprocess/crhmc/lewis_center.h" +#include "preprocess/crhmc/opts.h" +#include "preprocess/crhmc/two_sided_barrier.h" +#include +#include +#include +#include +#include + +#ifndef SIMD_LEN +#define SIMD_LEN 0 +#endif +const size_t chol_k = (SIMD_LEN == 0) ? 1 : SIMD_LEN; + +/// +/// Crhmc sampling problem: With this the user can define a crhmc polytope sampling problem +/// @tparam Point Point type +/// @tparam Input Input format +template +class crhmc_problem { +public: + using NT = double; + using PolytopeType = HPolytope; + using MT = Eigen::Matrix; + using VT = Eigen::Matrix; + using IVT = Eigen::Matrix; + using SpMat = Eigen::SparseMatrix; + using PM = Eigen::PermutationMatrix; + using IndexVector = Eigen::Matrix; + using CholObj = PackedChol; + using Triple = Eigen::Triplet; + using Barrier = two_sided_barrier; + using Tx = FloatArray; + using Opts = opts; + using Diagonal_MT = Eigen::DiagonalMatrix; + using Func = typename Input::Func; + using Grad = typename Input::Grad; + using Hess = typename Input::Hess; + using Crhmc_problem=crhmc_problem; + + unsigned int _d; // dimension + // Problem variables Ax=b st lb<=x<=ub + MT A; // matrix A input matrix + SpMat Asp; // problem matrix A in Sparse form + VT b; // vector b, s.t.: Ax=b + VT lb; // Lower bound for output coordinates + VT ub; // Upper bound for output coordinates + Barrier barrier; // Class that holds functions that handle the log barrier for + // lb and ub + Opts options; // problem parameters + // Transformation (T,y) such that the new variables x + // can be tranform to the original z (z= Tx+y) + SpMat T; + VT y; + // Non zero indices and values for fast tranform + std::vector Tidx; // T x = x(Tidx) .* Ta + VT Ta; // T x = x(Tidx) .* Ta + bool isempty_center = true; + VT center = VT::Zero(0, 1); // Resulting polytope Lewis or Analytic center + VT analytic_ctr; //analytic center vector (for testing) + VT w_center;// weights of the lewis center + + VT width; // width of the varibles + int nP;//input dimension + + Func &func; // function handle + Grad &df; // gradient handle + Hess &ddf; // hessian handle + bool fZero; // whether f is completely zero + bool fHandle; // whether f is handle or not + bool dfHandle; // whether df is handle or not + bool ddfHandle; // whether ddf is handle or not + /*Invalid polytope variables*/ + bool terminate=false; + std::string terminate_message; +#ifdef TIME_KEEPING +//Timing information + std::chrono::duration rescale_duration, sparsify_duration, + reordering_duration, rm_rows_duration, rm_fixed_vars_duration, + ex_collapsed_vars_duration, shift_barrier_duration, lewis_center_duration; +#endif + const NT inf = options.max_coord; // helper for barrier handling + const NT barrier_bound = 1e7; + int equations() const { return Asp.rows(); } + int dimension() const { return Asp.cols(); } + int nnz() const { return Asp.nonZeros(); } + + // Remove varibles that have width under some tolerance + int remove_fixed_variables(const NT tol = 1e-12) { + int m = Asp.rows(); + int n = Asp.cols(); + VT d = estimate_width(); + CholObj solver = CholObj(transform_format(Asp)); + solver.accuracyThreshold = 0; + VT w = VT::Ones(n, 1); + solver.decompose((Tx *)w.data()); + VT out_vector = VT(m, 1); + solver.solve((Tx *)b.data(), (Tx *)out_vector.data()); + VT x = Asp.transpose() * out_vector; + + x = ((x.array()).abs() < tol).select(0., x); + std::vector freeIndices; + std::vector indices; + int nFreeVars = 0; + for (int i = 0; i < n; i++) { + if (d(i) < tol * (1 + abs(x(i)))) { + } else { + freeIndices.push_back(Triple(i, nFreeVars, 1)); + nFreeVars++; + indices.push_back(i); + x(i) = 0.0; + } + } + + if (freeIndices.size() != n) { + SpMat S = SpMat(n, freeIndices.size()); + S.setFromTriplets(freeIndices.begin(), freeIndices.end()); + append_map(S, x); + copy_indicies(barrier.lb, barrier.lb, indices); + copy_indicies(barrier.ub, barrier.ub, indices); + barrier.lb.conservativeResize(indices.size()); + barrier.ub.conservativeResize(indices.size()); + + barrier.set_bound(barrier.lb, barrier.ub); + if (!isempty_center) { + copy_indicies(center, center, indices); + center.conservativeResize(indices.size()); + } + return 1; + } + return 0; + } + + int extract_collapsed_variables() { + SpMat Ac; + VT bc; + if (isempty_center) { + std::tie(center, Ac, bc) = analytic_center(Asp, b, *this, options); + isempty_center = false; + } else { + std::tie(center, Ac, bc) = + analytic_center(Asp, b, *this, options, center); + analytic_ctr=center; + } + if (Ac.rows() == 0) { + return 0; + } + SpMat _A = SpMat(Asp); + sparse_stack_v(_A, Ac, Asp); + b.conservativeResize(b.rows() + bc.rows(), 1); + b.bottomRows(bc.rows()) = bc; + return 1; + } + // Rescale the polytope for numerical stability + void rescale(const VT x = VT::Zero(0, 1)) { + if (std::min(equations(), dimension()) <= 1) { + return; + } + VT hess; + if (x.rows() == 0) { + hess = VT::Ones(dimension(), 1); + } else { + std::tie(std::ignore, hess) = analytic_center_oracle(x); + hess = hess + (width.cwiseProduct(width)).cwiseInverse(); + } + VT scale = (hess.cwiseSqrt()).cwiseInverse(); + SpMat Ain = Asp * scale.asDiagonal(); + VT cscale; + VT rscale; + + std::tie(cscale, rscale) = gmscale(Ain, 0.9); + Asp = (rscale.cwiseInverse()).asDiagonal() * Asp; + b = b.cwiseQuotient(rscale); + barrier.set_bound(barrier.lb.cwiseProduct(cscale), + barrier.ub.cwiseProduct(cscale)); + append_map((cscale.cwiseInverse()).asDiagonal(), VT::Zero(dimension(), 1)); + if (!isempty_center) { + center = center.cwiseProduct(cscale); + } + } + + // Rewrite P so that each cols has no more than maxNZ non-zeros + void splitDenseCols(const int maxnz) { + int m = Asp.rows(); + int n = Asp.cols(); + if (m <= maxnz) { + return; + } + if (Asp.nonZeros() > maxnz * n) { + return; + } + int numBadCols = 1; + lb = barrier.lb; + ub = barrier.ub; + //until there are columns with more than maxNZ elements + while (numBadCols > 0) { + m = Asp.rows(); + n = Asp.cols(); + std::vector colCounts(n); + std::vector badCols; + numBadCols = 0; + //find the columns with count larger than maxNZ + std::tie(colCounts, badCols) = nnzPerColumn(Asp, maxnz); + numBadCols = badCols.size(); + if (numBadCols == 0) { + break; + } + //create a new variable for each one and update Asp, b, lb, ub, T, y accordingly + SpMat A_; + SpMat Aj(m, numBadCols); + SpMat Ai(numBadCols, n + numBadCols); + std::vector newColumns; + std::vector newRows; + b.conservativeResize(m + numBadCols, 1); + lb.conservativeResize(n + numBadCols, 1); + ub.conservativeResize(n + numBadCols, 1); + + for (int j = 0; j < numBadCols; j++) { + int i = badCols[j]; + int k = 0; + for (SpMat::InnerIterator it(Asp, i); it; ++it) { + if (k >= colCounts[i] / 2) { + newColumns.push_back(Triple(it.row(), j, it.value())); + it.valueRef() = 0; + } + k++; + } + newRows.push_back(Triple(j, i, 1)); + newRows.push_back(Triple(j, j + n, -1)); + lb(n + j) = lb(i); + ub(n + j) = ub(i); + b(m + j) = 0; + } + Ai.setFromTriplets(newRows.begin(), newRows.end()); + Aj.setFromTriplets(newColumns.begin(), newColumns.end()); + Asp.prune(0, 0); + sparse_stack_h_inplace(Asp, Aj); + sparse_stack_v(Asp, Ai, A_); + Asp = A_; + } + SpMat _T = MT::Zero(T.rows(), ub.rows() - T.cols()).sparseView(); + sparse_stack_h_inplace(T, _T); + updateT(); + barrier.set_bound(lb, ub); + } + // Change A and the correpsonding Transformation + template + void append_map(MatrixType const &S, VT const &z) { + b = b - Asp * z; + Asp = Asp * S; + y = y + T * z; + T = T * S; + updateT(); + } + // Shift the problem with respect to x + void shift_barrier(VT const &x) { + int size = x.rows(); + b = b - Asp * x; + y = y + T * x; + barrier.set_bound(barrier.lb - x, barrier.ub - x); + if (!isempty_center) { + center = center - x; + } + } + // Reorder the polytope accordint to the AMD Reordering for better sparsity + // pattern in the Cholesky decomposition + void reorder() { + if (!options.EnableReordering || Asp.rows()*Asp.cols() < options.maxNZ) { + return; + } + fillin_reduce(Asp,b); + } +//Using the Cholesky decomposition remove dependent rows in the systen Asp*x=b + int remove_dependent_rows(NT tolerance = 1e-12, NT infinity = 1e+64) { + //this approach does not work with 0 rows + remove_zero_rows(Asp, b); + int m = Asp.rows(); + int n = Asp.cols(); + VT v = VT(m); + VT w = VT::Ones(n, 1); + CholObj solver = CholObj(transform_format(Asp)); + solver.accuracyThreshold = 0; + solver.decompose((Tx *)w.data()); + solver.diagL((Tx *)v.data()); + std::vector indices(m, false); + std::vector idx; + bool changed = false; + for (int i = 0; i < m; i++) { + if ((v(i) > tolerance) && (v(i) < infinity)) { + indices[i] = true; + idx.push_back(i); + }else{ + changed=true; + } + } + if (!changed) { + return 0; + } + + remove_rows(Asp, indices); + copy_indicies(b, b, idx); + b.conservativeResize(idx.size(), 1); + return 1; + } +//Apply number of operations that simplify the problem + void simplify() { +#ifdef TIME_KEEPING + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); +#endif + rescale(); +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + rescale_duration += end - start; + start = std::chrono::system_clock::now(); +#endif + splitDenseCols(options.maxNZ); +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + sparsify_duration += end - start; + start = std::chrono::system_clock::now(); +#endif + reorder(); +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + reordering_duration += end - start; +#endif + int changed = 1; + while (changed) { + while (changed) { + changed = 0; +#ifdef TIME_KEEPING + start = std::chrono::system_clock::now(); +#endif + changed += remove_dependent_rows(); +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + rm_rows_duration += end - start; + start = std::chrono::system_clock::now(); +#endif + changed += remove_fixed_variables(); +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + rm_fixed_vars_duration += end - start; + start = std::chrono::system_clock::now(); +#endif + reorder(); +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + reordering_duration += end - start; +#endif + } +#ifdef TIME_KEEPING + start = std::chrono::system_clock::now(); +#endif + changed += extract_collapsed_variables(); +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + ex_collapsed_vars_duration += end - start; +#endif + } + } + + VT estimate_width(bool use_center=false) { + int n = Asp.cols(); + VT hess = VT::Ones(n, 1); + if(use_center){ + std::tie(std::ignore, hess)=analytic_center_oracle(center); + } + CholObj solver = CholObj(transform_format(Asp)); + solver.accuracyThreshold = 0; + solver.decompose((Tx *)hess.data()); + VT w_vector(n, 1); + solver.leverageScoreComplement((Tx *)w_vector.data()); + w_vector = (w_vector.cwiseMax(0)).cwiseProduct(hess.cwiseInverse()); + VT tau = w_vector.cwiseSqrt(); + + return tau; + } + + template + void print(StreamType &stream, std::string const message = "Printing Sparse problem") { + stream << "----------------" << message << "--------------" << '\n'; + stream << "(m,n) = " << equations() << " , " << dimension() + << " nnz= " << Asp.nonZeros() << "\n"; + if (equations() > 20 || dimension() > 20) { + stream << "too big for complete visulization\n"; + return; + } + stream << "A=\n"; + + stream << MT(Asp); + stream << "\n"; + + stream << "b=\n"; + stream << b; + stream << "\n"; + + stream << "lb=\n"; + stream << barrier.lb; + stream << "\n"; + + stream << "ub=\n"; + stream << barrier.ub; + stream << "\n"; + + stream << "T=\n"; + stream << MT(T); + stream << "\n"; + + stream << "y=\n"; + stream << y; + stream << "\n"; + + stream << "center=\n"; + stream << center; + stream << "\n"; + } + + void make_format(Input const &input, MT const &S) { + nP = input.Aeq.cols(); + int nIneq = input.Aineq.rows(); + int nEq = input.Aeq.rows(); + A.resize(nEq + nIneq, nP + nIneq); + A << input.Aeq, MT::Zero(nEq, nIneq), input.Aineq, MT::Identity(nIneq, nIneq); + b.resize(nEq + nIneq, 1); + b << input.beq, input.bineq; + lb.resize(nP + nIneq, 1); + ub.resize(nP + nIneq, 1); + lb << input.lb, MT::Zero(nIneq, 1); + ub << input.ub, MT::Ones(nIneq, 1) * inf; + Asp.resize(nEq + nIneq, nP + nIneq); + int n = dimension(); + /*Move lb=ub to Ax=b*/ + for (int i = 0; i < n; i++) { + if (doubleVectorEqualityComparison(lb(i), ub(i))) { + MT temp = MT::Zero(1, n); + temp(i) = 1; + A.conservativeResize(A.rows() + 1, A.cols()); + A.row(A.rows() - 1) = temp; + b.conservativeResize(b.rows() + 1); + b(b.rows() - 1) = (lb(i) + ub(i)) / 2; + lb(i) = -inf; + ub(i) = inf; + } + } + Asp = A.sparseView(); + } + void make_format(Input const &input, SpMat const &S) { + nP = input.Aeq.cols(); + int nIneq = input.Aineq.rows(); + int nEq = input.Aeq.rows(); + Asp.resize(nEq + nIneq, nP + nIneq); + SpMat B = SpMat(input.Aeq); + SpMat C = SpMat(input.Aineq); + B.conservativeResize(nEq, nIneq + nP); + SpMat temp = SpMat(nIneq, nIneq); + temp.setIdentity(); + sparse_stack_h_inplace(C, temp); + sparse_stack_v(B, C, Asp); + b.resize(nEq + nIneq, 1); + b << input.beq, input.bineq; + lb.resize(nP + nIneq, 1); + ub.resize(nP + nIneq, 1); + lb << input.lb, MT::Zero(nIneq, 1); + ub << input.ub, MT::Ones(nIneq, 1) * inf; + int n = dimension(); + /*Move lb=ub to Ax=b*/ + for (int i = 0; i < n; i++) { + if (doubleVectorEqualityComparison(lb(i), ub(i))) { + B.resize(Asp.rows(), Asp.cols()); + B = SpMat(Asp); + MT temp = MT::Zero(1, n); + temp(i) = 1; + C = temp.sparseView(); + sparse_stack_v(B, C, Asp); + b.conservativeResize(b.rows() + 1); + b(b.rows() - 1) = (lb(i) + ub(i)) / 2; + lb(i) = -inf; + ub(i) = inf; + } + } + Asp.makeCompressed(); + } +//Class constructor + crhmc_problem(Input const &input, Opts _options = Opts()) + : options(_options), func(input.f), df(input.df), ddf(input.ddf), + fZero(input.fZero), fHandle(input.fHandle), dfHandle(input.dfHandle), + ddfHandle(input.ddfHandle) { +#ifdef TIME_KEEPING + rescale_duration = sparsify_duration = reordering_duration = + rm_rows_duration = rm_fixed_vars_duration = ex_collapsed_vars_duration = + shift_barrier_duration = lewis_center_duration = + std::chrono::duration::zero(); +#endif + + make_format(input, input.Aeq); + PreproccessProblem(); + } + // Initialization funciton + void PreproccessProblem() { + int n = dimension(); + barrier.set_bound(lb.cwiseMax(-barrier_bound), ub.cwiseMin(barrier_bound)); + NT tol = std::numeric_limits::epsilon(); + Asp.prune(tol, tol); + /*Update the transformation Tx + y*/ + T = SpMat(nP, n); + std::vector indices; + for (int i = 0; i < nP; i++) { + indices.push_back(Triple(i, i, 1)); + } + T.setFromTriplets(indices.begin(), indices.end()); + Tidx = std::vector(T.rows()); + updateT(); + y = VT::Zero(nP, 1); + /*Simplify*/ + if (!fZero) { + fZero = true; + simplify(); + fZero = false; + } + simplify(); +#ifdef TIME_KEEPING + std::chrono::time_point start, end; + start = std::chrono::system_clock::now(); +#endif + if (isempty_center) { + std::tie(center, std::ignore, std::ignore) = + analytic_center(Asp, b, *this, options); + isempty_center = false; + } + shift_barrier(center); +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + shift_barrier_duration += end - start; +#endif + reorder(); + + width = estimate_width(true); + // Recenter again and make sure it is feasible + VT hess; +#ifdef TIME_KEEPING + start = std::chrono::system_clock::now(); +#endif + std::tie(center, std::ignore, std::ignore, w_center) = + lewis_center(Asp, b, *this, options, center); + std::tie(std::ignore, hess) = lewis_center_oracle(center, w_center); + CholObj solver = CholObj(transform_format(Asp)); + solver.accuracyThreshold = 0; + VT Hinv = hess.cwiseInverse(); + solver.decompose((Tx *)Hinv.data()); + VT out(equations(), 1); + VT input = (b - Asp * center); + solver.solve((Tx *)input.data(), (Tx *)out.data()); + center = center + (Asp.transpose() * out).cwiseProduct(Hinv); +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + lewis_center_duration += end - start; +#endif + if ((center.array() > barrier.ub.array()).any() || + (center.array() < barrier.lb.array()).any()) { + terminate = true; + terminate_message = "Polytope:Infeasible. The algorithm cannot find a feasible point.\n"; + return; + } + } +#ifdef TIME_KEEPING +template +void print_preparation_time(StreamType& stream){ + stream << "---Preparation Timing Information"<< std::endl; + stream << "Rescale completed in time, "; + stream << rescale_duration.count() << " secs " << std::endl; + stream << "Split dense columns completed in time, "; + stream << sparsify_duration.count() << " secs " << std::endl; + stream << "Reordering completed in time, "; + stream << reordering_duration.count() << " secs " << std::endl; + stream << "Removing dependent rows completed in time, "; + stream << rm_rows_duration.count() << " secs " << std::endl; + stream << "Removing fixed variables completed in time, "; + stream << rm_fixed_vars_duration.count() << " secs " << std::endl; + stream << "Extracting collapsed variables completed in time, "; + stream << ex_collapsed_vars_duration.count() << " secs " << std::endl; + stream << "Shift_barrier completed in time, "; + stream << shift_barrier_duration.count() << " secs " << std::endl; + stream << "Finding Center completed in time, "; + stream << lewis_center_duration.count() << " secs " << std::endl; +} +#endif + // Gradient and hessian of for the analytic center + std::pair analytic_center_oracle(VT const &x) { + MT g, h; + std::tie(std::ignore, g, h) = f_oracle(x); + return std::make_pair(g + barrier.gradient(x), h + barrier.hessian(x)); + } + // Gradient and hessian of for the lewis center + std::pair lewis_center_oracle(VT const &x, VT const &w) { + MT g, h; + std::tie(std::ignore, g, h) = f_oracle(x); + return std::make_pair(g + w.cwiseProduct(barrier.gradient(x)), + h + w.cwiseProduct(barrier.hessian(x))); + } + // Function that uses the transformation (T,y) to apply the function to the + // original variables + std::tuple f_oracle(MT const &x) { + int n = x.rows(); + int m=x.cols(); + VT f=VT(m); + MT g=MT(n,m); + MT h=MT(n,m); + if (fZero) { + f = VT::Zero(m); + g = MT::Zero(n,m); + h = MT::Zero(n,m); + return std::make_tuple(f, g, h); + } + // Take the correpsonding point in the original space + MT z = MT::Zero(y.rows(), m); + if (fHandle || dfHandle || ddfHandle) { + for(int k=0;k +#include "PackedCSparse/SparseMatrix.h" +#include +#include + +template +struct lambda_as_visitor_wrapper : Func +{ + lambda_as_visitor_wrapper(const Func &f) : Func(f) {} + template + void init(const S &v, I i, I j) + { + return Func::operator()(v, i, j); + } +}; + +template +void visit_lambda(const Mat &m, const Func &f) +{ + lambda_as_visitor_wrapper visitor(f); + m.visit(visitor); +} + +template +void sparse_stack_v(const SparseMatrixType &top, const SparseMatrixType &bottom, + SparseMatrixType &stacked) +{ + assert(top.cols() == bottom.cols()); + stacked.resize(top.rows() + bottom.rows(), top.cols()); + stacked.resizeNonZeros(top.nonZeros() + bottom.nonZeros()); + + int i = 0; + + for (int col = 0; col < top.cols(); col++) + { + stacked.outerIndexPtr()[col] = i; + + for (int j = top.outerIndexPtr()[col]; j < top.outerIndexPtr()[col + 1]; + j++, i++) + { + stacked.innerIndexPtr()[i] = top.innerIndexPtr()[j]; + stacked.valuePtr()[i] = top.valuePtr()[j]; + } + + for (int j = bottom.outerIndexPtr()[col]; + j < bottom.outerIndexPtr()[col + 1]; j++, i++) + { + stacked.innerIndexPtr()[i] = (int)top.rows() + bottom.innerIndexPtr()[j]; + stacked.valuePtr()[i] = bottom.valuePtr()[j]; + } + } + stacked.outerIndexPtr()[top.cols()] = i; +} + +template +void sparse_stack_h(const SparseMatrixType &left, const SparseMatrixType &right, + SparseMatrixType &stacked) +{ + assert(left.rows() == right.rows()); + + stacked.resize(left.rows(), left.cols() + right.cols()); + stacked.resizeNonZeros(left.nonZeros() + right.nonZeros()); + + std::copy(left.innerIndexPtr(), left.innerIndexPtr() + left.nonZeros(), + stacked.innerIndexPtr()); + std::copy(right.innerIndexPtr(), right.innerIndexPtr() + right.nonZeros(), + stacked.innerIndexPtr() + left.nonZeros()); + + std::copy(left.valuePtr(), left.valuePtr() + left.nonZeros(), + stacked.valuePtr()); + std::copy(right.valuePtr(), right.valuePtr() + right.nonZeros(), + stacked.valuePtr() + left.nonZeros()); + + std::copy(left.outerIndexPtr(), left.outerIndexPtr() + left.cols(), + stacked.outerIndexPtr()); // dont need the last entry of + // A.outerIndexPtr() -- total length is + // AB.cols() + 1 = A.cols() + B.cols() + 1 + std::transform(right.outerIndexPtr(), + right.outerIndexPtr() + right.cols() + 1, + stacked.outerIndexPtr() + left.cols(), + [&](int i) + { return i + left.nonZeros(); }); +} +#include + +template +void sparse_stack_h_inplace(SparseMatrixType &left, + const SparseMatrixType &right) +{ + assert(left.rows() == right.rows()); + + const int leftcol = (int)left.cols(); + const int leftnz = (int)left.nonZeros(); + + left.conservativeResize(left.rows(), left.cols() + right.cols()); + left.resizeNonZeros(left.nonZeros() + right.nonZeros()); + + std::copy(right.innerIndexPtr(), right.innerIndexPtr() + right.nonZeros(), + left.innerIndexPtr() + leftnz); + std::copy(right.valuePtr(), right.valuePtr() + right.nonZeros(), + left.valuePtr() + leftnz); + std::transform( + right.outerIndexPtr(), right.outerIndexPtr() + right.cols() + 1, + left.outerIndexPtr() + leftcol, [&](int i) + { return i + leftnz; }); +} + +template +void remove_zero_rows(SparseMatrixType &A, VectorType &b) { + std::vector> tripletList; + unsigned Ndata = A.cols(); + unsigned Nbins = A.rows(); + for (int k = 0; k < A.outerSize(); ++k) { + for (typename SparseMatrixType::InnerIterator it(A, k); it; ++it) { + tripletList.push_back( + Eigen::Triplet(it.row(), it.col(), it.value())); + } + } + // get which rows are empty + std::vector has_value(Nbins, false); + for (auto tr : tripletList) + has_value[tr.row()] = true; + if (std::all_of(has_value.begin(), has_value.end(), + [](bool v) { return v; })) { + return; + } + // create map from old to new indices + std::map row_map; + unsigned new_idx = 0; + for (unsigned old_idx = 0; old_idx < Nbins; old_idx++) + if (has_value[old_idx]) { + row_map[old_idx] = new_idx; + b(new_idx) = b(old_idx); + new_idx++; + } + // make new triplet list, dropping empty rows + std::vector> newTripletList; + newTripletList.reserve(Ndata); + for (auto tr : tripletList) + newTripletList.push_back( + Eigen::Triplet(row_map[tr.row()], tr.col(), tr.value())); + + // form new matrix and return + A.resize(new_idx, Ndata); + A.setFromTriplets(newTripletList.begin(), newTripletList.end()); + b.conservativeResize(new_idx); +} + +template +void remove_rows(SparseMatrixType &A, std::vector ¬Removed) { + unsigned Ndata = A.cols(); + unsigned Nbins = A.rows(); + // create map from old to new indices + std::map row_map; + unsigned new_idx = 0; + for (unsigned old_idx = 0; old_idx < Nbins; old_idx++) + if (notRemoved[old_idx]) + row_map[old_idx] = new_idx++; + + std::vector> tripletList; + for (int k = 0; k < A.outerSize(); ++k) { + for (typename SparseMatrixType::InnerIterator it(A, k); it; ++it) { + if (notRemoved[it.row()]) { + tripletList.push_back( + Eigen::Triplet(row_map[it.row()], it.col(), it.value())); + } + } + } + // form new matrix and return + A.resize(new_idx, Ndata); + A.setFromTriplets(tripletList.begin(), tripletList.end()); +} + +template +std::pair colwiseMinMax(SparseMatrixType const &A) +{ + int n = A.cols(); + VectorType cmax(n); + VectorType cmin(n); + for (int k = 0; k < A.outerSize(); ++k) + { + Type minv = +std::numeric_limits::max(); + Type maxv = std::numeric_limits::lowest(); + for (typename SparseMatrixType::InnerIterator it(A, k); it; ++it) + { + minv = std::min(minv, it.value()); + maxv = std::max(maxv, it.value()); + } + cmin(k) = minv; + cmax(k) = maxv; + } + return std::make_pair(cmin, cmax); +} +template +void nextpow2(VectorType &a) +{ + a = (a.array() == 0).select(1, a); + a = (((a.array().log()) / std::log(2)).ceil()).matrix(); + a = pow(2, a.array()).matrix(); +} +template +std::pair gmscale(SparseMatrixType &Asp, + const Type scltol) +{ + using Diagonal_MT = Eigen::DiagonalMatrix; + int m = Asp.rows(); + int n = Asp.cols(); + SparseMatrixType A = Asp.cwiseAbs(); + A.makeCompressed(); + int maxpass = 10; + Type aratio = 1e+50; + Type sratio; + Type damp = 1e-4; + Type small = 1e-8; + VectorType rscale = VectorType ::Ones(m, 1); + VectorType cscale = VectorType ::Ones(n, 1); + VectorType cmax; + VectorType cmin; + VectorType rmax; + VectorType rmin; + VectorType eps = VectorType ::Ones(n, 1) * 1e-12; + SparseMatrixType SA; + for (int npass = 0; npass < maxpass; npass++) + { + + rscale = (rscale.array() == 0).select(1, rscale); + Diagonal_MT Rinv = (rscale.cwiseInverse()).asDiagonal(); + SA = Rinv * A; + std::tie(cmin, cmax) = + colwiseMinMax(SA); + + // cmin = (cmin + eps).cwiseInverse(); + sratio = (cmax.cwiseQuotient(cmin)).maxCoeff(); + + if (npass > 0) + { + cscale = ((cmin.cwiseMax(damp * cmax)).cwiseProduct(cmax)).cwiseSqrt(); + } + + if (npass >= 2 && sratio >= aratio * scltol) + { + break; + } + aratio = sratio; + nextpow2(cscale); + Diagonal_MT Cinv = (cscale.cwiseInverse()).asDiagonal(); + SA = A * Cinv; + std::tie(rmin, rmax) = + colwiseMinMax(SA.transpose()); + // rmin = (rmin + eps).cwiseInverse(); + rscale = ((rmin.cwiseMax(damp * rmax)).cwiseProduct(rmax)).cwiseSqrt(); + nextpow2(rscale); + } + rscale = (rscale.array() == 0).select(1, rscale); + Diagonal_MT Rinv = (rscale.cwiseInverse()).asDiagonal(); + SA = Rinv * A; + std::tie(std::ignore, cscale) = + colwiseMinMax(SA); + nextpow2(cscale); + return std::make_pair(cscale, rscale); +} +template +int doubleVectorEqualityComparison( + const Type a, const Type b, + const Type tol = std::numeric_limits::epsilon()) +{ + return (abs(a - b) < tol * (1 + abs(a) + abs(b))); +} + +template +std::pair, std::vector> +nnzPerColumn(SparseMatrixType const &A, const int threashold) +{ + int n = A.cols(); + std::vector colCounts(n); + std::vector badCols; + for (int k = 0; k < A.outerSize(); ++k) + { + int nnz = 0; + for (typename SparseMatrixType::InnerIterator it(A, k); it; ++it) + { + if (it.value() != 0) + { + nnz++; + } + } + colCounts[k] = nnz; + if (nnz > threashold) + { + badCols.push_back(k); + } + } + return std::make_pair(colCounts, badCols); +} +using PM = Eigen::PermutationMatrix; +template +PM permuteMatAMD(SparseMatrixType const &A) +{ + Eigen::AMDOrdering ordering; + PM perm; + ordering(A, perm); + return perm; +} +template +PM postOrderPerm(SparseMatrixType const &A) +{ + using IndexVector = Eigen::Matrix; + int n = A.rows(); + IndexVector m_etree; + IndexVector firstRowElt; + Eigen::internal::coletree(A, m_etree, firstRowElt); + IndexVector post; + Eigen::internal::treePostorder(int(A.cols()), m_etree, post); + PM post_perm(n); + for (int i = 0; i < n; i++) + post_perm.indices()(i) = post(i); + return post_perm; +} + +template +void fillin_reduce(SparseMatrixType &X,VectorType& b){ + SparseMatrixType I = SparseMatrixType(Eigen::VectorXd::Ones(X.rows()).asDiagonal()); + SparseMatrixType XX = X * X.transpose() + I; + XX.makeCompressed(); + Eigen::SimplicialLDLT> cholesky; + cholesky.analyzePattern(XX); + X = cholesky.permutationP() * X; + b = cholesky.permutationP() *b; +} +template +PackedCSparse::SparseMatrix transform_format(SparseMatrixType const &mat) { + PackedCSparse::SparseMatrix A = PackedCSparse::SparseMatrix(mat.rows(), mat.cols(), mat.nonZeros()); + IndexType nnz = 0; + for (IndexType outeindex = 0; outeindex < mat.outerSize(); ++outeindex) { + A.p[outeindex] = nnz; + for (typename SparseMatrixType::InnerIterator it(mat, outeindex); it; ++it) { + A.i[nnz] = it.row(); + A.x[nnz] = it.value(); + nnz++; + } + } + A.p[A.n] = nnz; + return A; +} +template +void copy_indicies(MatrixType& a, MatrixType& b, std::vectorconst & a_idx, std::vectorconst & b_idx){ +for(int i=0;i +void copy_indicies(MatrixType& a, MatrixType b, std::vectorconst & b_idx){ +for(int i=0;i +void set(MatrixType &a, std::vectorconst & idx, const Type c){ + for(int i=0;i +void saxpy(MatrixType &a,MatrixType const &b,MatrixType const& c, std::vectorconst & a_idx, std::vectorconst & b_idx){ +for(int i=0;i +void load_problem(SpMat &A, VT &b, VT &lb, VT &ub, int &dimension, + std::string problem_name) { + { + std::string fileName(problem_name); + fileName.append(".mm"); + SpMat X; + loadMarket(X, fileName); + int m = X.rows(); + dimension = X.cols() - 1; + A = X.leftCols(dimension); + b = VT(X.col(dimension)); + } + { + std::string fileName(problem_name); + fileName.append("_bounds.mm"); + SpMat bounds; + loadMarket(bounds, fileName); + lb = VT(bounds.col(0)); + ub = VT(bounds.col(1)); + } +} +#endif diff --git a/src/volesti/include/preprocess/crhmc/lewis_center.h b/src/volesti/include/preprocess/crhmc/lewis_center.h new file mode 100644 index 00000000..5bf89d15 --- /dev/null +++ b/src/volesti/include/preprocess/crhmc/lewis_center.h @@ -0,0 +1,188 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" +#ifndef LEWIS_CENTER_H +#define LEWIS_CENTER_H +#include "Eigen/Eigen" +#include "PackedCSparse/PackedChol.h" +#include "preprocess/crhmc/crhmc_utils.h" +#include "preprocess/crhmc/opts.h" +#include "preprocess/crhmc/two_sided_barrier.h" +#include +#include +#include +#ifndef SIMD_LEN +#define SIMD_LEN 0 +#endif +const size_t chol_k3 = (SIMD_LEN == 0) ? 1 : SIMD_LEN; + +/*This function computes the Lewis center of the polytope*/ +//And detects additional constraint that need to be added +// x - It outputs the minimizer of min f(x) subjects to {Ax=b} +// w - Output weights that correspond to the Lewis center, they are gone be used in the sampler to reduce the conditon number +// C - detected constraint matrix +// If the domain ({Ax=b} intersect dom(f)) is not full dimensional in {Ax=b} +// because of the dom(f), the algorithm will detect the collapsed dimension +// and output the detected constraint C x = d +// d - detected constraint vector +template +std::tuple lewis_center(SpMat const &A, VT const &b, Polytope &f, Opts const &options, VT x = VT::Zero(0, 1)) +{ + using CholObj = typename Polytope::CholObj; + using Triple = typename Polytope::Triple; + using Tx = typename Polytope::Tx; + NT epsilon = 1e-8; + // initial conditions + int n = A.cols(); + int m = A.rows(); + //If it is given use starting point + if (x.rows() == 0 || !f.barrier.feasible(x)) + { + x = f.barrier.center; + } + VT lambda = VT::Zero(n, 1); + int fullStep = 0; + NT tConst = 0; + NT primalErr = std::numeric_limits::max(); + NT dualErr = std::numeric_limits::max(); + NT primalErrMin = std::numeric_limits::max(); + NT primalFactor = 1; + NT dualFactor = 1 + b.norm(); + std::vector idx; + + CholObj solver = CholObj(transform_format(A)); + VT w = VT::Ones(n, 1); + VT wp = w; + for (int iter = 0; iter < options.ipmMaxIter; iter++) + { + std::pair pair_analytic_oracle = f.lewis_center_oracle(x, wp); + VT grad = pair_analytic_oracle.first; + VT hess = pair_analytic_oracle.second; + + // compute the residual + VT rx = lambda - grad; + VT rs = b - A * x; + + // check stagnation + primalErrMin = std::min(primalErr, primalErrMin); + primalErr = rx.norm() / primalFactor; + NT dualErrLast = dualErr; + dualErr = rs.norm() / dualFactor; + bool feasible = f.barrier.feasible(x); + if ((dualErr > (1 - 0.9 * tConst) * dualErrLast) || + (primalErr > 10 * primalErrMin) || !feasible) + { + VT dist = f.barrier.boundary_distance(x); + NT th = options.ipmDistanceTol; + visit_lambda(dist, [&idx, th](double v, int i, int j) + { + if (v < th) + idx.push_back(i); }); + + if (idx.size() > 0) + { + break; + } + } + + // compute the step direction + VT Hinv = hess.cwiseInverse(); + solver.decompose((Tx *)Hinv.data()); + VT out(m, 1); + solver.solve((Tx *)rs.data(), (Tx *)out.data()); + VT dr1 = A.transpose() * out; + VT in = A * Hinv.cwiseProduct(rx); + solver.solve((Tx *)in.data(), (Tx *)out.data()); + + VT dr2 = A.transpose() * out; + VT dx1 = Hinv.cwiseProduct(dr1); + VT dx2 = Hinv.cwiseProduct(rx - dr2); + + // compute the step size + VT dx = dx1 + dx2; + NT tGrad = std::min(f.barrier.step_size(x, dx), 1.0); + dx = dx1 + tGrad * dx2; + NT tConst = std::min(0.99 * f.barrier.step_size(x, dx), 1.0); + tGrad = tGrad * tConst; + + // make the step + x = x + tConst * dx; + lambda = lambda - dr2; + + // update weight + VT w_vector(n, 1); + solver.leverageScoreComplement((Tx *)w_vector.data()); + + VT wNew = w_vector.cwiseMax(0) + VT::Ones(n, 1) * epsilon; + w = (w + wNew) / 2; + wp = Eigen::pow(w.array(), 0.875).matrix(); + + if (!f.barrier.feasible(x)) + { + break; + } + + // stop if converged + if (tGrad == 1) + { + fullStep = fullStep + 1; + if (fullStep > log(dualErr / options.ipmDualTol) && + fullStep > options.min_convergence_steps) + { + break; + } + } + else + { + fullStep = 0; + } + } + + SpMat C; + VT d; + if (idx.size() == 0) + { + VT dist = f.barrier.boundary_distance(x); + NT th = options.ipmDistanceTol; + visit_lambda(dist, [&idx, th](double v, int i, int j) + { + if (v < th) + idx.push_back(i); }); + } + + if (idx.size() > 0) + { + C.resize(idx.size(), n); + std::pair pboundary = f.barrier.boundary(x); + VT A_ = pboundary.first; + VT b_ = pboundary.second; + std::vector sparseIdx; + for (int i = 0; i < idx.size(); i++) + { + sparseIdx.push_back(Triple(i, idx[i], A_(idx[i]))); + } + C.setFromTriplets(sparseIdx.begin(), sparseIdx.end()); + d.resize(idx.size(), 1); + copy_indicies(d, b_, idx); + } + else + { + C = MT::Zero(0, n).sparseView(); + d = VT::Zero(0, 1); + } + + return std::make_tuple(x, C, d, wp); +} +#endif diff --git a/src/volesti/include/preprocess/crhmc/opts.h b/src/volesti/include/preprocess/crhmc/opts.h new file mode 100644 index 00000000..573a69db --- /dev/null +++ b/src/volesti/include/preprocess/crhmc/opts.h @@ -0,0 +1,62 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" + +#ifndef OPTS_H +#define OPTS_H + +/// @brief Crhmc options +/// @tparam Type Numer type +template class opts { +public: + /*Preprocess options*/ + const int ipmMaxIter = 200; //Maximum number of iterations for finding the analytic and lewis center + const Type ipmDistanceTol = 1e-8; + const Type ipmDualTol = 1e-12; + int maxNZ = 30; + Type max_coord = 1e9; + bool EnableReordering = true; + const int min_convergence_steps=8; + + /*ODE options*/ + const Type implicitTol = 1e-5; + const int maxODEStep = 30; + Type initialStep = 0.2; + Type convergence_bound = 1e16; + + /*PackedCS Solver Options*/ + Type solver_accuracy_threshold=1e-2; + int simdLen=1; + + /*Sampler options*/ + bool DynamicWeight = true; //Enable the use of dynamic weights for each variable when sampling + bool DynamicStepSize = true; // Enable adaptive step size that avoids low acceptance probability + bool DynamicRegularizer = true; //Enable the addition of a regularization term + Type regularization_factor=1e-20; + /*Dynamic step choices*/ + Type warmUpStep = 10; + int maxConsecutiveBadStep = 10; + Type targetODEStep = 10; + Type shrinkFactor = 1.1; + Type minStepSize = 1e-5; + Type effectiveStepSize = 1; + + opts() {} + void operator=(const opts &rhs) { + EnableReordering = rhs.EnableReordering; + maxNZ = rhs.maxNZ; + } +}; +#endif diff --git a/src/volesti/include/preprocess/crhmc/two_sided_barrier.h b/src/volesti/include/preprocess/crhmc/two_sided_barrier.h new file mode 100644 index 00000000..7e279ab7 --- /dev/null +++ b/src/volesti/include/preprocess/crhmc/two_sided_barrier.h @@ -0,0 +1,173 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" + +// The log barrier for the domain {lu <= x <= ub}: +// phi(x) = - sum log(x - lb) - sum log(ub - x). +#ifndef TWO_SIDED_BARIER_H +#define TWO_SIDED_BARIER_H + +#include "Eigen/Eigen" +#include "cartesian_geom/cartesian_kernel.h" +#include "preprocess/crhmc/crhmc_utils.h" +#include + +/// @brief A two sided barrier used by crhmc sampler +/// @tparam Point Point Type +template class two_sided_barrier { + + using NT = typename Point::FT; + using MT = Eigen::Matrix; + using VT = Eigen::Matrix; + +public: + VT lb; + VT ub; + int vdim; + int n; + std::vector upperIdx; + std::vector lowerIdx; + std::vector freeIdx; + VT center; + const NT max_step = 1e16; // largest step size + const NT regularization_constant = 1e-20; // small regularization to not have a large inverse + const NT unbounded_center_coord = 1e6; + MT extraHessian; + + const NT inf = std::numeric_limits::infinity(); + + void set_bound(VT const &_lb, VT const &_ub) { + n = _lb.rows(); + extraHessian.resize(n, 1); + lb.resize(n); + ub.resize(n); + lb = _lb; + ub = _ub; + extraHessian = regularization_constant * MT::Ones(n, 1); + int x1 = 0, x2 = 0, x3 = 0; + for (int i = 0; i < n; i++) { + if (lb(i) == -inf) { + upperIdx.push_back(i); + x1++; + } + if (ub(i) == inf) { + lowerIdx.push_back(i); + x2++; + } + if (ub(i) == inf && lb(i) == -inf) { + freeIdx.push_back(i); + } + } + + VT c = (ub + lb) / 2; + VT bias1=VT::Ones(x2, 1) * unbounded_center_coord; + saxpy(c,lb,bias1,lowerIdx,lowerIdx); + VT bias2=-VT::Ones(x1, 1) * unbounded_center_coord; + saxpy(c,ub,bias2,upperIdx,upperIdx); + set(c, freeIdx, 0.0); + + center = c; + } + two_sided_barrier(VT const &_lb, VT const &_ub, int _vdim = 1) { + set_bound(_lb, _ub); + vdim = _vdim; + extraHessian = regularization_constant * MT::Ones(n,1); + } + two_sided_barrier() { vdim = 1; } + + VT gradient(VT const &x) { + return (ub - x).cwiseInverse() - (x - lb).cwiseInverse(); + } + + VT hessian(VT const &x) { + VT d = ((ub - x).cwiseProduct((ub - x))).cwiseInverse() + + ((x - lb).cwiseProduct((x - lb))).cwiseInverse(); + return d + extraHessian; + } + MT hessian(MT const &x){ + MT d = (((- x).colwise()+ub).cwiseProduct(((- x).colwise()+ub))).cwiseInverse() + + ((x.colwise() - lb).cwiseProduct((x.colwise() - lb))).cwiseInverse(); + return d + extraHessian; + } + MT tensor(MT const &x) { + MT d = 2 * ((((-x).colwise()+ub).cwiseProduct(((-x).colwise()+ub))).cwiseProduct(((-x).colwise()+ub))).cwiseInverse() - + 2 * (((x.colwise() - lb).cwiseProduct(( x.colwise() - lb))).cwiseProduct(( x.colwise() - lb))).cwiseInverse(); + return d; + } + MT quadratic_form_gradient(MT const &x, MT const &u) { + // Output the -grad of u' (hess phi(x)) u. + return (u.cwiseProduct(u)).cwiseProduct(tensor(x)); + } + VT quadratic_form_gradient(VT const &x, VT const &u) { + // Output the -grad of u' (hess phi(x)) u. + + return (u.cwiseProduct(u)).cwiseProduct(tensor(x)); + } + NT step_size(VT const &x, VT const &v) { + // Output the maximum step size from x with direction v. + + // check positive direction + VT temp = (v.array() > 0).select((ub - x).cwiseQuotient(v), max_step); + NT t1 = temp.minCoeff(); + + // check negative direction + temp = (v.array() < 0).select((lb - x).cwiseQuotient(v), max_step); + NT t2 = temp.minCoeff(); + + return std::min(t1, t2); + } + VT boundary_distance(VT const &x) { + // Output the distance of x with its closest boundary for each + // coordinate + return ((x - lb).cwiseMin(ub - x)).cwiseAbs(); + } + + bool feasible(VT const &x) { + return (x.array() > lb.array() && x.array() < ub.array()).all(); + } + VT feasible(MT const &x) { + VT result=VT::Ones(x.cols()); + for(int i=0;i lb.array() && x.col(i).array() < ub.array()).all(); + } + return result; + } + + std::pair analytic_center_oracle(VT const &x) { + VT g = VT::Zero(n, 1); + VT h = VT::Zero(n, 1); + return std::make_pair(g + gradient(x), h + hessian(x)); + } + + std::pair lewis_center_oracle(VT const &x, VT const &w) { + VT g = VT::Zero(n, 1); + VT h = VT::Zero(n, 1); + return std::make_pair(g + w.cwiseProduct(gradient(x)),h + w.cwiseProduct(hessian(x))); + } + + std::pair boundary(VT const &x) { + // Output the normal at the boundary around x for each barrier. + // Assume: only 1 vector is given + + VT A = VT::Ones(x.rows(), 1); + VT b = ub; + + b = (x.array() < center.array()).select(-lb, b); + A = (x.array() < center.array()).select(-A, A); + + return std::make_pair(A, b); + } +}; +#endif diff --git a/src/volesti/include/preprocess/crhmc/weighted_two_sided_barrier.h b/src/volesti/include/preprocess/crhmc/weighted_two_sided_barrier.h new file mode 100644 index 00000000..09f5eb79 --- /dev/null +++ b/src/volesti/include/preprocess/crhmc/weighted_two_sided_barrier.h @@ -0,0 +1,166 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" + +// The log barrier for the domain {lu <= x <= ub}: +// phi(x) = - sum log(x - lb) - sum log(ub - x). +#ifndef WEIGHTED_TWO_SIDED_BARIER_H +#define WEIGHTED_TWO_SIDED_BARIER_H + +#include "Eigen/Eigen" +#include "cartesian_geom/cartesian_kernel.h" +#include "preprocess/crhmc/crhmc_utils.h" +#include + +/// @brief A weighted two sided barrier used by crhmc sampler +/// @tparam Point Point Type +template class weighted_two_sided_barrier { + + using NT = typename Point::FT; + using MT = Eigen::Matrix; + using VT = Eigen::Matrix; + +public: + VT lb; + VT ub; + int vdim; + int n; + std::vector upperIdx; + std::vector lowerIdx; + std::vector freeIdx; + VT center; + const NT max_step = 1e16; // largest step size + const NT regularization_constant = 1e-20; // small regularization to not have a large inverse + const NT unbounded_center_coord = 1e6; + MT extraHessian; + const NT inf = std::numeric_limits::infinity(); + + VT w; + + weighted_two_sided_barrier(VT const &_lb, VT const &_ub, VT const &_w, + int _vdim = 1) { + set_bound(_lb, _ub); + w = _w; + vdim = _vdim; + extraHessian = regularization_constant * VT::Ones(n,1); + } + weighted_two_sided_barrier() { vdim = 1; } + + VT gradient(VT const &x) { + return w.cwiseQuotient(ub - x) - w.cwiseQuotient(x - lb); + } + + VT hessian(VT const &x) { + VT d = w.cwiseQuotient((ub - x).cwiseProduct((ub - x))) + + w.cwiseQuotient((x - lb).cwiseProduct((x - lb))); + return d + extraHessian; + } + MT hessian(MT const &x){ + MT d = (((- x).colwise()+ub).cwiseProduct(((- x).colwise()+ub))).cwiseInverse() + + ((x.colwise() - lb).cwiseProduct((x.colwise() - lb))).cwiseInverse(); + return w.asDiagonal()*d + extraHessian; + } + VT tensor(VT const &x) { + VT d = 2 * w.cwiseQuotient(((ub - x).cwiseProduct((ub - x))).cwiseProduct((ub - x))) - + 2 * w.cwiseQuotient(((x - lb).cwiseProduct((x - lb))).cwiseProduct((x - lb))); + return d; + } + MT tensor(MT const &x) { + MT d = 2 * ((((-x).colwise()+ub).cwiseProduct(((-x).colwise()+ub))).cwiseProduct(((-x).colwise()+ub))).cwiseInverse() - + 2 * (((x.colwise() - lb).cwiseProduct(( x.colwise() - lb))).cwiseProduct(( x.colwise() - lb))).cwiseInverse(); + return w.asDiagonal()*d; + } + MT quadratic_form_gradient(MT const &x, MT const &u) { + // Output the -grad of u' (hess phi(x)) u. + return (u.cwiseProduct(u)).cwiseProduct(tensor(x)); + } + VT quadratic_form_gradient(VT const &x, VT const &u) { + // Output the -grad of u' (hess phi(x)) u. + + return (u.cwiseProduct(u)).cwiseProduct(tensor(x)); + } + NT step_size(VT const &x, VT const &v) { + // Output the maximum step size from x with direction v or -v. + + // check positive direction + VT temp = (v.array() > 0).select((ub - x).cwiseQuotient(v), max_step); + NT t1 = temp.minCoeff(); + + // check negative direction + temp = (v.array() < 0).select((lb - x).cwiseQuotient(v), max_step); + NT t2 = temp.minCoeff(); + + return std::min(t1, t2); + } + VT boundary_distance(VT const &x) { + // Output the distance of x with its closest boundary for each + // coordinate + return ((x - lb).cwiseMin(ub - x)).cwiseAbs(); + } + + bool feasible(VT const &x) { + return (x.array() > lb.array() && x.array() < ub.array()).all(); + } + VT feasible(MT const &x) { + VT result=VT::Ones(x.cols()); + for(int i=0;i lb.array() && x.col(i).array() < ub.array()).all(); + } + return result; + } + void set_bound(VT const &_lb, VT const &_ub) { + + lb = _lb; + ub = _ub; + n = lb.rows(); + extraHessian = regularization_constant * VT::Ones(n); + int x1 = 0, x2 = 0, x3 = 0; + for (int i = 0; i < n; i++) { + if (lb(i) == -inf) { + upperIdx.push_back(i); + x1++; + } + if (ub(i) == inf) { + lowerIdx.push_back(i); + x2++; + } + if (ub(i) == inf && lb(i) == -inf) { + freeIdx.push_back(i); + } + } + + VT c = (ub + lb) / 2; + VT bias1=VT::Ones(x2, 1) * unbounded_center_coord; + saxpy(c,lb,bias1,lowerIdx,lowerIdx); + VT bias2=-VT::Ones(x1, 1) * unbounded_center_coord; + saxpy(c,ub,bias2,upperIdx,upperIdx); + set(c, freeIdx, 0.0); + + center = c; + } + + std::pair boundary(VT const &x) { + // Output the normal at the boundary around x for each barrier. + // Assume: only 1 vector is given + VT A = VT::Ones(x.rows(), 1); + VT b = ub; + + b = (x.array() < center.array()).select(-lb, b); + A = (x.array() < center.array()).select(-A, A); + + return std::make_pair(A, b); + } +}; +#endif diff --git a/src/volesti/include/preprocess/estimate_L_smooth_parameter.hpp b/src/volesti/include/preprocess/estimate_L_smooth_parameter.hpp new file mode 100644 index 00000000..8f5a1e89 --- /dev/null +++ b/src/volesti/include/preprocess/estimate_L_smooth_parameter.hpp @@ -0,0 +1,73 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + + +#ifndef ESTIMATE_L_SMOOTH_PARAMETER_HPP +#define ESTIMATE_L_SMOOTH_PARAMETER_HPP + +#include "random_walks/random_walks.hpp" + +template +< + typename WalkTypePolicy = AcceleratedBilliardWalk, + typename Polytope, + typename Point, + typename NegativeGradientFunctor, + typename RandomNumberGenerator +> +double estimate_L_smooth(Polytope &P, Point &p, unsigned int const& walk_length, + NegativeGradientFunctor F, RandomNumberGenerator &rng) +{ + typedef typename Point::FT NT; + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > RandomWalk; + + P.ComputeInnerBall(); + + unsigned int d = P.dimension(); + unsigned int rnum = 5 * d; + std::vector randPoints(1); + std::vector vecPoint1; + std::vector vecPoint2; + std::vector< std::vector > listOfPoints; + Point F1; + + RandomWalk walk(P, p, rng); + for (unsigned int i=0; i::lowest(), Ltemp; + + for (auto pit=listOfPoints.begin(); pit!=(listOfPoints.end()-1); ++pit) + { + F1 = F(1, *pit, 0); + + for (auto qit=(pit+1); qit!=listOfPoints.end(); ++qit) + { + vecPoint2 = *qit; + Ltemp = (F1 - F(1, *qit, 0)).length() / ((*pit)[0] - (*qit)[0]).length(); + + if (Ltemp > L) + { + L = Ltemp; + } + } + } + return L; +} + + +#endif diff --git a/src/volesti/include/preprocess/max_inscribed_ball.hpp b/src/volesti/include/preprocess/max_inscribed_ball.hpp new file mode 100644 index 00000000..6fe70e9a --- /dev/null +++ b/src/volesti/include/preprocess/max_inscribed_ball.hpp @@ -0,0 +1,216 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + + +#ifndef MAX_INNER_BALL +#define MAX_INNER_BALL + +/* + This implmentation computes the largest inscribed ball in a given convex polytope P. + The polytope has to be given in H-representation P = {x | Ax <= b} and the rows of A + has to be normalized. It solves the Linear program: max t, s.t. Ax + t*e <= b, where + e is the vector of ones. + + The implementation is based on Yin Zhang's Matlab implementation in https://github.com/Bounciness/Volume-and-Sampling/blob/1c7adfb46c2c01037e625db76ff00e73616441d4/external/mve11/mve_cobra/mve_presolve_cobra.m + + Input: matrix A, vector b such that the polytope P = {x | Ax<=b} + tolerance parameter tol + + Output: center of the ball x + radius r +*/ + +template +void calcstep(MT const& A, MT const& A_trans, MT const& R, VT &s, + VT &y, VT &r1, VT const& r2, NT const& r3, VT &r4, + VT &dx, VT &ds, NT &dt, VT &dy, VT &tmp, VT &rhs) +{ + int m = A.rows(), n = A.cols(); + NT *vec_iter1 = tmp.data(), *vec_iter2 = y.data(), *vec_iter3 = s.data(), + *vec_iter4 = r1.data(), *vec_iter5 = r4.data(); + for (int i = 0; i < m; ++i) { + *vec_iter1 = ((*vec_iter4) / (*vec_iter2) - (*vec_iter5)) / (*vec_iter3); + vec_iter1++; vec_iter2++; vec_iter3++; vec_iter4++; vec_iter5++; + } + + rhs.block(0,0,n,1).noalias() = r2 + A_trans * tmp; + rhs(n) = r3 + tmp.sum(); + VT dxdt = R.colPivHouseholderQr().solve(R.transpose().colPivHouseholderQr().solve(rhs)); + + dx = dxdt.block(0,0,n,1); + dt = dxdt(n); + ds.noalias() = r1 - A*dx - VT::Ones(m) * dt; + vec_iter1 = dy.data(); vec_iter2 = r4.data(); vec_iter3 = y.data(); + vec_iter4 = ds.data(); vec_iter5 = s.data(); + + for (int i = 0; i < m; ++i) { + *vec_iter1 = ((*vec_iter2) - (*vec_iter3) * (*vec_iter4)) / (*vec_iter5); + vec_iter1++; vec_iter2++; vec_iter3++; vec_iter4++; vec_iter5++; + } +} + + +template +std::tuple max_inscribed_ball(MT const& A, VT const& b, unsigned int maxiter, NT tol) +{ + int m = A.rows(), n = A.cols(); + bool converge; + + NT bnrm = b.norm(); + VT o_m = VT::Zero(m), o_n = VT::Zero(n), e_m = VT::Ones(m); + + VT x = o_n, y = e_m / m; + NT t = b.minCoeff() - 1.0; + VT s = b - e_m * t; + + VT dx = o_n; + VT dxc = dx, ds = o_m; + VT dsc = ds, dy = o_m, mu_ds_dy(m), tmp(m), rhs(n + 1); + VT dyc = dy, r1(m), r2(n), r4(m), r23(n + 1), AtDe(n), d(m); + + NT dt = NT(0), dtc = NT(0), tau, sigma0 = 0.2, r3, gap, + prif, drif, rgap, total_err, alphap, alphad, + ratio, sigma, mu, t_prev = 1000.0 * t + 100.0; + + NT const tau0 = 0.995, power_num = 5.0 * std::pow(10.0, 15.0); + NT *vec_iter1, *vec_iter2, *vec_iter3, *vec_iter4; + + MT B(n + 1, n + 1), AtD(n, m), R(n + 1, n + 1), + eEye = std::pow(10.0, -14.0) * MT::Identity(n + 1, n + 1), + A_trans = A.transpose(); + + for (unsigned int i = 0; i < maxiter; ++i) { + + // KKT residuals + r1.noalias() = b - (A * x + s + t * e_m); + r2.noalias() = -A_trans * y; + r3 = 1.0 - y.sum(); + r4 = -s.cwiseProduct(y); + + r23.block(0, 0, n, 1) = r2; + r23(n) = r3; + gap = -r4.sum(); + + // relative residual norms and gap + prif = r1.norm() / (1.0 + bnrm); + drif = r23.norm() / 10.0; + rgap = std::abs(b.dot(y) - t) / (1.0 + std::abs(t)); + total_err = std::max(prif, drif); + total_err = std::max(total_err, rgap); + + // progress output & check stopping + if (total_err < tol || ( t > 0 && ( (std::abs(t - t_prev) <= tol * t && std::abs(t - t_prev) <= tol * t_prev) + || (t_prev >= (1.0 - tol) * t && i > 0) + || (t <= (1.0 - tol) * t_prev && i > 0) ) ) ) + { + //converged + converge = true; + break; + } + + if (dt > 1000.0 * bnrm || t > 1000000.0 * bnrm) + { + //unbounded + converge = false; + break; + } + + // Shur complement matrix + vec_iter1 = d.data(); + vec_iter3 = s.data(); + vec_iter2 = y.data(); + for (int j = 0; j < m; ++j) { + *vec_iter1 = std::min(power_num, (*vec_iter2) / (*vec_iter3)); + AtD.col(j).noalias() = A_trans.col(j) * (*vec_iter1); + vec_iter1++; + vec_iter3++; + vec_iter2++; + } + + AtDe.noalias() = AtD * e_m; + B.block(0, 0, n, n).noalias() = AtD * A; + B.block(0, n, n, 1).noalias() = AtDe; + B.block(n, 0, 1, n).noalias() = AtDe.transpose(); + B(n, n) = d.sum(); + B.noalias() += eEye; + + // Cholesky decomposition + Eigen::LLT lltOfB(B); + R = lltOfB.matrixL().transpose(); + + // predictor step & length + calcstep(A, A_trans, R, s, y, r1, r2, r3, r4, dx, ds, dt, dy, tmp, rhs); + + alphap = -1.0; + alphad = -1.0; + vec_iter1 = ds.data(); + vec_iter2 = s.data(); + vec_iter3 = dy.data(); + vec_iter4 = y.data(); + + for (int j = 0; j < m; ++j) { + alphap = std::min(alphap, (*vec_iter1) / (*vec_iter2)); + alphad = std::min(alphad, (*vec_iter3) / (*vec_iter4)); + vec_iter1++; + vec_iter2++; + vec_iter3++; + vec_iter4++; + } + alphap = -1.0 / alphap; + alphad = -1.0 / alphad; + + // determine mu + ratio = (s + alphap * ds).dot((y + alphad * dy)) / gap; + sigma = std::min(sigma0, ratio * ratio); + mu = (sigma * gap) / NT(m); + + // corrector and combined step & length + mu_ds_dy.noalias() = e_m * mu - ds.cwiseProduct(dy); + calcstep(A, A_trans, R, s, y, o_m, o_n, 0.0, mu_ds_dy, dxc, dsc, dtc, dyc, tmp, rhs); + + dx += dxc; + ds += dsc; + dt += dtc; + dy += dyc; + + alphap = -0.5; + alphad = -0.5; + vec_iter1 = ds.data(); + vec_iter2 = s.data(); + vec_iter3 = dy.data(); + vec_iter4 = y.data(); + + for (int j = 0; j < m; ++j) { + alphap = std::min(alphap, (*vec_iter1) / (*vec_iter2)); + alphad = std::min(alphad, (*vec_iter3) / (*vec_iter4)); + vec_iter1++; + vec_iter2++; + vec_iter3++; + vec_iter4++; + } + alphap = -1.0 / alphap; + alphad = -1.0 / alphad; + + // update iterates + tau = std::max(tau0, 1.0 - gap / NT(m)); + alphap = std::min(1.0, tau * alphap); + alphad = std::min(1.0, tau * alphad); + + x += alphap * dx; + s += alphap * ds; + t_prev = t; + t += alphap * dt; + y += alphad * dy; + } + + std::tuple result = std::make_tuple(x, t, converge); + return result; +} + +#endif diff --git a/src/volesti/include/preprocess/max_inscribed_ellipsoid.hpp b/src/volesti/include/preprocess/max_inscribed_ellipsoid.hpp new file mode 100644 index 00000000..ebdb261e --- /dev/null +++ b/src/volesti/include/preprocess/max_inscribed_ellipsoid.hpp @@ -0,0 +1,236 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2021 Vaibhav Thakkar + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. +//Contributed and/or modified by Vaibhav Thakkar, as part of Google Summer of Code 2021 program. + +// Licensed under GNU LGPL.3, see LICENCE file + + +#ifndef MVE_COMPUTATION_HPP +#define MVE_COMPUTATION_HPP + +#include +#include + + +/* + Implementation of the interior point method to compute the largest inscribed ellipsoid in a + given convex polytope by "Yin Zhang, An Interior-Point Algorithm for the Maximum-Volume Ellipsoid + Problem (1999)". + + This C++ implementation is based on the Matlab implementation in https://github.com/Bounciness/Volume-and-Sampling/blob/1c7adfb46c2c01037e625db76ff00e73616441d4/external/mve11/mve_cobra/mve_solver_cobra.m + + The implmentation computes the largest inscribed ellipsoid {x | x = y + Es, ||s|| = 1} + + Input: matrix A, vector b such that the polytope P = {x | Ax<=b} + interior point x0 + tolerance parameters tol, reg + + Output: center of the ellipsoid y + matrix V = E_transpose * E +*/ + +// using Custom_MT as to deal with both dense and sparse matrices, MT will be the type of result matrix +template +std::pair, bool> max_inscribed_ellipsoid(Custom_MT A, VT b, VT const& x0, + unsigned int const& maxiter, + NT const& tol, NT const& reg) +{ + typedef Eigen::DiagonalMatrix Diagonal_MT; + + int m = A.rows(), n = A.cols(); + bool converged = false; + + NT bnrm = b.norm(), + last_r1 = std::numeric_limits::lowest(), + last_r2 = std::numeric_limits::lowest(), + prev_obj = std::numeric_limits::lowest(), + gap, rmu, res, objval, r1, r2 ,r3, rel, Rel, + astep, ax, ay, az, tau; + + NT const reg_lim = std::pow(10.0, -10.0), tau0 = 0.75, minmu = std::pow(10.0, -8.0); + + NT *vec_iter1, *vec_iter2, *vec_iter3; + + VT x = VT::Zero(n), y = VT::Ones(m), bmAx = VT::Ones(m), + h(m), z(m), yz(m), yh(m), R1(n), R2(m), R3(m), y2h(m), y2h_z(m), h_z(m), + R3Dy(m), R23(m), dx(n), Adx(m), dyDy(m), dy(m), dz(m); + + VT const bmAx0 = b - A * x0, ones_m = VT::Ones(m); + + MT Q(m, m), E2(n, n), YQ(m,m), G(m,m), T(m,n), ATP(n,m), ATP_A(n,n); + Diagonal_MT Y(m); + Custom_MT YA(m, n); + + A = (ones_m.cwiseProduct(bmAx0.cwiseInverse())).asDiagonal() * A, b = ones_m; + Custom_MT A_trans = A.transpose(); + + int i = 1; + while (i <= maxiter) { + + Y = y.asDiagonal(); + + E2.noalias() = MT(A_trans * Y * A).inverse(); + + Q.noalias() = A * E2 * A_trans; + h = Q.diagonal(); + h = h.cwiseSqrt(); + + if (i == 1) { + // perform those computations only during the first iteration + NT t = bmAx.cwiseProduct(h.cwiseInverse()).minCoeff(); + y *= (1.0 / (t * t)); + h *= t; + vec_iter1 = bmAx.data(); + vec_iter2 = h.data(); + vec_iter3 = z.data(); + for (int j = 0; j < m; ++j) { + *vec_iter3 = std::max(0.1, (*vec_iter1 - (*vec_iter2))); + vec_iter1++; + vec_iter2++; + vec_iter3++; + } + Q *= (t * t); + Y = Y * (1.0 / (t * t)); + } + + yz = y.cwiseProduct(z); + yh = y.cwiseProduct(h); + + gap = yz.sum() / NT(m); // compute the gap between primal and dual solution + rmu = std::min(0.5, gap) * gap; + rmu = std::max(rmu, minmu); + + R1.noalias() = - A_trans * yh; + R2 = bmAx - h - z; + R3.noalias() = rmu * ones_m - yz; + + r1 = R1.template lpNorm(); + r2 = R2.template lpNorm(); + r3 = R3.template lpNorm(); + + res = std::max(r1, r2); + res = std::max(res, r3); + objval = std::log(E2.determinant()) / 2.0; + + Eigen::SelfAdjointEigenSolver eigensolver(E2); // E2 is positive definite matrix + // computing eigenvalues of E2 + rel = eigensolver.eigenvalues().minCoeff(); + Rel = eigensolver.eigenvalues().maxCoeff(); + + if (i % 10 == 0) { + + if (std::abs((last_r1 - r1) / std::min(NT(std::abs(last_r1)), NT(std::abs(r1)))) < 0.01 && + std::abs((last_r2 - r2) / std::min(NT(abs(last_r2)), NT(std::abs(r2)))) < 0.01 && + Rel / rel > 100.0 && + reg > reg_lim) { + converged = false; + //Stopped making progress + break; + } + last_r2 = r2; + last_r1 = r1; + } + + // stopping criterion + if ((res < tol * (1.0 + bnrm) && rmu <= minmu) || + (i > 4 && prev_obj != std::numeric_limits::lowest() && + ((std::abs(objval - prev_obj) <= tol * objval && std::abs(objval - prev_obj) <= tol * prev_obj) || + (prev_obj >= (1.0 - tol) * objval || objval <= (1.0 - tol) * prev_obj) ) ) ) { + //converged + x += x0; + converged = true; + break; + } + + prev_obj = objval; // storing the objective value of the previous iteration + YQ.noalias() = Y * Q; + G = YQ.cwiseProduct(YQ.transpose()); + y2h = 2.0 * yh; + YA = Y * A; + + vec_iter1 = y2h.data(); + vec_iter2 = z.data(); + vec_iter3 = y2h_z.data(); + for (int j = 0; j < m; ++j) { + *vec_iter3 = std::max(reg, (*vec_iter1) * (*vec_iter2)); + vec_iter1++; + vec_iter2++; + vec_iter3++; + } + + G.diagonal() += y2h_z; + h_z = h + z; + + for (int j = 0; j < n; ++j) { + T.col(j) = G.colPivHouseholderQr().solve( VT(YA.col(j).cwiseProduct(h_z)) ); + } + ATP.noalias() = MT(y2h.asDiagonal()*T - YA).transpose(); + + vec_iter1 = R3.data(); + vec_iter2 = y.data(); + vec_iter3 = R3Dy.data(); + for (int j = 0; j < m; ++j) { + *vec_iter3 = (*vec_iter1) / (*vec_iter2); + vec_iter1++; + vec_iter2++; + vec_iter3++; + } + + R23 = R2 - R3Dy; + ATP_A.noalias() = ATP * A; + ATP_A.diagonal() += ones_m * reg; + dx = ATP_A.colPivHouseholderQr().solve(R1 + ATP * R23); // predictor step + + // corrector and combined step & length + Adx.noalias() = A * dx; + dyDy = G.colPivHouseholderQr().solve(y2h.cwiseProduct(Adx-R23)); + + dy = y.cwiseProduct(dyDy); + dz = R3Dy - z.cwiseProduct(dyDy); + + vec_iter1 = Adx.data(); + vec_iter2 = bmAx.data(); + ax = -0.5; + for (int j = 0; j < m; ++j) { + ax = std::min(ax, - (*vec_iter1) / (*vec_iter2)); + vec_iter1++; + vec_iter2++; + } + + ax = -1.0 / ax; + ay = -1.0 / std::min(dyDy.minCoeff(), -0.5); + + vec_iter1 = dz.data(); + vec_iter2 = z.data(); + az = -0.5; + for (int j = 0; j < m; ++j) { + az = std::min(az, (*vec_iter1) / (*vec_iter2)); + vec_iter1++; + vec_iter2++; + } + + az = -1.0 / az; + tau = std::max(tau0, 1.0 - res); + astep = tau * std::min(std::min(1.0, ax), std::min(ay, az)); // compute the step + + // update iterates + x += astep * dx; + y += astep * dy; + z += astep * dz; + + bmAx -= astep * Adx; + + i++; + } + + return std::pair, bool>(std::pair(E2, x), converged); + +} + + +#endif diff --git a/src/volesti/include/preprocess/max_inscribed_ellipsoid_rounding.hpp b/src/volesti/include/preprocess/max_inscribed_ellipsoid_rounding.hpp new file mode 100644 index 00000000..c13d53f7 --- /dev/null +++ b/src/volesti/include/preprocess/max_inscribed_ellipsoid_rounding.hpp @@ -0,0 +1,77 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + + +#ifndef MAX_ELLIPSOID_ROUNDING_HPP +#define MAX_ELLIPSOID_ROUNDING_HPP + +#include "max_inscribed_ellipsoid.hpp" + +template +< + typename MT, + typename VT, + typename NT, + typename Polytope, + typename Point +> +std::tuple max_inscribed_ellipsoid_rounding(Polytope &P, + Point const& InnerPoint) +{ + std::pair, bool> iter_res; + iter_res.second = false; + + VT x0 = InnerPoint.getCoefficients(); + MT E, L; + unsigned int maxiter = 150, iter = 1, d = P.dimension(); + + NT R = 100.0, r = 1.0, tol = std::pow(10, -6.0), reg = std::pow(10, -4.0), round_val = 1.0; + + MT T = MT::Identity(d, d); + VT shift = VT::Zero(d); + + while (true) + { + // compute the largest inscribed ellipsoid in P centered at x0 + iter_res = max_inscribed_ellipsoid(P.get_mat(), P.get_vec(), x0, maxiter, tol, reg); + E = iter_res.first.first; + E = (E + E.transpose()) / 2.0; + E = E + MT::Identity(d, d)*std::pow(10, -8.0); //normalize E + + Eigen::LLT lltOfA(E); // compute the Cholesky decomposition of E + L = lltOfA.matrixL(); + + Eigen::SelfAdjointEigenSolver eigensolver(L); + r = eigensolver.eigenvalues().minCoeff(); + R = eigensolver.eigenvalues().maxCoeff(); + + // check the roundness of the polytope + if(((std::abs(R / r) <= 2.3 && iter_res.second) || iter >= 20) && iter>2){ + break; + } + + // shift polytope and apply the linear transformation on P + P.shift(iter_res.first.second); + shift += T * iter_res.first.second; + T = T * L; + round_val *= L.transpose().determinant(); + P.linear_transformIt(L); + + reg = std::max(reg / 10.0, std::pow(10, -10.0)); + P.normalize(); + x0 = VT::Zero(d); + + iter++; + } + + std::tuple result = std::make_tuple(T, shift, std::abs(round_val)); + return result; +} + +#endif diff --git a/src/volesti/include/preprocess/min_sampling_covering_ellipsoid_rounding.hpp b/src/volesti/include/preprocess/min_sampling_covering_ellipsoid_rounding.hpp new file mode 100644 index 00000000..1e97835d --- /dev/null +++ b/src/volesti/include/preprocess/min_sampling_covering_ellipsoid_rounding.hpp @@ -0,0 +1,123 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef MIN_ELLIPSOID_ROUNDING_HPP +#define MIN_ELLIPSOID_ROUNDING_HPP + +#include +#include "khach.h" +#include "sampling/random_point_generators.hpp" +#include "volume/sampling_policies.hpp" + +template +< + typename WalkTypePolicy, + typename MT, + typename VT, + typename Polytope, + typename Point, + typename NT, + typename RandomNumberGenerator +> +std::tuple min_sampling_covering_ellipsoid_rounding(Polytope &P, + std::pair &InnerBall, + const unsigned int &walk_length, + RandomNumberGenerator &rng) +{ + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > WalkType; + typedef RandomPointGenerator RandomPointGenerator; + + unsigned int d = P.dimension(); + std::list randPoints; //ds for storing rand points + NT ratio = 10, round_val = 1.0; + unsigned int iter = 0, j, i; + const unsigned int num_of_samples = 10*d;//this is the number of sample points will used to compute min_ellipoid + PushBackWalkPolicy push_back_policy; + + MT T = MT::Identity(d,d); + VT shift = VT::Zero(d); + + while (ratio > 6.0 && iter < 3) + { + randPoints.clear(); + if (!P.get_points_for_rounding(randPoints)) + { // If P is a V-polytope then it will store its vertices in randPoints + // If P is not a V-Polytope or number_of_vertices>20*domension + // 2. Generate the first random point in P + // Perform random walk on random point in the Chebychev ball + Point c = InnerBall.first; + NT radius = InnerBall.second; + Point p = GetPointInDsphere::apply(d, radius, rng); + p += c; + RandomPointGenerator::apply(P, p, num_of_samples, walk_length, + randPoints, push_back_policy, rng); + } + + // Store points in a matrix to call Khachiyan algorithm for the minimum volume enclosing ellipsoid + MT Ap(d, randPoints.size()); + typename std::list::iterator rpit=randPoints.begin(); + + j = 0; + for ( ; rpit!=randPoints.end(); rpit++, j++) { + for (i=0 ; idimension(); i++){ + Ap(i,j)=double((*rpit)[i]); + } + } + MT Q(d,d); //TODO: remove dependence on ublas and copy to eigen + VT c2(d); + size_t w=1000; + KhachiyanAlgo(Ap,0.01,w,Q,c2); // call Khachiyan algorithm + + MT E(d, d); + VT e(d); + + //Get ellipsoid matrix and center as Eigen objects + for(i=0; i eigensolver(E); + NT rel = std::real(eigensolver.eigenvalues()[0]); + NT Rel = std::real(eigensolver.eigenvalues()[0]); + for(i=1; i Rel) Rel = std::real(eigensolver.eigenvalues()[i]); + } + + Eigen::LLT lltOfA(E); // compute the Cholesky decomposition of E + MT L = lltOfA.matrixL(); // retrieve factor L in the decomposition + + //Shift polytope in order to contain the origin (center of the ellipsoid) + P.shift(e); + + MT L_1 = L.inverse(); + shift = shift + T * e; + T = T * L_1.transpose(); + + P.linear_transformIt(L_1.transpose()); + InnerBall = P.ComputeInnerBall(); + round_val *= L_1.determinant(); + ratio = Rel / rel; + iter++; + } + + std::tuple result = std::make_tuple(T, shift, std::abs(round_val)); + return result; +} + + +#endif // MIN_ELLIPSOID_ROUNDING_HPP diff --git a/src/volesti/include/preprocess/svd_rounding.hpp b/src/volesti/include/preprocess/svd_rounding.hpp new file mode 100644 index 00000000..e62c03d2 --- /dev/null +++ b/src/volesti/include/preprocess/svd_rounding.hpp @@ -0,0 +1,176 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + + +#ifndef SVD_ROUNDING_HPP +#define SVD_ROUNDING_HPP + + +template +< + typename WalkTypePolicy, + typename Polytope, + typename Point, + typename MT, + typename VT, + typename RandomNumberGenerator +> +void svd_on_sample(Polytope &P, Point &p, unsigned int const& num_rounding_steps, MT &V, VT &s, VT &Means, + unsigned int const& walk_length, RandomNumberGenerator &rng) +{ + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > walk; + + typedef RandomPointGenerator RandomPointGenerator; + PushBackWalkPolicy push_back_policy; + + unsigned int N = num_rounding_steps; + + std::list randPoints; + MT RetMat(N, P.dimension()); + RandomPointGenerator::apply(P, p, N, walk_length, randPoints, + push_back_policy, rng); + + int jj = 0; + for (typename std::list::iterator rpit = randPoints.begin(); rpit!=randPoints.end(); rpit++, jj++) + { + RetMat.row(jj) = (*rpit).getCoefficients().transpose(); + } + + for (int i = 0; i < P.dimension(); ++i) { + Means(i) = RetMat.col(i).mean(); + } + + for (int i = 0; i < N; ++i) { + RetMat.row(i) = RetMat.row(i) - Means.transpose(); + } + + Eigen::BDCSVD svd(RetMat, Eigen::ComputeFullV); + s = svd.singularValues() / svd.singularValues().minCoeff(); + + if (s.maxCoeff() >= 2.0) { + for (int i = 0; i < s.size(); ++i) { + if (s(i) < 2.0) { + s(i) = 1.0; + } + } + V = svd.matrixV(); + } else { + s = VT::Ones(P.dimension()); + V = MT::Identity(P.dimension(), P.dimension()); + } +} + + +template +< + typename WalkTypePolicy, + typename MT, + typename VT, + typename Polytope, + typename Point, + typename NT, + typename RandomNumberGenerator +> + +std::tuple svd_rounding(Polytope &P, + std::pair &InnerBall, + const unsigned int &walk_length, + RandomNumberGenerator &rng) +{ + NT tol = 0.00000001; + NT R = std::pow(10,10), r = InnerBall.second; + + int n = P.dimension(), m = P.num_of_hyperplanes(); + + Polytope P_old(P); + + MT old_A(m,n), A = P.get_mat(), T = MT::Identity(n,n), round_mat, r_inv; + VT T_shift = VT::Zero(n), shift(n), s(n); + + Point p(n); + + bool done = false, last_round_under_p, fail; + + unsigned int tries=0, num_rounding_steps = 10 * n, rounding_samples = 0, round_it; + NT max_s, s_cutof, p_cutof, num_its, prev_max_s = std::numeric_limits::max(), + s_cutoff, p_cutoff; + MT V(n,n), S(n,n); + + while (!done) { + + T = MT::Identity(n, n); + T_shift = VT::Zero(n); + + round_it = 1; + max_s = std::numeric_limits::max(); + s_cutoff = 2.3; + p_cutoff = 10.0; + last_round_under_p = false; + fail = false; + num_its = 20; + + while (max_s > s_cutoff && round_it <= num_its) { + + p = InnerBall.first; + svd_on_sample(P, p, num_rounding_steps, V, s, + shift, walk_length, rng); + + rounding_samples = rounding_samples + num_rounding_steps; + max_s = s.maxCoeff(); + + if (max_s <= p_cutoff && max_s > s_cutoff) { + if (last_round_under_p) { + num_rounding_steps = num_rounding_steps * 2; + p = InnerBall.first; + svd_on_sample(P, p, num_rounding_steps, V, s, + shift, walk_length, rng); + max_s = s.maxCoeff(); + } else { + last_round_under_p = true; + } + } else { + last_round_under_p = false; + } + S = s.asDiagonal(); + round_mat = V * S; + r_inv = VT::Ones(n).cwiseProduct(s.cwiseInverse()).asDiagonal() * V.transpose(); + + if (round_it != 1 && max_s >= NT(4) * prev_max_s) { + fail = true; + break; + } + + round_it++; + prev_max_s = max_s; + + P.shift(shift); + P.linear_transformIt(round_mat); + InnerBall = P.ComputeInnerBall(); + T_shift += T * shift; + T = T * round_mat; + } + if (round_it <= num_its && !fail) { + done = true; + } else { + tries = tries + 1; + num_rounding_steps = num_rounding_steps * 2.0; + P = P_old; + } + } + + std::tuple result = std::make_tuple(T, shift, std::abs(T.determinant())); + return result; +} + + +#endif diff --git a/src/volesti/include/random_walks/boltzmann_hmc_walk.hpp b/src/volesti/include/random_walks/boltzmann_hmc_walk.hpp new file mode 100644 index 00000000..e69816e9 --- /dev/null +++ b/src/volesti/include/random_walks/boltzmann_hmc_walk.hpp @@ -0,0 +1,216 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLESTI_BOLTZMANN_HMC_WALK_HPP +#define VOLESTI_BOLTZMANN_HMC_WALK_HPP + +#include "generators/boost_random_number_generator.hpp" +#include "../sampling/sphere.hpp" + +/// The Hamiltonian Monte Carlo random walk, to sample from the Boltzmann distribution, i.e. e^(-c*x/T). +struct BoltzmannHMCWalk { +public: + + struct parameters {}; + parameters param; + + + /// The implementation of the walk + /// Currently implemented only for spectrahedra + /// with template specialization + ///@tparam ConvexBody a convex body + ///@tparam RandomNumberGenerator + template + struct Walk { + + /// The matrix/vector types we use + typedef typename ConvexBody::PointType Point; + typedef typename ConvexBody::MT MT; + typedef typename ConvexBody::VT VT; + typedef typename Point::FT NT; + + /// A struct containing the parameters for the random walk + struct Settings { + /// The number of points to "burn", before keeping the following as a sample + int walk_length; + /// For generating random numbers + RandomNumberGenerator randomNumberGenerator; + /// The c in the distribution + VT c; + /// The T in the distribution + NT temperature; + /// The diameter of the body + NT diameter; + /// Set the number of allowed reflections at each step: #reflections < reflectionsBound * dimension + unsigned int reflectionsBound; + /// When determining we can move d long till we reach the boundary, we walk d*dl, for numerical stability + NT dl; + + /// Constructs an object of Settings + /// \param[in] walkLength The number of points to "burn", before keeping the following as a sample + /// \param[in] rng For generating random numbers + /// \param[in] c The c in the distribution + /// \param[in] temperature The T in the distribution + /// \param[in] diameter The diameter of the convexbody + /// \param[in] reflectionsBound at each iteration allow reflectionsBound*dimension reflections at most + /// \param[in] dl approach the boundary with a factor of dl, for numerical stability + /// \return An instance of this struct + template + Settings(const int walkLength, const RandomNumberGenerator &randomNumberGenerator, const Point &c, const NT temperature, const NT diameter, + unsigned int reflectionsBound = 100, NT dl = 0.995) : walk_length(walkLength), randomNumberGenerator(randomNumberGenerator), + c(c.getCoefficients()), + temperature(temperature), + diameter(diameter), + reflectionsBound(reflectionsBound), dl(dl) {} + + Settings() {} + }; + + /// The parameters of the random walk + Settings settings; + + Walk() {} + + /// Constructor + /// \param[in] settings The settings of the random walk + Walk(Settings &settings) : settings(settings) {} + + /// Change the settings + /// \param[in] settings The settings of the random walk + void setSettings(Settings &settings) { + this->settings = settings; + } + + /// Samples random points from the convexbody from the Boltzmann distribution + /// \param[in] convexbody A convexbody + /// \param[in] interiorPoint A point in the interior of the convexbody + /// \param[in] pointsNum The number of points to sample + /// \param[out] points The list of the sampled points + /// \tparam Point class Point with NT and VT as declared above in this class + template + void apply(ConvexBody &convexbody, Point const & interiorPoint, const unsigned int pointsNum, + std::list &points) { + // store intermediate results between successive calls of methods + // of the class convexbody, to avoid repeating computations + + VT p = interiorPoint.getCoefficients(); + + // sample #pointsNum points + for (unsigned int i = 1; i <= pointsNum; ++i) { + // burn #walk_length points to get one sample + for (unsigned int j = 0; j < settings.walk_length; ++j) { + getNextPoint(convexbody, p); + } + + // add the sample in the return list + points.push_back(Point(p)); + } + } + + + /// A single step of the HMC random walk: choose a direction and walk on the trajectory for a random distance. + /// If it hits the boundary, the trajectory is reflected. If #reflections < reflectionsBound * dimension, it returns the same point + /// \param[in] convexbody A convexbody + /// \param[in, out] p An interior point, and the next point in the random walk + /// \tparam Point + template + void getNextPoint(ConvexBody &convexbody, VT &p) { + + // initialize + RandomNumberGenerator &rng = settings.randomNumberGenerator; + boost::random::uniform_real_distribution<> urdist(0, 1); + const NT dl = settings.dl; + unsigned int n = convexbody.dimension(); + int reflectionsNum = 0; + int reflectionsNumBound = settings.reflectionsBound * n; + VT previousPoint; + VT p0 = p; + + // choose a distance to walk + NT T = rng.sample_urdist() * settings.diameter; + + // The trajectory will be of the form a*t^2 + vt + p + // where a = -c / 2*temperature + // and at each reflection, v and p will change + + // crate vector a + VT a = -settings.c / (2 * settings.temperature); + + // The vector v will be a random a direction + VT v = GetDirection::apply(n, rng).getCoefficients(); + + // Begin a step of the random walk + // Also, count the reflections and respect the bound + while (reflectionsNum++ < reflectionsNumBound) { + + // we are at point p and the trajectory a*t^2 + vt + p + // find how long we can walk till we hit the boundary + NT lambda = convexbody.positiveQuadIntersection(a, v, p); + + // We just solved a quadratic polynomial eigenvalue system At^2 + Bt + C, + // where A = lmi(a) - A0, B = lmi(v) - A0 and C = lmi(p) + // and also did a linearization creating two matrices X, Y. + // For the subsequent calls, we don't have to compute these matrices from scratch, + // but can efficiently update them. + // A remains the same + // C := A*lambda^2 + B*lambda + C + // X, Y will be updated in class convexbody + // Set the flags + convexbody.set_flags(true); + + // if we can walk the remaining distance without reaching he boundary + if (T <= lambda) { + // set the new point p:= (T^2)*a + T*V + p + p += (T * T) * a + T * v; + + // update matrix C + convexbody.update_C(T); + return; + } + + // we hit the boundary and still have to walk + // don't go all the way to the boundary, for numerical stability + lambda *= dl; + + // save current and set new point + previousPoint = p; + p += (lambda * lambda) * a + lambda * v; + + // update remaining distance we must walk + T -= lambda; + + // update matrix C + convexbody.update_C(lambda); + //precomputedValues.C += (lambda * lambda) * precomputedValues.A + lambda * precomputedValues.B; + + // Set v to have the direction of the trajectory at t = lambda + // i.e. the gradient of at^2 + vt + p, for t = lambda + v += (lambda * 2) * a; + + // compute reflected direction + convexbody.compute_reflection(v, p); + } + + // if the #reflections exceeded the limit, don't move + if (reflectionsNum == reflectionsNumBound) { + p = p0; + convexbody.set_flags(false); + } + } + + /// Sets the temperature in the distribution + /// \param[in] temperature New value of temperature + void setTemperature(NT temperature) { + settings.temperature = temperature; + } + }; + +}; + +#endif //VOLESTI_BOLTZMANN_HMC_WALK_HPP diff --git a/src/volesti/include/random_walks/boundary_cdhr_walk.hpp b/src/volesti/include/random_walks/boundary_cdhr_walk.hpp new file mode 100644 index 00000000..5e6b5224 --- /dev/null +++ b/src/volesti/include/random_walks/boundary_cdhr_walk.hpp @@ -0,0 +1,91 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_BOUNDARY_CDHR_WALK_HPP +#define RANDOM_WALKS_BOUNDARY_CDHR_WALK_HPP + +#include "sampling/sphere.hpp" + +// random directions hit-and-run walk with uniform target distribution +// from boundary + +struct BCDHRWalk +{ + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + + template + Walk(GenericPolytope& P, Point const& p, RandomNumberGenerator& rng) + { + initialize(P, p, rng); + } + + template + < + typename BallPolytope + > + inline void apply(BallPolytope const& P, + Point& p1, // a point to start + Point& p2, + unsigned int const& walk_length, + RandomNumberGenerator& rng) + { + std::pair bpair; + for (auto j = 0u; j < walk_length; ++j) + { + auto rand_coord_prev = _rand_coord; + _rand_coord = rng.sample_uidist(); + NT kapa = rng.sample_urdist(); + bpair = P.line_intersect_coord(_p, + _p_prev, + _rand_coord, + rand_coord_prev, + _lamdas); + _p_prev = _p; + _p.set_coord(_rand_coord, _p[_rand_coord] + bpair.first + kapa + * (bpair.second - bpair.first)); + } + p1 = _p_prev; + p2 = _p_prev; + p1.set_coord(_rand_coord, _p_prev[_rand_coord] + bpair.first); + p2.set_coord(_rand_coord, _p_prev[_rand_coord] + bpair.second); + } + + private : + + template + inline void initialize(GenericBody const& P, + Point const& p, + RandomNumberGenerator& rng) + { + _lamdas.setZero(P.num_of_hyperplanes()); + _rand_coord = rng.sample_uidist(); + NT kapa = rng.sample_urdist(); + _p = p; + std::pair bpair = P.line_intersect_coord(_p, _rand_coord, + _lamdas); + _p_prev = _p; + _p.set_coord(_rand_coord, _p[_rand_coord] + bpair.first + kapa + * (bpair.second - bpair.first)); + } + + unsigned int _rand_coord; + Point _p; + Point _p_prev; + typename Point::Coeff _lamdas; + }; + +}; + +#endif // RANDOM_WALKS_BOUNDARY_CDHR_WALK_HPP diff --git a/src/volesti/include/random_walks/boundary_rdhr_walk.hpp b/src/volesti/include/random_walks/boundary_rdhr_walk.hpp new file mode 100644 index 00000000..29d500b6 --- /dev/null +++ b/src/volesti/include/random_walks/boundary_rdhr_walk.hpp @@ -0,0 +1,85 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_BOUNDARY_RDHR_WALK_HPP +#define RANDOM_WALKS_BOUNDARY_RDHR_WALK_HPP + +#include "sampling/sphere.hpp" + +// Random directions hit-and-run walk with uniform target distribution +// from the boundary + +struct BRDHRWalk +{ + + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + + template + Walk(GenericPolytope& P, Point const& p, RandomNumberGenerator& rng) + { + initialize(P, p, rng); + } + + template + < + typename BallPolytope + > + inline void apply(BallPolytope const& P, + Point& p1, // a point to start + Point& p2, + unsigned int const& walk_length, + RandomNumberGenerator& rng) + { + for (auto j=0u; j::apply(P.dimension(), rng); + std::pair bpair = P.line_intersect(_p, v, _lamdas, _Av, + _lambda); + _lambda = rng.sample_urdist() * (bpair.first - bpair.second) + + bpair.second; + p1 = (bpair.first * v); + p1 += _p; + p2 = (bpair.second * v); + p2 += _p; + _p += (_lambda * v); + } + } + + private : + + template + inline void initialize(GenericBody const& P, + Point const& p, + RandomNumberGenerator& rng) + { + _lamdas.setZero(P.num_of_hyperplanes()); + _Av.setZero(P.num_of_hyperplanes()); + + Point v = GetDirection::apply(P.dimension(), rng); + std::pair bpair = P.line_intersect(p, v, _lamdas, _Av); + _lambda = rng.sample_urdist() * (bpair.first - bpair.second) + bpair.second; + _p = (_lambda * v) + p; + } + + Point _p; + NT _lambda; + typename Point::Coeff _lamdas; + typename Point::Coeff _Av; + }; + +}; + + +#endif // RANDOM_WALKS_BOUNDARY_RDHR_WALK_HPP diff --git a/src/volesti/include/random_walks/compute_diameter.hpp b/src/volesti/include/random_walks/compute_diameter.hpp new file mode 100644 index 00000000..b20aa839 --- /dev/null +++ b/src/volesti/include/random_walks/compute_diameter.hpp @@ -0,0 +1,225 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2022 Vissarion Fisikopoulos +// Copyright (c) 2018-2022 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_COMPUTE_DIAMETER_HPP +#define RANDOM_WALKS_COMPUTE_DIAMETER_HPP + +#include "convex_bodies/ball.h" +#include "convex_bodies/ballintersectconvex.h" +#include "convex_bodies/hpolytope.h" +#include "convex_bodies/spectrahedra/spectrahedron.h" +#ifndef DISABLE_LPSOLVE + #include "convex_bodies/vpolytope.h" + #include "convex_bodies/vpolyintersectvpoly.h" + #include "convex_bodies/zpolytope.h" + #include "convex_bodies/zonoIntersecthpoly.h" +#endif +#include "convex_bodies/orderpolytope.h" +#include "convex_bodies/ellipsoid.h" + + +template +struct compute_diameter +{ + template + static NT compute(GenericPolytope) {return NT(0);} +}; + + +template +struct compute_diameter> +{ + template + static NT compute(HPolytope &P) + { + return NT(2) * std::sqrt(NT(P.dimension())) * P.InnerBall().second; + } +}; + +template +struct compute_diameter> +{ + template + static NT compute(Spectrahedron &P) + { + std::pair inner_ball = P.ComputeInnerBall(); + return NT(6) * NT(P.dimension()) * inner_ball.second; + } +}; + +template +struct compute_diameter> +{ + template + static NT compute(CorrelationSpectrahedron &P) + { + std::pair inner_ball = P.getInnerBall(); + return NT(P.dimension()) * inner_ball.second; + } +}; + +template +struct compute_diameter> +{ + template + static NT compute(CorrelationSpectrahedron_MT &P) + { + std::pair inner_ball = P.getInnerBall(); + return NT(P.dimension()) * inner_ball.second; + } +}; + +#ifndef DISABLE_LPSOLVE +template +struct compute_diameter> +{ + template + static NT compute(VPolytope &P) + { + typedef typename VPolytope::MT MT; + NT diameter = NT(0), diam_iter; + MT V = P.get_mat(); + for (int i = 0; i < V.rows(); ++i) { + for (int j = 0; j < V.rows(); ++j) { + if (i != j) { + diam_iter = (V.row(i) - V.row(j)).norm(); + if (diam_iter > diameter) diameter = diam_iter; + } + } + } + return diameter; + } +}; + +template +struct compute_diameter> +{ + template + static NT compute(Zonotope &P) + { + typedef typename Zonotope::MT MT; + typedef typename Zonotope::VT VT; + + MT V = P.get_mat(); + int k = V.rows(), max_index = -1; + MT D = V.transpose() * V; + D = (D + D.transpose()) / 2.0; + Eigen::SelfAdjointEigenSolver es(D); + MT D2 = es.eigenvalues().asDiagonal(), Q = es.eigenvectors(); + + NT max_eig = NT(0); + for (int i = 0; i < P.dimension(); ++i) { + if (es.eigenvalues()[i] > max_eig) { + max_eig = es.eigenvalues()[i]; + max_index = i; + } + } + + VT max_eigvec = -1.0 * Q.col(max_index); + VT obj_fun = max_eigvec.transpose() * V.transpose(), x0(k); + + for (int j = 0; j < k; ++j) x0(j) = (obj_fun(j) < 0.0) ? -1.0 : 1.0; + + return NT(2) * (V.transpose() * x0).norm(); + } +}; + +template +struct compute_diameter, RandomNumberGenerator>> +{ + template + static NT compute(IntersectionOfVpoly, RandomNumberGenerator> &P) + { + return NT(2) * NT(P.dimension()) * P.InnerBall().second; + } +}; + +template +struct compute_diameter, HPolytope>> +{ + template + static NT compute(ZonoIntersectHPoly, HPolytope> &P) + { + typedef typename ZonoIntersectHPoly, HPolytope>::VT VT; + typedef typename ZonoIntersectHPoly, HPolytope>::MT MT; + typedef HPolytope Hpolytope; + + typedef BoostRandomNumberGenerator RandomNumberGenerator; + PushBackWalkPolicy push_back_policy; + typedef typename BCDHRWalk::template Walk + < + Hpolytope, + RandomNumberGenerator + > BCdhrWalk; + typedef BoundaryRandomPointGenerator BCdhrRandomPointGenerator; + + MT G = P.get_T().transpose(); + MT AG = P.get_mat()*G; + int k = G.cols(), d = P.dimension(); + MT eyes1(k, 2*k); + eyes1 << MT::Identity(k,k), NT(-1) * MT::Identity(k,k); + MT M1(k, 4*k); + M1 << AG.transpose(), eyes1; + MT M = M1.transpose(); + VT b = P.get_vec(); + + VT bb(4*k); + for (int i = 0; i < 4*k; ++i) bb(i) = (i < 2*k) ? b(i) : 1.0; + + Hpolytope HP(d, M, bb); + + RandomNumberGenerator rng(HP.dimension()); + + std::list randPoints; + std::pair InnerBall = HP.ComputeInnerBall(); + Point q = InnerBall.first; + BCdhrRandomPointGenerator::apply(HP, q, 4*d*d, 1, + randPoints, push_back_policy, rng); + typename std::list::iterator rpit=randPoints.begin(); + NT max_norm = NT(0), iter_norm; + for ( ; rpit!=randPoints.end(); rpit++) { + iter_norm = (G*(*rpit).getCoefficients()).norm(); + if (iter_norm > max_norm) max_norm = iter_norm; + } + return NT(2) * max_norm; + } +}; + +#endif + +template +struct compute_diameter>> +{ + template + static NT compute(BallIntersectPolytope> &P) + { + return NT(2) * P.radius(); + } +}; + +template +struct compute_diameter> +{ + template + static NT compute(OrderPolytope& P) + { + return std::sqrt(NT(P.dimension())); + } +}; + +template +struct compute_diameter, Ellipsoid > > +{ + template + static NT compute(BallIntersectPolytope, Ellipsoid>& P) + { + NT polytope_diameter = std::sqrt(NT(P.dimension())); + return std::min(polytope_diameter, (NT(2) * P.radius())); + } +}; + +#endif // RANDOM_WALKS_COMPUTE_DIAMETER_HPP diff --git a/src/volesti/include/random_walks/crhmc/additional_units/auto_tuner.hpp b/src/volesti/include/random_walks/crhmc/additional_units/auto_tuner.hpp new file mode 100644 index 00000000..66a8d51a --- /dev/null +++ b/src/volesti/include/random_walks/crhmc/additional_units/auto_tuner.hpp @@ -0,0 +1,64 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" +#ifndef AUTO_TUNER_HPP +#define AUTO_TUNER_HPP +#include "random_walks/crhmc/additional_units/dynamic_regularizer.hpp" +#include "random_walks/crhmc/additional_units/dynamic_step_size.hpp" +#include "random_walks/crhmc/additional_units/dynamic_weight.hpp" + +// This class is responsible for calling the additional crhmc modules for: +// modifying the weights, ode step size and regularizer factor addaptively. + +template +class auto_tuner { + using weight_tuner = dynamic_weight; + using regularizion_tuner = + dynamic_regularizer; + using step_size_tuner = dynamic_step_size; + + using Opts = typename Sampler::Opts; + +public: + Opts options; + std::unique_ptr tune_weights; + std::unique_ptr tune_regularization; + std::unique_ptr tune_step_size; + auto_tuner(Sampler &s) : + options(s.params.options) + { + if (options.DynamicWeight) { + tune_weights = std::unique_ptr(new weight_tuner(s)); + } + if (options.DynamicRegularizer) { + tune_regularization = std::unique_ptr(new regularizion_tuner(s)); + } + if (options.DynamicStepSize) { + tune_step_size = std::unique_ptr(new step_size_tuner(s)); + } + } + void updateModules(Sampler &s, RandomNumberGenerator &rng) { + if (options.DynamicWeight) { + tune_weights->update_weights(s, rng); + } + if (options.DynamicRegularizer) { + tune_regularization->update_regularization_factor(s, rng); + } + if (options.DynamicStepSize) { + tune_step_size->update_step_size(s); + } + } +}; +#endif diff --git a/src/volesti/include/random_walks/crhmc/additional_units/dynamic_regularizer.hpp b/src/volesti/include/random_walks/crhmc/additional_units/dynamic_regularizer.hpp new file mode 100644 index 00000000..81449836 --- /dev/null +++ b/src/volesti/include/random_walks/crhmc/additional_units/dynamic_regularizer.hpp @@ -0,0 +1,59 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" +#ifndef DYNAMIC_REGULARIZER_HPP +#define DYNAMIC_REGULARIZER_HPP +#include "Eigen/Eigen" + +/// Module for updating the extra term we add to the barrier +/// This is nessecary for any polytope with free variables +/// Part of crhmc sampler +template +class dynamic_regularizer { +public: + using NT = typename Sampler::NT; + using Point = typename Sampler::point; + using MT = Eigen::Matrix; + using Opts = typename Sampler::Opts; + int n; + int simdLen; + MT bound; + Opts &options; + MT &extraHessian; + dynamic_regularizer(Sampler &s) : + simdLen(s.simdLen), + options(s.params.options), + extraHessian(options.DynamicWeight + ? s.solver->ham.weighted_barrier->extraHessian + : s.solver->ham.barrier->extraHessian) + { + n = s.dim; + bound = MT::Ones(n, simdLen); + extraHessian = MT::Ones(n, simdLen); + } + + void update_regularization_factor(Sampler &s, RandomNumberGenerator &rng) { + MT x = s.x; + x = (x.cwiseAbs()).cwiseMax(1); + bound = bound.cwiseMax(x); + if ((2 / (bound.array() * bound.array()) < n * extraHessian.array()).any()) { + extraHessian = (0.5 / n) * (bound.cwiseProduct(bound)).cwiseInverse(); + s.solver->ham.forceUpdate = true; + s.solver->ham.move({s.x, s.v}); + s.v = s.get_direction_with_momentum(n, rng, s.x, MT::Zero(n, simdLen), 0, false); + } + } +}; +#endif diff --git a/src/volesti/include/random_walks/crhmc/additional_units/dynamic_step_size.hpp b/src/volesti/include/random_walks/crhmc/additional_units/dynamic_step_size.hpp new file mode 100644 index 00000000..36721196 --- /dev/null +++ b/src/volesti/include/random_walks/crhmc/additional_units/dynamic_step_size.hpp @@ -0,0 +1,118 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" +#ifndef DYNAMIC_STEP_SIZE_HPP +#define DYNAMIC_STEP_SIZE_HPP + +/// Module for dynamically choosing the ODE step size and the velocity momentum +/// Part of crhmc sampler +template +class dynamic_step_size { + using NT = typename Sampler::NT; + using Opts = typename Sampler::Opts; + using IVT = Eigen::Array; + using VT = Eigen::Array; + +public: + int simdLen; + IVT consecutiveBadStep; + int iterSinceShrink = 0; + VT rejectSinceShrink; + int ODEStepSinceShrink = 0; + int effectiveStep = 0; + bool warmupFinished = false; + Opts &options; + NT η + NT &momentum; + VT acceptedStep; + VT nEffectiveStep; // number of effective steps + NT accumulatedMomentum = 0; + dynamic_step_size(Sampler &s) : + simdLen(s.simdLen), + options(s.params.options), + eta(s.solver->eta), + momentum(s.params.momentum) + { + nEffectiveStep = VT::Zero(simdLen); + acceptedStep = VT::Zero(simdLen); + consecutiveBadStep = IVT::Zero(simdLen); + rejectSinceShrink = VT::Zero(simdLen); + + if (options.warmUpStep > 0) { + eta = 1e-3; + } else { + warmupFinished = true; + } + } + void update_step_size(Sampler &s) { + acceptedStep = acceptedStep + s.prob.array(); + accumulatedMomentum = s.prob.mean() * momentum * accumulatedMomentum + eta; + Eigen::Matrix accept = (s.accept.template cast()); + nEffectiveStep = nEffectiveStep + eta * accumulatedMomentum * accept.array(); + IVT bad_step = IVT::Zero(simdLen); + if (s.solver->num_steps == options.maxODEStep) { + bad_step += 1; + } else { + bad_step = (s.prob.array() < 0.5).select(1, IVT::Zero(simdLen)); + } + consecutiveBadStep = bad_step * consecutiveBadStep + bad_step; + + NT warmupRatio = nEffectiveStep.mean() / options.warmUpStep; + if (warmupRatio < 1 && !warmupFinished && + consecutiveBadStep.maxCoeff() < options.maxConsecutiveBadStep) { + eta = options.initialStep * std::min(warmupRatio + 1e-2, 1.0); + momentum = 1 - std::min(1.0, eta / options.effectiveStepSize); + return; + } + + if (!warmupFinished) { + acceptedStep = VT::Zero(simdLen); + nEffectiveStep = VT::Zero(simdLen); + warmupFinished = true; + } + + iterSinceShrink++; + rejectSinceShrink += 1 - s.prob.array(); + ODEStepSinceShrink += s.solver->num_steps; + + int shrink = 0; + NT shiftedIter = iterSinceShrink + 20 / (1 - momentum); + + NT targetProbability = std::pow((1.0 - momentum), (2 / 3)) / 4; + if (rejectSinceShrink.maxCoeff() > targetProbability * shiftedIter|| + consecutiveBadStep.maxCoeff() > options.maxConsecutiveBadStep || + ODEStepSinceShrink > options.targetODEStep * shiftedIter) { + shrink = 1; + } + + if (shrink == 1) { + iterSinceShrink = 0; + ODEStepSinceShrink = 0; + rejectSinceShrink = VT::Zero(simdLen); + consecutiveBadStep = IVT::Zero(simdLen); + + eta /= options.shrinkFactor; + momentum = 1 - std::min(0.999, eta / options.effectiveStepSize); + + if (eta < options.minStepSize) { + s.P.terminate=true; + s.P.terminate_message="Algorithm fails to converge even with step size h = "+std::to_string(eta)+"\n"; + } + } + + iterSinceShrink = iterSinceShrink + 1; + } +}; +#endif diff --git a/src/volesti/include/random_walks/crhmc/additional_units/dynamic_weight.hpp b/src/volesti/include/random_walks/crhmc/additional_units/dynamic_weight.hpp new file mode 100644 index 00000000..6b943377 --- /dev/null +++ b/src/volesti/include/random_walks/crhmc/additional_units/dynamic_weight.hpp @@ -0,0 +1,76 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" +#ifndef DYNAMIC_WEIGHT_HPP +#define DYNAMIC_WEIGHT_HPP + +/// Class responsible for updating the weights of the barrier +/// Part of crhmc sampler +template +class dynamic_weight { + using NT = typename Sampler::NT; + using Point = typename Sampler::point; + using VT = Eigen::Matrix; + using MT = Eigen::Matrix; + using IVT = Eigen::Array; + using Opts = typename Sampler::Opts; + +public: + int simdLen; + IVT consecutiveBadStep; + int n; + VT &w; + Opts options; + dynamic_weight(Sampler &s) + : simdLen(s.simdLen), w(s.solver->ham.weighted_barrier->w), options(s.params.options) + { + n = s.dim; + consecutiveBadStep = IVT::Zero(simdLen); + } + // If we have consecutive bad steps update the weights with + // the help of the leverage scores. + void update_weights(Sampler &s, RandomNumberGenerator &rng) + { + IVT bad_step = IVT::Zero(simdLen); + if (s.solver->num_steps == options.maxODEStep) { + bad_step += 1; + } else { + bad_step = (s.prob.array() < 0.5).select(1, IVT::Zero(simdLen)); + } + NT threshold; + consecutiveBadStep = bad_step * consecutiveBadStep + bad_step; + + if (s.accept.sum() < simdLen) { + VT lsc = s.solver->ham.lsc.colwise().maxCoeff().transpose(); + /*The more bad steps in a row we have the higher we want the threshold to be + In order to change w more drasticaly according to the leverage scores. + So if we have more than 2 bad steps in a row we elect to set the threshold to 4 + else to 16. Not many changes will be possible as the w should be upperbounded by 1*/ + if (consecutiveBadStep.maxCoeff() > 2) { + threshold = 4; + } else { + threshold = 16; + } + bool changed = (lsc.array() > threshold * w.array()).any(); + if (changed) { + w = (lsc.array() > threshold * w.array()).select((w * threshold).cwiseMin(1), w); + s.solver->ham.forceUpdate = true; + s.solver->ham.move({s.x, s.v}); + s.v = s.get_direction_with_momentum(n, rng, s.x, MT::Zero(n, simdLen), false); + } + } + } +}; +#endif diff --git a/src/volesti/include/random_walks/crhmc/crhmc_walk.hpp b/src/volesti/include/random_walks/crhmc/crhmc_walk.hpp new file mode 100644 index 00000000..21ed8d6b --- /dev/null +++ b/src/volesti/include/random_walks/crhmc/crhmc_walk.hpp @@ -0,0 +1,245 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" +#ifndef CRHMC_WALK_HPP +#define CRHMC_WALK_HPP +#include "generators/boost_random_number_generator.hpp" +#include "ode_solvers/ode_solvers.hpp" +#include "random_walks/crhmc/additional_units/auto_tuner.hpp" +#include "random_walks/gaussian_helpers.hpp" +#include +struct CRHMCWalk { + template + < + typename NT, + typename OracleFunctor + > + struct parameters { + using Opts = opts; + NT epsilon; // tolerance in mixing + NT eta = 0.2; // step size + NT momentum; + NT effectiveStepSize = 1; + Opts &options; + parameters(OracleFunctor const &F, + unsigned int dim, + Opts &user_options, + NT epsilon_ = 2) : + options(user_options) + { + epsilon = epsilon_; + eta = 1.0 / (dim * sqrt(F.params.L)); + momentum = 1 - std::min(1.0, eta / effectiveStepSize); + } + }; + + template + < + typename Point, + typename Polytope, + typename RandomNumberGenerator, + typename NegativeGradientFunctor, + typename NegativeLogprobFunctor, + typename Solver + > + struct Walk { + using point = Point; + using pts = std::vector; + using NT = typename Point::FT; + using VT = Eigen::Matrix; + using MT = Eigen::Matrix; + using Sampler = CRHMCWalk::Walk; + + using Opts = typename Polytope::Opts; + using IVT = Eigen::Matrix; + + // Hyperparameters of the sampler + parameters ¶ms; + + // Numerical ODE solver + std::unique_ptr solver; + + // Dimension + unsigned int dim; + + // Polytope + Polytope &P; + + // Discarded Samples + long total_discarded_samples = 0; + long num_runs = 0; + float discard_ratio = 0; + + // Average acceptance probability + float total_acceptance_prob = 0; + float average_acceptance_prob = 0; + + // Acceptance probability + VT prob; + bool accepted; + IVT accept; + bool update_modules; + int simdLen; + // References to xs + MT x, v; + + // Proposal points + MT x_tilde, v_tilde; + + // Gradient function + NegativeGradientFunctor &F; + + // Auto tuner + std::unique_ptr>module_update; + + // Helper variables + VT H, H_tilde; + // Density exponent + NegativeLogprobFunctor &f; +#ifdef TIME_KEEPING + std::chrono::time_point start, end; + std::chrono::duration H_duration = std::chrono::duration::zero(); +#endif + Walk(Polytope &Problem, + Point &p, + NegativeGradientFunctor &neg_grad_f, + NegativeLogprobFunctor &neg_logprob_f, + parameters ¶m) : + params(param), + P(Problem), + F(neg_grad_f), + f(neg_logprob_f) + { + + dim = p.dimension(); + simdLen = params.options.simdLen; + // Starting point is provided from outside + x = p.getCoefficients() * MT::Ones(1, simdLen); + accepted = false; + // Initialize solver + solver = std::unique_ptr(new Solver(0.0, params.eta, {x, x}, F, Problem, params.options)); + v = MT::Zero(dim, simdLen); + module_update = std::unique_ptr>(new auto_tuner(*this)); + update_modules = params.options.DynamicWeight || + params.options.DynamicRegularizer || + params.options.DynamicStepSize; + }; + // Sample a new velocity with momentum + MT get_direction_with_momentum(unsigned int const &dim, + RandomNumberGenerator &rng, MT const &x, MT v, + NT momentum = 0, bool normalize = true) + { + MT z = MT(dim, simdLen); + for (int i = 0; i < simdLen; i++) + { + z.col(i) = GetDirection::apply(dim, rng, normalize).getCoefficients(); + } + solver->ham.move({x, v}); + MT sqrthess = (solver->ham.hess).cwiseSqrt(); + z = sqrthess.cwiseProduct(z); + return v * std::sqrt(momentum) + z * std::sqrt(1 - momentum); + } + // Returns the current point in the tranformed in the original space + inline MT getPoints() { return (P.T * x).colwise() + P.y; } + // Returns the current point in the tranformed in the original space + inline Point getPoint() { return Point(P.T * x.col(0) + P.y); } + + inline MT masked_choose(MT &x, MT &x_tilde, IVT &accept) { + return accept.transpose().replicate(x.rows(), 1).select(x_tilde, x); + } + inline void disable_adaptive(){ + update_modules=false; + } + inline void apply(RandomNumberGenerator &rng, + int walk_length = 1, + bool metropolis_filter = true) + { + num_runs++; + // Pick a random velocity with momentum + v = get_direction_with_momentum(dim, rng, x, v, params.momentum, false); + solver->set_state(0, x); + solver->set_state(1, v); + // Get proposals + solver->steps(walk_length, accepted); + x_tilde = solver->get_state(0); + v_tilde = solver->get_state(1); + if (metropolis_filter) { +#ifdef TIME_KEEPING + start = std::chrono::system_clock::now(); +#endif + // Calculate initial Hamiltonian + H = solver->ham.hamiltonian(x, v); + + // Calculate new Hamiltonian + H_tilde = solver->ham.hamiltonian(x_tilde, -v_tilde); + +#ifdef TIME_KEEPING + end = std::chrono::system_clock::now(); + H_duration += end - start; +#endif + VT feasible = solver->ham.feasible(x_tilde, + v_tilde); + prob = (1.0 < exp((H - H_tilde).array())).select(1.0, exp((H - H_tilde).array())); + prob = (feasible.array() > 0.5).select(prob, 0); + + total_acceptance_prob += prob.sum(); + VT rng_vector = VT(simdLen); + for (int i = 0; i < simdLen; i++) + { + rng_vector(i) = rng.sample_urdist(); + } + accept = (rng_vector.array() < prob.array()).select(1 * IVT::Ones(simdLen), 0 * IVT::Ones(simdLen)); + + x = masked_choose(x, x_tilde, accept); + v = -v; + v = masked_choose(v, v_tilde, accept); + total_discarded_samples += simdLen - accept.sum(); + discard_ratio = (1.0 * total_discarded_samples) / (num_runs * simdLen); + average_acceptance_prob = total_acceptance_prob / (num_runs * simdLen); + } else { + x = x_tilde; + v = v_tilde; + } + if (update_modules) { + module_update->updateModules(*this, rng); + } + } +#ifdef TIME_KEEPING + void initialize_timers() { + H_duration = std::chrono::duration::zero(); + solver->DU_duration = std::chrono::duration::zero(); + solver->approxDK_duration = std::chrono::duration::zero(); + } + template + void print_timing_information(StreamType &stream) { + stream << "---Sampling Timing Information" << std::endl; + double DU_time = solver->DU_duration.count(); + double DK_time = solver->approxDK_duration.count(); + double H_time = H_duration.count(); + double total_time = H_time + DK_time + DU_time; + stream << "Computing the Hamiltonian in time, " << H_time << " secs\n"; + stream << "Computing DU partial derivatives in time, " << DU_time + << " secs\n"; + stream << "Computing DK partial derivatives in time, " << DK_time + << " secs\n"; + stream << "H_time + DK_time + DU_time: " << total_time << "\n"; + } +#endif + }; +}; + +#endif diff --git a/src/volesti/include/random_walks/crhmc/hamiltonian.hpp b/src/volesti/include/random_walks/crhmc/hamiltonian.hpp new file mode 100644 index 00000000..0d9ef546 --- /dev/null +++ b/src/volesti/include/random_walks/crhmc/hamiltonian.hpp @@ -0,0 +1,249 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2022-2022 Ioannis Iakovidis + +// Contributed and/or modified by Ioannis Iakovidis, as part of Google Summer of +// Code 2022 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Yunbum Kook, Yin Tat Lee, Ruoqi Shen, Santosh S. Vempala. "Sampling with +// Riemannian Hamiltonian +// Monte Carlo in a Constrained Space" +#ifndef HAMILTONIAN_HPP +#define HAMILTONIAN_HPP +#include "preprocess/crhmc/two_sided_barrier.h" +#include "preprocess/crhmc/weighted_two_sided_barrier.h" +#include "PackedCSparse/PackedChol.h" +#include "preprocess/crhmc/crhmc_utils.h" +#include + +template +class Hamiltonian { + using VT = typename Polytope::VT; + using IVT = Eigen::Array; + using BVT = Eigen::Matrix; + using NT = typename Polytope::NT; + using MT = typename Polytope::MT; + using SpMat = typename Polytope::SpMat; + using Tx = FloatArray; + using CholObj = PackedChol; + using Opts = typename Polytope::Opts; + using pts = std::vector; + using Barrier = two_sided_barrier; + using WeightedBarrier = weighted_two_sided_barrier; + +public: + bool prepared = false; + bool forceUpdate = true; // Update function oracle temporary varibles + Polytope &P; + MT hess; + bool dUDx_empty = true; + MT last_dUdx; + CholObj solver; + pts xs; + MT x; + MT dfx; + MT lsc; + VT fx; + int n; + int m; + int num_runs = 0; + Barrier *barrier; + std::unique_ptr weighted_barrier; + Opts &options; + Hamiltonian(Polytope &boundaries) : + P(boundaries), + solver(CholObj(transform_format(boundaries.Asp))), + options(boundaries.options) + { + n = P.dimension(); + m = P.equations(); + x = MT::Zero(n, simdLen); + xs = {x, x}; + lsc = MT::Zero(simdLen, n); + solver.accuracyThreshold = options.solver_accuracy_threshold; + if (options.DynamicWeight) + { + weighted_barrier = + std::unique_ptr(new WeightedBarrier(P.barrier.lb, P.barrier.ub, P.w_center)); + weighted_barrier->extraHessian.resize(n, simdLen); + weighted_barrier->extraHessian = MT::Ones(n, simdLen) * options.regularization_factor; + } + barrier = &P.barrier; + barrier->extraHessian.resize(n, simdLen); + barrier->extraHessian = MT::Ones(n, simdLen) * options.regularization_factor; + } + + // Compute H(x,v) + VT hamiltonian(MT x, MT v) + { + prepare({x, v}); + pts pd = DK({x, v}); + VT K = 0.5 * (v.cwiseProduct(pd[0])).colwise().sum(); + Tx out=solver.logdet(); + VT logdet=VT(simdLen); + for (int i = 0; i < simdLen; i++) + logdet(i) = get(out, i); + VT U = ((hess.array()).log()).colwise().sum(); + U = (U + logdet) * 0.5 + fx; + VT E = U + K; + return E; + } + // Helper is nan function for vectors + template + IVT is_not_nan(MatrixType x) + { + IVT result = IVT::Ones(x.cols()); + for (int i = 0; i < x.rows(); i++) { + for (int j = 0; j < x.cols(); j++) { + if (std::isnan(x(i, j))) { + result(j) = 0; + } + } + } + return result; + } + // Test if the values of x and v are valid and if x is feasible + VT feasible(MT x, MT v) + { + VT feasible_coordinate = VT::Ones(x.cols()); + if (options.DynamicWeight) { + feasible_coordinate = weighted_barrier->feasible(x); + } else { + feasible_coordinate = barrier->feasible(x); + } + VT r = feasible_coordinate.cwiseProduct((is_not_nan(x) * is_not_nan(v)).matrix()); + return r; + } + // prepare the solver weighted by the hessian + void prepare(pts const &xs) + { + move(xs); + if (!prepared) { + MT Hinv = (hess.cwiseInverse()).transpose(); + solver.decompose((Tx *)Hinv.data()); + dUDx_empty = true; + } + prepared = true; + } + // Computation of the partial derivatives of the K term + pts DK(pts const &x_bar) + { + MT x = x_bar[0]; + MT v = x_bar[1]; + move(x_bar); + MT invHessV = v.cwiseQuotient(hess); + MT input_vector = P.Asp * invHessV; + input_vector.transposeInPlace(); + MT out_vector = MT::Zero(simdLen, m); + solver.solve((Tx *)input_vector.data(), (Tx *)out_vector.data()); + out_vector.transposeInPlace(); + MT dKdv = + invHessV - (P.Asp.transpose() * out_vector).cwiseQuotient(hess); + + MT dKdx = MT::Zero(n, simdLen); + if (options.DynamicWeight) { + dKdx = + weighted_barrier->quadratic_form_gradient(x, dKdv) / + 2; + } else { + dKdx = barrier->quadratic_form_gradient(x, dKdv) / + 2; + } + + return {dKdv, dKdx}; + } + // Approximate computation of the partial derivatives of the K term + pts approxDK(pts const &x_bar, MT &nu) + { + MT x = x_bar[0]; + MT v = x_bar[1]; + move(x_bar); + MT dUdv_b = P.Asp * (v - P.Asp.transpose() * nu).cwiseQuotient(hess); + dUdv_b.transposeInPlace(); + MT out_solver = MT(nu.cols(), nu.rows()); + solver.solve((Tx *)dUdv_b.data(), (Tx *)out_solver.data()); + nu = nu + out_solver.transpose(); + + MT dKdv = (v - P.Asp.transpose() * nu).cwiseQuotient(hess); + MT dKdx = MT::Zero(n, simdLen); + if (options.DynamicWeight) { + dKdx = + weighted_barrier->quadratic_form_gradient(x, dKdv) / + 2; + } else { + dKdx = barrier->quadratic_form_gradient(x, dKdv) / + 2; + } + return {dKdv, dKdx}; + } + // Compute the partial derivatives of one term + // This is only dependent on x and so DU/Dv=0 + pts DU(pts const &x_bar) + { + MT x = x_bar[0]; + move(x_bar); + if (!prepared || dUDx_empty) { + prepare(x_bar); + solver.leverageScoreComplement((Tx *)lsc.data()); + + if (options.DynamicWeight) { + last_dUdx = (weighted_barrier->tensor(x).cwiseProduct(lsc.transpose())) + .cwiseQuotient(2 * hess) + + dfx; + } else { + last_dUdx = + (barrier->tensor(x).cwiseProduct(lsc.transpose())).cwiseQuotient(2 * hess) + + dfx; + } + dUDx_empty = false; + } + + return {MT::Zero(n, simdLen), -last_dUdx}; + } + // Compute the computations involving only x iff x has been changed + // Else they are stored + void move(pts const &y) + { + if (y[0] == xs[0] && !forceUpdate) { + return; + } + xs = y; + x = xs[0]; + MT h; + std::tie(fx, dfx, h) = P.f_oracle(x); + if (options.DynamicWeight) { + hess = weighted_barrier->hessian(x) + h; + } else { + hess = barrier->hessian(x) + h; + } + forceUpdate = false; + prepared = false; + } + // Project x to the polytope + void project(pts &xs) { + move(xs); + MT x = xs[0]; + int m = P.Asp.rows(); + MT out_vector = MT(simdLen, m); + MT in_vector = (-P.Asp * x).colwise() + P.b; + in_vector.transposeInPlace(); + solver.solve((Tx *)in_vector.data(), (Tx *)out_vector.data()); + out_vector.transposeInPlace(); + out_vector = P.Asp.transpose() * out_vector; + xs[0] = xs[0] + (out_vector).cwiseQuotient(hess); + } + // Get the inner product of x and ds weighted by the hessian + VT x_norm(pts const &xs, pts const &dx) + { + move(xs); + MT dx_x = dx[0]; + MT r = (dx_x.cwiseProduct(dx_x)).cwiseProduct(hess); + return r.colwise().sum(); + } +}; +#endif diff --git a/src/volesti/include/random_walks/ellipsoid_walks/README.md b/src/volesti/include/random_walks/ellipsoid_walks/README.md new file mode 100644 index 00000000..ec1e5785 --- /dev/null +++ b/src/volesti/include/random_walks/ellipsoid_walks/README.md @@ -0,0 +1,7 @@ +1) This folder contains the C++ implementations -with several modifications- of the Dikin walk, + the Vaidya walk and the John walk from https://github.com/rzrsk/vaidya-walk by Raaz Dwivedi. + +2) The implemented random walks are presented in the paper of + Y. Chen, R. Dwivedi, M. J. Wainwright and B. Yu, + "Fast MCMC Sampling Algorithms on Polytopes", + Journal of Machine Learning Research, 2018. diff --git a/src/volesti/include/random_walks/ellipsoid_walks/dikin_walker.h b/src/volesti/include/random_walks/ellipsoid_walks/dikin_walker.h new file mode 100644 index 00000000..b16d997b --- /dev/null +++ b/src/volesti/include/random_walks/ellipsoid_walks/dikin_walker.h @@ -0,0 +1,157 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +// Original C++ code from https://github.com/rzrsk/vaidya-walk by Raaz Dwivedi. + +// Modified by Alexandros Manochis to be integrated in volesti, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// The implemented random walk is presented in the paper of +// Y. Chen, R. Dwivedi, M. J. Wainwright and B. Yu, +// "Fast MCMC Sampling Algorithms on Polytopes", +// Journal of Machine Learning Research, 2018. + +#ifndef PWALK_DIKIN_WALKER_HPP_ +#define PWALK_DIKIN_WALKER_HPP_ + +#include +#include "math_functions.h" + +/// @brief Class that defines the Dikin walk sampler +/// @tparam Dtype Number Type +template +class DikinWalker { +public: + + DikinWalker() {} + + DikinWalker(const Eigen::Matrix &initialization, const Eigen::Matrix &cons_A, const Eigen::Matrix &cons_b, const Dtype r){ + nb_dim_ = cons_A.cols(); + nb_cons_ = cons_A.rows(); + nb_curr_samples_ = 1; + initialization_ = initialization; + cons_A_ = cons_A; + cons_b_ = cons_b; + curr_sample_ = initialization; + r_ = r; + } + + // getter for radius + Dtype getRadius() { + return r_; + } + + // check whether a given point is in th polytope + bool checkInPolytope(const Eigen::Matrix &new_sample) { + return (cons_A_ * new_sample - cons_b_).maxCoeff() < 0; + } + + // getter for dimension + int getNbDim() { + return nb_dim_; + } + + // getter for nb current samples + int getNbCurrSamples() { + return nb_curr_samples_; + } + + // getter for current sample + Eigen::Matrix &getCurrSample() { + return curr_sample_; + } + + void proposal(Eigen::Matrix& new_sample){ + Eigen::Matrix gaussian_step = + Eigen::Matrix::Zero(this->nb_dim_); + sample_gaussian(this->nb_dim_, 0., 1., gaussian_step); + + // get hessian + Eigen::Matrix new_sqrt_inv_hess = + Eigen::Matrix::Zero(this->nb_dim_, this->nb_dim_); + sqrtInvHessBarrier(this->curr_sample_, new_sqrt_inv_hess); + + new_sample = this->curr_sample_ + r_ / std::sqrt(Dtype(this->nb_dim_)) * (new_sqrt_inv_hess * gaussian_step); + } + + bool acceptRejectReverse(const Eigen::Matrix& new_sample){ + // get hessian on x + Eigen::Matrix new_sqrt_inv_hess_x = + Eigen::Matrix::Zero(this->nb_dim_, this->nb_dim_); + sqrtInvHessBarrier(this->curr_sample_, new_sqrt_inv_hess_x); + // get hessian on y + Eigen::Matrix new_sqrt_inv_hess_y = + Eigen::Matrix::Zero(this->nb_dim_, this->nb_dim_); + sqrtInvHessBarrier(new_sample, new_sqrt_inv_hess_y); + + Dtype scale = r_/std::sqrt(Dtype(this->nb_dim_)); + Dtype p_y_to_x = gaussian_density(this->curr_sample_, new_sample, new_sqrt_inv_hess_y.inverse()/scale); + Dtype p_x_to_y = gaussian_density(new_sample, this->curr_sample_, new_sqrt_inv_hess_x.inverse()/scale); + + Dtype ar_ratio = std::min(1., p_y_to_x/p_x_to_y); + + Dtype random_num = rng_uniform(0., 1.); + // lazy version of the walk + if (random_num > ar_ratio) { + return false; + } + + return true; + } + + bool doSample(Eigen::Matrix& new_sample, const Dtype lazy = Dtype(0.5)){ + proposal(new_sample); + this->nb_curr_samples_ += 1; + // for lazy markov chain + Dtype random_num = rng_uniform(0., 1.); + // check balance and check in polytope + if (random_num < lazy && this->checkInPolytope(new_sample) && acceptRejectReverse(new_sample)){ + this->curr_sample_ = new_sample; + return true; + } else { + new_sample = this->curr_sample_; + return false; + } + } + + void sqrtInvHessBarrier(const Eigen::Matrix& new_sample, Eigen::Matrix& new_sqrt_inv_hess) + { + Eigen::Matrix inv_slack = (this->cons_b_ - this->cons_A_ * new_sample).cwiseInverse(); + + Eigen::Matrix half_hess = inv_slack.asDiagonal()* this->cons_A_; + Eigen::Matrix new_hess = half_hess.transpose() * half_hess; + + // compute eigenvectors and eigenvalues + Eigen::SelfAdjointEigenSolver > es(new_hess); + + Eigen::Matrix V = es.eigenvectors(); + Eigen::Matrix Dv = es.eigenvalues(); + new_sqrt_inv_hess = V * Dv.cwiseInverse().cwiseSqrt().asDiagonal() * V.transpose(); + } + +private: + Dtype r_; + + // Dimension + int nb_dim_; + // number of constraints + int nb_cons_; + // current sample size + int nb_curr_samples_; + // Initial vector + Eigen::Matrix initialization_; + // constraints matrix A + Eigen::Matrix cons_A_; + // constraint vector b + Eigen::Matrix cons_b_; + // Current vector + Eigen::Matrix curr_sample_; +}; + + +#endif // PWALK_DIKIN_WALKER_HPP_ diff --git a/src/volesti/include/random_walks/ellipsoid_walks/john_walker.h b/src/volesti/include/random_walks/ellipsoid_walks/john_walker.h new file mode 100644 index 00000000..fd1d25f9 --- /dev/null +++ b/src/volesti/include/random_walks/ellipsoid_walks/john_walker.h @@ -0,0 +1,204 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +// Original C++ code from https://github.com/rzrsk/vaidya-walk by Raaz Dwivedi. + +// Modified by Alexandros Manochis to be integrated in volesti, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// The implemented random walk is presented in the paper of +// Y. Chen, R. Dwivedi, M. J. Wainwright and B. Yu, +// "Fast MCMC Sampling Algorithms on Polytopes", +// Journal of Machine Learning Research, 2018. + +#ifndef PWALK_JOHN_WALKER_HPP_ +#define PWALK_JOHN_WALKER_HPP_ + +#include +#include +#include "math_functions.h" + + +/// @brief Class that defines the John walk sampler +/// @tparam Dtype Number Type +template +class JohnWalker { +public: + + JohnWalker() {} + + JohnWalker(const Eigen::Matrix &initialization, const Eigen::Matrix &cons_A, const Eigen::Matrix &cons_b, const Dtype r) + { + nb_dim_ = cons_A.cols(); + nb_cons_ = cons_A.rows(); + nb_curr_samples_ = 1; + initialization_ = initialization; + cons_A_ = cons_A; + cons_b_ = cons_b; + curr_sample_ = initialization; + r_ = r; + alpha_ = 1. - 1. / std::log2(2. * Dtype(cons_A.rows()) / Dtype(cons_A.cols())); + beta_ = Dtype(cons_A.cols()) / 2. / Dtype(cons_A.rows()); + curr_weight_ = Eigen::Matrix::Ones(cons_A.rows()); + } + + // getter for radius + Dtype getRadius() { + return r_; + } + + // check whether a given point is in th polytope + bool checkInPolytope(const Eigen::Matrix &new_sample) { + return (cons_A_ * new_sample - cons_b_).maxCoeff() < 0; + } + + // getter for dimension + int getNbDim() { + return nb_dim_; + } + + // getter for nb current samples + int getNbCurrSamples() { + return nb_curr_samples_; + } + + // getter for current sample + Eigen::Matrix &getCurrSample() { + return curr_sample_; + } + + void proposal(Eigen::Matrix &new_sample) { + Eigen::Matrix gaussian_step = Eigen::Matrix::Zero( + this->nb_dim_); + sample_gaussian(this->nb_dim_, 0., 1., gaussian_step); + + // get hessian + Eigen::Matrix new_sqrt_inv_hess = + Eigen::Matrix::Zero( + this->nb_dim_, this->nb_dim_); + sqrtInvHessBarrier(this->curr_sample_, new_sqrt_inv_hess); + + new_sample = this->curr_sample_ + r_ / std::sqrt(Dtype(this->nb_dim_)) * (new_sqrt_inv_hess * gaussian_step); + } + + bool acceptRejectReverse(const Eigen::Matrix &new_sample) { + // get hessian on y + Eigen::Matrix new_sqrt_inv_hess_y = + Eigen::Matrix::Zero( + this->nb_dim_, this->nb_dim_); + + sqrtInvHessBarrier(new_sample, new_sqrt_inv_hess_y); + // get hessian on x + Eigen::Matrix new_sqrt_inv_hess_x = + Eigen::Matrix::Zero( + this->nb_dim_, this->nb_dim_); + sqrtInvHessBarrier(this->curr_sample_, new_sqrt_inv_hess_x); + + Dtype scale = r_ / std::sqrt(Dtype(this->nb_dim_)); + Dtype p_y_to_x = gaussian_density(this->curr_sample_, new_sample, + new_sqrt_inv_hess_y.inverse() / scale); + Dtype p_x_to_y = gaussian_density(new_sample, this->curr_sample_, + new_sqrt_inv_hess_x.inverse() / scale); + + Dtype ar_ratio = std::min(1., p_y_to_x / p_x_to_y); + + Dtype random_num = rng_uniform(0., 1.); + // lazy version of the walk + if (random_num > ar_ratio) { + return false; + } + + return true; + } + + bool doSample(Eigen::Matrix &new_sample, const Dtype lazy = Dtype(0.5)) { + proposal(new_sample); + this->nb_curr_samples_ += 1; + // for lazy markov chain + Dtype random_num = rng_uniform(0., 1.); + // check balance and check in polytope + if (random_num < lazy && this->checkInPolytope(new_sample) && acceptRejectReverse(new_sample)) { + this->curr_sample_ = new_sample; + return true; + } else { + new_sample = this->curr_sample_; + return false; + } + } + + void sqrtInvHessBarrier(const Eigen::Matrix &new_sample, + Eigen::Matrix &new_sqrt_inv_hess) { + Eigen::Matrix inv_slack = (this->cons_b_ - this->cons_A_ * new_sample).cwiseInverse(); + + Eigen::Matrix half_hess = inv_slack.asDiagonal() * this->cons_A_; + + Eigen::Matrix gradient; + Eigen::Matrix score; + Eigen::Matrix weight_half_alpha; + Eigen::Matrix weight_half_hess; + Eigen::Matrix new_hess; + Eigen::Matrix new_hess_inv; + Eigen::Matrix beta_ones = + beta_ * Eigen::Matrix::Ones(this->nb_cons_); + + Eigen::Matrix next_weight = curr_weight_; + // compute scores using gradient descent + do { + curr_weight_ = next_weight; + weight_half_alpha = curr_weight_.array().pow(alpha_ / 2.); + + weight_half_hess = (weight_half_alpha.cwiseProduct(inv_slack)).asDiagonal() * this->cons_A_; + + new_hess = weight_half_hess.transpose() * weight_half_hess; + + new_hess_inv = new_hess.inverse(); + + score = ((weight_half_hess * new_hess_inv).cwiseProduct(weight_half_hess)).rowwise().sum(); + + next_weight = (0.5 * (curr_weight_ + score + beta_ones)).cwiseMax(beta_ones); + + } while ((next_weight - curr_weight_).template lpNorm() > Dtype(0.00001)); + + // compute john hessian + Eigen::Matrix john_new_hess = + half_hess.transpose() * curr_weight_.asDiagonal() * half_hess; + + // compute eigenvectors and eigenvalues + Eigen::SelfAdjointEigenSolver > es(john_new_hess); + + Eigen::Matrix V = es.eigenvectors(); + Eigen::Matrix Dv = es.eigenvalues(); + new_sqrt_inv_hess = V * Dv.cwiseInverse().cwiseSqrt().asDiagonal() * V.transpose(); + } + +private: + + Dtype alpha_; + Dtype beta_; + + Dtype r_; + + // Dimension + int nb_dim_; + // number of constraints + int nb_cons_; + // current sample size + int nb_curr_samples_; + // Initial vector + Eigen::Matrix initialization_; + // constraints matrix A + Eigen::Matrix cons_A_; + // constraint vector b + Eigen::Matrix cons_b_; + // Current vector + Eigen::Matrix curr_sample_; + + Eigen::Matrix curr_weight_; +}; + +#endif // PWALK_JOHN_WALKER_HPP_ + diff --git a/src/volesti/include/random_walks/ellipsoid_walks/math_functions.h b/src/volesti/include/random_walks/ellipsoid_walks/math_functions.h new file mode 100644 index 00000000..c1c9566f --- /dev/null +++ b/src/volesti/include/random_walks/ellipsoid_walks/math_functions.h @@ -0,0 +1,54 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +// Original C++ code from https://github.com/rzrsk/vaidya-walk by Raaz Dwivedi. + +// Modified by Alexandros Manochis to be integrated in volesti, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef PWALK_UTIL_MATH_FUNCTIONS_HPP_ +#define PWALK_UTIL_MATH_FUNCTIONS_HPP_ + +#include +#include +#include + +// Define random number generator type +typedef boost::mt19937 rng_t; + +template +void sample_gaussian(const int n, const Dtype a, + const Dtype sigma, Eigen::Matrix& r) { + static rng_t gen(1234567); + static boost::normal_distribution random_distribution(a, sigma); + static boost::variate_generator > + variate_generator(gen, random_distribution); + + for (int i = 0; i < n; ++i) { + r[i] = variate_generator(); + } +} + +template +Dtype rng_uniform(const Dtype a, const Dtype b) { + static rng_t gen(1234567); + static boost::uniform_real random_distribution(a, b); + static boost::variate_generator > + variate_generator(gen, random_distribution); + + return variate_generator(); +} + +template +Dtype gaussian_density(const Eigen::Matrix& x, + const Eigen::Matrix& mu, + const Eigen::Matrix& sqrt_cov) { + Eigen::Matrix c = sqrt_cov * (x - mu); + return std::exp(-0.5*c.dot(c)) * sqrt_cov.determinant(); +} + + +#endif // PWALK_UTIL_MATH_FUNTIONS_HPP_ diff --git a/src/volesti/include/random_walks/ellipsoid_walks/vaidya_walker.h b/src/volesti/include/random_walks/ellipsoid_walks/vaidya_walker.h new file mode 100644 index 00000000..6da8b5a7 --- /dev/null +++ b/src/volesti/include/random_walks/ellipsoid_walks/vaidya_walker.h @@ -0,0 +1,171 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +// Original C++ code from https://github.com/rzrsk/vaidya-walk by Raaz Dwivedi. + +// Modified by Alexandros Manochis to be integrated in volesti, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// The implemented random walk is presented in the paper of +// Y. Chen, R. Dwivedi, M. J. Wainwright and B. Yu, +// "Fast MCMC Sampling Algorithms on Polytopes", +// Journal of Machine Learning Research, 2018. + +#ifndef PWALK_VAIDYA_WALKER_HPP_ +#define PWALK_VAIDYA_WALKER_HPP_ + +#include +#include "math_functions.h" + + +/// @brief Class that defines the Vaidya walk sampler +/// @tparam Dtype Number Type +template +class VaidyaWalker { +public: + + VaidyaWalker() {} + + VaidyaWalker(const Eigen::Matrix &initialization, const Eigen::Matrix &cons_A, const Eigen::Matrix &cons_b, const Dtype r) + { + nb_dim_ = cons_A.cols(); + nb_cons_ = cons_A.rows(); + nb_curr_samples_ = 1; + initialization_ = initialization; + cons_A_ = cons_A; + cons_b_ = cons_b; + curr_sample_ = initialization; + r_ = r; + } + + // getter for radius + Dtype getRadius() { + return r_; + } + + // check whether a given point is in th polytope + bool checkInPolytope(const Eigen::Matrix &new_sample) { + return (cons_A_ * new_sample - cons_b_).maxCoeff() < 0; + } + + // getter for dimension + int getNbDim() { + return nb_dim_; + } + + // getter for nb current samples + int getNbCurrSamples() { + return nb_curr_samples_; + } + + // getter for current sample + Eigen::Matrix &getCurrSample() { + return curr_sample_; + } + + void proposal(Eigen::Matrix &new_sample) { + Eigen::Matrix gaussian_step = Eigen::Matrix::Zero( + this->nb_dim_); + sample_gaussian(this->nb_dim_, 0., 1., gaussian_step); + + // get hessian + Eigen::Matrix new_sqrt_inv_hess = Eigen::Matrix::Zero(this->nb_dim_, this->nb_dim_); + sqrtInvHessBarrier(this->curr_sample_, new_sqrt_inv_hess); + + new_sample = this->curr_sample_ + r_ / std::sqrt(std::sqrt(Dtype(this->nb_dim_) * Dtype(this->nb_cons_))) + * (new_sqrt_inv_hess * gaussian_step); + } + + bool acceptRejectReverse(const Eigen::Matrix &new_sample) { + // get hessian on x + Eigen::Matrix new_sqrt_inv_hess_x = Eigen::Matrix::Zero(this->nb_dim_, this->nb_dim_); + sqrtInvHessBarrier(this->curr_sample_, new_sqrt_inv_hess_x); + // get hessian on y + Eigen::Matrix new_sqrt_inv_hess_y = Eigen::Matrix::Zero(this->nb_dim_, this->nb_dim_); + sqrtInvHessBarrier(new_sample, new_sqrt_inv_hess_y); + + Dtype scale = r_ / std::sqrt(std::sqrt(Dtype(this->nb_dim_) * Dtype(this->nb_cons_))); + Dtype p_y_to_x = gaussian_density(this->curr_sample_, new_sample, new_sqrt_inv_hess_y.inverse() / scale); + Dtype p_x_to_y = gaussian_density(new_sample, this->curr_sample_, new_sqrt_inv_hess_x.inverse() / scale); + + Dtype ar_ratio = std::min(1., p_y_to_x / p_x_to_y); + + Dtype random_num = rng_uniform(0., 1.); + // lazy version of the walk + if (random_num > ar_ratio) { + return false; + } + + return true; + } + + bool doSample(Eigen::Matrix &new_sample, const Dtype lazy = Dtype(0.5)) { + proposal(new_sample); + this->nb_curr_samples_ += 1; + // for lazy markov chain + Dtype random_num = rng_uniform(0., 1.); + // check balance and check in polytope + if (random_num < lazy && this->checkInPolytope(new_sample) && acceptRejectReverse(new_sample)) { + this->curr_sample_ = new_sample; + return true; + } else { + new_sample = this->curr_sample_; + return false; + } + } + + void sqrtInvHessBarrier(const Eigen::Matrix &new_sample, + Eigen::Matrix &new_sqrt_inv_hess) { + Eigen::Matrix inv_slack = (this->cons_b_ - this->cons_A_ * new_sample).cwiseInverse(); + + Eigen::Matrix half_hess = inv_slack.asDiagonal() * this->cons_A_; + Eigen::Matrix new_hess = half_hess.transpose() * half_hess; + + Eigen::Matrix new_hess_inv = new_hess.inverse(); + + // compute leverage scores + // Eigen::Matrix score = ((this->cons_A_ * new_hess_inv).cwiseProduct(this->cons_A_)).rowwise().sum().cwiseProduct(inv_slack).cwiseProduct(inv_slack); + Eigen::Matrix score = ((half_hess * new_hess_inv).cwiseProduct( + half_hess)).rowwise().sum(); + + // compute vaidya hessian + Eigen::Matrix vaidya_new_hess = + half_hess.transpose() * score.asDiagonal() * half_hess + + Dtype(this->nb_dim_) / Dtype(this->nb_cons_) * new_hess; + + // compute eigenvectors and eigenvalues + Eigen::SelfAdjointEigenSolver > es(vaidya_new_hess); + + Eigen::Matrix V = es.eigenvectors(); + Eigen::Matrix Dv = es.eigenvalues(); + new_sqrt_inv_hess = V * Dv.cwiseInverse().cwiseSqrt().asDiagonal() * V.transpose(); + } + +private: + Dtype r_; + + // Dimension + int nb_dim_; + // number of constraints + int nb_cons_; + // current sample size + int nb_curr_samples_; + // Initial vector + Eigen::Matrix initialization_; + // constraints matrix A + Eigen::Matrix cons_A_; + // constraint vector b + Eigen::Matrix cons_b_; + // Current vector + Eigen::Matrix curr_sample_; +}; + +#endif // PWALK_VAIDYA_WALKER_HPP_ + diff --git a/src/volesti/include/random_walks/exponential_hamiltonian_monte_carlo_exact_walk.hpp b/src/volesti/include/random_walks/exponential_hamiltonian_monte_carlo_exact_walk.hpp new file mode 100644 index 00000000..6e5d2e00 --- /dev/null +++ b/src/volesti/include/random_walks/exponential_hamiltonian_monte_carlo_exact_walk.hpp @@ -0,0 +1,300 @@ +// VolEsti (volume computation and sampling library) +// Copyright (c) 2021 Vissarion Fisikopoulos +// Copyright (c) 2021 Apostolos Chalkis + + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_EXPONENTIAL_EXACT_HMC_WALK_HPP +#define RANDOM_WALKS_EXPONENTIAL_EXACT_HMC_WALK_HPP + +#define INSIDE_BODY_TOLLERANCE 1e-10 + +#include "sampling/sphere.hpp" + + +// Exact HMC for sampling from the Exponential distribution restricted to a convex polytope +struct ExponentialHamiltonianMonteCarloExactWalk +{ + ExponentialHamiltonianMonteCarloExactWalk(double L) + : param(L, true, 0, false) + {} + + ExponentialHamiltonianMonteCarloExactWalk(double L, unsigned int rho) + : param(L, true, rho, true) + {} + + ExponentialHamiltonianMonteCarloExactWalk() + : param(0, false, 0, false) + {} + + struct parameters + { + parameters(double L, bool set, unsigned int _rho, bool _set_rho) + : m_L(L), set_L(set), rho(_rho), set_rho(_set_rho) + {} + double m_L; + bool set_L; + unsigned int rho; + bool set_rho; + }; + + parameters param; + + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + + template + Walk(GenericPolytope &P, Point const& p, Point const& c, NT const& T, RandomNumberGenerator &rng) + { + _Len = compute_diameter + ::template compute(P); + _Ac = P.get_mat() * c.getCoefficients(); + _Temp = T; + _c = c; + _rho = 100 * P.dimension(); // upper bound for the number of reflections (experimental) + initialize(P, p, rng); + } + + template + Walk(GenericPolytope &P, Point const& p, Point const& c, NT const& T, RandomNumberGenerator &rng, + parameters const& params) + { + _Len = params.set_L ? params.m_L + : compute_diameter + ::template compute(P); + _Ac = P.get_mat() * c.getCoefficients(); + _Temp = T; + _c = c; + _rho = 100 * P.dimension(); // upper bound for the number of reflections (experimental) + initialize(P, p, rng); + } + + template + < + typename GenericPolytope + > + inline bool apply(GenericPolytope& P, + Point& p, // a point to start + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT T; + int failures = 0, it; + Point p0 = _p; + + for (auto j=0u; j::apply(n, rng, false); + + it = 0; + while (it < _rho) + { + auto pbpair = P.quadratic_positive_intersect(_p, _v, _Ac, _Temp, _lambdas, + _Av, _lambda_prev, _facet_prev); + if (T <= pbpair.first) { + _p += ((T * T) / (-2.0*_Temp)) *_c + (T * _v); + _lambda_prev = T; + break; + } + _lambda_prev = pbpair.first; + _p += ((_lambda_prev * _lambda_prev) / (-2.0*_Temp)) *_c + (_lambda_prev * _v); + T -= _lambda_prev; + _v += (-_lambda_prev/_Temp) * _c; + P.compute_reflection(_v, _p, pbpair.second); + it++; + } + + } while (P.is_in(_p, INSIDE_BODY_TOLLERANCE) == 0); + if (it == _rho) { + _p = p0; + } + } + p = _p; + return true; + } + + + template + < + typename GenericPolytope + > + inline void get_starting_point(GenericPolytope& P, + Point const& center, + Point &q, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT radius = P.InnerBall().second; + + q = GetPointInDsphere::apply(n, radius, rng); + q += center; + initialize(P, q, rng); + + apply(P, q, walk_length, rng); + } + + + template + < + typename GenericPolytope + > + inline void parameters_burnin(GenericPolytope& P, + Point const& center, + unsigned int const& num_points, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + Point p(n); + std::vector pointset; + pointset.push_back(center); + pointset.push_back(_p); + NT rad = NT(0), max_dist, Lmax = NT(0), radius = P.InnerBall().second; + + for (int i = 0; i < num_points; i++) + { + p = GetPointInDsphere::apply(n, radius, rng); + p += center; + initialize(P, p, rng); + + apply(P, p, walk_length, rng); + max_dist = get_max_distance(pointset, p, rad); + if (max_dist > Lmax) + { + Lmax = max_dist; + } + if (2.0 * rad > Lmax) + { + Lmax = 2.0 * rad; + } + pointset.push_back(p); + } + + if (Lmax > _Len) + { + update_delta(Lmax); + } + pointset.clear(); + } + + + inline void update_delta(NT L) + { + _Len = L; + } + + +private : + + template + < + typename GenericPolytope + > + inline void initialize(GenericPolytope& P, + Point const& p, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT T; + _lambdas.setZero(P.num_of_hyperplanes()); + _Av.setZero(P.num_of_hyperplanes()); + _v = GetDirection::apply(n, rng, false); + + do { + _p = p; + T = rng.sample_urdist() * _Len; + int it = 0; + + std::pair pbpair + = P.quadratic_positive_intersect(_p, _v, _Ac, _Temp, _lambdas, _Av, _facet_prev); + if (T <= pbpair.first || pbpair.second < 0) { + _p += ((T * T) / (-2.0*_Temp)) *_c + (T * _v); + _lambda_prev = T; + return; + } + _lambda_prev = pbpair.first; + _p += ((_lambda_prev * _lambda_prev) / (-2.0*_Temp)) *_c + (_lambda_prev * _v); + _v += (-_lambda_prev/_Temp) * _c; + T -= _lambda_prev; + P.compute_reflection(_v, _p, pbpair.second); + + while (it <= _rho) + { + std::pair pbpair + = P.quadratic_positive_intersect(_p, _v, _Ac, _Temp, _lambdas, _Av, _lambda_prev, _facet_prev); + if (T <= pbpair.first || pbpair.second < 0) { + _p += ((T * T) / (-2.0*_Temp)) *_c + (T * _v); + _lambda_prev = T; + break; + } else if (it == _rho) { + _lambda_prev = rng.sample_urdist() * pbpair.first; + _p += ((_lambda_prev * _lambda_prev) / (-2.0*_Temp)) *_c + (_lambda_prev * _v); + break; + } + _lambda_prev = pbpair.first; + _p += ((_lambda_prev * _lambda_prev) / (-2.0*_Temp)) *_c + (_lambda_prev * _v); + _v += (-_lambda_prev/_Temp) * _c; + T -= _lambda_prev; + P.compute_reflection(_v, _p, pbpair.second); + it++; + } + } while (P.is_in(_p, INSIDE_BODY_TOLLERANCE) == 0); + } + + + inline double get_max_distance(std::vector &pointset, Point const& q, double &rad) + { + double dis = -1.0, temp_dis; + int jj = 0; + for (auto vecit = pointset.begin(); vecit!=pointset.end(); vecit++, jj++) + { + temp_dis = (q.getCoefficients() - (*vecit).getCoefficients()).norm(); + if (temp_dis > dis) { + dis = temp_dis; + } + if (jj == 0 && temp_dis > rad) { + rad = temp_dis; + } + } + return dis; + } + + + NT _Len; + VT _Ac; + Point _p; + Point _v; + Point _c; + NT _Temp; + NT _lambda_prev; + int _facet_prev; + unsigned int _rho; + typename Point::Coeff _lambdas; + typename Point::Coeff _Av; +}; + +}; + + +#endif // RANDOM_WALKS_EXPONENTIAL_HMC_WALK_HPP + diff --git a/src/volesti/include/random_walks/gaussian_accelerated_billiard_walk.hpp b/src/volesti/include/random_walks/gaussian_accelerated_billiard_walk.hpp new file mode 100644 index 00000000..fbf43f44 --- /dev/null +++ b/src/volesti/include/random_walks/gaussian_accelerated_billiard_walk.hpp @@ -0,0 +1,248 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2021 Vaibhav Thakkar + +// Contributed and/or modified by Vaibhav Thakkar, as part of Google Summer of Code 2021 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_GAUSSIAN_ACCELERATED_BILLIARD_WALK_HPP +#define RANDOM_WALKS_GAUSSIAN_ACCELERATED_BILLIARD_WALK_HPP + +#include "convex_bodies/orderpolytope.h" +#include "convex_bodies/ellipsoid.h" +#include "convex_bodies/ballintersectconvex.h" +#include "generators/boost_random_number_generator.hpp" +#include "sampling/ellipsoid.hpp" +#include "random_walks/uniform_billiard_walk.hpp" + +#include "random_walks/compute_diameter.hpp" + +// Billiard walk which accelarates each step for uniform distribution and also takes into account +// the shape of the polytope for generating directions. + +struct GaussianAcceleratedBilliardWalk +{ + GaussianAcceleratedBilliardWalk(double L) + : param(L, true) + {} + + GaussianAcceleratedBilliardWalk() + : param(0, false) + {} + + struct parameters + { + parameters(double L, bool set) + : m_L(L), set_L(set) + {} + double m_L; + bool set_L; + }; + + struct update_parameters + { + update_parameters() + : facet_prev(0), hit_ball(false), inner_vi_ak(0.0), ball_inner_norm(0.0) + {} + int facet_prev; + bool hit_ball; + double inner_vi_ak; + double ball_inner_norm; + }; + + parameters param; + + + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + // typedef typename Polytope::MT MT; + typedef typename Point::FT NT; + + template + Walk(GenericPolytope& P, + Point const& p, + Ellipsoid const& E, // ellipsoid representing the Gaussian distribution + RandomNumberGenerator &rng) + { + _update_parameters = update_parameters(); + _Len = compute_diameter + ::template compute(P); + + // Removed as will be used for sparse matrices only + // _AA.noalias() = P.get_mat() * P.get_mat().transpose(); + initialize(P, p, E, rng); + } + + template + Walk(GenericPolytope& P, + Point const& p, + Ellipsoid const& E, // ellipsoid representing the Gaussian distribution + RandomNumberGenerator &rng, + parameters const& params) + { + _update_parameters = update_parameters(); + _Len = params.set_L ? params.m_L + : compute_diameter + ::template compute(P); + + // Removed as will be used for sparse matrices only + // _AA.noalias() = P.get_mat() * P.get_mat().transpose(); + initialize(P, p, E, rng); + } + + template + < + typename GenericPolytope, + typename Ellipsoid + > + inline void apply(GenericPolytope& P, + Point &p, // a point to return the result + Ellipsoid const& E, // ellipsoid representing the Gaussian distribution + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT T; + const NT dl = 0.995; + int it; + + for (auto j=0u; j::apply(n, E, rng); + NT norm_v = _v.length(); + _v /= norm_v; + + Point p0 = _p; + Point v0 = _v; + typename Point::Coeff lambdas0 = _lambdas; + typename Point::Coeff Av0 = _Av; + NT lambda_prev0 = _lambda_prev; + + it = 0; + while (it < 100*n) + { + std::pair pbpair + = P.line_positive_intersect(_p, _v, _lambdas, _Av, _lambda_prev, _update_parameters); + if (T <= pbpair.first) { + _p += (T * _v); + _lambda_prev = T; + break; + } + _lambda_prev = dl * pbpair.first; + _p += (_lambda_prev * _v); + T -= _lambda_prev; + P.compute_reflection(_v, _p, _update_parameters); + it++; + } + if (it == 100*n) + { + _p = p0; + _lambdas = lambdas0; + _Av = Av0; + _lambda_prev = lambda_prev0; + } + else + { + // metropolis filter + NT u_logprob = log(rng.sample_urdist()); + NT accept_logprob = 0.5 * (norm_v * norm_v) * (E.mat_mult(v0) - E.mat_mult(_v)); + // std::cout << "diff: " << (accept_logprob - u_logprob) << std::endl; + if(u_logprob > accept_logprob) { + // reject + _p = p0; + _lambdas = lambdas0; + _Av = Av0; + _lambda_prev = lambda_prev0; + } + } + } + p = _p; + } + + inline void update_delta(NT L) + { + _Len = L; + } + + private : + + template + < + typename GenericPolytope, + typename Ellipsoid + > + inline void initialize(GenericPolytope& P, + Point const& p, // a point to start + Ellipsoid const& E, // ellipsoid representing the Gaussian distribution + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + const NT dl = 0.995; + _lambdas.setZero(P.num_of_hyperplanes()); + _Av.setZero(P.num_of_hyperplanes()); + _p = p; + _v = GetGaussianDirection::apply(n, E, rng); + NT norm_v = _v.length(); + _v /= norm_v; + + NT T = -std::log(rng.sample_urdist()) * _Len; + Point p0 = _p; + int it = 0; + + std::pair pbpair + = P.line_first_positive_intersect(_p, _v, _lambdas, _Av, _update_parameters); + if (T <= pbpair.first) { + _p += (T * _v); + _lambda_prev = T; + return; + } + _lambda_prev = dl * pbpair.first; + _p += (_lambda_prev * _v); + T -= _lambda_prev; + P.compute_reflection(_v, _p, _update_parameters); + + while (it <= 100*n) + { + std::pair pbpair + = P.line_positive_intersect(_p, _v, _lambdas, _Av, _lambda_prev, _update_parameters); + if (T <= pbpair.first) { + _p += (T * _v); + _lambda_prev = T; + break; + } else if (it == 100*n) { + _lambda_prev = rng.sample_urdist() * pbpair.first; + _p += (_lambda_prev * _v); + break; + } + _lambda_prev = dl * pbpair.first; + _p += (_lambda_prev * _v); + T -= _lambda_prev; + P.compute_reflection(_v, _p, _update_parameters); + it++; + } + } + + NT _Len; + Point _p; + Point _v; + NT _lambda_prev; + // MT _AA; // Removed as will be used for sparse matrices only + update_parameters _update_parameters; + typename Point::Coeff _lambdas; + typename Point::Coeff _Av; + }; + +}; + + +#endif diff --git a/src/volesti/include/random_walks/gaussian_ball_walk.hpp b/src/volesti/include/random_walks/gaussian_ball_walk.hpp new file mode 100644 index 00000000..8c267e0f --- /dev/null +++ b/src/volesti/include/random_walks/gaussian_ball_walk.hpp @@ -0,0 +1,107 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_GAUSSIAN_BALL_WALK_HPP +#define RANDOM_WALKS_GAUSSIAN_BALL_WALK_HPP + +#include "sampling/sphere.hpp" +#include "random_walks/gaussian_helpers.hpp" + +// Ball walk with spherical Gaussian target distribution + +struct GaussianBallWalk +{ + + GaussianBallWalk(double L) + : param(L, true) + {} + + GaussianBallWalk() + : param(0, false) + {} + + struct parameters + { + parameters(double L, bool set) + : m_L(L), set_delta(set) + {} + double m_L; + bool set_delta; + }; + + parameters param; + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + + template + static inline NT compute_delta(GenericPolytope& P, NT const& a) + { + //return ((P.InnerBall()).second * NT(4)) / NT(P.dimension()); + return (NT(4) * (P.InnerBall()).second) / std::sqrt(std::max(NT(1), a) * NT(P.dimension())); + } + + Walk (Polytope& P, Point const& p, NT const& a, + RandomNumberGenerator &rng) + { + _delta = compute_delta(P, a); + } + + Walk (Polytope& P, + Point const& p, + NT const& a, + RandomNumberGenerator &rng, + parameters const& params) + { + _delta = params.set_delta ? params.m_L + : compute_delta(P, a); + } + + template + inline void apply(BallPolytope const& P, + Point &p, // a point to start + NT const& a_i, + unsigned int const& walk_length, + RandomNumberGenerator& rng) + { + for (auto j = 0u; j < walk_length; ++j) + { + Point y = GetPointInDsphere::apply(P.dimension(), + _delta, + rng); + y += p; + if (P.is_in(y) == -1) + { + NT f_x = eval_exp(p, a_i); + NT f_y = eval_exp(y, a_i); + NT rnd = rng.sample_urdist(); + if (rnd <= f_y / f_x) { + p = y; + } + } + } + } + + inline void update_delta(NT delta) + { + _delta = delta; + } + +private : + NT _delta; +}; + +}; + +#endif // RANDOM_WALKS_GAUSSIAN_BALL_WALK_HPP diff --git a/src/volesti/include/random_walks/gaussian_cdhr_walk.hpp b/src/volesti/include/random_walks/gaussian_cdhr_walk.hpp new file mode 100644 index 00000000..04ebedbc --- /dev/null +++ b/src/volesti/include/random_walks/gaussian_cdhr_walk.hpp @@ -0,0 +1,151 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_GAUSSIAN_CDHR_WALK_HPP +#define RANDOM_WALKS_GAUSSIAN_CDHR_WALK_HPP + +#include "sampling/sphere.hpp" +#include "generators/boost_random_number_generator.hpp" +#include "random_walks/gaussian_helpers.hpp" + +// Pick a point from the distribution exp(-a_i||x||^2) on the coordinate chord +template +< + typename NT, + typename RandomNumberGenerator +> +NT chord_random_point_generator_exp_coord(const NT &l, + const NT &u, + const NT &a_i, + RandomNumberGenerator& rng) +{ + NT r, r_val, fn, dis; + // pick from 1-dimensional gaussian if enough weight is inside polytope P + if (a_i > EXP_CHORD_TOLERENCE && u - l >= 2.0 / std::sqrt(2.0 * a_i)) + { + while (true) { + r = rng.sample_ndist();//rdist(rng2); + r = r / std::sqrt(2.0 * a_i); + if (r >= l && r <= u) { + break; + } + } + dis = r; + + // select using rejection sampling from a bounding rectangle + } + else + { + NT M = get_max_coord(l, u, a_i); + while (true) + { + r = rng.sample_urdist(); + dis = (1.0 - r) * l + r * u; + r_val = M * rng.sample_urdist(); + fn = std::exp(-a_i * dis * dis); + if (r_val < fn) + { + break; + } + } + } + return dis; +} + + +// Coordinate directions hit-and-run walk with spherical Gaussian target distribution + +struct GaussianCDHRWalk +{ + + struct parameters {}; + parameters param; + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + + Walk(Polytope& P, + Point const& p, + NT const& a_i, + RandomNumberGenerator &rng) + { + initialize(P, p, a_i, rng); + } + + Walk(Polytope& P, + Point const& p, + NT const& a_i, + RandomNumberGenerator& rng, + parameters&) + { + initialize(P, p, a_i, rng); + } + + template + < + typename BallPolytope + > + inline void apply(BallPolytope const& P, + Point &p, // a point to start + NT const& a_i, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + for (auto j = 0u; j < walk_length; ++j) + { + auto rand_coord_prev = _rand_coord; + _rand_coord = rng.sample_uidist(); + std::pair bpair = + P.line_intersect_coord(_p, _p_prev, _rand_coord, + rand_coord_prev, _lamdas); + NT dis = chord_random_point_generator_exp_coord + (_p[_rand_coord] + bpair.second, + _p[_rand_coord] + bpair.first, + a_i, + rng); + _p_prev = _p; + _p.set_coord(_rand_coord, dis); + } + p = _p; + } + +private : + + template + inline void initialize(BallPolytope const& P, + Point const& p, + NT const& a_i, + RandomNumberGenerator &rng) + { + _lamdas.setZero(P.num_of_hyperplanes()); + _rand_coord = rng.sample_uidist(); + _p = p; + std::pair bpair = P.line_intersect_coord(_p, _rand_coord, _lamdas); + NT dis = chord_random_point_generator_exp_coord + (_p[_rand_coord] + bpair.second, + _p[_rand_coord] + bpair.first, + a_i, rng); + _p_prev = p; + _p.set_coord(_rand_coord, dis); + } + + unsigned int _rand_coord; + Point _p; + Point _p_prev; + typename Point::Coeff _lamdas; +}; + +}; + +#endif // RANDOM_WALKS_GAUSSIAN_CDHR_WALK_HPP diff --git a/src/volesti/include/random_walks/gaussian_hamiltonian_monte_carlo_exact_walk.hpp b/src/volesti/include/random_walks/gaussian_hamiltonian_monte_carlo_exact_walk.hpp new file mode 100644 index 00000000..8b83b053 --- /dev/null +++ b/src/volesti/include/random_walks/gaussian_hamiltonian_monte_carlo_exact_walk.hpp @@ -0,0 +1,275 @@ +// VolEsti (volume computation and sampling library) +// Copyright (c) 2021 Vissarion Fisikopoulos +// Copyright (c) 2021 Apostolos Chalkis + + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_GAUSSIAN_EXACT_HMC_WALK_HPP +#define RANDOM_WALKS_GAUSSIAN_EXACT_HMC_WALK_HPP + +#include "sampling/sphere.hpp" + + + +// Exact HMC for sampling from the spherical Gaussian distribution + +struct GaussianHamiltonianMonteCarloExactWalk +{ + GaussianHamiltonianMonteCarloExactWalk(double L, unsigned int _rho) + : param(L, true, _rho, true) + {} + + GaussianHamiltonianMonteCarloExactWalk(double L) + : param(L, true, 0, false) + {} + + GaussianHamiltonianMonteCarloExactWalk() + : param(0, false, 0, false) + {} + + + struct parameters + { + parameters(double L, bool set, unsigned int _rho, bool _set_rho) + : m_L(L), set_L(set), rho(_rho), set_rho(_set_rho) + {} + double m_L; + bool set_L; + unsigned int rho; + bool set_rho; + }; + + parameters param; + + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + typedef typename Polytope::VT VT; + + template + Walk(GenericPolytope &P, Point const& p, NT const& a_i, RandomNumberGenerator &rng) + { + _Len = compute_diameter + ::template compute(P); + _omega = std::sqrt(NT(2) * a_i); + _rho = 100 * P.dimension(); // upper bound for the number of reflections (experimental) + initialize(P, p, a_i, rng); + } + + template + Walk(GenericPolytope &P, Point const& p, NT const& a_i, RandomNumberGenerator &rng, + parameters const& params) + { + _Len = params.set_L ? params.m_L + : compute_diameter + ::template compute(P); + _omega = std::sqrt(NT(2) * a_i); + _rho = 100 * P.dimension(); // upper bound for the number of reflections (experimental) + initialize(P, p, a_i, rng); + } + + template + < + typename GenericPolytope + > + inline void apply(GenericPolytope& P, + Point& p, + NT const& a_i, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT T; + + for (auto j=0u; j::apply(n, rng, false); + Point p0 = _p; + int it = 0; + while (it < _rho) + { + auto pbpair = P.trigonometric_positive_intersect(_p, _v, _omega, _facet_prev); + if (T <= pbpair.first) { + update_position(_p, _v, T, _omega); + break; + } + _lambda_prev = pbpair.first; + T -= _lambda_prev; + update_position(_p, _v, _lambda_prev, _omega); + P.compute_reflection(_v, _p, pbpair.second); + it++; + } + if (it == _rho){ + _p = p0; + } + } + p = _p; + } + + + template + < + typename GenericPolytope + > + inline void get_starting_point(GenericPolytope& P, + Point const& center, + Point &q, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT radius = P.InnerBall().second; + + q = GetPointInDsphere::apply(n, radius, rng); + q += center; + initialize(P, q, rng); + + apply(P, q, walk_length, rng); + } + + + template + < + typename GenericPolytope + > + inline void parameters_burnin(GenericPolytope& P, + Point const& center, + unsigned int const& num_points, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + Point p(n); + std::vector pointset; + pointset.push_back(center); + pointset.push_back(_p); + NT rad = NT(0), max_dist, Lmax = NT(0), radius = P.InnerBall().second; + + for (int i = 0; i < num_points; i++) + { + p = GetPointInDsphere::apply(n, radius, rng); + p += center; + initialize(P, p, rng); + + apply(P, p, walk_length, rng); + max_dist = get_max_distance(pointset, p, rad); + if (max_dist > Lmax) + { + Lmax = max_dist; + } + if (2.0*rad > Lmax) + { + Lmax = 2.0 * rad; + } + pointset.push_back(p); + } + + if (Lmax > _Len) + { + update_delta(Lmax); + } + pointset.clear(); + } + + inline void update_delta(NT L) + { + _Len = L; + } + +private : + + template + < + typename GenericPolytope + > + inline void initialize(GenericPolytope& P, + Point const& p, + NT const& a_i, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + _facet_prev = -1; + _p = p; + _v = GetDirection::apply(n, rng, false); + + NT T = rng.sample_urdist() * _Len; + int it = 0; + + while (it <= _rho) + { + auto pbpair + = P.trigonometric_positive_intersect(_p, _v, _omega, _facet_prev); + if (T <= pbpair.first) { + update_position(_p, _v, T, _omega); + break; + } else if (it == _rho) { + _lambda_prev = rng.sample_urdist() * pbpair.first; + update_position(_p, _v, _lambda_prev, _omega); + break; + } + _lambda_prev = pbpair.first; + update_position(_p, _v, _lambda_prev, _omega); + T -= _lambda_prev; + P.compute_reflection(_v, _p, pbpair.second); + it++; + } + } + + inline void update_position(Point &p, Point &v, NT const& T, NT const& omega) + { + NT C, Phi; + for (size_t i = 0; i < p.dimension(); i++) + { + C = std::sqrt(p[i] * p[i] + (v[i] * v[i]) / (omega * omega)); + Phi = std::atan((-v[i]) / (p[i] * omega)); + if (v[i] < 0.0 && Phi < 0.0) { + Phi += M_PI; + } else if (v[i] > 0.0 && Phi > 0.0) { + Phi -= M_PI; + } + p.set_coord(i, C * std::cos(omega * T + Phi)); + v.set_coord(i, -C * omega * std::sin(omega * T + Phi)); + } + + } + + inline double get_max_distance(std::vector &pointset, Point const& q, double &rad) + { + double dis = -1.0, temp_dis; + int jj = 0; + for (auto vecit = pointset.begin(); vecit!=pointset.end(); vecit++, jj++) + { + temp_dis = (q.getCoefficients() - (*vecit).getCoefficients()).norm(); + if (temp_dis > dis) { + dis = temp_dis; + } + if (jj == 0 && temp_dis > rad) { + rad = temp_dis; + } + } + return dis; + } + + int _facet_prev; + unsigned int _rho; + NT _Len; + Point _p; + Point _v; + NT _omega; + NT _lambda_prev; +}; + +}; + + +#endif // RANDOM_WALKS_GAUSSIAN_HMC_WALK_HPP + diff --git a/src/volesti/include/random_walks/gaussian_helpers.hpp b/src/volesti/include/random_walks/gaussian_helpers.hpp new file mode 100644 index 00000000..d1ab5429 --- /dev/null +++ b/src/volesti/include/random_walks/gaussian_helpers.hpp @@ -0,0 +1,48 @@ +#ifndef GAUSSIAN_HELPERS_HPP +#define GAUSSIAN_HELPERS_HPP + +#define EXP_CHORD_TOLERENCE 0.00000001 + +// evaluate the pdf of point p +template +NT eval_exp(Point const& p, NT const& a) +{ + return std::exp(-a * p.squared_length()); +} + + +template +NT get_max(Point const& l, Point const& u, NT const& a_i) +{ + NT res; + Point a = -1.0 * l; + Point bef = u - l; + Point b = (1.0 / std::sqrt((bef).squared_length())) * bef; + Point z = (a.dot(b) * b) + l; + NT low_bd = (l[0] - z[0]) / b[0], up_bd = (u[0] - z[0]) / b[0]; + if (low_bd * up_bd > 0) + { + //if(std::signbit(low_bd)==std::signbit(up_bd)){ + res = std::max(eval_exp(u, a_i), eval_exp(l, a_i)); + } + else + { + res = eval_exp(z, a_i); + } + + return res; +} + + +template +NT get_max_coord(NT const& l, NT const& u, NT const& a_i) +{ + const NT zero = 0; + if (l < zero && u > zero) + { + return NT(1); + } + return std::max(std::exp(-a_i * l * l), std::exp(-a_i * u * u)); +} + +#endif // GAUSSIAN_HELPERS_HPP diff --git a/src/volesti/include/random_walks/gaussian_rdhr_walk.hpp b/src/volesti/include/random_walks/gaussian_rdhr_walk.hpp new file mode 100644 index 00000000..b7a53abe --- /dev/null +++ b/src/volesti/include/random_walks/gaussian_rdhr_walk.hpp @@ -0,0 +1,118 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_GAUSSIAN_RDHR_WALK_HPP +#define RANDOM_WALKS_GAUSSIAN_RDHR_WALK_HPP + +#include "random_walks/gaussian_helpers.hpp" +#include "generators/boost_random_number_generator.hpp" + + +// Pick a point from the distribution exp(-a_i||x||^2) on the chord +template +< + typename Point, + typename NT, + typename RandomNumberGenerator +> +void chord_random_point_generator_exp(Point &lower, + Point & upper, + const NT &a_i, + Point &p, + RandomNumberGenerator& rng) +{ + NT r, r_val, fn; + Point bef = upper - lower; + // pick from 1-dimensional gaussian if enough weight is inside polytope P + if (a_i > EXP_CHORD_TOLERENCE && std::sqrt(bef.squared_length()) >= (2.0 / std::sqrt(2.0 * a_i))) + { + Point a = -1.0 * lower; + Point b = (1.0 / std::sqrt(bef.squared_length())) * bef; + Point z = (a.dot(b) * b) + lower; + NT low_bd = (lower[0] - z[0]) / b[0]; + NT up_bd = (upper[0] - z[0]) / b[0]; + while (true) { + r = rng.sample_ndist();//rdist(rng2); + r = r / std::sqrt(2.0 * a_i); + if (r >= low_bd && r <= up_bd) { + break; + } + } + p = (r * b) + z; + + // select using rejection sampling from a bounding rectangle + } else { + NT M = get_max(lower, upper, a_i); + while (true) { + r = rng.sample_urdist();//urdist(rng2); + Point pef = r * upper; + p = ((1.0 - r) * lower) + pef; + r_val = M * rng.sample_urdist();//urdist(var.rng); + fn = eval_exp(p, a_i); + if (r_val < fn) { + break; + } + } + } +} + +// Random directions hit-and-run walk with spherical Gaussian target distribution + +struct GaussianRDHRWalk +{ + + struct parameters {}; + parameters param; + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + + Walk(Polytope&, Point const&, NT const&, RandomNumberGenerator&) + {} + + Walk(Polytope&, Point const&, NT const&, RandomNumberGenerator&, + parameters&) + {} + + template + < + typename BallPolytope + > + inline void apply(BallPolytope const& P, + Point &p, // a point to start + NT const& a_i, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + for (auto j = 0u; j < walk_length; ++j) + { + Point v = GetDirection::apply(p.dimension(), rng); + std::pair dbpair = P.line_intersect(p, v); + + NT min_plus = dbpair.first; + NT max_minus = dbpair.second; + Point upper = (min_plus * v) + p; + Point lower = (max_minus * v) + p; + + chord_random_point_generator_exp(lower, upper, a_i, p, rng); + } + } +}; + +}; + + +#endif // RANDOM_WALKS_GAUSSIAN_RDHR_WALK_HPP diff --git a/src/volesti/include/random_walks/hamiltonian_monte_carlo_walk.hpp b/src/volesti/include/random_walks/hamiltonian_monte_carlo_walk.hpp new file mode 100644 index 00000000..23342ea5 --- /dev/null +++ b/src/volesti/include/random_walks/hamiltonian_monte_carlo_walk.hpp @@ -0,0 +1,179 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Lee, Yin Tat, Ruoqi Shen, and Kevin Tian. "Logsmooth Gradient Concentration +// and Tighter Runtimes for Metropolized Hamiltonian Monte Carlo." +#ifndef HAMILTONIAN_MONTE_CARLO_WALK_HPP +#define HAMILTONIAN_MONTE_CARLO_WALK_HPP + + +#include "generators/boost_random_number_generator.hpp" +#include "random_walks/gaussian_helpers.hpp" +#include "ode_solvers/ode_solvers.hpp" + +struct HamiltonianMonteCarloWalk { + + template + < + typename NT, + typename OracleFunctor + > + struct parameters { + NT epsilon; // tolerance in mixing + NT eta; // step size + + parameters( + OracleFunctor const& F, + unsigned int dim, + NT epsilon_=2) + { + epsilon = epsilon_; + eta = 1.0 / (dim * sqrt(F.params.L)); + // eta = 1.0 / + // (sqrt(20 * F.params.L * pow(dim, 3))); + } + }; + + template + < + typename Point, + typename Polytope, + typename RandomNumberGenerator, + typename NegativeGradientFunctor, + typename NegativeLogprobFunctor, + typename Solver + > + struct Walk { + + typedef std::vector pts; + typedef typename Point::FT NT; + typedef std::vector bounds; + + // Hyperparameters of the sampler + parameters ¶ms; + + // Numerical ODE solver + Solver *solver; + + // Dimension + unsigned int dim; + + // Discarded Samples + long total_discarded_samples = 0; + long num_runs = 0; + float discard_ratio = 0; + + // Average acceptance probability + float total_acceptance_log_prob = 0; + float average_acceptance_log_prob = 0; + + // References to xs + Point x, v; + + // Proposal points + Point x_tilde, v_tilde; + + // Gradient function + NegativeGradientFunctor &F; + + bool accepted; + + // Helper variables + NT H, H_tilde, log_prob, u_logprob; + + // Density exponent + NegativeLogprobFunctor &f; + + Walk(Polytope *P, + Point &p, + NegativeGradientFunctor &neg_grad_f, + NegativeLogprobFunctor &neg_logprob_f, + parameters ¶m) : + params(param), + F(neg_grad_f), + f(neg_logprob_f) + { + + dim = p.dimension(); + + // Starting point is provided from outside + x = p; + + accepted = false; + + // Initialize solver + solver = new Solver(0, params.eta, pts{x, x}, F, bounds{P, NULL}); + + }; + + inline void apply(RandomNumberGenerator &rng, + int walk_length=1, + bool metropolis_filter=true) + { + num_runs++; + + // Pick a random velocity + v = GetDirection::apply(dim, rng, false); + + solver->set_state(0, x); + solver->set_state(1, v); + + // Get proposals + solver->steps(walk_length, accepted); + x_tilde = solver->get_state(0); + v_tilde = solver->get_state(1); + + if (metropolis_filter) { + // Calculate initial Hamiltonian + H = hamiltonian(x, v); + + // Calculate new Hamiltonian + H_tilde = hamiltonian(x_tilde, v_tilde); + + // Log-sum-exp trick + log_prob = H - H_tilde < 0 ? H - H_tilde : 0; + + // Decide to switch + u_logprob = log(rng.sample_urdist()); + total_acceptance_log_prob += log_prob; + if (u_logprob < log_prob) { + x = x_tilde; + accepted = true; + } + else { + total_discarded_samples++; + accepted = false; + } + } else { + x = x_tilde; + accepted = true; + } + + discard_ratio = (1.0 * total_discarded_samples) / num_runs; + average_acceptance_log_prob = total_acceptance_log_prob / num_runs; + + } + + inline NT hamiltonian(Point &pos, Point &vel) const { + return f(pos) + 0.5 * vel.dot(vel); + } + + void disable_adaptive() { + solver->disable_adaptive(); + } + + void enable_adaptive() { + solver->enable_adaptive(); + } + }; +}; + +#endif // HAMILTONIAN_MONTE_CARLO_WALK_HPP diff --git a/src/volesti/include/random_walks/langevin_walk.hpp b/src/volesti/include/random_walks/langevin_walk.hpp new file mode 100644 index 00000000..df23dbac --- /dev/null +++ b/src/volesti/include/random_walks/langevin_walk.hpp @@ -0,0 +1,153 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Shen, Ruoqi, and Yin Tat Lee. "The randomized midpoint method for +// log-concave sampling." Advances in Neural Information Processing Systems. 2019. + +#ifndef LANGEVIN_WALK_HPP +#define LANGEVIN_WALK_HPP + +#include "generators/boost_random_number_generator.hpp" +#include "random_walks/gaussian_helpers.hpp" + +struct UnderdampedLangevinWalk { + + template + < + typename NT, + typename OracleFunctor + > + struct parameters { + NT epsilon; // tolerance in mixing + NT eta; // step size + NT u; + parameters( + OracleFunctor const& F, + unsigned int dim, + NT epsilon_=1e-4) + { + epsilon = epsilon_; + u = 1.0 / F.params.L; + eta = 1.0 / (sqrt(20 * F.params.L)); + + // eta = std::min(pow(epsilon, 1.0 / 3) / + // pow(F.params.kappa, 1.0 / 6) * + // pow(log(1.0 / epsilon), - 1.0 / 6), + // pow(epsilon, 2.0 / 3) * + // pow(log(1.0 / epsilon), - 1.0 / 3)); + } + }; + + template + < + typename Point, + typename Polytope, + typename RandomNumberGenerator, + typename NegativeGradientFunctor, + typename NegativeLogprobFunctor, + typename Solver_ // Dummy template argument + > + struct Walk { + + typedef std::vector pts; + typedef typename Point::FT NT; + typedef std::vector bounds; + typedef RandomizedMipointSDESolver Solver; + + parameters ¶ms; + + // Numerical ODE solver + Solver *solver; + + unsigned int dim; + + // References to xs + Point x, v; + // Proposal points + Point x_tilde, v_tilde; + + // Gradient Function + NegativeGradientFunctor &F; + + // Density exponent + NegativeLogprobFunctor &f; + + NT H_tilde, H, log_prob, u_logprob; + + Walk(Polytope *P, + Point &initial_x, + NegativeGradientFunctor &neg_grad_f, + NegativeLogprobFunctor &neg_logprob_f, + parameters ¶m) : + params(param), + F(neg_grad_f), + f(neg_logprob_f) + { + // Starting point is provided from outside + x = initial_x; + dim = initial_x.dimension(); + v = Point(dim); + + solver = new Solver(0, params.eta, pts{x, v}, F, bounds{P, NULL}, params.u); + + }; + + inline void apply( + RandomNumberGenerator &rng, + int walk_length=1, + bool metropolis_filter=false) + { + solver->set_state(0, x); + solver->set_state(1, v); + + // Get proposals + solver->steps(walk_length, rng); + x_tilde = solver->get_state(0); + v_tilde = solver->get_state(1); + + if (metropolis_filter) { + // Calculate initial Hamiltonian + H = hamiltonian(x, v); + + // Calculate new Hamiltonian + H_tilde = hamiltonian(x_tilde, v_tilde); + + // Log-sum-exp trick + log_prob = H - H_tilde < 0 ? H - H_tilde : 0; + + // Decide to switch + u_logprob = log(rng.sample_urdist()); + if (u_logprob < log_prob) { + x = x_tilde; + v = v_tilde; + } + } else { + x = x_tilde; + v = v_tilde; + } + + } + + inline NT hamiltonian(Point &pos, Point &vel) const { + return f(pos) + 1.0 / (2 * params.u) * vel.dot(vel); + } + + void disable_adaptive() { + // TODO Implement + } + + void enable_adaptive() { + // TODO Implement + } + }; +}; + +#endif // LANGEVIN_WALK_HPP diff --git a/src/volesti/include/random_walks/multithread_walks.hpp b/src/volesti/include/random_walks/multithread_walks.hpp new file mode 100644 index 00000000..c42fba1f --- /dev/null +++ b/src/volesti/include/random_walks/multithread_walks.hpp @@ -0,0 +1,739 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Contributed and/or modified by Konstantinos Pallikaris, as part of Google Summer of Code 2021 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +// THIS IS A TEMPORAL FILE. IT CONTAINS A FEW PROTOTYPES + +#ifndef RANDOM_WALKS_MULTITHREAD_WALKS_HPP +#define RANDOM_WALKS_MULTITHREAD_WALKS_HPP + +#include "sampling/sphere.hpp" +#include "generators/boost_random_number_generator.hpp" +#include "random_walks/gaussian_helpers.hpp" +#include "random_walks/gaussian_cdhr_walk.hpp" +#include "random_walks/gaussian_rdhr_walk.hpp" + + +// random directions hit-and-run walk with uniform target distribution +// from boundary + +struct BCDHRWalk_multithread +{ + template + struct thread_parameters + { + thread_parameters(unsigned int d, unsigned int m) + { + p = Point(d); + p1 = Point(d); + p2 = Point(d); + p_prev = Point(d); + lambdas.setZero(m); + } + + Point p; + Point p1; + Point p2; + Point p_prev; + unsigned int rand_coord_prev; + unsigned int rand_coord; + typename Point::Coeff lambdas; + }; + + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + typedef thread_parameters thread_parameters_; + //typedef thread_params thread_parameters_; + + template + Walk(GenericPolytope& P, thread_parameters_ ¶meters, RandomNumberGenerator& rng) + { + initialize(P, parameters, rng); + } + + template + < + typename BallPolytope + > + inline void apply(BallPolytope const& P, + thread_parameters_ ¶ms, // parameters + unsigned int const& walk_length, + RandomNumberGenerator& rng) + { + std::pair bpair; + for (auto j = 0u; j < walk_length; ++j) + { + params.rand_coord_prev = params.rand_coord; + params.rand_coord = rng.sample_uidist(); + NT kapa = rng.sample_urdist(); + bpair = P.line_intersect_coord(params.p, + params.p_prev, + params.rand_coord, + params.rand_coord_prev, + params.lambdas); + params.p_prev = params.p; + params.p.set_coord(params.rand_coord, params.p[params.rand_coord] + bpair.first + kapa + * (bpair.second - bpair.first)); + } + params.p1 = params.p_prev; + params.p2 = params.p_prev; + params.p1.set_coord(params.rand_coord, params.p_prev[params.rand_coord] + bpair.first); + params.p2.set_coord(params.rand_coord, params.p_prev[params.rand_coord] + bpair.second); + } + + private : + + template + inline void initialize(GenericBody const& P, + thread_parameters_ ¶ms, // parameters + RandomNumberGenerator& rng) + { + params.lambdas.setZero(P.num_of_hyperplanes()); + params.rand_coord = rng.sample_uidist(); + NT kapa = rng.sample_urdist(); + + std::pair bpair = P.line_intersect_coord(params.p, params.rand_coord, + params.lambdas); + params.p_prev = params.p; + params.p.set_coord(params.rand_coord, params.p[params.rand_coord] + bpair.first + kapa + * (bpair.second - bpair.first)); + } + + }; + +}; + + +// Random directions hit-and-run walk with uniform target distribution +// from the boundary, multithread version + +struct BRDHRWalk_multithread +{ + + template + struct thread_parameters + { + thread_parameters(unsigned int d, unsigned int m) + { + p = Point(d); + p1 = Point(d); + p2 = Point(d); + v = Point(d); + lambdas.setZero(m); + Av.setZero(m); + lambda_prev = NT(0); + } + + Point p; + Point p1; + Point p2; + Point v; + NT lambda_prev; + typename Point::Coeff lambdas; + typename Point::Coeff Av; + }; + + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + typedef thread_parameters thread_parameters_; + //typedef thread_params thread_parameters_; + + template + Walk(GenericPolytope& P, thread_parameters_ ¶meters, RandomNumberGenerator& rng) + { + initialize(P, parameters, rng); + } + + template + < + typename BallPolytope + > + inline void apply(BallPolytope const& P, + thread_parameters_ ¶ms, // parameters + unsigned int const& walk_length, + RandomNumberGenerator& rng) + { + for (auto j=0u; j::apply(P.dimension(), rng); + std::pair bpair = P.line_intersect(params.p, params.v, params.lambdas, params.Av, + params.lambda_prev); + params.lambda_prev = rng.sample_urdist() * (bpair.first - bpair.second) + + bpair.second; + params.p1 = (bpair.first * params.v); + params.p1 += params.p; + params.p2 = (bpair.second * params.v); + params.p2 += params.p; + params.p += (params.lambda_prev * params.v); + } + } + + private : + + template + inline void initialize(GenericBody const& P, + thread_parameters_ ¶ms, // parameters + RandomNumberGenerator& rng) + { + params.lambdas.setZero(P.num_of_hyperplanes()); + params.Av.setZero(P.num_of_hyperplanes()); + + params.v = GetDirection::apply(P.dimension(), rng); + std::pair bpair = P.line_intersect(params.p, params.v, params.lambdas, params.Av); + params.lambda_prev = rng.sample_urdist() * (bpair.first - bpair.second) + bpair.second; + params.p = (params.lambda_prev * params.v) + params.p; + } + + }; + +}; + + +// Coordinate directions hit-and-run walk with spherical Gaussian target distribution +// Multithred version + +struct GaussianCDHRWalk_multithread +{ + + template + struct thread_parameters + { + thread_parameters(unsigned int d, unsigned int m) + { + p = Point(d); + p_prev = Point(d); + lambdas.setZero(m); + } + + Point p; + Point p_prev; + unsigned int rand_coord_prev; + unsigned int rand_coord; + typename Point::Coeff lambdas; + }; + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + typedef thread_parameters thread_parameters_; + //typedef thread_params thread_parameters_; + + Walk(Polytope& P, + thread_parameters_ ¶ms, + NT const& a_i, + RandomNumberGenerator &rng) + { + initialize(P, params, a_i, rng); + } + + template + Walk(Polytope& P, + thread_parameters_ ¶ms, + NT const& a_i, + RandomNumberGenerator &rng, + parameters&) + { + initialize(P, params, a_i, rng); + } + + + template + < + typename BallPolytope + > + inline void apply(BallPolytope const& P, + thread_parameters_ ¶ms, // parameters + NT const& a_i, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + for (auto j = 0u; j < walk_length; ++j) + { + params.rand_coord_prev = params.rand_coord; + params.rand_coord = rng.sample_uidist(); + std::pair bpair = + P.line_intersect_coord(params.p, params.p_prev, params.rand_coord, + params.rand_coord_prev, params.lambdas); + NT dis = chord_random_point_generator_exp_coord + (params.p[params.rand_coord] + bpair.second, + params.p[params.rand_coord] + bpair.first, + a_i, + rng); + params.p_prev = params.p; + params.p.set_coord(params.rand_coord, dis); + } + } + +private : + + template + inline void initialize(BallPolytope const& P, + thread_parameters_ ¶ms, // parameters + NT const& a_i, + RandomNumberGenerator &rng) + { + params.lambdas.setZero(P.num_of_hyperplanes()); + params.rand_coord = rng.sample_uidist(); + + std::pair bpair = P.line_intersect_coord(params.p, params.rand_coord, params.lambdas); + NT dis = chord_random_point_generator_exp_coord + (params.p[params.rand_coord] + bpair.second, + params.p[params.rand_coord] + bpair.first, + a_i, rng); + params.p_prev = params.p; + params.p.set_coord(params.rand_coord, dis); + } + +}; + +}; + + +// Random directions hit-and-run walk with spherical Gaussian target distribution +// multithread version + +struct GaussianRDHRWalk_multithread +{ + + template + struct thread_parameters + { + thread_parameters(unsigned int d, unsigned int m) + { + p = Point(d); + v = Point(d); + } + + Point p; + Point v; + }; + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + typedef thread_parameters thread_parameters_; + //typedef thread_params thread_parameters_; + + Walk(Polytope&, thread_parameters_ &, NT const&, RandomNumberGenerator&) + {} + + template + Walk(Polytope&, thread_parameters_ &, NT const&, RandomNumberGenerator&, + parameters&) + {} + + template + < + typename BallPolytope + > + inline void apply(BallPolytope const& P, + thread_parameters_ ¶ms, // parameters + NT const& a_i, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + for (auto j = 0u; j < walk_length; ++j) + { + params.v = GetDirection::apply(P.dimension(), rng); + std::pair dbpair = P.line_intersect(params.p, params.v); + + NT min_plus = dbpair.first; + NT max_minus = dbpair.second; + Point upper = (min_plus * params.v) + params.p; + Point lower = (max_minus * params.v) + params.p; + + chord_random_point_generator_exp(lower, upper, a_i, params.p, rng); + } + } +}; + +}; + + +// Billiard walk for uniform distribution + +struct BilliardWalk_multithread +{ + + template + struct thread_parameters + { + thread_parameters(unsigned int d, unsigned int m) + { + p = Point(d); + p0 = Point(d); + v = Point(d); + lambdas.setZero(m); + Av.setZero(m); + lambda_prev = NT(0); + } + + Point p; + Point p0; + Point v; + NT lambda_prev; + typename Point::Coeff lambdas; + typename Point::Coeff Av; + }; + + BilliardWalk_multithread(double L) + : param(L, true) + {} + + BilliardWalk_multithread() + : param(0, false) + {} + + struct parameters + { + parameters(double L, bool set) + : m_L(L), set_L(set) + {} + double m_L; + bool set_L; + }; + + parameters param; + + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + typedef thread_parameters thread_parameters_; + //typedef thread_params thread_parameters_; + + template + Walk(GenericPolytope& P, thread_parameters_ ¶meters, RandomNumberGenerator &rng) + { + _Len = compute_diameter + ::template compute(P); + initialize(P, parameters, rng); + } + + template + Walk(GenericPolytope& P, thread_parameters_ ¶meters, RandomNumberGenerator &rng, + parameters_ const& params) + { + _Len = params.set_L ? params.m_L + : compute_diameter + ::template compute(P); + initialize(P, parameters, rng); + } + + template + < + typename GenericPolytope + > + inline void apply(GenericPolytope& P, + thread_parameters_ ¶meters, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT T = rng.sample_urdist() * _Len; + const NT dl = 0.995; + + for (auto j=0u; j::apply(n, rng); + parameters.p0 = parameters.p; + int it = 0; + while (it < 50*n) + { + auto pbpair = P.line_positive_intersect(parameters.p, parameters.v, parameters.lambdas, + parameters.Av, parameters.lambda_prev); + if (T <= pbpair.first) { + parameters.p += (T * parameters.v); + parameters.lambda_prev = T; + break; + } + parameters.lambda_prev = dl * pbpair.first; + parameters.p += (parameters.lambda_prev * parameters.v); + T -= parameters.lambda_prev; + P.compute_reflection(parameters.v, parameters.p, pbpair.second); + it++; + } + if (it == 50*n) + { + parameters.p = parameters.p0; + } + } + } + + inline void update_delta(NT L) + { + _Len = L; + } + +private : + + template + < + typename GenericPolytope + > + inline void initialize(GenericPolytope& P, + thread_parameters_ ¶meters, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + const NT dl = 0.995; + parameters.lambdas.setZero(P.num_of_hyperplanes()); + parameters.Av.setZero(P.num_of_hyperplanes()); + + parameters.v = GetDirection::apply(n, rng); + NT T = rng.sample_urdist() * _Len; + int it = 0; + + std::pair pbpair + = P.line_positive_intersect(parameters.p, parameters.v, parameters.lambdas, parameters.Av); + if (T <= pbpair.first) + { + parameters.p += (T * parameters.v); + parameters.lambda_prev = T; + return; + } + parameters.lambda_prev = dl * pbpair.first; + parameters.p += (parameters.lambda_prev * parameters.v); + T -= parameters.lambda_prev; + P.compute_reflection(parameters.v, parameters.p, pbpair.second); + + while (it <= 50*n) + { + std::pair pbpair + = P.line_positive_intersect(parameters.p, parameters.v, parameters.lambdas, + parameters.Av, parameters.lambda_prev); + if (T <= pbpair.first) + { + parameters.p += (T * parameters.v); + parameters.lambda_prev = T; + break; + }else if (it == 50*n) { + parameters.lambda_prev = rng.sample_urdist() * pbpair.first; + parameters.p += (parameters.lambda_prev * parameters.v); + break; + } + parameters.lambda_prev = dl * pbpair.first; + parameters.p += (parameters.lambda_prev * parameters.v); + T -= parameters.lambda_prev; + P.compute_reflection(parameters.v, parameters.p, pbpair.second); + it++; + } + } + + NT _Len; +}; + +}; + + +// coordinate directions hit-and-run walk with uniform target distribution +// Parallel version + +struct CDHRWalk_multithread +{ + template + struct thread_parameters + { + thread_parameters(unsigned int d, unsigned int m) + { + p = Point(d); + p_prev = Point(d); + lambdas.setZero(m); + } + + Point p; + Point p_prev; + unsigned int rand_coord_prev; + unsigned int rand_coord; + typename Point::Coeff lambdas; + }; + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + typedef thread_parameters thread_parameters_; + //typedef thread_params thread_parameters_; + + template + Walk(GenericPolytope& P, thread_parameters_ ¶meters, RandomNumberGenerator& rng) + { + initialize(P, parameters, rng); + } + + + template + < + typename BallPolytope + > + inline void apply(BallPolytope const& P, + thread_parameters_ ¶ms, // parameters + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + for (auto j=0u; j bpair = P.line_intersect_coord(params.p, + params.p_prev, + params.rand_coord, + params.rand_coord_prev, + params.lambdas); + params.p_prev = params.p; + params.p.set_coord(params.rand_coord, params.p[params.rand_coord] + bpair.first + kapa + * (bpair.second - bpair.first)); + } + } + +private : + + template + inline void initialize(BallPolytope const& P, + thread_parameters_ ¶ms, // parameters + RandomNumberGenerator &rng) + { + params.lambdas.setZero(P.num_of_hyperplanes()); + params.rand_coord = rng.sample_uidist(); + NT kapa = rng.sample_urdist(); + + std::pair bpair = P.line_intersect_coord(params.p, params.rand_coord, params.lambdas); + params.p_prev = params.p; + params.p.set_coord(params.rand_coord, params.p[params.rand_coord] + bpair.first + kapa + * (bpair.second - bpair.first)); + } + +}; + +}; + + +// Random directions hit-and-run walk with uniform target distribution +// Parallel version + +struct RDHRWalk_multithread +{ + + template + struct thread_parameters + { + thread_parameters(unsigned int d, unsigned int m) + { + p = Point(d); + v = Point(d); + lambdas.setZero(m); + Av.setZero(m); + lambda_prev = NT(0); + } + + Point p; + Point v; + NT lambda_prev; + typename Point::Coeff lambdas; + typename Point::Coeff Av; + }; + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + typedef thread_parameters thread_parameters_; + //typedef thread_params thread_parameters_; + + template + Walk(GenericPolytope& P, thread_parameters_ ¶meters, RandomNumberGenerator& rng) + { + initialize(P, parameters, rng); + } + + template + < + typename BallPolytope + > + inline void apply(BallPolytope const& P, + thread_parameters_ ¶ms, // parameters + unsigned int const& walk_length, + RandomNumberGenerator& rng) + { + for (auto j=0u; j::apply(P.dimension(), rng); + std::pair bpair = P.line_intersect(params.p, params.v, params.lambdas, params.Av, + params.lambda_prev); + params.lambda_prev = rng.sample_urdist() * (bpair.first - bpair.second) + + bpair.second; + params.p += (params.lambda_prev * params.v); + } + } + +private : + + template + inline void initialize(BallPolytope const& P, + thread_parameters_ ¶ms, // parameters + RandomNumberGenerator &rng) + { + params.lambdas.setZero(P.num_of_hyperplanes()); + params.Av.setZero(P.num_of_hyperplanes()); + + params.v = GetDirection::apply(P.dimension(), rng); + std::pair bpair = P.line_intersect(params.p, params.v, params.lambdas, params.Av); + params.lambda_prev = rng.sample_urdist() * (bpair.first - bpair.second) + bpair.second; + params.p += (params.lambda_prev * params.v); + } + +}; + +}; + + +#endif // RANDOM_WALKS_MULTITHREAD_WALKS_HPP diff --git a/src/volesti/include/random_walks/nuts_hmc_walk.hpp b/src/volesti/include/random_walks/nuts_hmc_walk.hpp new file mode 100644 index 00000000..857064c0 --- /dev/null +++ b/src/volesti/include/random_walks/nuts_hmc_walk.hpp @@ -0,0 +1,380 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2022 Vissarion Fisikopoulos +// Copyright (c) 2018-2022 Apostolos Chalkis +// Copyright (c) 2020-2022 Elias Tsigaridas +// Copyright (c) 2020-2022 Marios Papachristou + +// Licensed under GNU LGPL.3, see LICENCE file + +// References +// Matthew D. Hoffman, Andrew Gelman. "The No-U-Turn Sampler: +// Adaptively Setting Path Lengths in Hamiltonian Monte Carlo", 2011. + +// Comment: Compared to [Matthew D. Hoffman, Andrew Gelman, 2011] +// we modify the step of Nesterov's algorithm in the burn in phase. + +#ifndef NUTS_HAMILTONIAN_MONTE_CARLO_WALK_HPP +#define NUTS_HAMILTONIAN_MONTE_CARLO_WALK_HPP + + +#include "generators/boost_random_number_generator.hpp" +#include "random_walks/gaussian_helpers.hpp" +#include "ode_solvers/ode_solvers.hpp" +#include "preprocess/estimate_L_smooth_parameter.hpp" + +struct NutsHamiltonianMonteCarloWalk { + + template + < + typename NT, + typename OracleFunctor + > + struct parameters { + NT epsilon; // tolerance in mixing + NT eta; // step size + + parameters( + OracleFunctor const& F, + unsigned int dim, + NT epsilon_=2) + { + epsilon = epsilon_; + eta = F.params.L > 0 ? 10.0 / (dim * sqrt(F.params.L)) : 0.005; + } + }; + + template + < + typename Point, + typename Polytope, + typename RandomNumberGenerator, + typename NegativeGradientFunctor, + typename NegativeLogprobFunctor, + typename Solver + > + struct Walk { + + typedef std::vector pts; + typedef typename Point::FT NT; + typedef std::vector bounds; + + // Hyperparameters of the sampler + parameters ¶ms; + + // Numerical ODE solver + Solver *solver; + + // Dimension + unsigned int dim; + + // Discarded Samples + long num_runs = 0; + long total_acceptance = 0; + + // Average acceptance probability + NT average_acceptance = 0; + + // References to xs + Point x, v; + + // Helper points + Point v_pl, v_min, v_min_j, v_pl_j, X_pl, X_pl_j, X_min, X, X_rnd_j, X_min_j, x_pl_min; + + // Gradient function + NegativeGradientFunctor &F; + + bool accepted; + + // Burnin parameters + NT eps_step, mu, log_tilde_eps, H_tilde, alpha, na; + const NT delta = NT(0.65), Delta_max = NT(1000), gamma = NT(0.05), t0 = NT(10), kk = NT(0.85); + + // Density exponent + NegativeLogprobFunctor &f; + + Walk(Polytope *P, + Point &p, + NegativeGradientFunctor &neg_grad_f, + NegativeLogprobFunctor &neg_logprob_f, + parameters ¶m, + bool burn_in_phase = true) : + params(param), + F(neg_grad_f), + f(neg_logprob_f) + { + dim = p.dimension(); + + v_pl.set_dimension(dim); + v_min.set_dimension(dim); + v_min_j.set_dimension(dim); + v_pl_j.set_dimension(dim); + X_pl.set_dimension(dim); + X_pl_j.set_dimension(dim); + X_min.set_dimension(dim); + X.set_dimension(dim); + X_rnd_j.set_dimension(dim); + X_min_j.set_dimension(dim); + x_pl_min.set_dimension(dim); + + eps_step = params.eta; + mu = std::log(10*eps_step); + log_tilde_eps = NT(0); + H_tilde = NT(0); + alpha = NT(0); + na = NT(0); + + // Starting point is provided from outside + x = p; + + accepted = false; + + // Initialize solver + solver = new Solver(0, params.eta, pts{x, x}, F, bounds{P, NULL}); + disable_adaptive(); + + if (burn_in_phase) + { + RandomNumberGenerator rng(dim); + burnin(rng); + } + }; + + + inline void burnin(RandomNumberGenerator &rng, + unsigned int N = 1000, + unsigned int walk_length=1) + { + reset_num_runs(); + Point p = x; + NT L; + + if ((solver->get_bounds())[0] == NULL) + { + L = (NT(100) / NT(dim)) * (NT(100) / NT(dim)); + } + else + { + Polytope K = *(solver->get_bounds())[0]; + L = estimate_L_smooth(K, p, walk_length, F, rng); + } + + eps_step = NT(5) / (NT(dim) * std::sqrt(L)); + solver->set_eta(eps_step); + + for (int i = 0; i < N; i++) + { + apply(rng, walk_length, true); + solver->set_eta(eps_step); + } + reset_num_runs(); + } + + + inline void apply(RandomNumberGenerator &rng, + unsigned int walk_length=1, + bool burnin = false) + { + num_runs++; + + int x_counting_total = 0; + + // Pick a random velocity + v = GetDirection::apply(dim, rng, false); + + v_pl = v; + v_min = NT(-1) * v; + X_pl = x; + X_min = x; + + NT h1 = hamiltonian(x, v); + + NT uu = std::log(rng.sample_urdist()) - h1; + int j = -1; + bool s = true; + bool updated = false; + bool pos_state_single = false; + + if (burnin) + { + alpha = NT(0); + } + + while (s) + { + j++; + + if (burnin) + { + na = std::pow(NT(2), NT(j)); + } + + NT dir = rng.sample_urdist(); + + if (dir > 0.5) + { + v = v_pl; + X = X_pl; + } + else + { + v = v_min; + X = X_min; + } + X_rnd_j = X; + + int x_counting = 0; + int num_samples = int(std::pow(NT(2), NT(j))); + accepted = false; + + for (int k = 1; k <= num_samples; k++) + { + if (!accepted) + { + solver->set_state(0, X); + solver->set_state(1, v); + } + + // Get proposals + solver->steps(walk_length, accepted); + accepted = true; + + X = solver->get_state(0); + v = solver->get_state(1); + + NT hj = hamiltonian(X, v); + + if (burnin) + { + alpha += std::min(NT(1), std::exp(-hj + h1)); + } + + if (uu > Delta_max - hj) + { + s = false; + break; + } + + bool pos_state = false; + if (uu < -hj) + { + pos_state = true; + pos_state_single = true; + x_counting = x_counting + 1; + x_counting_total = x_counting_total + 1; + } + + if (k == 1) + { + if (dir > 0.5) + { + X_min_j = X; + v_min_j = v; + } + else + { + X_pl_j = X; + v_pl_j = v; + } + } + if (k == num_samples) + { + if (dir > 0.5) + { + x_pl_min = X - X_min_j; + if ((x_pl_min.dot(v) < 0) || (x_pl_min.dot(v_min_j) < 0)) + { + s = false; + } + } + else + { + x_pl_min = X_pl_j - X; + if ((x_pl_min.dot(v) < 0) || (x_pl_min.dot(v_pl_j) < 0)) + { + s = false; + } + } + } + if ((rng.sample_urdist() < (1/NT(x_counting))) && pos_state) + { + X_rnd_j = X; + } + } + + if (dir > 0.5) + { + X_pl = X; + v_pl = v; + } + else + { + X_min = X; + v_min = v; + } + + if (s && (rng.sample_urdist() < (NT(x_counting) / NT(x_counting_total)))) + { + x = X_rnd_j; + if (pos_state_single) + { + updated = true; + } + } + + if (s) + { + x_pl_min = X_pl - X_min; + if ((x_pl_min.dot(v_min) < 0) || (x_pl_min.dot(v_pl) < 0)) + { + s = false; + } + } + } + + if (updated) + { + total_acceptance++; + } + average_acceptance = NT(total_acceptance) / NT(num_runs); + + if (burnin) + { + H_tilde = (NT(1) - NT(1) / (NT(num_runs) + t0)) * H_tilde + (NT(1) / (NT(num_runs) + t0)) * (delta - alpha / na); + NT log_eps = mu - (std::sqrt(NT(num_runs)) / gamma) * H_tilde; + + // TODO: use the following to generalize Nesterov's algorithm + //log_tilde_eps = std::pow(mu,-kk) * log_eps + (NT(1) - std::pow(mu,-kk))*log_tilde_eps; + + eps_step = std::exp(log_eps); + } + } + + inline NT hamiltonian(Point &pos, Point &vel) const { + return f(pos) + 0.5 * vel.dot(vel); + } + + inline NT get_eta_solver() { + return solver->get_eta(); + } + + void disable_adaptive() { + solver->disable_adaptive(); + } + + void enable_adaptive() { + solver->enable_adaptive(); + } + + void reset_num_runs() { + num_runs = 0; + total_acceptance = 0; + } + + NT get_ratio_acceptance() { + return average_acceptance; + } + }; +}; + +#endif // HAMILTONIAN_MONTE_CARLO_WALK_HPP diff --git a/src/volesti/include/random_walks/random_walks.hpp b/src/volesti/include/random_walks/random_walks.hpp new file mode 100644 index 00000000..6c36457c --- /dev/null +++ b/src/volesti/include/random_walks/random_walks.hpp @@ -0,0 +1,34 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2020-2021 Vissarion Fisikopoulos +// Copyright (c) 2020-2021 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_RANDOM_WALKS_HPP +#define RANDOM_WALKS_RANDOM_WALKS_HPP + +#include + +#include "random_walks/boundary_cdhr_walk.hpp" +#include "random_walks/boundary_rdhr_walk.hpp" +#include "random_walks/gaussian_ball_walk.hpp" +#include "random_walks/gaussian_cdhr_walk.hpp" +#include "random_walks/gaussian_rdhr_walk.hpp" +#include "random_walks/uniform_ball_walk.hpp" +#include "random_walks/uniform_billiard_walk.hpp" +#include "random_walks/uniform_cdhr_walk.hpp" +#include "random_walks/uniform_rdhr_walk.hpp" +#include "random_walks/uniform_dikin_walk.hpp" +#include "random_walks/uniform_john_walk.hpp" +#include "random_walks/uniform_vaidya_walk.hpp" +#include "random_walks/uniform_accelerated_billiard_walk.hpp" +#include "random_walks/gaussian_accelerated_billiard_walk.hpp" +#include "random_walks/gaussian_hamiltonian_monte_carlo_exact_walk.hpp" +#include "random_walks/exponential_hamiltonian_monte_carlo_exact_walk.hpp" +#include "random_walks/uniform_accelerated_billiard_walk_parallel.hpp" +#include "random_walks/hamiltonian_monte_carlo_walk.hpp" +#include "random_walks/nuts_hmc_walk.hpp" +#include "random_walks/langevin_walk.hpp" +#include "random_walks/crhmc/crhmc_walk.hpp" +#endif // RANDOM_WALKS_RANDOM_WALKS_HPP diff --git a/src/volesti/include/random_walks/uniform_accelerated_billiard_walk.hpp b/src/volesti/include/random_walks/uniform_accelerated_billiard_walk.hpp new file mode 100644 index 00000000..980c8e5d --- /dev/null +++ b/src/volesti/include/random_walks/uniform_accelerated_billiard_walk.hpp @@ -0,0 +1,309 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_ACCELERATED_IMPROVED_BILLIARD_WALK_HPP +#define RANDOM_WALKS_ACCELERATED_IMPROVED_BILLIARD_WALK_HPP + +#include "sampling/sphere.hpp" + + +// Billiard walk which accelarates each step for uniform distribution + +struct AcceleratedBilliardWalk +{ + AcceleratedBilliardWalk(double L) + : param(L, true) + {} + + AcceleratedBilliardWalk() + : param(0, false) + {} + + struct parameters + { + parameters(double L, bool set) + : m_L(L), set_L(set) + {} + double m_L; + bool set_L; + }; + + struct update_parameters + { + update_parameters() + : facet_prev(0), hit_ball(false), inner_vi_ak(0.0), ball_inner_norm(0.0) + {} + int facet_prev; + bool hit_ball; + double inner_vi_ak; + double ball_inner_norm; + }; + + parameters param; + + + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + typedef typename Polytope::MT MT; + typedef typename Point::FT NT; + + template + Walk(GenericPolytope &P, Point const& p, RandomNumberGenerator &rng) + { + _update_parameters = update_parameters(); + _L = compute_diameter + ::template compute(P); + _AA.noalias() = P.get_mat() * P.get_mat().transpose(); + _rho = 1000 * P.dimension(); // upper bound for the number of reflections (experimental) + initialize(P, p, rng); + } + + template + Walk(GenericPolytope &P, Point const& p, RandomNumberGenerator &rng, + parameters const& params) + { + _update_parameters = update_parameters(); + _L = params.set_L ? params.m_L + : compute_diameter + ::template compute(P); + _AA.noalias() = P.get_mat() * P.get_mat().transpose(); + _rho = 1000 * P.dimension(); // upper bound for the number of reflections (experimental) + initialize(P, p, rng); + } + + template + < + typename GenericPolytope + > + inline void apply(GenericPolytope &P, + Point &p, // a point to start + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT T; + const NT dl = 0.995; + int it; + + for (auto j=0u; j::apply(n, rng); + Point p0 = _p; + + it = 0; + std::pair pbpair = P.line_positive_intersect(_p, _v, _lambdas, _Av, + _lambda_prev, _update_parameters); + if (T <= pbpair.first) { + _p += (T * _v); + _lambda_prev = T; + continue; + } + + _lambda_prev = dl * pbpair.first; + _p += (_lambda_prev * _v); + T -= _lambda_prev; + P.compute_reflection(_v, _p, _update_parameters); + it++; + + while (it < _rho) + { + std::pair pbpair + = P.line_positive_intersect(_p, _v, _lambdas, _Av, _lambda_prev, + _AA, _update_parameters); + if (T <= pbpair.first) { + _p += (T * _v); + _lambda_prev = T; + break; + } + _lambda_prev = dl * pbpair.first; + _p += (_lambda_prev * _v); + T -= _lambda_prev; + P.compute_reflection(_v, _p, _update_parameters); + it++; + } + if (it == _rho) _p = p0; + } + p = _p; + } + + + template + < + typename GenericPolytope + > + inline void get_starting_point(GenericPolytope &P, + Point const& center, + Point &q, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT radius = P.InnerBall().second; + + q = GetPointInDsphere::apply(n, radius, rng); + q += center; + initialize(P, q, rng); + + apply(P, q, walk_length, rng); + } + + + template + < + typename GenericPolytope + > + inline void parameters_burnin(GenericPolytope &P, + Point const& center, + unsigned int const& num_points, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + Point p(P.dimension()); + std::vector pointset; + pointset.push_back(center); + pointset.push_back(_p); + NT rad = NT(0), max_dist, Lmax = get_delta(), radius = P.InnerBall().second; + + for (int i = 0; i < num_points; i++) + { + Point p = GetPointInDsphere::apply(P.dimension(), radius, rng); + p += center; + initialize(P, p, rng); + + apply(P, p, walk_length, rng); + max_dist = get_max_distance(pointset, p, rad); + if (max_dist > Lmax) + { + Lmax = max_dist; + } + if (2.0*rad > Lmax) { + Lmax = 2.0 * rad; + } + pointset.push_back(p); + } + + if (Lmax > _L) { + if (P.dimension() <= 2500) + { + update_delta(Lmax); + } + else{ + update_delta(2.0 * get_delta()); + } + } + pointset.clear(); + } + + + + inline void update_delta(NT L) + { + _L = L; + } + + NT get_delta() + { + return _L; + } + + private : + + template + < + typename GenericPolytope + > + inline void initialize(GenericPolytope &P, + Point const& p, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + const NT dl = 0.995; + _lambdas.setZero(P.num_of_hyperplanes()); + _Av.setZero(P.num_of_hyperplanes()); + _p = p; + _v = GetDirection::apply(n, rng); + + NT T = -std::log(rng.sample_urdist()) * _L; + Point p0 = _p; + int it = 0; + + std::pair pbpair + = P.line_first_positive_intersect(_p, _v, _lambdas, _Av, _update_parameters); + if (T <= pbpair.first) { + _p += (T * _v); + _lambda_prev = T; + return; + } + _lambda_prev = dl * pbpair.first; + _p += (_lambda_prev * _v); + T -= _lambda_prev; + P.compute_reflection(_v, _p, _update_parameters); + + while (it <= _rho) + { + std::pair pbpair + = P.line_positive_intersect(_p, _v, _lambdas, _Av, _lambda_prev, _AA, _update_parameters); + if (T <= pbpair.first) { + _p += (T * _v); + _lambda_prev = T; + break; + } else if (it == _rho) { + _lambda_prev = rng.sample_urdist() * pbpair.first; + _p += (_lambda_prev * _v); + break; + } + _lambda_prev = dl * pbpair.first; + _p += (_lambda_prev * _v); + T -= _lambda_prev; + P.compute_reflection(_v, _p, _update_parameters); + it++; + } + } + + inline double get_max_distance(std::vector &pointset, Point const& q, double &rad) + { + double dis = -1.0, temp_dis; + int jj = 0; + for (auto vecit = pointset.begin(); vecit!=pointset.end(); vecit++, jj++) + { + temp_dis = (q.getCoefficients() - (*vecit).getCoefficients()).norm(); + if (temp_dis > dis) { + dis = temp_dis; + } + if (jj == 0) { + if (temp_dis > rad) { + rad = temp_dis; + } + } + } + return dis; + } + + double _L; + Point _p; + Point _v; + NT _lambda_prev; + MT _AA; + unsigned int _rho; + update_parameters _update_parameters; + typename Point::Coeff _lambdas; + typename Point::Coeff _Av; + }; + +}; + + +#endif diff --git a/src/volesti/include/random_walks/uniform_accelerated_billiard_walk_parallel.hpp b/src/volesti/include/random_walks/uniform_accelerated_billiard_walk_parallel.hpp new file mode 100644 index 00000000..1c2630f9 --- /dev/null +++ b/src/volesti/include/random_walks/uniform_accelerated_billiard_walk_parallel.hpp @@ -0,0 +1,311 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_ACCELERATED_IMPROVED_BILLIARD_WALK_PARALLEL_HPP +#define RANDOM_WALKS_ACCELERATED_IMPROVED_BILLIARD_WALK_PARALLEL_HPP + +#include "sampling/sphere.hpp" + + +// Billiard walk which accelarates each step for uniform distribution and can be used for a parallel use by threads + +struct AcceleratedBilliardWalkParallel +{ + AcceleratedBilliardWalkParallel() + {} + + + struct update_parameters + { + update_parameters() + : facet_prev(0), hit_ball(false), inner_vi_ak(0.0), ball_inner_norm(0.0) + {} + int facet_prev; + bool hit_ball; + double inner_vi_ak; + double ball_inner_norm; + }; + + template + struct thread_parameters + { + thread_parameters(unsigned int d, unsigned int m) + { + update_step_parameters = update_parameters(); + p = Point(d); + v = Point(d); + lambdas.setZero(m); + Av.setZero(m); + lambda_prev = NT(0); + } + + update_parameters update_step_parameters; + Point p; + Point v; + NT lambda_prev; + typename Point::Coeff lambdas; + typename Point::Coeff Av; + }; + + + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + typedef typename Polytope::MT MT; + typedef typename Point::FT NT; + + template + Walk(GenericPolytope &P) + { + _L = compute_diameter + ::template compute(P); + _AA.noalias() = P.get_mat() * P.get_mat().transpose(); + _p0 = Point(P.dimension()); + _rho = 1000 * P.dimension(); + } + + template + Walk(GenericPolytope &P, NT const& L) + { + _L = L > NT(0) ? L + : compute_diameter + ::template compute(P); + _AA.noalias() = P.get_mat() * P.get_mat().transpose(); + _p0 = Point(P.dimension()); + _rho = 1000 * P.dimension(); + } + + template + < + typename GenericPolytope, + typename thread_params + > + inline void apply(GenericPolytope &P, + thread_params ¶ms, // a point to start + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT T; + const NT dl = 0.995; + int it; + + for (auto j=0u; j::apply(n, rng); + _p0 = params.p; + + it = 0; + std::pair pbpair = P.line_positive_intersect(params.p, params.v, params.lambdas, params.Av, + params.lambda_prev, params.update_step_parameters); + if (T <= pbpair.first) + { + params.p += (T * params.v); + params.lambda_prev = T; + continue; + } + + params.lambda_prev = dl * pbpair.first; + params.p += (params.lambda_prev * params.v); + T -= params.lambda_prev; + P.compute_reflection(params.v, params.p, params.update_step_parameters); + it++; + + while (it < _rho) + { + std::pair pbpair + = P.line_positive_intersect(params.p, params.v, params.lambdas, params.Av, + params.lambda_prev, _AA, params.update_step_parameters); + if (T <= pbpair.first) { + params.p += (T * params.v); + params.lambda_prev = T; + break; + } + params.lambda_prev = dl * pbpair.first; + params.p += (params.lambda_prev * params.v); + T -= params.lambda_prev; + P.compute_reflection(params.v, params.p, params.update_step_parameters); + it++; + } + if (it == _rho) params.p = _p0; + } + } + + + template + < + typename GenericPolytope, + typename thread_params + > + inline void get_starting_point(GenericPolytope &P, + Point const& center, + thread_params ¶ms, + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT radius = P.InnerBall().second; + + params.p = GetPointInDsphere::apply(n, radius, rng); + params.p += center; + initialize(P, params, rng); + + apply(P, params, walk_length, rng); + } + + + template + < + typename GenericPolytope, + typename thread_params + > + inline void parameters_burnin(GenericPolytope &P, + Point const& center, + unsigned int const& num_points, + unsigned int const& walk_length, + RandomNumberGenerator &rng, + thread_params ¶ms) + { + std::vector pointset; + pointset.push_back(center); + + params.p = Point(P.dimension()); + NT rad = NT(0), max_dist, Lmax = get_delta(), radius = P.InnerBall().second; + + for (int i = 0; i < num_points; i++) + { + params.p = GetPointInDsphere::apply(P.dimension(), radius, rng); + params.p += center; + initialize(P, params, rng); + + apply(P, params, walk_length, rng); + max_dist = get_max_distance(pointset, params.p, rad); + if (max_dist > Lmax) + { + Lmax = max_dist; + } + if (2.0*rad > Lmax) { + Lmax = 2.0 * rad; + } + pointset.push_back(params.p); + } + + if (Lmax > _L) + { + if (P.dimension() <= 2500) + { + update_delta(Lmax); + } + else{ + update_delta(2.0 * get_delta()); + } + } + pointset.clear(); + } + + + + inline void update_delta(NT L) + { + _L = L; + } + + NT get_delta() + { + return _L; + } + + private : + + template + < + typename GenericPolytope, + typename thread_params + > + inline void initialize(GenericPolytope &P, + thread_params ¶ms, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + const NT dl = 0.995; + params.v = GetDirection::apply(n, rng); + + NT T = -std::log(rng.sample_urdist()) * _L; + int it = 0; + + std::pair pbpair + = P.line_first_positive_intersect(params.p, params.v, params.lambdas, + params.Av, params.update_step_parameters); + if (T <= pbpair.first) { + params.p += (T * params.v); + params.lambda_prev = T; + return; + } + params.lambda_prev = dl * pbpair.first; + params.p += (params.lambda_prev * params.v); + T -= params.lambda_prev; + P.compute_reflection(params.v, params.p, params.update_step_parameters); + + while (it <= _rho) + { + std::pair pbpair + = P.line_positive_intersect(params.p, params.v, params.lambdas, params.Av, + params.lambda_prev, _AA, params.update_step_parameters); + if (T <= pbpair.first) { + params.p += (T * params.v); + params.lambda_prev = T; + break; + } else if (it == _rho) { + params.lambda_prev = rng.sample_urdist() * pbpair.first; + params.p += (params.lambda_prev * params.v); + break; + } + params.lambda_prev = dl * pbpair.first; + params.p += (params.lambda_prev * params.v); + T -= params.lambda_prev; + P.compute_reflection(params.v, params.p, params.update_step_parameters); + it++; + } + } + + inline double get_max_distance(std::vector &pointset, Point const& q, double &rad) + { + double dis = -1.0, temp_dis; + int jj = 0; + for (auto vecit = pointset.begin(); vecit!=pointset.end(); vecit++, jj++) + { + temp_dis = (q.getCoefficients() - (*vecit).getCoefficients()).norm(); + if (temp_dis > dis) { + dis = temp_dis; + } + if (jj == 0) { + if (temp_dis > rad) { + rad = temp_dis; + } + } + } + return dis; + } + + NT _L; + MT _AA; + Point _p0; + unsigned int _rho; + }; + +}; + + +#endif + + diff --git a/src/volesti/include/random_walks/uniform_ball_walk.hpp b/src/volesti/include/random_walks/uniform_ball_walk.hpp new file mode 100644 index 00000000..77c608b7 --- /dev/null +++ b/src/volesti/include/random_walks/uniform_ball_walk.hpp @@ -0,0 +1,96 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_UNIFORM_BALL_WALK_HPP +#define RANDOM_WALKS_UNIFORM_BALL_WALK_HPP + +#include "generators/boost_random_number_generator.hpp" + +// Ball walk with uniform target distribution + +struct BallWalk +{ + BallWalk(double L) + : param(L, true) + {} + + BallWalk() + : param(0, false) + {} + + struct parameters + { + parameters(double L, bool set) + : m_L(L), set_delta(set) + {} + double m_L; + bool set_delta; + }; + + parameters param; + + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + + template + Walk(GenericPolytope& P, Point const& /*p*/, + RandomNumberGenerator& /*rng*/) + { + _delta = compute_delta(P); + } + + template + Walk(GenericPolytope& P, Point const& /*p*/, + RandomNumberGenerator& /*rng*/, parameters const& params) + { + _delta = params.set_delta ? params.m_L + : compute_delta(P); + } + + template + static inline NT compute_delta(GenericPolytope& P) + { + //return ((P.InnerBall()).second * NT(4)) / NT(P.dimension()); + return (NT(4) * (P.InnerBall()).second) / std::sqrt(NT(P.dimension())); + } + + template + inline void apply(BallPolytope const& P, + Point &p, // a point to start + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + for (auto j = 0u; j < walk_length; ++j) + { + Point y = GetPointInDsphere::apply(P.dimension(), + _delta, + rng); + y += p; + if (P.is_in(y) == -1) p = y; + } + } + + inline void update_delta(NT delta) + { + _delta = delta; + } + + private: + double _delta; + }; +}; + +#endif // RANDOM_WALKS_UNIFORM_BALL_WALK_HPP diff --git a/src/volesti/include/random_walks/uniform_billiard_walk.hpp b/src/volesti/include/random_walks/uniform_billiard_walk.hpp new file mode 100644 index 00000000..dcbc96b1 --- /dev/null +++ b/src/volesti/include/random_walks/uniform_billiard_walk.hpp @@ -0,0 +1,200 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2019 program. +// Contributed and modified by Huu Phuoc Le as part of Google Summer of Code 2022 program + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_UNIFORM_BILLIARD_WALK_HPP +#define RANDOM_WALKS_UNIFORM_BILLIARD_WALK_HPP + +#include + +#include "convex_bodies/ball.h" +#include "convex_bodies/ballintersectconvex.h" +#include "convex_bodies/hpolytope.h" +#include "convex_bodies/spectrahedra/spectrahedron.h" +#include "convex_bodies/correlation_matrices/correlation_spectrahedron.hpp" +#include "convex_bodies/correlation_matrices/correlation_spectrahedron_MT.hpp" +#ifndef DISABLE_LPSOLVE + #include "convex_bodies/vpolytope.h" + #include "convex_bodies/vpolyintersectvpoly.h" + #include "convex_bodies/zpolytope.h" + #include "convex_bodies/zonoIntersecthpoly.h" +#endif +#include "sampling/sphere.hpp" +#include "random_walks/boundary_cdhr_walk.hpp" +#include "generators/boost_random_number_generator.hpp" +#include "sampling/random_point_generators.hpp" +#include "volume/sampling_policies.hpp" +#include "random_walks/compute_diameter.hpp" + +// Billiard walk for uniform distribution + +struct BilliardWalk +{ + BilliardWalk(double L) + : param(L, true) + {} + + BilliardWalk() + : param(0, false) + {} + + struct parameters + { + parameters(double L, bool set) + : m_L(L), set_L(set) + {} + double m_L; + bool set_L; + }; + + parameters param; + + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + + template + Walk(GenericPolytope &P, Point const& p, RandomNumberGenerator &rng) + { + _Len = compute_diameter + ::template compute(P); + initialize(P, p, rng); + } + + template + Walk(GenericPolytope &P, Point const& p, RandomNumberGenerator &rng, + parameters const& params) + { + _Len = params.set_L ? params.m_L + : compute_diameter + ::template compute(P); + initialize(P, p, rng); + } + + template + < + typename GenericPolytope + > + inline void apply(GenericPolytope &P, + Point& p, // a point to start + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + NT T = rng.sample_urdist() * _Len; + const NT dl = 0.995; + + for (auto j=0u; j::apply(n, rng); + + Point p0 = _p; + int it = 0; + while (it < 50*n) + { + auto pbpair = P.line_positive_intersect(_p, _v, _lambdas, + _Av, _lambda_prev); + + if (T <= pbpair.first) { + _p += (T * _v); + _lambda_prev = T; + break; + } + + _lambda_prev = dl * pbpair.first; + _p += (_lambda_prev * _v); + T -= _lambda_prev; + + P.compute_reflection(_v, _p, pbpair.second); + + it++; + } + if (it == 50*n){ + _p = p0; + } + } + p = _p; + } + + inline void update_delta(NT L) + { + _Len = L; + } + +private : + + template + < + typename GenericPolytope + > + inline void initialize(GenericPolytope &P, + Point const& p, + RandomNumberGenerator &rng) + { + unsigned int n = P.dimension(); + const NT dl = 0.995; + _lambdas.setZero(P.num_of_hyperplanes()); + _Av.setZero(P.num_of_hyperplanes()); + _p = p; + _v = GetDirection::apply(n, rng); + + NT T = rng.sample_urdist() * _Len; + Point p0 = _p; + int it = 0; + std::pair pbpair + = P.line_positive_intersect(_p, _v, _lambdas, _Av); + if (T <= pbpair.first) { + _p += (T * _v); + _lambda_prev = T; + return; + } + _lambda_prev = dl * pbpair.first; + _p += (_lambda_prev * _v); + T -= _lambda_prev; + P.compute_reflection(_v, _p, pbpair.second); + while (it <= 50*n) + { + std::pair pbpair + = P.line_positive_intersect(_p, _v, _lambdas, _Av, _lambda_prev); + if (T <= pbpair.first) { + _p += (T * _v); + _lambda_prev = T; + break; + }else if (it == 50*n) { + _lambda_prev = rng.sample_urdist() * pbpair.first; + _p += (_lambda_prev * _v); + break; + } + _lambda_prev = dl * pbpair.first; + _p += (_lambda_prev * _v); + T -= _lambda_prev; + P.compute_reflection(_v, _p, pbpair.second); + it++; + } + } + + NT _Len; + Point _p; + Point _v; + NT _lambda_prev; + typename Point::Coeff _lambdas; + typename Point::Coeff _Av; +}; + +}; + +#endif // RANDOM_WALKS_UNIFORM_BILLIARD_WALK_HPP diff --git a/src/volesti/include/random_walks/uniform_cdhr_walk.hpp b/src/volesti/include/random_walks/uniform_cdhr_walk.hpp new file mode 100644 index 00000000..12d2ff78 --- /dev/null +++ b/src/volesti/include/random_walks/uniform_cdhr_walk.hpp @@ -0,0 +1,97 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_UNIFORM_CDHR_WALK_HPP +#define RANDOM_WALKS_UNIFORM_CDHR_WALK_HPP + +#include "sampling/sphere.hpp" + +// coordinate directions hit-and-run walk with uniform target distribution + +struct CDHRWalk +{ + struct parameters {}; + parameters param; + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + + template + Walk(GenericPolytope& P, Point const& p, RandomNumberGenerator& rng) + { + initialize(P, p, rng); + } + + template + Walk(GenericPolytope& P, Point const& p, + RandomNumberGenerator& rng, parameters const& params) + { + initialize(P, p, rng); + } + + template + < + typename BallPolytope + > + inline void apply(BallPolytope const& P, + Point &p, // a point to start + unsigned int const& walk_length, + RandomNumberGenerator &rng) + { + for (auto j=0u; j bpair = P.line_intersect_coord(_p, + _p_prev, + _rand_coord, + rand_coord_prev, + _lamdas); + _p_prev = _p; + _p.set_coord(_rand_coord, _p[_rand_coord] + bpair.first + kapa + * (bpair.second - bpair.first)); + } + p = _p; + } + +private : + + template + inline void initialize(BallPolytope const& P, + Point const& p, + RandomNumberGenerator &rng) + { + _lamdas.setZero(P.num_of_hyperplanes()); + _rand_coord = rng.sample_uidist(); + NT kapa = rng.sample_urdist(); + _p = p; + std::pair bpair = P.line_intersect_coord(_p, _rand_coord, _lamdas); + _p_prev = _p; + _p.set_coord(_rand_coord, _p[_rand_coord] + bpair.first + kapa + * (bpair.second - bpair.first)); + } + + unsigned int _rand_coord; + Point _p; + Point _p_prev; + typename Point::Coeff _lamdas; +}; + +}; + + +#endif // RANDOM_WALKS_UNIFORM_CDHR_WALK_HPP diff --git a/src/volesti/include/random_walks/uniform_dikin_walk.hpp b/src/volesti/include/random_walks/uniform_dikin_walk.hpp new file mode 100644 index 00000000..e791b254 --- /dev/null +++ b/src/volesti/include/random_walks/uniform_dikin_walk.hpp @@ -0,0 +1,102 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_DIKIN_WALK_HPP +#define RANDOM_WALKS_DIKIN_WALK_HPP + +#include "convex_bodies/ball.h" +#include "convex_bodies/ballintersectconvex.h" +#include "convex_bodies/hpolytope.h" +#ifndef DISABLE_LPSOLVE + #include "convex_bodies/vpolytope.h" + #include "convex_bodies/vpolyintersectvpoly.h" + #include "convex_bodies/zpolytope.h" +#endif +#include "convex_bodies/zonoIntersecthpoly.h" +#include "ellipsoid_walks/dikin_walker.h" + + +// Dikin walk for uniform distribution + +struct DikinWalk +{ + DikinWalk(double L) + : param(L, true) + {} + + DikinWalk() + : param(0, false) + {} + + struct parameters + { + parameters(double L, bool set) + : m_L(L), set_L(set) + {} + double m_L; + bool set_L; + }; + + parameters param; + + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Point::FT NT; + + Walk(Polytope &P, Point &p, RandomNumberGenerator &) + { + MT A = P.get_mat(); + VT b = P.get_vec(), _vec_point = VT::Zero(P.dimension()), p0 = p.getCoefficients(); + NT r = P.ComputeInnerBall().second; + dikinw = DikinWalker(p0, A, b, r); + } + + Walk(Polytope &P, Point & p, RandomNumberGenerator &, parameters const& params) + { + MT A = P.get_mat(); + VT b = P.get_vec(), _vec_point = VT::Zero(P.dimension()), p0 = p.getCoefficients(); + NT r = params.set_L ? params.m_L + : P.ComputeInnerBall().second; + dikinw = DikinWalker(p0, A, b, r); + } + + template + < + typename GenericPolytope + > + inline void apply(GenericPolytope &, + Point &p, // a point to start + unsigned int const& walk_length, + RandomNumberGenerator &) + { + for (auto j=0u; j dikinw; + VT _vec_point; + }; + +}; + + +#endif diff --git a/src/volesti/include/random_walks/uniform_john_walk.hpp b/src/volesti/include/random_walks/uniform_john_walk.hpp new file mode 100644 index 00000000..2c6bc42e --- /dev/null +++ b/src/volesti/include/random_walks/uniform_john_walk.hpp @@ -0,0 +1,94 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_JOHN_WALK_HPP +#define RANDOM_WALKS_JOHN_WALK_HPP + + +#include "ellipsoid_walks/john_walker.h" + + +// John walk for uniform distribution + +struct JohnWalk +{ + JohnWalk(double L) + : param(L, true) + {} + + JohnWalk() + : param(0, false) + {} + + struct parameters + { + parameters(double L, bool set) + : m_L(L), set_L(set) + {} + double m_L; + bool set_L; + }; + + parameters param; + + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Point::FT NT; + + Walk(Polytope &P, Point &p, RandomNumberGenerator &) + { + MT A = P.get_mat(); + VT b = P.get_vec(), _vec_point = VT::Zero(P.dimension()), p0 = p.getCoefficients(); + NT r = P.ComputeInnerBall().second; + johnw = JohnWalker(p0, A, b, r); + } + + Walk(Polytope &P, Point & p, RandomNumberGenerator &, parameters const& params) + { + MT A = P.get_mat(); + VT b = P.get_vec(), _vec_point = VT::Zero(P.dimension()), p0 = p.getCoefficients(); + NT r = params.set_L ? params.m_L + : P.ComputeInnerBall().second; + johnw = JohnWalker(p0, A, b, r); + } + + template + < + typename GenericPolytope + > + inline void apply(GenericPolytope &, + Point &p, // a point to start + unsigned int const& walk_length, + RandomNumberGenerator &) + { + for (auto j=0u; j johnw; + VT _vec_point; + }; + +}; + + +#endif diff --git a/src/volesti/include/random_walks/uniform_rdhr_walk.hpp b/src/volesti/include/random_walks/uniform_rdhr_walk.hpp new file mode 100644 index 00000000..5e0eb475 --- /dev/null +++ b/src/volesti/include/random_walks/uniform_rdhr_walk.hpp @@ -0,0 +1,92 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_UNIFORM_RDHR_WALK_HPP +#define RANDOM_WALKS_UNIFORM_RDHR_WALK_HPP + + +#include "sampling/sphere.hpp" + +// Random directions hit-and-run walk with uniform target distribution + +struct RDHRWalk +{ + struct parameters {}; + parameters param; + +template +< + typename Polytope, + typename RandomNumberGenerator +> +struct Walk +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + + template + Walk(GenericPolytope& P, Point const& p, RandomNumberGenerator& rng) + { + initialize(P, p, rng); + } + + template + Walk(GenericPolytope& P, Point const& p, + RandomNumberGenerator& rng, parameters const& params) + { + initialize(P, p, rng); + } + + template + < + typename BallPolytope + > + inline void apply(BallPolytope& P, + Point& p, // a point to start + unsigned int const& walk_length, + RandomNumberGenerator& rng) + { + for (auto j=0u; j::apply(p.dimension(), rng); + std::pair bpair = P.line_intersect(_p, v, _lamdas, _Av, + _lambda); + _lambda = rng.sample_urdist() * (bpair.first - bpair.second) + + bpair.second; + _p += (_lambda * v); + } + p = _p; + } + +private : + + template + inline void initialize(BallPolytope& P, + Point const& p, + RandomNumberGenerator &rng) + { + _lamdas.setZero(P.num_of_hyperplanes()); + _Av.setZero(P.num_of_hyperplanes()); + + Point v = GetDirection::apply(p.dimension(), rng); + std::pair bpair = P.line_intersect(p, v, _lamdas, _Av); + _lambda = rng.sample_urdist() * (bpair.first - bpair.second) + bpair.second; + _p = (_lambda * v) + p; + } + + Point _p; + NT _lambda; + typename Point::Coeff _lamdas; + typename Point::Coeff _Av; +}; + +}; + + +#endif // RANDOM_WALKS_UNIFORM_RDHR_WALK_HPP diff --git a/src/volesti/include/random_walks/uniform_vaidya_walk.hpp b/src/volesti/include/random_walks/uniform_vaidya_walk.hpp new file mode 100644 index 00000000..5bbbe991 --- /dev/null +++ b/src/volesti/include/random_walks/uniform_vaidya_walk.hpp @@ -0,0 +1,95 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Contributed and/or modified by Alexandros Manochis, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef RANDOM_WALKS_VAIDYA_WALK_HPP +#define RANDOM_WALKS_VAIDYA_WALK_HPP + + +#include "ellipsoid_walks/vaidya_walker.h" + + +// Vaidya walk for uniform distribution + +struct VaidyaWalk +{ + VaidyaWalk(double L) + : param(L, true) + {} + + VaidyaWalk() + : param(0, false) + {} + + struct parameters + { + parameters(double L, bool set) + : m_L(L), set_L(set) + {} + double m_L; + bool set_L; + }; + + parameters param; + + + template + < + typename Polytope, + typename RandomNumberGenerator + > + struct Walk + { + typedef typename Polytope::PointType Point; + typedef typename Polytope::MT MT; + typedef typename Polytope::VT VT; + typedef typename Point::FT NT; + + Walk(Polytope &P, Point &p, RandomNumberGenerator &) + { + MT A = P.get_mat(); + VT b = P.get_vec(), _vec_point = VT::Zero(P.dimension()), p0 = p.getCoefficients(); + NT r = P.ComputeInnerBall().second; + vaidyaw = VaidyaWalker(p0, A, b, r); + } + + Walk(Polytope &P, Point & p, RandomNumberGenerator &, parameters const& params) + { + MT A = P.get_mat(); + VT b = P.get_vec(), _vec_point = VT::Zero(P.dimension()), p0 = p.getCoefficients(); + NT r = params.set_L ? params.m_L + : P.ComputeInnerBall().second; + vaidyaw = VaidyaWalker(p0, A, b, r); + } + + template + < + typename GenericPolytope + > + inline void apply(GenericPolytope &, + Point &p, // a point to start + unsigned int const& walk_length, + RandomNumberGenerator &) + { + for (auto j=0u; j vaidyaw; + VT _vec_point; + }; + +}; + + +#endif diff --git a/src/volesti/include/root_finders/mp_solve_wrapper.hpp b/src/volesti/include/root_finders/mp_solve_wrapper.hpp new file mode 100644 index 00000000..1193f07c --- /dev/null +++ b/src/volesti/include/root_finders/mp_solve_wrapper.hpp @@ -0,0 +1,75 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef MP_SOLVE_WRAPPER_HPP +#define MP_SOLVE_WRAPPER_HPP + +template +std::vector> mpsolve(std::vector &coeffs, bool positive_real=false) { + + long n = (long) coeffs.size(); + + while (std::abs(coeffs[n-1]) < NT(1e-9)) { + n--; + + } + + + mps_monomial_poly *p; + mps_context *s; + + s = mps_context_new (); + p = mps_monomial_poly_new (s, n-1); + + mps_context_select_algorithm(s, MPS_ALGORITHM_SECULAR_GA); + + for (long i = 0; i < n; i++) { + mps_monomial_poly_set_coefficient_d (s, p, i, coeffs[i], 0); + } + + + /* Set the input polynomial */ + mps_context_set_input_poly (s, MPS_POLYNOMIAL (p)); + + /* Allocate space to hold the results. We check only floating point results + * in here */ + cplx_t *results = cplx_valloc (n-1); + + /* Actually solve the polynomial */ + mps_mpsolve (s); + + /* Save roots computed in the vector results */ + mps_context_get_roots_d (s, &results, NULL); + + std::vector> results_vector; + + NT real, im; + + for (long i = 0; i < n - 1; i++) { + real = (NT) cplx_Re(*results); + im = (NT) cplx_Im(*results); + + #ifdef VOLESTI_DEBUG + std::cout << real << " + " << im << "i" << std::endl; + #endif + results++; + if (positive_real) { + if (real > 0 && std::abs(im) < 1e-8) { + results_vector.push_back(std::make_pair(real, im)); + } + } else { + results_vector.push_back(std::make_pair(real, im)); + } + } + + return results_vector; +} + +#endif diff --git a/src/volesti/include/root_finders/newton_raphson.hpp b/src/volesti/include/root_finders/newton_raphson.hpp new file mode 100644 index 00000000..27411d76 --- /dev/null +++ b/src/volesti/include/root_finders/newton_raphson.hpp @@ -0,0 +1,49 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef NEWTON_RAPHSON_HPP +#define NEWTON_RAPHSON_HPP + +/// @brief Function implementing the Newton-Raphson numerical method +/// @tparam NT Number type +/// @tparam func Function type +template +std::pair newton_raphson(NT t0, func f, func grad_f, const NT rtol, + const NT reg=0, const unsigned int max_tries=1000000) { + NT t, t_prev, err; + NT y, y_prime; + t = t0; + unsigned int tries = 0; + + do { + tries++; + y = f(t_prev); + y_prime = grad_f(t_prev); + + if (std::abs(y_prime) < rtol) y_prime += reg; + + t = t_prev - y / y_prime; + if (t_prev != 0) { + err = std::abs(t - t_prev) / t_prev; + } else { + err = std::abs(t - t_prev); + } + + t_prev = t; + + if (tries > max_tries) break; + + } while (err > rtol); + + return std::make_pair(t, tries > max_tries); + +} + +#endif diff --git a/src/volesti/include/root_finders/quadratic_polynomial_solvers.hpp b/src/volesti/include/root_finders/quadratic_polynomial_solvers.hpp new file mode 100644 index 00000000..ad39c2ef --- /dev/null +++ b/src/volesti/include/root_finders/quadratic_polynomial_solvers.hpp @@ -0,0 +1,42 @@ +// VolEsti (volume computation and sampling library) +// Copyright (c) 2021 Vissarion Fisikopoulos +// Copyright (c) 2021 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + + +#ifndef QUADRATIC_POLYNOMIAL_SOLVERS_H +#define QUADRATIC_POLYNOMIAL_SOLVERS_H + +// The function compute the roots of a quadratic polynomial equation +template +bool solve_quadratic_polynomial(NT const& a, NT const& b, NT const& c, NT &x1, NT &x2) +{ + if (a == NT(0)) { + x1 = -c / b; + x2 = x1; + return true; + } + + NT Delta = b * b - 4.0 * a * c; + if (Delta < NT(0)) + { + return false; + } + + if (b >= NT(0)) + { + x1 = (- b - std::sqrt(Delta)) / (2.0 * a); + x2 = (2.0 * c) / (- b - std::sqrt(Delta)); + } + else + { + x1 = (2.0 * c) / (- b + std::sqrt(Delta)); + x2 = (- b + std::sqrt(Delta)) / (2.0 * a); + } + return true; +} + + +#endif + diff --git a/src/volesti/include/root_finders/root_finders.hpp b/src/volesti/include/root_finders/root_finders.hpp new file mode 100644 index 00000000..d9b36f17 --- /dev/null +++ b/src/volesti/include/root_finders/root_finders.hpp @@ -0,0 +1,28 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2020-2020 Marios Papachristou + +// Contributed and/or modified by Marios Papachristou, as part of Google Summer of Code 2020 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "newton_raphson.hpp" +#include "mp_solve_wrapper.hpp" + +#ifndef ROOT_FINDERS_HPP +#define ROOT_FINDERS_HPP + +#endif diff --git a/src/volesti/include/sampling/ellipsoid.hpp b/src/volesti/include/sampling/ellipsoid.hpp new file mode 100644 index 00000000..7507b989 --- /dev/null +++ b/src/volesti/include/sampling/ellipsoid.hpp @@ -0,0 +1,82 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2021 Vissarion Fisikopoulos +// Copyright (c) 2018-2021 Apostolos Chalkis +// Copyright (c) 2021 Vaibhav Thakkar + +//Contributed and/or modified by Vaibhav Thakkar, as part of Google Summer of Code 2021 program. + + +#ifndef SAMPLERS_ELLIPSOID_HPP +#define SAMPLERS_ELLIPSOID_HPP + +#include "sphere.hpp" +#include "Eigen/Eigen" + + +template +struct GetPointInDellipsoid +{ + typedef typename Point::FT NT; + + template + inline static Point apply(unsigned int const& dim, + Ellipsoid const& E, + RandomNumberGenerator& rng) + { + // Generate a point inside a sphere of radius 1.0 + Point p = GetPointInDsphere::apply(dim, NT(1.0), rng); + + // transform it to a point inside an ellipsoid + return Point(E.mult_Lcov(p.getCoefficients())); + } +}; + + +template +struct GetGaussianDirection +{ + typedef typename Point::FT NT; + typedef typename Eigen::Matrix VT; + + template + inline static Point apply(unsigned int const& dim, + Ellipsoid const& E, // ellipsoid representing the Gaussian distribution + RandomNumberGenerator &rng) + { + // Generate a point inside a sphere of radius 1.0 + Point p = GetDirection::apply(dim, rng, false); + + // Multiply with cholesky matrix + VT gaussian_vec = E.mult_Lcov(p.getCoefficients()); + + // convert to point + return Point(gaussian_vec); + } +}; + + +template +struct GetPointOnDellipsoid +{ + typedef typename Point::FT NT; + typedef typename Eigen::Matrix VT; + + template + inline static Point apply(unsigned int const& dim, + Ellipsoid const& E, // ellipsoid representing the Gaussian distribution + RandomNumberGenerator &rng) + { + // Generate a point inside a sphere of radius 1.0 + Point p = GetDirection::apply(dim, rng, true); + + // Multiply with cholesky matrix + VT gaussian_vec = E.mult_Lcov(p.getCoefficients()); + + // convert to point + return Point(gaussian_vec); + } +}; + + +#endif // ELLIPSOID_HPP \ No newline at end of file diff --git a/src/volesti/include/sampling/mmcs.hpp b/src/volesti/include/sampling/mmcs.hpp new file mode 100644 index 00000000..07529ad6 --- /dev/null +++ b/src/volesti/include/sampling/mmcs.hpp @@ -0,0 +1,128 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2021 Vissarion Fisikopoulos +// Copyright (c) 2021 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef MMCS_HPP +#define MMCS_HPP + +#include "diagnostics/ess_window_updater.hpp" + +/** + * The class implements a single step of the Multiphase Monte Carlo Sampling algorithm + * given in, + * + * A. Chalkis, V. Fisikopoulos, E. Tsigaridas, H. Zafeiropoulos, Geometric algorithms for sampling the flux space of metabolic networks, SoCG 21. + * + * @tparam Polytope convex polytope type + * @tparam RandomNumberGenerator random number generator type + * @tparam MT matrix type + * @tparam Point cartensian point type + * @tparam WalkTypePolicy random walk type +*/ +template +< + typename Polytope, + typename RandomNumberGenerator, + typename MT, + typename Point, + typename WalkTypePolicy +> +bool perform_mmcs_step(Polytope &P, + RandomNumberGenerator &rng, + unsigned int const& walk_length, + unsigned int const& target_ess, + unsigned int const& max_num_samples, + unsigned int const& window, + unsigned int &Neff_sampled, + unsigned int &total_samples, + unsigned int const& num_rounding_steps, + MT &TotalRandPoints, + const Point &starting_point, + unsigned int const& nburns, + bool request_rounding, + WalkTypePolicy &WalkType) +{ + typedef typename Polytope::NT NT; + typedef typename Polytope::VT VT; + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > Walk; + + bool done = false; + unsigned int points_to_sample = target_ess; + int min_eff_samples; + total_samples = 0; + MT winPoints(P.dimension(), window); + Point q(P.dimension()); + + Point p = starting_point; + + if (request_rounding) + { + TotalRandPoints.setZero(num_rounding_steps, P.dimension()); + } + else + { + TotalRandPoints.setZero(max_num_samples, P.dimension()); + } + + Walk walk(P, p, rng, WalkType.param); + ESSestimator estimator(window, P.dimension()); + + walk.template parameters_burnin(P, p, 10 + int(std::log(NT(P.dimension()))), 10, rng); + + while (!done) + { + walk.template get_starting_point(P, p, q, 10, rng); + for (int i = 0; i < window; i++) + { + walk.template apply(P, q, walk_length, rng); + winPoints.col(i) = q.getCoefficients(); + } + estimator.update_estimator(winPoints); + total_samples += window; + if (total_samples >= TotalRandPoints.rows()) + { + if (total_samples > TotalRandPoints.rows()) + { + TotalRandPoints.conservativeResize(total_samples, P.dimension()); + } + if (request_rounding || total_samples >= max_num_samples) + { + done = true; + } + } + TotalRandPoints.block(total_samples - window, 0, window, P.dimension()) = winPoints.transpose(); + if (done || total_samples >= points_to_sample) + { + estimator.estimate_effective_sample_size(); + min_eff_samples = int(estimator.get_effective_sample_size().minCoeff()); + if (done && min_eff_samples < target_ess) + { + Neff_sampled = min_eff_samples; + return false; + } + if (min_eff_samples >= target_ess) + { + Neff_sampled = min_eff_samples; + return true; + } + if (min_eff_samples > 0) + { + points_to_sample += (total_samples / min_eff_samples) * (target_ess - min_eff_samples) + 100; + } + else + { + points_to_sample = total_samples + 100; + } + } + } + return false; +} + +#endif diff --git a/src/volesti/include/sampling/parallel_mmcs.hpp b/src/volesti/include/sampling/parallel_mmcs.hpp new file mode 100644 index 00000000..a8308f4c --- /dev/null +++ b/src/volesti/include/sampling/parallel_mmcs.hpp @@ -0,0 +1,203 @@ +// VolEsti (volume computation and sampling library) +// Copyright (c) 2021 Vissarion Fisikopoulos +// Copyright (c) 2021 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef PARALLEL_MMCS_HPP +#define PARALLEL_MMCS_HPP + + +#include +#include +#include +#include "diagnostics/ess_window_updater.hpp" + +/** + * The class implements a single step of the Parallel Multiphase Monte Carlo Sampling algorithm + * given in, + * + * A. Chalkis, V. Fisikopoulos, E. Tsigaridas, H. Zafeiropoulos, Geometric algorithms for sampling the flux space of metabolic networks, SoCG 21. + * + * @tparam WalkTypePolicy random walk type + * @tparam Polytope convex polytope type + * @tparam RandomNumberGenerator random number generator type + * @tparam MT matrix type + * @tparam Point cartensian point type + * @tparam NT number type +*/ +template +< + typename WalkTypePolicy, + typename Polytope, + typename RandomNumberGenerator, + typename MT, + typename Point, + typename NT +> +bool perform_parallel_mmcs_step(Polytope &P, + RandomNumberGenerator &rng, + unsigned int const& walk_length, + unsigned int const& target_ess, + unsigned int const& max_num_samples, + unsigned int const& window, + unsigned int &Neff_sampled, + unsigned int &total_samples, + unsigned int const& num_rounding_steps, + MT &TotalRandPoints, + const Point &starting_point, + unsigned int const& nburns, + unsigned int const& num_threads, + bool request_rounding, + NT L) +{ + typedef typename Polytope::VT VT; // vector type + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > Walk; + + typedef typename WalkTypePolicy::template thread_parameters + < + NT, + Point + > _thread_parameters; + + omp_set_num_threads(num_threads); + std::vector num_starting_points_per_thread(num_threads, 0); + std::vector bound_on_num_points_per_thread(num_threads, 0); + std::vector num_generated_points_per_thread(num_threads, 0); + unsigned int jj = 0, d = P.dimension(), m = P.num_of_hyperplanes(); + bool complete = false; + + while (jj < nburns) + { + for (unsigned int i = 0; i < num_threads; i++) + { + num_starting_points_per_thread[i]++; + bound_on_num_points_per_thread[i] += window; + jj++; + } + } + + std::vector winPoints_per_thread(num_threads, MT::Zero(d, window)); + std::vector TotalRandPoints_per_thread(num_threads); + + ESSestimator estimator(window, P.dimension()); + + bool done = false, done_all = false; + unsigned int points_to_sample = target_ess; + int min_eff_samples; + total_samples = 0; + + Point pp = starting_point; + for (unsigned int i = 0; i < num_threads; i++) + { + TotalRandPoints_per_thread[i].setZero(bound_on_num_points_per_thread[i], d); + } + unsigned int upper_bound_on_total_num_of_samples; + if (request_rounding) + { + upper_bound_on_total_num_of_samples = num_rounding_steps; + } + else + { + upper_bound_on_total_num_of_samples = max_num_samples; + } + TotalRandPoints.resize(0, 0); + Walk walk(P, L); + + _thread_parameters random_walk_parameters(d, m); + walk.template parameters_burnin(P, pp, 10 + int(std::log(NT(d))), 10, rng, random_walk_parameters); + Point const p = pp; + + #pragma omp parallel + { + int thread_index = omp_get_thread_num(); + _thread_parameters thread_random_walk_parameters(d, m); + + for (unsigned int it = 0; it < num_starting_points_per_thread[thread_index]; it++) + { + if (done_all) + { + break; + } + walk.template get_starting_point(P, p, thread_random_walk_parameters, 10, rng); + for (int i = 0; i < window; i++) + { + walk.template apply(P, thread_random_walk_parameters, walk_length, rng); + winPoints_per_thread[thread_index].col(i) = thread_random_walk_parameters.p.getCoefficients(); + } + + #pragma omp critical + { + estimator.update_estimator(winPoints_per_thread[thread_index]); + } + + num_generated_points_per_thread[thread_index] += window; + + #pragma omp critical + { + total_samples += window; + } + #pragma omp single + { + if (total_samples >= upper_bound_on_total_num_of_samples) + { + done = true; + } + } + TotalRandPoints_per_thread[thread_index].block(num_generated_points_per_thread[thread_index] - window, 0, window, d) = winPoints_per_thread[thread_index].transpose(); + #pragma omp single + { + if (done || (total_samples >= points_to_sample)) + { + estimator.estimate_effective_sample_size(); + + min_eff_samples = int(estimator.get_effective_sample_size().minCoeff()); + if (done && min_eff_samples < target_ess) + { + Neff_sampled = min_eff_samples; + done_all = true; + } + if (min_eff_samples >= target_ess) + { + complete = true; + Neff_sampled = min_eff_samples; + done_all = true; + } + if (min_eff_samples > 0 && !done_all) + { + points_to_sample += (total_samples / min_eff_samples) * (target_ess - min_eff_samples) + 100; + } + else if (!done_all) + { + points_to_sample = total_samples + 100; + } + } + } + } + } + + estimator.estimate_effective_sample_size(); + min_eff_samples = int(estimator.get_effective_sample_size().minCoeff()); + Neff_sampled = min_eff_samples; + if (min_eff_samples >= target_ess) + { + complete = true; + } + + unsigned int current_num_samples = 0; + for (unsigned int i = 0; i < num_threads; i++) + { + TotalRandPoints.conservativeResize(TotalRandPoints.rows() + num_generated_points_per_thread[i], d); + TotalRandPoints.block(current_num_samples, 0, num_generated_points_per_thread[i], d) = TotalRandPoints_per_thread[i].block(0, 0, num_generated_points_per_thread[i], d); + current_num_samples += num_generated_points_per_thread[i]; + TotalRandPoints_per_thread[i].resize(0, 0); + } + + return complete; +} + +#endif diff --git a/src/volesti/include/sampling/random_point_generators.hpp b/src/volesti/include/sampling/random_point_generators.hpp new file mode 100644 index 00000000..e70e1461 --- /dev/null +++ b/src/volesti/include/sampling/random_point_generators.hpp @@ -0,0 +1,395 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef SAMPLERS_RANDOM_POINT_GENERATORS_HPP +#define SAMPLERS_RANDOM_POINT_GENERATORS_HPP + +template +< + typename Walk +> +struct RandomPointGenerator +{ + template + < + typename Polytope, + typename Point, + typename PointList, + typename WalkPolicy, + typename RandomNumberGenerator, + typename Parameters + > + static void apply(Polytope& P, + Point &p, // a point to start + unsigned int const& rnum, + unsigned int const& walk_length, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng, + Parameters const& parameters) + { + Walk walk(P, p, rng, parameters); + for (unsigned int i=0; i + static void apply(Polytope& P, + Point &p, // a point to start + unsigned int const& rnum, + unsigned int const& walk_length, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng) + { + Walk walk(P, p, rng); + for (unsigned int i=0; i +struct MultivariateGaussianRandomPointGenerator +{ + template + < + typename Polytope, + typename Point, + typename Ellipsoid, + typename PointList, + typename WalkPolicy, + typename RandomNumberGenerator, + typename Parameters + > + static void apply(Polytope& P, + Point &p, // a point to start + Ellipsoid const& E, // ellipsoid representing the Gaussian distribution + unsigned int const& rnum, + unsigned int const& walk_length, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng, + Parameters const& parameters) + { + Walk walk(P, p, E, rng, parameters); + for (unsigned int i=0; i + static void apply(Polytope& P, + Point &p, // a point to start + Ellipsoid const& E, // ellipsoid representing the Gaussian distribution + unsigned int const& rnum, + unsigned int const& walk_length, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng) + { + Walk walk(P, p, E, rng); + for (unsigned int i=0; i +struct GaussianRandomPointGenerator +{ + template + < + typename Polytope, + typename Point, + typename NT, + typename PointList, + typename WalkPolicy, + typename RandomNumberGenerator + > + static void apply(Polytope& P, + Point &p, // a point to start + NT const& a_i, + unsigned int const& rnum, + unsigned int const& walk_length, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng) + { + Walk walk(P, p, a_i, rng); + for (unsigned int i=0; i + static void apply(Polytope& P, + Point &p, // a point to start + NT const& a_i, + unsigned int const& rnum, + unsigned int const& walk_length, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng, + Parameters const& parameters) + { + Walk walk(P, p, a_i, rng, parameters); + + for (unsigned int i=0; i +struct BoundaryRandomPointGenerator +{ + template + < + typename Polytope, + typename Point, + typename PointList, + typename WalkPolicy, + typename RandomNumberGenerator + > + static void apply(Polytope& P, + Point &p, // a point to start + unsigned int const& rnum, + unsigned int const& walk_length, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng) + { + Walk walk(P, p, rng); + Point p1(P.dimension()), p2(P.dimension()); + for (unsigned int i=0; i +struct LogconcaveRandomPointGenerator +{ + + template + < typename PointList, + typename WalkPolicy, + typename RandomNumberGenerator + > + static void apply(unsigned int const& rnum, + unsigned int const& walk_length, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng, + Walk &walk) + { + typedef double NT; + + for (unsigned int i = 0; i < rnum; ++i) + { + // Gather one sample + walk.apply(rng, walk_length); + + // Use PushBackWalkPolicy + policy.apply(randPoints, walk.x); + } + } +}; + +template +< + typename Walk +> +struct CrhmcRandomPointGenerator +{ + + template + < + typename Polytope, + typename Point, + typename PointList, + typename WalkPolicy, + typename RandomNumberGenerator, + typename NegativeGradientFunctor, + typename NegativeLogprobFunctor, + typename Parameters + > + static void apply(Polytope &P, + Point &p, // a point to start + unsigned int const& rnum, + unsigned int const& walk_length, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng, + NegativeGradientFunctor &F, + NegativeLogprobFunctor &f, + Parameters ¶meters, + Walk &walk, + int simdLen=1, + bool raw_output= false) + { + typedef typename Walk::MT MT; + for (unsigned int i = 0; i < std::ceil((float)rnum/simdLen); ++i) + { + // Gather one sample + walk.apply(rng, walk_length); + if(walk.P.terminate){return;} + MT x; + if(raw_output){ + x=walk.x; + }else{ + x=walk.getPoints(); + } + if((i + 1) * simdLen > rnum){ + for(int j = 0; j < rnum-simdLen*i; j++){ + Point p = Point(x.col(j)); + policy.apply(randPoints, p); + } + break; + } + // Use PushBackWalkPolicy + for(int j=0; j +struct ExponentialRandomPointGenerator +{ + template + < + typename Polytope, + typename Point, + typename NT, + typename PointList, + typename WalkPolicy, + typename RandomNumberGenerator + > + static void apply(Polytope& P, + Point &p, // a point to start + Point const& c, // bias function + NT const& T, // temperature/variance + unsigned int const& rnum, + unsigned int const& walk_length, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng) + { + Walk walk(P, p, c, T, rng); + bool success; + for (unsigned int i=0; i + static void apply(Polytope& P, + Point &p, // a point to start + Point const& c, // bias function + NT const& T, // temperature/variance + unsigned int const& rnum, + unsigned int const& walk_length, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng, + Parameters const& parameters) + { + Walk walk(P, p, c, T, rng, parameters); + bool success; + + for (unsigned int i=0; i +#include +#include + + +template +struct policy_storing +{ + template + static void store(WalkPolicy &policy, PointList &randPoints, ThreadParameters &thread_random_walk_parameters) + { + policy.apply(randPoints, thread_random_walk_parameters.p); + } +}; + + +template <> +struct policy_storing +{ + template + static void store(WalkPolicy &policy, PointList &randPoints, ThreadParameters &thread_random_walk_parameters) + { + policy.apply(randPoints, thread_random_walk_parameters.p1); + policy.apply(randPoints, thread_random_walk_parameters.p2); + } +}; + +template <> +struct policy_storing : policy_storing +{}; + + +template +< + typename Walk +> +struct RandomPointGeneratorMultiThread +{ + template + < + typename Polytope, + typename Point, + typename PointList, + typename WalkPolicy, + typename RandomNumberGenerator, + typename Parameters + > + static void apply(Polytope& P, + Point &p, // a point to start + unsigned int const& rnum, + unsigned int const& walk_length, + unsigned int const& num_threads, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng, + Parameters const& parameters) + { + typedef typename Point::FT NT; + typedef typename Walk::thread_parameters_ _thread_parameters; + + omp_set_num_threads(num_threads); + unsigned int d = P.dimension(), m = P.num_of_hyperplanes(); + + std::vector num_points_per_thread(rnum%num_threads, rnum/num_threads+1); + std::vector a(num_threads - rnum%num_threads, rnum/num_threads); + num_points_per_thread.insert(num_points_per_thread.end(), a.begin(), a.end()); + + _thread_parameters thread_random_walk_parameters_temp(d, m); + Walk walk(P, thread_random_walk_parameters_temp, rng, parameters); + + #pragma omp parallel + { + int thread_index = omp_get_thread_num(); + _thread_parameters thread_random_walk_parameters(d, m); + thread_random_walk_parameters = thread_random_walk_parameters_temp; + + for (unsigned int it = 0; it < num_points_per_thread[thread_index]; it++) + { + walk.template apply(P, thread_random_walk_parameters, walk_length, rng); + #pragma omp critical + { + policy_storing::template store(policy, randPoints, thread_random_walk_parameters); + } + } + } + } + + template + < + typename Polytope, + typename Point, + typename PointList, + typename WalkPolicy, + typename RandomNumberGenerator + > + static void apply(Polytope& P, + Point &p, // a point to start + unsigned int const& rnum, + unsigned int const& walk_length, + unsigned int const& num_threads, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng) + { + typedef typename Point::FT NT; + typedef typename Walk::thread_parameters_ _thread_parameters; + + omp_set_num_threads(num_threads); + unsigned int d = P.dimension(), m = P.num_of_hyperplanes(); + + std::vector num_points_per_thread(rnum%num_threads, rnum/num_threads+1); + std::vector a(num_threads - rnum%num_threads, rnum/num_threads); + num_points_per_thread.insert(num_points_per_thread.end(), a.begin(), a.end()); + + _thread_parameters thread_random_walk_parameters_temp(d, m); + Walk walk(P, thread_random_walk_parameters_temp, rng); + + #pragma omp parallel + { + int thread_index = omp_get_thread_num(); + _thread_parameters thread_random_walk_parameters(d, m); + thread_random_walk_parameters = thread_random_walk_parameters_temp; + + for (unsigned int it = 0; it < num_points_per_thread[thread_index]; it++) + { + walk.template apply(P, thread_random_walk_parameters, walk_length, rng); + #pragma omp critical + { + policy_storing::template store(policy, randPoints, thread_random_walk_parameters); + } + } + } + } +}; + + + +template +< + typename Walk +> +struct GaussianPointGeneratorMultiThread +{ + template + < + typename Polytope, + typename Point, + typename NT, + typename PointList, + typename WalkPolicy, + typename RandomNumberGenerator, + typename Parameters + > + static void apply(Polytope& P, + Point &p, // a point to start + NT const& a_i, + unsigned int const& rnum, + unsigned int const& walk_length, + unsigned int const& num_threads, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng, + Parameters const& parameters) + { + typedef typename Walk::thread_parameters_ _thread_parameters; + + omp_set_num_threads(num_threads); + unsigned int d = P.dimension(), m = P.num_of_hyperplanes(); + + std::vector num_points_per_thread(rnum%num_threads, rnum/num_threads+1); + std::vector a(num_threads - rnum%num_threads, rnum/num_threads); + num_points_per_thread.insert(num_points_per_thread.end(), a.begin(), a.end()); + + _thread_parameters thread_random_walk_parameters_temp(d, m); + Walk walk(P, thread_random_walk_parameters_temp, a_i, rng, parameters); + + #pragma omp parallel + { + int thread_index = omp_get_thread_num(); + _thread_parameters thread_random_walk_parameters(d, m); + thread_random_walk_parameters = thread_random_walk_parameters_temp; + + for (unsigned int it = 0; it < num_points_per_thread[thread_index]; it++) + { + walk.template apply(P, thread_random_walk_parameters, a_i, walk_length, rng); + #pragma omp critical + { + policy_storing::template store(policy, randPoints, thread_random_walk_parameters); + } + } + } + } + + template + < + typename Polytope, + typename Point, + typename NT, + typename PointList, + typename WalkPolicy, + typename RandomNumberGenerator + > + static void apply(Polytope& P, + Point &p, // a point to start + NT const& a_i, + unsigned int const& rnum, + unsigned int const& walk_length, + unsigned int const& num_threads, + PointList &randPoints, + WalkPolicy &policy, + RandomNumberGenerator &rng) + { + typedef typename Walk::thread_parameters_ _thread_parameters; + + omp_set_num_threads(num_threads); + unsigned int d = P.dimension(), m = P.num_of_hyperplanes(); + + std::vector num_points_per_thread(rnum%num_threads, rnum/num_threads+1); + std::vector a(num_threads - rnum%num_threads, rnum/num_threads); + num_points_per_thread.insert(num_points_per_thread.end(), a.begin(), a.end()); + + _thread_parameters thread_random_walk_parameters_temp(d, m); + Walk walk(P, thread_random_walk_parameters_temp, a_i, rng); + + #pragma omp parallel + { + int thread_index = omp_get_thread_num(); + _thread_parameters thread_random_walk_parameters(d, m); + thread_random_walk_parameters = thread_random_walk_parameters_temp; + + for (unsigned int it = 0; it < num_points_per_thread[thread_index]; it++) + { + walk.template apply(P, thread_random_walk_parameters, a_i, walk_length, rng); + #pragma omp critical + { + policy_storing::template store(policy, randPoints, thread_random_walk_parameters); + } + } + } + } +}; + + +#endif // SAMPLERS_RANDOM_POINT_GENERATORS_MULTITHREAD_HPP diff --git a/src/volesti/include/sampling/sample_correlation_matrices.hpp b/src/volesti/include/sampling/sample_correlation_matrices.hpp new file mode 100644 index 00000000..9a1de231 --- /dev/null +++ b/src/volesti/include/sampling/sample_correlation_matrices.hpp @@ -0,0 +1,153 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2020 Apostolos Chalkis + +// Contributed by Huu Phuoc Le as part of Google Summer of Code 2022 program + +// Licensed under GNU LGPL.3, see LICENCE file + +/// Functions to sample correlation matrices w.r.t. a truncated density + +#ifndef VOLESTI_SAMPLING_SAMPLE_CORRELATION_MATRICES_HPP +#define VOLESTI_SAMPLING_SAMPLE_CORRELATION_MATRICES_HPP + +#include + +// New implementations for sampling correlation matrices with CorrelationSpectrahedron and CorrelationSpectrahedron_MT + +template +< + typename WalkTypePolicy, + typename PointType, + typename RNGType, + typename PointList +> +void uniform_correlation_sampling(const unsigned int &n, + PointList &randPoints, + const unsigned int &walkL, + const unsigned int &num_points, + unsigned int const& nburns){ + CorrelationSpectrahedron P(n); + const unsigned int d = P.dimension(); + PointType startingPoint(d); + RNGType rng(d); + + uniform_sampling(randPoints, P, rng, walkL, num_points, startingPoint, nburns); +} + +template +< + typename WalkTypePolicy, + typename PointType, + typename RNGType, + typename PointList +> +void uniform_correlation_sampling_MT( const unsigned int &n, + PointList &randPoints, + const unsigned int &walkL, + const unsigned int &num_points, + unsigned int const& nburns){ + CorrelationSpectrahedron_MT P(n); + const unsigned int d = P.dimension(); + PointType startingPoint(n); + RNGType rng(d); + + uniform_sampling(randPoints, P, rng, walkL, num_points, startingPoint, nburns); +} + +template +< + typename WalkTypePolicy, + typename PointType, + typename RNGType, + typename PointList, + typename NT +> +void gaussian_correlation_sampling( const unsigned int &n, + PointList &randPoints, + const unsigned int &walkL, + const unsigned int &num_points, + const NT &a, + unsigned int const& nburns = 0){ + CorrelationSpectrahedron P(n); + const unsigned int d = P.dimension(); + PointType startingPoint(d); + RNGType rng(d); + + gaussian_sampling(randPoints, P, rng, walkL, num_points, a, startingPoint, nburns); +} + +template +< + typename WalkTypePolicy, + typename PointType, + typename RNGType, + typename PointList, + typename NT +> +void gaussian_correlation_sampling_MT( const unsigned int &n, + PointList &randPoints, + const unsigned int &walkL, + const unsigned int &num_points, + const NT &a, + unsigned int const& nburns = 0){ + CorrelationSpectrahedron_MT P(n); + const unsigned int d = P.dimension(); + PointType startingPoint(n); + RNGType rng(d); + + gaussian_sampling(randPoints, P, rng, walkL, num_points, a, startingPoint, nburns); +} + +template +< + typename WalkTypePolicy, + typename PointType, + typename RNGType, + typename PointList, + typename NT, + typename VT +> +void exponential_correlation_sampling( const unsigned int &n, + PointList &randPoints, + const unsigned int &walkL, + const unsigned int &num_points, + const VT &c, + const NT &T, + unsigned int const& nburns = 0){ + CorrelationSpectrahedron P(n); + const unsigned int d = P.dimension(); + PointType startingPoint(d); + RNGType rng(d); + PointType _c(c); + + exponential_sampling(randPoints, P, rng, walkL, num_points, _c, T, startingPoint, nburns); +} + +template +< + typename WalkTypePolicy, + typename PointType, + typename RNGType, + typename PointList, + typename NT, + typename VT +> +void exponential_correlation_sampling_MT( const unsigned int &n, + PointList &randPoints, + const unsigned int &walkL, + const unsigned int &num_points, + const VT &c, + const NT &T, + unsigned int const& nburns = 0){ + CorrelationSpectrahedron_MT P(n); + const unsigned int d = P.dimension(); + PointType startingPoint(n); + RNGType rng(d); + PointType _c(c); + + exponential_sampling(randPoints, P, rng, walkL, num_points, _c, T, startingPoint, nburns); +} + +#endif //VOLESTI_SAMPLING_SAMPLE_CORRELATION_MATRICES_HPP diff --git a/src/volesti/include/sampling/sampling.hpp b/src/volesti/include/sampling/sampling.hpp new file mode 100644 index 00000000..69618609 --- /dev/null +++ b/src/volesti/include/sampling/sampling.hpp @@ -0,0 +1,603 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2018 Vissarion Fisikopoulos, Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. + +// VolEsti is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// VolEsti is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// See the file COPYING.LESSER for the text of the GNU Lesser General +// Public License. If you did not receive this file along with HeaDDaCHe, +// see . + + +#ifndef SAMPLE_ONLY_H +#define SAMPLE_ONLY_H + +template +void uniform_sampling(PointList &randPoints, + Polytope &P, + RandomNumberGenerator &rng, + const unsigned int &walk_len, + const unsigned int &rnum, + const Point &starting_point, + unsigned int const& nburns) +{ + + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > walk; + + //RandomNumberGenerator rng(P.dimension()); + PushBackWalkPolicy push_back_policy; + + Point p = starting_point; + + typedef RandomPointGenerator RandomPointGenerator; + if (nburns > 0) { + RandomPointGenerator::apply(P, p, nburns, walk_len, randPoints, + push_back_policy, rng); + randPoints.clear(); + } + RandomPointGenerator::apply(P, p, rnum, walk_len, randPoints, + push_back_policy, rng); + + +} + +template < + typename PointList, + typename Polytope, + typename RandomNumberGenerator, + typename WalkTypePolicy, + typename Point +> +void uniform_sampling(PointList &randPoints, + Polytope &P, + RandomNumberGenerator &rng, + WalkTypePolicy &WalkType, + const unsigned int &walk_len, + const unsigned int &rnum, + const Point &starting_point, + unsigned int const& nburns) +{ + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > walk; + + //RandomNumberGenerator rng(P.dimension()); + PushBackWalkPolicy push_back_policy; + typedef RandomPointGenerator RandomPointGenerator; + + Point p = starting_point; + if (nburns > 0) { + RandomPointGenerator::apply(P, p, nburns, walk_len, randPoints, + push_back_policy, rng, WalkType.param); + randPoints.clear(); + } + RandomPointGenerator::apply(P, p, rnum, walk_len, randPoints, + push_back_policy, rng, WalkType.param); +} + + +template +< + typename WalkTypePolicy, + typename PointList, + typename Polytope, + typename RandomNumberGenerator, + typename Point +> +void uniform_sampling_boundary(PointList &randPoints, + Polytope &P, + RandomNumberGenerator &rng, + const unsigned int &walk_len, + const unsigned int &rnum, + const Point &starting_point, + unsigned int const& nburns) +{ + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > walk; + + //RandomNumberGenerator rng(P.dimension()); + PushBackWalkPolicy push_back_policy; + + Point p = starting_point; + + typedef BoundaryRandomPointGenerator BoundaryRandomPointGenerator; + if (nburns > 0) { + BoundaryRandomPointGenerator::apply(P, p, nburns, walk_len, + randPoints, push_back_policy, rng); + randPoints.clear(); + } + unsigned int n = rnum / 2; + BoundaryRandomPointGenerator::apply(P, p, rnum / 2, walk_len, + randPoints, push_back_policy, rng); + +} + + +template +< + typename WalkTypePolicy, + typename PointList, + typename Polytope, + typename RandomNumberGenerator, + typename NT, + typename Point +> +void gaussian_sampling(PointList &randPoints, + Polytope &P, + RandomNumberGenerator &rng, + const unsigned int &walk_len, + const unsigned int &rnum, + const NT &a, + const Point &starting_point, + unsigned int const& nburns) +{ + + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > walk; + + //RandomNumberGenerator rng(P.dimension()); + PushBackWalkPolicy push_back_policy; + + Point p = starting_point; + + typedef GaussianRandomPointGenerator RandomPointGenerator; + if (nburns > 0) { + RandomPointGenerator::apply(P, p, a, nburns, walk_len, randPoints, + push_back_policy, rng); + randPoints.clear(); + } + RandomPointGenerator::apply(P, p, a, rnum, walk_len, randPoints, + push_back_policy, rng); + + +} + + +template < + typename PointList, + typename Polytope, + typename RandomNumberGenerator, + typename WalkTypePolicy, + typename NT, + typename Point + > +void gaussian_sampling(PointList &randPoints, + Polytope &P, + RandomNumberGenerator &rng, + WalkTypePolicy &WalkType, + const unsigned int &walk_len, + const unsigned int &rnum, + const NT &a, + const Point &starting_point, + unsigned int const& nburns) +{ + + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > walk; + + //RandomNumberGenerator rng(P.dimension()); + PushBackWalkPolicy push_back_policy; + + Point p = starting_point; + + typedef GaussianRandomPointGenerator RandomPointGenerator; + if (nburns > 0) { + RandomPointGenerator::apply(P, p, a, nburns, walk_len, randPoints, + push_back_policy, rng, WalkType.param); + randPoints.clear(); + } + RandomPointGenerator::apply(P, p, a, rnum, walk_len, randPoints, + push_back_policy, rng, WalkType.param); +} + +template < + typename PointList, + typename Polytope, + typename RandomNumberGenerator, + typename WalkTypePolicy, + typename NT, + typename Point, + typename NegativeGradientFunctor, + typename NegativeLogprobFunctor, + typename Solver + > +void logconcave_sampling(PointList &randPoints, + Polytope &P, + RandomNumberGenerator &rng, + const unsigned int &walk_len, + const unsigned int &rnum, + const Point &starting_point, + unsigned int const& nburns, + NegativeGradientFunctor &F, + NegativeLogprobFunctor &f) +{ + typedef typename WalkTypePolicy::template Walk + < + Point, + Polytope, + RandomNumberGenerator, + NegativeGradientFunctor, + NegativeLogprobFunctor, + Solver + > walk; + + typedef typename WalkTypePolicy::template parameters + < + NT, + NegativeGradientFunctor + > walk_params; + + // Initialize random walk parameters + unsigned int dim = starting_point.dimension(); + walk_params params(F, dim); + + if (F.params.eta > 0) { + params.eta = F.params.eta; + } + + PushBackWalkPolicy push_back_policy; + + Point p = starting_point; + + walk logconcave_walk(&P, p, F, f, params); + + typedef LogconcaveRandomPointGenerator RandomPointGenerator; + + if (nburns > 0) { + RandomPointGenerator::apply(nburns, walk_len, randPoints, + push_back_policy, rng, logconcave_walk); + } + logconcave_walk.disable_adaptive(); + randPoints.clear(); + + RandomPointGenerator::apply(rnum, walk_len, randPoints, + push_back_policy, rng, logconcave_walk); +} +#include "preprocess/crhmc/crhmc_input.h" +#include "preprocess/crhmc/crhmc_problem.h" +template + < + typename PointList, + typename Polytope, + typename RandomNumberGenerator, + typename WalkTypePolicy, + typename NT, + typename Point, + typename NegativeGradientFunctor, + typename NegativeLogprobFunctor, + typename HessianFunctor, + typename Solver + > +void crhmc_sampling(PointList &randPoints, + Polytope &P, + RandomNumberGenerator &rng, + const int walk_len, + const unsigned int rnum, + const unsigned int nburns, + NegativeGradientFunctor &F, + NegativeLogprobFunctor &f, + HessianFunctor &h, + int simdLen = 1, + bool raw_output=false) { + typedef typename Polytope::MT MatrixType; + typedef crhmc_input + < + MatrixType, + Point, + NegativeLogprobFunctor, + NegativeGradientFunctor, + HessianFunctor + > Input; + Input input = convert2crhmc_input(P, f, F, h); + typedef crhmc_problem CrhmcProblem; + CrhmcProblem problem = CrhmcProblem(input); + if(problem.terminate){return;} + typedef typename WalkTypePolicy::template Walk + < + Point, + CrhmcProblem, + RandomNumberGenerator, + NegativeGradientFunctor, + NegativeLogprobFunctor, + Solver + > walk; + typedef typename WalkTypePolicy::template parameters + < + NT, + NegativeGradientFunctor + > walk_params; + Point p = Point(problem.center); + problem.options.simdLen=simdLen; + walk_params params(input.df, p.dimension(), problem.options); + + if (input.df.params.eta > 0) { + params.eta = input.df.params.eta; + } + + PushBackWalkPolicy push_back_policy; + + walk crhmc_walk = walk(problem, p, input.df, input.f, params); + + typedef CrhmcRandomPointGenerator RandomPointGenerator; + + RandomPointGenerator::apply(problem, p, nburns, walk_len, randPoints, + push_back_policy, rng, F, f, params, crhmc_walk); + //crhmc_walk.disable_adaptive(); + randPoints.clear(); + RandomPointGenerator::apply(problem, p, rnum, walk_len, randPoints, + push_back_policy, rng, F, f, params, crhmc_walk, simdLen, raw_output); +} +#include "ode_solvers/ode_solvers.hpp" +template < + typename Polytope, + typename RNGType, + typename PointList, + typename NegativeGradientFunctor, + typename NegativeLogprobFunctor, + typename HessianFunctor, + typename CRHMCWalk, + int simdLen=1 +> +void execute_crhmc(Polytope &P, RNGType &rng, PointList &randPoints, + unsigned int const& walkL, unsigned int const& numpoints, + unsigned int const& nburns, NegativeGradientFunctor *F=NULL, + NegativeLogprobFunctor *f=NULL, HessianFunctor *h=NULL, bool raw_output= false){ +typedef typename Polytope::MT MatrixType; +typedef typename Polytope::PointType Point; +typedef typename Point::FT NT; +if(h!=NULL){ +typedef crhmc_input + < + MatrixType, + Point, + NegativeLogprobFunctor, + NegativeGradientFunctor, + HessianFunctor + > Input; +typedef crhmc_problem CrhmcProblem; +crhmc_sampling < + PointList, + Polytope, + RNGType, + CRHMCWalk, + NT, + Point, + NegativeGradientFunctor, + NegativeLogprobFunctor, + HessianFunctor, + ImplicitMidpointODESolver < + Point, + NT, + CrhmcProblem, + NegativeGradientFunctor, + simdLen + > +>(randPoints, P, rng, walkL, numpoints, nburns, *F, *f, *h, simdLen, raw_output); +}else{ + typedef crhmc_input + < + MatrixType, + Point, + NegativeLogprobFunctor, + NegativeGradientFunctor, + ZeroFunctor + > Input; + typedef crhmc_problem CrhmcProblem; + ZeroFunctor zerof; +crhmc_sampling < + PointList, + Polytope, + RNGType, + CRHMCWalk, + NT, + Point, + NegativeGradientFunctor, + NegativeLogprobFunctor, + ZeroFunctor, + ImplicitMidpointODESolver < + Point, + NT, + CrhmcProblem, + NegativeGradientFunctor, + simdLen + > +>(randPoints, P, rng, walkL, numpoints, nburns, *F, *f, zerof, simdLen, raw_output); +} +} +template +< + typename WalkTypePolicy, + typename PointList, + typename Polytope, + typename RandomNumberGenerator, + typename NT, + typename Point +> +void exponential_sampling(PointList &randPoints, + Polytope &P, + RandomNumberGenerator &rng, + const unsigned int &walk_len, + const unsigned int &rnum, + const Point &c, + const NT &a, + const Point &starting_point, + unsigned int const& nburns) +{ + + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > walk; + + PushBackWalkPolicy push_back_policy; + + Point p = starting_point; + + typedef ExponentialRandomPointGenerator RandomPointGenerator; + if (nburns > 0) { + RandomPointGenerator::apply(P, p, c, a, nburns, walk_len, randPoints, + push_back_policy, rng); + randPoints.clear(); + } + RandomPointGenerator::apply(P, p, c, a, rnum, walk_len, randPoints, + push_back_policy, rng); +} + + +template < + typename PointList, + typename Polytope, + typename RandomNumberGenerator, + typename WalkTypePolicy, + typename NT, + typename Point + > +void exponential_sampling(PointList &randPoints, + Polytope &P, + RandomNumberGenerator &rng, + WalkTypePolicy &WalkType, + const unsigned int &walk_len, + const unsigned int &rnum, + const Point &c, + const NT &a, + const Point &starting_point, + unsigned int const& nburns) +{ + + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > walk; + + PushBackWalkPolicy push_back_policy; + + Point p = starting_point; + + typedef ExponentialRandomPointGenerator RandomPointGenerator; + if (nburns > 0) { + RandomPointGenerator::apply(P, p, c, a, nburns, walk_len, randPoints, + push_back_policy, rng, WalkType.param); + randPoints.clear(); + } + RandomPointGenerator::apply(P, p, c, a, rnum, walk_len, randPoints, + push_back_policy, rng, WalkType.param); +} + + +template +< + typename WalkTypePolicy, + typename PointList, + typename Polytope, + typename RandomNumberGenerator, + typename NT, + typename Point +> +void exponential_sampling(PointList &randPoints, + Polytope &P, + RandomNumberGenerator &rng, + const unsigned int &walk_len, + const unsigned int &rnum, + const Point &c, + const NT &a, + const NT &eta, + const Point &starting_point, + unsigned int const& nburns) +{ + + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > walk; + + PushBackWalkPolicy push_back_policy; + + Point p = starting_point; + + typedef ExponentialRandomPointGenerator RandomPointGenerator; + if (nburns > 0) { + RandomPointGenerator::apply(P, p, c, a, eta, nburns, walk_len, randPoints, + push_back_policy, rng); + randPoints.clear(); + } + RandomPointGenerator::apply(P, p, c, a, eta, rnum, walk_len, randPoints, + push_back_policy, rng); +} + + +template < + typename PointList, + typename Polytope, + typename RandomNumberGenerator, + typename WalkTypePolicy, + typename NT, + typename Point + > +void exponential_sampling(PointList &randPoints, + Polytope &P, + RandomNumberGenerator &rng, + WalkTypePolicy &WalkType, + const unsigned int &walk_len, + const unsigned int &rnum, + const Point &c, + const NT &a, + const NT &eta, + const Point &starting_point, + unsigned int const& nburns) +{ + + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > walk; + + PushBackWalkPolicy push_back_policy; + + Point p = starting_point; + + typedef ExponentialRandomPointGenerator RandomPointGenerator; + if (nburns > 0) { + RandomPointGenerator::apply(P, p, c, a, eta, nburns, walk_len, randPoints, + push_back_policy, rng, WalkType.param); + randPoints.clear(); + } + RandomPointGenerator::apply(P, p, c, a, eta, rnum, walk_len, randPoints, + push_back_policy, rng, WalkType.param); +} + + +#endif diff --git a/src/volesti/include/sampling/simplex.hpp b/src/volesti/include/sampling/simplex.hpp new file mode 100644 index 00000000..92534993 --- /dev/null +++ b/src/volesti/include/sampling/simplex.hpp @@ -0,0 +1,392 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2018-2020 Vissarion Fisikopoulos, Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. + +// VolEsti is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// VolEsti is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// See the file COPYING.LESSER for the text of the GNU Lesser General +// Public License. If you did not receive this file along with HeaDDaCHe, +// see . + +#ifndef SAMPLERS_SIMPLEX_HPP +#define SAMPLERS_SIMPLEX_HPP + +#ifndef isnan + using std::isnan; +#endif + +template +void Sam_Unit(unsigned int dim, + unsigned int num, + std::list &points, + double seed = std::numeric_limits::signaling_NaN()) +{ + + unsigned int j,i,x_rand,M=2147483647,pr,divisors,pointer; // M is the largest possible integer + std::vector x_vec; + std::vector y; + + boost::random::uniform_int_distribution<> uidist(1,M); + unsigned rng_seed = std::chrono::system_clock::now().time_since_epoch().count(); + RNGType rng(rng_seed); + if (!isnan(seed)) { + unsigned rng_seed2 = seed; + rng.seed(rng_seed2); + } + //unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); + //RNGType rng(seed); + + if (dim<=60){ + + x_vec.assign(dim+1,0); + + for (i=0; i filter; + bool t1=true,t2=true; + pr=3*dim+1; + if(pr%2==0) pr+=1; + + while(t1){ + t2=true; + divisors=(int)floor(sqrt((NT)pr))+1; + for (i=3; i x_vec2; + NT Ti,sum; + + x_vec2.assign(dim+1,0.0); + + // Generate the number of points requested + for (i=0; i +void Sam_Canon_Unit(unsigned int dim, + unsigned int num, + std::list &points, + double seed = std::numeric_limits::signaling_NaN()) +{ + + unsigned int j,i,x_rand,M=2147483647,pointer; // M is the largest possible integer + std::vector y; + dim--; + boost::random::uniform_int_distribution<> uidist(1,M); + + unsigned rng_seed = std::chrono::system_clock::now().time_since_epoch().count(); + RNGType rng(rng_seed); + if (!isnan(seed)) { + unsigned rng_seed2 = seed; + rng.seed(rng_seed2); + } + + std::vector x_vec2; + NT Ti,sum; + + x_vec2.assign(dim+1,0.0); + + // Generate the number of points requested + for (i=0; i +void Sam_arb_simplex(const Vpolytope &P, unsigned int num, PointList &points){ + + typedef typename Vpolytope::MT MT; + typedef typename Vpolytope::NT NT; + typedef typename Vpolytope::rngtype RNGType; + typedef typename Vpolytope::PolytopePoint Point; + + MT V = P.get_mat(); + std::vector vec_point; + + unsigned int n=V.rows(),j,i,k,x_rand,M=2147483647,pr,divisors,pointer; // M is the largest possible integer + unsigned int dim = V.cols(); + std::vector temp_p(dim, 0.0); + std::vector x_vec; + std::vector y; + + for (int i = 0; i < V.rows(); ++i) { + for (int j = 0; j < V.cols(); ++j) { + temp_p[j] = V(i,j); + } + vec_point.push_back(Point(dim, temp_p.begin(), temp_p.end())); + } + typename std::vector::iterator it_beg = vec_point.begin(); + + NT Xj; + Point p0=*it_beg; + + boost::random::uniform_int_distribution<> uidist(1,M); + unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); + RNGType rng(seed); + + if (dim<=60){ + + x_vec.assign(dim+2,0); x_vec[dim+1]=M; + + for (i=0; i filter; + x_vec.assign(dim+2,0); x_vec[dim+1]=M; + + bool t1=true,t2=true; + pr=3*dim+1; + if(pr%2==0) pr+=1; + + while(t1){ + t2=true; + divisors=(int)floor(sqrt((NT)pr))+1; + for (i=3; i x_vec2; + NT Ti,sum; + x_vec2.assign(dim+1,0.0); + + // Generate the number of points requested + for (i=0; i +struct GetDirection +{ + typedef typename Point::FT NT; + + template + inline static Point apply(unsigned int const& dim, + RandomNumberGenerator &rng, + bool normalize=true) + { + NT normal = NT(0); + Point p(dim); + NT* data = p.pointerToData(); + + if(normalize) + { + for (unsigned int i=0; i +struct GetDirection> +{ + template + inline static CorreMatrix apply(unsigned int const& dim, + RandomNumberGenerator &rng, + bool normalize=true) + { + typedef Eigen::Matrix MT; + typedef Eigen::Matrix VT; + + unsigned int n = std::ceil(std::sqrt(2*dim)); + MT mat = MT::Zero(n,n); + NT normal = NT(0), coeff; + + int i, j; + + if(normalize) + { + for(i = 0; i < n ; ++i) + { + for(j = 0; j < i; ++j) + { + coeff = rng.sample_ndist(); + mat(i,j) = coeff; + normal += coeff * coeff; + } + } + normal = NT(1)/std::sqrt(normal); + mat *= normal; + }else + { + for(i = 0; i < n ; ++i) + { + for(j = 0; j < i; ++j) + { + coeff = rng.sample_ndist(); + mat(i,j) = coeff; + } + } + } + return CorreMatrix(mat); + } +}; + +template +struct GetPointInDsphere +{ + template + inline static Point apply(unsigned int const& dim, + NT const& radius, + RandomNumberGenerator &rng) + { + Point p = GetDirection::apply(dim, rng); + NT U = rng.sample_urdist(); + U = std::pow(U, NT(1)/(NT(dim))); + p *= radius * U; + return p; + } +}; + +template +struct GetPointOnDsphere +{ + template + inline static Point apply(unsigned int const& dim, + NT const& radius, + RandomNumberGenerator &rng) + { + Point p = GetDirection::apply(dim, rng); + if (radius != 0) p *= radius; + return p; + } +}; + + + +#endif // SPHERE_HPP diff --git a/src/volesti/include/sos/NonSymmetricIPM.h b/src/volesti/include/sos/NonSymmetricIPM.h new file mode 100644 index 00000000..b5c5b95e --- /dev/null +++ b/src/volesti/include/sos/NonSymmetricIPM.h @@ -0,0 +1,333 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file +#ifndef SOS_NONSYMMETRICIPM_H +#define SOS_NONSYMMETRICIPM_H + +#include "barriers/LHSCB.h" +#include "barriers/ProductBarrier.h" +#include +#include +#include + + +template +class Instance { +public: + Constraints constraints; + LHSCB *barrier; +}; + +template +class DirectionDecomposition { +public: + DirectionDecomposition(Vector v, unsigned const n, unsigned const m) { + assert(v.rows() == m + n + 1 + n + 1); + y = v.block(0, 0, m, 1); + x = v.block(m, 0, n, 1); + tau = v.block(m + n, 0, 1, 1).sum(); + s = v.block(m + n + 1, 0, n, 1); + kappa = v.block(m + n + 1 + n, 0, 1, 1).sum(); + } + + friend std::ostream &operator<<(std::ostream &os, const DirectionDecomposition &dir); + + Vector y, x, s; + T kappa, tau; +}; + +template +class ErrorConstants { +public: + + ErrorConstants() {}; + + template + ErrorConstants(const ErrorConstants other) { + //Note: Boost Dependency + primal = boost::numeric_cast(other.primal); + dual = boost::numeric_cast(other.dual); + complementary = boost::numeric_cast(other.complementary); + } + + void set(Matrix A, Vector b, Vector c) { + //Constant are used to conform with the implementation by Papp & Yildiz. + primal = std::max(T(1), sqrt(pow(A.norm(), 2) + pow(b.norm(), 2))); + Matrix Id = Matrix::Identity(A.cols(), A.cols()); + dual = std::max(T(1), sqrt(pow(A.transpose().norm(), 2) + pow(Id.norm(), 2) + pow(c.norm(), 2))); + complementary = sqrt(pow(c.norm(), 2) + pow(b.norm(), 2) + 1); + } + + T primal; + T dual; + T complementary; +}; + +template +class NonSymmetricIPM { + + typedef Matrix Matrix; + typedef Vector Vector; + +public: + + NonSymmetricIPM(Matrix &A_, Vector &b_, Vector &c_, LHSCB *barrier_); + + NonSymmetricIPM(Instance &instance) : NonSymmetricIPM(instance.constraints.A, + instance.constraints.b, instance.constraints.c, + instance.barrier) { + _logger->set_level(spdlog::level::info); + }; + + NonSymmetricIPM(Instance &, std::string); + + + template + void cast_members_from(const NonSymmetricIPM &other) { + A_sparse = other.A_sparse.template cast(); + _basis_ker_A = other._basis_ker_A.template cast(); + _config = other._config; + + _num_predictor_steps = other._num_predictor_steps; + _num_corrector_steps = other._num_corrector_steps; + _param_step_length_predictor = boost::numeric_cast(other._param_step_length_predictor); + _step_length_predictor = boost::numeric_cast(other._step_length_predictor); + _step_length_corrector = boost::numeric_cast(other._step_length_corrector); + _epsilon = boost::numeric_cast(other._epsilon); + + _large_neighborhood = boost::numeric_cast(other._large_neighborhood); + _small_neighborhood = boost::numeric_cast(other._small_neighborhood); + //copy current solution + + x = other.x.template cast(); + y = other.y.template cast(); + s = other.s.template cast(); + kappa = boost::numeric_cast(other.kappa); + tau = boost::numeric_cast(other.tau); + + _last_predictor_direction = other._last_predictor_direction.template cast(); + + _err_consts = ErrorConstants(other._err_consts); + + //copy configuration; + + _use_line_search = other._use_line_search; + } + + template + NonSymmetricIPM *cast_with_product_barrier() { + + //cast initialisatino data + Eigen::Matrix A_ = A.template cast(); + Eigen::Matrix b_ = b.template cast(); + Eigen::Matrix c_ = c.template cast(); + + //TODO: High priority. Figure out how to undo the cast to the ProductBarrier. + ProductBarrier *barrier_ = static_cast *>(_barrier)->template cast(); + + assert(barrier_ != nullptr); + + NonSymmetricIPM *nonSymmetricIPM = new NonSymmetricIPM(A_, b_, c_, barrier_); + + //cast members + + nonSymmetricIPM->template cast_members_from(*this); + + //copy configuration; + + assert(nonSymmetricIPM->x.rows() == nonSymmetricIPM->s.rows()); + assert(nonSymmetricIPM->x.rows() == nonSymmetricIPM->c.rows()); + + return nonSymmetricIPM; + } + + IPMDouble calc_step_length_predictor() { + IPMDouble const epsilon = .5; + IPMDouble const eta = _large_neighborhood * pow(epsilon, _num_corrector_steps); + IPMDouble const k_x = eta + sqrt(2 * eta * eta + _barrier->concordance_parameter(x) + 1); + return _param_step_length_predictor / k_x; + } + + int run_solver(); + + enum Termination { + SUCCESS, + FAILURE + }; + + inline IPMDouble primal_error() { + return (A * x - tau * b).norm() / _err_consts.primal; + } + + inline IPMDouble dual_error() { + return (A.transpose() * y + s - tau * c).norm() / _err_consts.dual; + } + + inline IPMDouble primal_error_rescaled() { + return (A * x / tau - b).norm(); + } + + inline IPMDouble dual_error_rescaled() { + return (A.transpose() * y / tau + s / tau - c).norm(); + } + + inline IPMDouble duality_gap() { + return x.dot(s) / _barrier->concordance_parameter(x); + + } + + inline IPMDouble complementarity() { + return abs(c.dot(x) - b.dot(y)) / _err_consts.complementary; + } + + bool verify_solution(IPMDouble precision = 10e-5) { + if (not _barrier->in_interior(x)) { + return false; + } + if (primal_error_rescaled() > precision) { + return false; + } + if (dual_error_rescaled() > precision) { + return false; + } + + //TODO: Add duality gap check. In general we don't have strict duality as exploited below. + if (duality_gap() > precision) { + return false; + } + return true; + } + + Solution get_solution() { + Solution sol; + assert(tau != 0); + sol.x = x / tau; + sol.s = s / tau; + sol.centrality = centrality(); + sol.gap = mu(); + return sol; + } + + std::shared_ptr _logger; + std::shared_ptr _benchmark_logger; + + + //TODO:Figure out how to make these variables while being able to compile it. + + Matrix A; + Vector b; + Vector c; + + Eigen::SparseMatrix A_sparse; + + //Matrix whose columns are a basis of the kernel of A. Currently unused. + Matrix _basis_ker_A; + + Vector x; + Vector y; + Vector s; + + pt::ptree _config; + + unsigned _num_predictor_steps = 500; + unsigned _num_corrector_steps; + + IPMDouble _param_step_length_predictor = 0.02; + + IPMDouble _step_length_predictor; + IPMDouble _step_length_corrector; + + IPMDouble _epsilon = 10e-5; + + unsigned _total_num_line_steps; + + //Large neighborhood + IPMDouble _large_neighborhood; + //Small neighborhood + IPMDouble _small_neighborhood; + + bool _check_centrality_in_every_segment = true; + + bool _type_cast_if_unsuccessful = true; + + bool _use_line_search = true; + + IPMDouble kappa, tau; + + Vector _last_predictor_direction; + + ErrorConstants _err_consts; + + void initialize(); + +private: + + cxxtimer::Timer _predictor_timer; + cxxtimer::Timer _corrector_timer; + cxxtimer::Timer _andersen_sys_timer; + cxxtimer::Timer _centrality_timer; + + std::vector _custom_timers; + + cxxtimer::Timer _general_method_timer; + cxxtimer::Timer _total_runtime_timer; + + + void set_configuration_variables(); + + bool terminate(); + + bool terminate_successfully_wrapper(); + + bool terminate_successfully(); + + bool terminate_infeasible_wrapper(); + + bool terminate_infeasible(); + + + Vector _stored_x_centrality; + Vector _stored_s_centrality; + IPMDouble _stored_centrality_error; + + + IPMDouble mu(); + + Vector psi(IPMDouble t); + + LHSCB *_barrier; + + std::vector > + solve_andersen_andersen_subsystem(std::vector > &); + + Vector andersen_andersen_solve(Vector const); + + Vector solve(Matrix &, Vector const); + + Vector create_predictor_RHS(); + + Vector create_corrector_RHS(); + + Vector solve_predictor_system(); + + Vector solve_corrector_system(); + + IPMDouble centrality(); + + + void print_status(); + + void apply_update(Vector concat); + + Vector build_update_vector(); + + void test_gradient(); + + void test_hessian(); +}; + +#include "NonSymmetricIPM.hpp" + +#endif //SOS_NONSYMMETRICIPM_H diff --git a/src/volesti/include/sos/NonSymmetricIPM.hpp b/src/volesti/include/sos/NonSymmetricIPM.hpp new file mode 100644 index 00000000..a634c545 --- /dev/null +++ b/src/volesti/include/sos/NonSymmetricIPM.hpp @@ -0,0 +1,781 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#include +#include "NonSymmetricIPM.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/fmt/ostr.h" +#include "barriers/ProductBarrier.h" +#include "barriers/SumBarrier.h" +#include "barriers/InterpolantDualSOSBarrier.h" + +template +std::ostream &operator<<(std::ostream &os, const DirectionDecomposition &dir) { + os << "x " << dir.x.transpose() << std::endl; + os << "s: " << dir.s.transpose() << std::endl; + os << "y: " << dir.y.transpose() << std::endl; + os << "kappa: " << dir.kappa << ", tau: " << dir.tau << std::endl; + return os; +} + + +template +NonSymmetricIPM::NonSymmetricIPM(Instance &instance, std::string config_json) : NonSymmetricIPM( + instance) { + pt::read_json(config_json, _config); + _config = _config.get_child("IPM"); + std::cout << "NonSymmetricIPM configuration..." << std::endl; + pt::write_json(std::cout, _config); + initialize(); + set_configuration_variables(); +} + +template +void NonSymmetricIPM::set_configuration_variables() { + _epsilon = _config.get("epsilon"); + _logger->info("epsilon set to {}", _epsilon); + _num_corrector_steps = _config.get("num_corrector_steps"); + _large_neighborhood = _config.get("large_neighborhood"); + _small_neighborhood = _config.get("small_neighborhood"); + _param_step_length_predictor = _config.get("scale_predictor_step"); + _step_length_predictor = calc_step_length_predictor(); + _step_length_corrector = _config.get("length_corrector_step"); + _check_centrality_in_every_segment = _config.get("check_centrality_in_every_segment"); + _type_cast_if_unsuccessful = _config.get("type_cast_if_unsuccessful"); + + _logger->info("Set log level to {}", spdlog::level::level_enum(_config.get("logger_level"))); + _logger->set_level(spdlog::level::level_enum(_config.get("logger_level"))); + + _use_line_search = _config.get("use_line_search"); +} + +template +Vector NonSymmetricIPM::solve(Matrix &M_, Vector const v_) { + Vector sol = M_.colPivHouseholderQr().solve(v_); + return sol; +} + +//TODO:For sparse systems we might create a dense matrix in the LLT decomposition. Implement separate solver for this case +//TODO:make sure all memory is preallocated. + +template +std::vector, Vector > > +NonSymmetricIPM::solve_andersen_andersen_subsystem( + std::vector > &v) { + + _custom_timers[8].start(); + _custom_timers[4].start(); + //TODO: double transposition. Figure out how to multiply solve from RHS. +// Matrix A_H_inv = LLT.solve(A.transpose()).transpose() / mu(); + Matrix A_H_inv = _barrier->llt_solve(x, A.transpose()).transpose() / mu(); + A_H_inv.eval(); + _custom_timers[4].stop(); + _custom_timers[5].start(); + + //TODO: Use better method to sparsify A. + Matrix A_H_inv_A_top = A_H_inv * A_sparse.transpose(); + A_H_inv_A_top.eval(); + + _custom_timers[5].stop(); + _custom_timers[6].start(); + Eigen::LLT A_H_inv_A_top_LLT = A_H_inv_A_top.llt(); + _custom_timers[6].stop(); + _custom_timers[8].stop(); + _custom_timers[9].start(); + + std::vector > results; + //TODO: might be possible to solve these system in batches! + //TODO: check whether Conjugate Gradient Method solves Normal Equations more efficiently. + for (unsigned i = 0; i < v.size(); i++) { + Vector &r1 = v[i].first; + Vector &r2 = v[i].second; + Vector r2_solve = A * _barrier->llt_solve(x, r2) / mu(); +// Vector new_s_intermediate = A_H_inv_A_top_LLT.matrixL().solve(-(r2_solve - r1)); +// Vector new_s = A_H_inv_A_top_LLT.matrixU().solve(new_s_intermediate); + Vector new_s = A_H_inv_A_top_LLT.solve(-(r2_solve - r1)); +// Vector new_t_intermediate = LLT.matrixL().solve((r2 + A.transpose() * new_s) / mu()); +// Vector new_t = LLT.matrixU().solve(new_t_intermediate); + Vector new_t = _barrier->llt_solve(x, (r2 + A.transpose() * new_s) / mu()); + results.emplace_back(std::pair(new_s, new_t)); + } + _custom_timers[9].stop(); + return results; +} + +template +Vector NonSymmetricIPM::andersen_andersen_solve(Vector const rhs) { + + _andersen_sys_timer.start(); + + const int m = A.rows(); + const int n = A.cols(); + + //TODO: figure out whether rescaling makes sense. + Vector const r_p = rhs.segment(0, m); + Vector const r_d = rhs.segment(m, n); + Vector const r_g = rhs.segment(m + n, 1); + Vector const r_xs = rhs.segment(m + n + 1, n); + Vector const r_tk = rhs.segment(m + n + 1 + n, 1); + + Matrix mu_H_x = mu() * _barrier->hessian(x); + IPMDouble mu_H_tau = mu() / (tau * tau); + + std::vector > new_rhs_vectors; + new_rhs_vectors.emplace_back(std::pair(b, -c)); + new_rhs_vectors.emplace_back(std::pair(r_p, r_d + r_xs)); + + std::vector > ret = solve_andersen_andersen_subsystem(new_rhs_vectors); + + Vector p = ret[0].first; + Vector q = ret[0].second; + + Vector u = ret[1].first; + Vector v = ret[1].second; + + Vector pq(m + n); + pq << p, q; + + Vector uv(m + n); + uv << u, v; + + SPDLOG_TRACE("{}", pq.segment(m, n).transpose()); + SPDLOG_TRACE("{}", q.transpose()); + + Vector rhs_uv(m + n); + rhs_uv << r_p, r_d + r_xs; + + IPMDouble d_tau = (r_g.sum() + r_tk.sum() - b.dot(u) + c.dot(v)) / (mu() / (tau * tau) + b.dot(p) - c.dot(q)); + + Vector d_yx = uv + d_tau * pq; + Vector d_x = d_yx.segment(m, n); + Vector d_s = r_xs - mu_H_x * d_x; + IPMDouble d_kappa = r_tk.sum() - mu_H_tau * d_tau; + + Vector d_tau_vec(1); + d_tau_vec(0) = d_tau; + Vector d_kappa_vec(1); + d_kappa_vec(0) = d_kappa; + + Vector sol(m + n + 1 + n + 1); + sol << d_yx, d_tau_vec, d_s, d_kappa_vec; + + _andersen_sys_timer.stop(); + + //Check for dual error in this solution and potentially run iterative refinement. + + Vector dual_err = -A.transpose() * d_yx.segment(0, y.rows()) + d_tau * c - d_s - r_d; + IPMDouble rel_dual_error = dual_err.norm() / r_d.norm(); + + _logger->debug("relative dual error is {}", rel_dual_error); + if (rel_dual_error > 10e-2) { + _logger->warn("relative dual error is {}", rel_dual_error); + } + return sol; +} + +template +Vector NonSymmetricIPM::build_update_vector() { + Vector concat(y.rows() + x.rows() + 1 + s.rows() + 1); + concat << y, x, tau * Matrix::Identity(1, 1), s, kappa * Matrix::Identity(1, 1); + return concat; +} + +template +void NonSymmetricIPM::apply_update(Vector concat) { + y = concat.block(0, 0, y.rows(), 1); + x = concat.block(y.rows(), 0, x.rows(), 1); + tau = concat.block(y.rows() + x.rows(), 0, 1, 1).sum(); + s = concat.block(y.rows() + x.rows() + 1, 0, s.rows(), 1); + kappa = concat.block(y.rows() + x.rows() + 1 + s.rows(), 0, 1, 1).sum(); +} + +//Heuristic for steplength. +template +IPMDouble calc_new_alpha(int num_steps, IPMDouble step_length) { + IPMDouble scale_fac = pow(2, num_steps) * step_length; + IPMDouble const threshold = .4; + if (scale_fac > threshold) { + IPMDouble ratio = log2(scale_fac / threshold); + scale_fac = 1. - (1 - threshold) / pow(2, ratio); + } + return scale_fac; +} + +template +int NonSymmetricIPM::run_solver() { + + _logger->info("Solver started."); + + _logger->trace("b: {}", b.transpose()); + _logger->trace("c: {}", c.transpose()); + _logger->trace("gradient: {}", _barrier->gradient(x).transpose()); + + print_status(); + + _total_num_line_steps = 0; + _total_runtime_timer.start(); + unsigned pred_iteration = 0; + for (; pred_iteration < _num_predictor_steps; ++pred_iteration) { + Vector vec_begin_predictor_direction = build_update_vector(); + _logger->debug("Begin predictor iteration {}", pred_iteration); + _predictor_timer.start(); + if (terminate_successfully_wrapper()) { + _logger->info("Interior point method terminated successfully with required proximity."); + break; + } + + if (terminate_infeasible_wrapper()) { + _logger->info("Interior point method terminated with infeasible solution."); + break; + } + +#ifndef NDEBUG + if (centrality() > _large_neighborhood) { + _logger->warn("Centrality at beginning of predictor step is {}, large neighborhood is {}", centrality(), + _large_neighborhood); + } +#endif + + _logger->trace("Solve predictor system..."); + Vector predictor_direction = solve_predictor_system(); + _logger->trace("Finished solving predictor system"); + + + DirectionDecomposition pred_dir(predictor_direction, x.rows(), y.rows()); + + //Check predictor direction + + IPMDouble err_primal = (A * pred_dir.x - b * pred_dir.tau + A * x - b * tau).norm() + / (A * x - b * tau).norm(); + IPMDouble err_dual = (-A.transpose() * pred_dir.y + c * pred_dir.tau - pred_dir.s + - A.transpose() * y + c * tau - s).norm() / (A.transpose() * y - c * tau + s).norm(); + IPMDouble err_opt = abs(b.dot(pred_dir.y) - c.dot(pred_dir.x) - pred_dir.kappa + + b.dot(y) - c.dot(x) - kappa) / abs(-b.dot(y) + c.dot(x) + kappa); + IPMDouble err_cent = (pred_dir.s + mu() * _barrier->hessian(x) * pred_dir.x + s).norm() / s.norm(); + IPMDouble err_cent2 = abs(pred_dir.kappa + mu() / (tau * tau) * pred_dir.tau + kappa) / kappa; + + IPMDouble err_sum = err_primal + err_dual + err_opt + err_cent + err_cent2; + + if (_logger->level() <= spdlog::level::debug) { + _logger->debug("Errors in predictor direction {} {} {} {} {}", err_primal, err_dual, err_opt, err_cent, + err_cent2); + } + + const IPMDouble ERR_THRESHOLD = .1; + if (err_sum > ERR_THRESHOLD) { + _logger->warn("Error in predictor direction too big. Terminate. (Error is {}. Threshold is {})", + err_sum, ERR_THRESHOLD); + _logger->info("Predictor direction: "); + std::cout << pred_dir.x.transpose() << std::endl << pred_dir.s.transpose() << std::endl; + //TODO: Iterative refinement. + return Termination::FAILURE; + } + + + Vector curr_vec = build_update_vector(); + Vector orig_vector = curr_vec; + unsigned num_line_steps = 0; + if (not _use_line_search) { + curr_vec += _step_length_predictor * predictor_direction; + apply_update(curr_vec); + + } else { + //find longest step length such that we remain in beta environment. + //TODO: find sophisticated way of computing this efficiently. Currently we do simple repeated squaring, + // irrespective of the barrier function. + + Vector fallback_vec = orig_vector; + curr_vec = orig_vector + calc_new_alpha(num_line_steps, _step_length_predictor) * predictor_direction; + apply_update(curr_vec); + while (kappa > 0 and tau > 0 and _barrier->in_interior(x) + and (not _check_centrality_in_every_segment or (centrality() < _large_neighborhood))) { + _logger->debug("Another iteration in line search..."); + fallback_vec = curr_vec; + curr_vec = orig_vector + calc_new_alpha(num_line_steps, _step_length_predictor) * predictor_direction; + num_line_steps++; + apply_update(curr_vec); + } + + //Dynamically adjust step length + if (num_line_steps == 0) { + _step_length_predictor /= 2; + } + + if (num_line_steps > 3) { + _step_length_predictor *= 2; + } + + if (num_line_steps == 0) { + _logger->info("Reason for stopping: "); + if (not _barrier->in_interior(x)) { + _logger->info("Not in interior"); + } else if (centrality() >= _large_neighborhood) { + _logger->info("Centrality {} is worse than neighborhood {}", centrality(), _large_neighborhood); + } + } + _logger->debug("Applied {} line steps in iteration {}", num_line_steps, pred_iteration); + apply_update(fallback_vec); + +#ifndef NDEBUG + if(not terminate_successfully() and num_line_steps == 0){ + _logger->warn("Could not perform a single predictor step"); + } +#endif + } + + _total_num_line_steps += num_line_steps; + _logger->info("End of predictor step {} with {} line steps and total num line steps {}:", + pred_iteration, num_line_steps, _total_num_line_steps); + if (_logger->level() <= spdlog::level::info) { + print_status(); + } + + _predictor_timer.stop(); + _corrector_timer.start(); + + for (unsigned corr_iteration = 0; corr_iteration < _num_corrector_steps; ++corr_iteration) { + //TODO: figure out if this is already as expensive as running another corrector step. (probably not, as the crrent value can be used for next predictor step if true). + + IPMDouble cur_centrality = centrality(); + if (cur_centrality < _small_neighborhood) { + _logger->info("Central enough to skip corrector iteration {}", corr_iteration); + break; + } + + Vector psi_full = psi(mu()); + + //TODO: Use Woodburry matrix identity for corrector step. + + Vector corrector_direction = solve_corrector_system(); + + DirectionDecomposition dir(corrector_direction, x.rows(), y.rows()); + Vector concat = build_update_vector(); + concat += _step_length_corrector * corrector_direction; + apply_update(concat); + + if (centrality() > cur_centrality / 2) { + //In this case we are too far away from the central path. Revert back to and shrink large neighborhood + _logger->info("Shrink large neighborhood and use stored iterate, " + "because centering step converges too slowly."); + apply_update(vec_begin_predictor_direction); + _large_neighborhood = _small_neighborhood + (_large_neighborhood - _small_neighborhood) / 2; + break; + } + + _logger->info("End of corrector step {} :", corr_iteration); + if (_logger->level() <= spdlog::level::info) { + print_status(); + } + + if (_logger->level() <= spdlog::level::debug) { + assert(kappa > 0); + assert(tau > 0); + assert(_barrier->in_interior(x)); + assert(centrality() < _large_neighborhood); + } + } + _corrector_timer.stop(); + } + + _total_runtime_timer.stop(); + _benchmark_logger->info("{} {} {} {} {}", (_barrier->getNumVariables() / 2 - 1) / 2, pred_iteration + 1, + _total_runtime_timer.count() / 1000., + _total_runtime_timer.count() / (1000. * _total_num_line_steps), + _epsilon); + return Termination::SUCCESS; +} + +template +NonSymmetricIPM::NonSymmetricIPM(Matrix &A_, Vector &b_, Vector &c_, LHSCB *barrier_) : + A(A_), b(b_), c(c_), kappa(1.), tau(1.), _barrier(barrier_) { + y = Matrix::Zero(A.rows(), 1); + + //TODO: use proper tolerance / reference. + A_sparse = A.sparseView(IPMDouble(10e-10), 1e-10); + + _stored_x_centrality.resize(c.rows()); + _stored_s_centrality.resize(c.rows()); + + _custom_timers.resize(10); + + _logger = spdlog::get("NonSymmetricIPM"); + + + if (_logger == nullptr) { + std::vector sinks; + sinks.push_back(std::make_shared()); + sinks.push_back(std::make_shared("logs/logfile.txt")); + _logger = std::make_shared("NonSymmetricIPM", begin(sinks), end(sinks)); + _logger->set_level(spdlog::level::info); + + sinks.push_back(std::make_shared("logs/benchmark.txt")); + _benchmark_logger = std::make_shared("", begin(sinks) + 2, end(sinks)); + } + + _logger->info("A has dimensions {} x {}", A.rows(), A.cols()); + _logger->info("The number of non-zero entries is {}", A_sparse.nonZeros()); + +} + +template +void NonSymmetricIPM::initialize() { + + //Rescale instance for stability/conditioning (See Papp & Yildiz paper) + + x = _barrier->initialize_x(); + s = _barrier->initialize_s(); + + _err_consts.set(A,b,c); + + IPMDouble scaling_delta_primal = 0; + for (int i = 0; i < A.rows(); i++) { + IPMDouble const row_ratio = (1. + abs(b(i))) / (1. + abs(A.row(i).sum())); + scaling_delta_primal = std::max(scaling_delta_primal, row_ratio); + } + + IPMDouble scaling_delta_dual = 0; + for (int i = 0; i < s.rows(); i++) { + IPMDouble const entry_ratio = (1 + abs(s(i))) / (1 + abs(c(i))); + scaling_delta_dual = std::max(scaling_delta_dual, entry_ratio); + } + + IPMDouble const scaling_delta = sqrt(scaling_delta_dual * scaling_delta_primal); + + _logger->debug("Norm of x is {} and norm of s is {} before rescaling.", x.norm(), s.norm()); + + x = _barrier->initialize_x(scaling_delta); + s = _barrier->initialize_s(scaling_delta); + + _logger->debug("Rescaled initial point by {}", scaling_delta); + _logger->debug("Norm of c is {}, Norm of b is {} and norm of A is {} ", c.norm(), b.norm(), A.norm()); + + assert(x.rows() == _barrier->getNumVariables()); + + //TODO: switch to dense operation based on number of nonzeros. +// Matrix QR = A.transpose().householderQr().householderQ(); + + Eigen::SparseQR, Eigen::COLAMDOrdering > QR_sparse; + + Eigen::SparseMatrix tmp_sparse = A_sparse.transpose(); + tmp_sparse.makeCompressed(); + QR_sparse.compute(tmp_sparse); + +// _basis_ker_A = QR.block(0, A.rows(), QR.rows(), QR.cols() - A.rows()); + _basis_ker_A = Matrix(QR_sparse.matrixQ()).block(0, A.rows(), QR_sparse.rows(), QR_sparse.cols() - A.rows()); + + _logger->trace("Matrix A is: \n {}", A); + _logger->trace("Basis of ker A is: \n {}", _basis_ker_A); + _logger->trace("Check correctness: \n {}", A * _basis_ker_A); + + _num_corrector_steps = 3; + _large_neighborhood = .99; + _small_neighborhood = 0.1; + + _step_length_predictor = calc_step_length_predictor(); + _step_length_corrector = 1; + + + +} + +template +Vector NonSymmetricIPM::create_predictor_RHS() { + Vector v(y.rows() + x.rows() + 1 + s.rows() + 1); + v << y, x, tau * Matrix::Identity(1, 1), s, kappa * Matrix::Identity(1, 1); + DirectionDecomposition cur_sol(v, x.rows(), y.rows()); + Vector v1 = -(A * x - b * tau); + Vector v2 = -(-A.transpose() * y + c * tau - s); + Vector v3 = -(b.dot(y) - c.dot(x) - kappa) * Matrix::Identity(1, 1); + Vector rhs(v1.rows() + v2.rows() + v3.rows() + s.rows() + 1); + rhs << v1, v2, v3, -s, -kappa * Matrix::Identity(1, 1); + return rhs; +} + +template +Vector NonSymmetricIPM::create_corrector_RHS() { + Vector v1 = Vector::Zero(A.rows() + A.cols() + 1); + Vector v2 = -psi(mu()); + Vector rhs(v1.rows() + s.rows() + 1); + rhs << v1, v2; + _logger->trace("Corrector RHS is \n {}", rhs.transpose()); + return rhs; +}; + +template +IPMDouble NonSymmetricIPM::mu() { + return (x.dot(s) + (tau * kappa)) / (_barrier->concordance_parameter(x) + 1); +}; + +template +Vector NonSymmetricIPM::psi(IPMDouble t) { + Vector v(s.rows()); + v = s + t * _barrier->gradient(x); + Vector v_aux = (kappa - t / tau) * Matrix::Identity(1, 1); + Vector res(s.rows() + 1); + res << v, v_aux; + return res; +} + +template +Vector NonSymmetricIPM::solve_predictor_system() { + Vector rhs = create_predictor_RHS(); + SPDLOG_LOGGER_DEBUG(_logger, "System RHS for predictor step is {}", rhs.transpose()); + Vector andersen_direction = andersen_andersen_solve(rhs); + return andersen_direction; +} + +template +Vector NonSymmetricIPM::solve_corrector_system() { + Vector rhs = create_corrector_RHS(); + SPDLOG_LOGGER_DEBUG(_logger, "Psi is {}", psi(mu()).transpose()); + SPDLOG_LOGGER_DEBUG(_logger, "Barrier is {}", _barrier->gradient(x).transpose()); + SPDLOG_LOGGER_DEBUG(_logger, "s is {}", s.transpose()); + SPDLOG_LOGGER_DEBUG(_logger, "System RHS for corrector step is {}", rhs.transpose()); + SPDLOG_LOGGER_TRACE(_logger, "Corrector matrix is: \n{}", _M); + + //TODO: check if iterative refinement makes sense + auto andersen_dir = andersen_andersen_solve(rhs); + + return andersen_dir; +} + +template +void NonSymmetricIPM::print_status() { + + std::string format_ = "{:<25}:{:20.2}"; + + Double const step_length_predictor = static_cast(_step_length_predictor); + Double const step_length_corrector = static_cast(_step_length_corrector); + + _logger->debug(format_, "alpha_predictor", step_length_predictor); + _logger->debug(format_, "alpha_corrector", step_length_corrector); + + Matrix aux(2, x.rows()); + aux.block(0, 0, 1, x.rows()) = x.transpose(); + aux.block(1, 0, 1, s.rows()) = s.transpose(); + + + if (_logger->level() <= spdlog::level::trace) { + _logger->trace("Current primal/dual x, s pair :\n{}", aux); + _logger->trace("Current primal/dual rescaled x, s pair :\n{}", aux / tau); + } + + _logger->info(format_, "kappa", static_cast(kappa)); + _logger->info(format_, "tau", static_cast(tau)); + + IPMDouble mu_ipm_scaled = mu() / (tau * tau); + Double const mu_scaled = static_cast(mu_ipm_scaled); + _logger->debug(format_, "mu scaled", mu_scaled); + + IPMDouble mu_ipm = mu(); + Double const mu_ = static_cast(mu_ipm); + _logger->info(format_, "mu", boost::numeric_cast(mu())); + + if (_logger->level() <= spdlog::level::debug) { + IPMDouble centrality_ipm_ = centrality(); + Double const centrality_ = static_cast(centrality_ipm_); + _logger->debug(format_, "centrality error", centrality_); + } + + IPMDouble duality_gap_ipm_ = kappa / tau; + Double duality_gap_ = static_cast(duality_gap_ipm_); + _logger->info(format_, "duality gap", duality_gap_); + + IPMDouble primal_inf_ipm_ = primal_error(); + Double primal_inf_ = static_cast(primal_inf_ipm_); + _logger->info(format_, "primal infeas. ", primal_inf_); + + IPMDouble primal_inf_unscaled_ipm_ = primal_error_rescaled(); + Double primal_inf_unscaled_ = static_cast(primal_inf_unscaled_ipm_); + _logger->debug(format_, "primal infeas. unscaled", primal_inf_unscaled_); + + IPMDouble dual_inf_ipm_ = dual_error(); + Double dual_inf_ = static_cast(dual_inf_ipm_); + _logger->info(format_, "dual infeas.", dual_inf_); + + IPMDouble dual_inf_unscaled_ipm_ = dual_error_rescaled(); + Double dual_inf_unscaled_ = static_cast(dual_inf_unscaled_ipm_); + _logger->debug(format_, "dual infeas. unscaled", dual_inf_unscaled_); + + _logger->trace("last predictor direction: {}", _last_predictor_direction.transpose()); + + _logger->info(format_, "Predictor time (s)", _predictor_timer.count() / 1000.); + _logger->info(format_, "Corrector time (s)", _corrector_timer.count() / 1000.); + + _logger->info(format_, "Total andersen time (s)", + _andersen_sys_timer.count() / 1000.); + _logger->info(format_, "Total runtime (s)", + _total_runtime_timer.count() / 1000.); + _logger->info(format_, "Calc centrality time (s)", + _centrality_timer.count() / 1000.); + _logger->info(format_, "Time checking interior(s)", + _barrier->_in_interior_timer.template count() / 1000.); + + + _logger->info(format_, "Time per step: ", + _total_runtime_timer.count() / (1000. * _total_num_line_steps)); + if (_logger->level() <= spdlog::level::debug) { + for (unsigned idx = 0; idx < _custom_timers.size(); idx++) { + std::string s = "Custom timer " + std::to_string(idx); + _logger->info(format_, s, + _custom_timers[idx].template count() / 1000.); + } + ProductBarrier *productBarrier = static_cast *>(_barrier); + SumBarrier *sumBarrier = static_cast *>(productBarrier->get_barriers()[0]); + InterpolantDualSOSBarrier *interpBarrier = static_cast *>(sumBarrier->get_barriers()[0]); + _logger->info("Runtimes for updating gradient/hessian/LLT"); + for (unsigned idx = 0; idx < interpBarrier->_custom_timers.size(); idx++) { + std::string s = "Custom timer " + std::to_string(idx); + _logger->info(format_, s, + interpBarrier->_custom_timers[idx].template count() / 1000.); + } + } + _logger->info("--------------------------------------------------------------------------------------"); + +} + +template +bool NonSymmetricIPM::terminate_successfully_wrapper() { +// Eigen::internal::set_is_malloc_allowed(true); + bool result = terminate_successfully(); +// Eigen::internal::set_is_malloc_allowed(false); + return result; +} + +template +bool NonSymmetricIPM::terminate_successfully() { + if (primal_error() > _epsilon) { + return false; + } + + if (dual_error() > _epsilon) { + return false; + } + + if (complementarity() > _epsilon){ + return false; + } + + return true; +} + +// Termination criteria taken from Skajaa - Ye "A Homogeneous Interior-Point Algorithm for +// Nonsymmetric Convex Conic Optimization" https://web.stanford.edu/~yyye/nonsymmhsdimp.pdf page 15. + +template +bool NonSymmetricIPM::terminate_infeasible_wrapper() { + +// Eigen::internal::set_is_malloc_allowed(true); + bool result = terminate_infeasible(); +// Eigen::internal::set_is_malloc_allowed(false); + return result; +} + +template +bool NonSymmetricIPM::terminate_infeasible() { + + //TODO: Figure out if initialization scaling (delta) should influence the termination criteria. + + //Primal feasibility + IPMDouble const ipm_1 = IPMDouble(1.); + IPMDouble const primal_error = (A * x - b * tau).template lpNorm(); + IPMDouble const A_norm = A.template lpNorm(); + IPMDouble const b_norm = b.template lpNorm(); + if (primal_error > _epsilon * std::max(ipm_1, A_norm + b_norm)) { + return false; + } + + IPMDouble const dual_error = (A.transpose() * y + s - c * tau).template lpNorm(); + IPMDouble const c_norm = c.template lpNorm(); + //Dual feasibility + if (dual_error > _epsilon * std::max(ipm_1, A_norm + c_norm)) { + return false; + } + + //Duality gap + if (abs(-c.dot(x) + b.dot(y) - kappa) + > + _epsilon * std::max(ipm_1, static_cast(c_norm + b_norm))) { + return false; + }; + + //tiny tau + if (tau > _epsilon * 10e-2 * std::max(ipm_1, kappa)) { + return false; + } + return true; + +} + +template +bool NonSymmetricIPM::terminate() { + return terminate_successfully_wrapper() or terminate_infeasible_wrapper(); +} + + +template +IPMDouble NonSymmetricIPM::centrality() { + + _centrality_timer.start(); + if (_stored_x_centrality == x and _stored_s_centrality == s) { + return _stored_centrality_error; + } + IPMDouble const mu_d = mu(); + _custom_timers[0].start(); + Vector const psi_vec = psi(mu_d); + + _logger->trace("Vector Psi is: {}", psi_vec.transpose()); + + _custom_timers[0].stop(); + _custom_timers[2].start(); + IPMDouble tau_kappa_entry = tau * psi_vec.segment(psi_vec.rows() - 1, 1).sum(); + + Vector LLT_sol = _barrier->llt_L_solve(x, psi_vec.segment(0, psi_vec.rows() - 1)); + Vector err_L(psi_vec.rows()); + err_L << LLT_sol, tau_kappa_entry; + + if (_logger->level() <= spdlog::level::debug) { + ProductBarrier *pb = static_cast * >(_barrier); + auto &segs = pb->get_segments(); + Vector seg_norms(segs.size() + 1); + for (int i = 0; i < segs.size(); i++) { + seg_norms(i) = err_L.segment(segs[i].first, segs[i].second - segs[i].first).norm(); + } + seg_norms(segs.size()) = tau_kappa_entry; + seg_norms /= mu_d; + _logger->info("Segments error norms are: {}", seg_norms.transpose()); + } + + if (_logger->level() <= spdlog::level::trace) { + _logger->trace("Linear system error vector is {}", err_L.transpose()); + } + _custom_timers[2].stop(); + + IPMDouble centr_err_L = err_L.norm() / mu_d; + + _stored_x_centrality = x; + _stored_s_centrality = s; + _stored_centrality_error = centr_err_L; + _centrality_timer.stop(); + return centr_err_L; +} + +template +void NonSymmetricIPM::test_hessian() { + for (int i = 0; i < x.rows(); i++) { + Vector dir = Vector::Zero(x.rows()); + dir(i) = 10e-4; + _logger->debug("Test hessian at: {}", x.transpose()); + _logger->debug("with update {}", dir.transpose()); + Matrix M(2, x.rows()); + M.block(0, 0, 1, x.rows()) = _barrier->gradient(x + dir).transpose() + - _barrier->gradient(x).transpose(); + M.block(1, 0, 1, x.rows()) = (_barrier->hessian(x) * dir).transpose(); + _logger->debug("Comparison of gradient and hessian for {}: \n{}", dir.transpose(), M); + } + return; +}; + diff --git a/src/volesti/include/sos/README.md b/src/volesti/include/sos/README.md new file mode 100644 index 00000000..6c9eef4c --- /dev/null +++ b/src/volesti/include/sos/README.md @@ -0,0 +1,104 @@ +## Sum of Squares optimization + +This subproject implements the algorithm(s) in [1,2,3]. +Supplementary material describing the mathematical theory behind the code can be found [here](http://personal.lse.ac.uk/natura/gsoc2020/supplementary.pdf) + +#### Usage the SOS-solver for Polynomial Envelope problems + +For precise computation of Chebyshev Points and Lagrange Polynomials boost::multiprecision is used. Boost is also +used for the Property Tree in instances and configuration as well as for typecasting templated classes. The boost headers in this project do not contain the needed header files. Please provide the link to the boost files (version 1.67 or higher) via thet `-DBOOST_DIR` flag. Also [spdlog](https://github.com/gabime/spdlog) is used. Please provide link to installed package via `-DSPDLOG_DIR` flag. (The link also provides manuals for installation with various package managers.) +Navigate to the SOS envelope example and and compile: + +``` +cd examples/EnvelopeProblemSOS +cmake -DCMAKE_BUILD_TYPE=Release_double -DBOOST_DIR=your_boost_include_directory -DSPDLOG_DIR=your_spdlog_include_dir . +make +``` + +Run + +``` +./EnvelopeProblemSOS +``` + +See an example plot below. +![image](plot_saved.png "Example envelope") + +To parse a custom file invoke with added file argument: + +``` +./NonSymmetricConicOptimization file.json +``` + +where `file.json` has format + +``` json +{ + "max_degree": 30, + "num_variables": 1, + "polynomials": [ + [1,-1, 3, -4, 7], + [0.5,2, 8, -3, 5] + ] +} + +``` + +Each array in "polynomials" stands for a polynomial. The entries of its array a are the coefficients of the first length(a) coefficients in standard monomial basis or Chebyshev basis. The basis choice can be adjusted in the configuration json file. + + +#### Implemented + +* The generic implementation of the algorithm in [1] with barrier methods for the following cones: + * Non-negative orthant + * SDP cone + * Dual of SOS cone with following bases: + * monomial + * interpolant +* Generation of Lagrange polynomials for Chebyshev points of the second kind +* Tool that approximates the polynomial lower envelope of any (stable to degree approx. 100) set of univariate polynomials +on the interval [-1,1]. +* Visualisation via Matplotlib plot. +* Support for several floating-point precision data types for the IPM +(Primitive Floating Point and boost:multiprecision floats) +* Added multivariate support +* Added multithreading. +* Added LAPACK and BLAS support +* Added MKL Support (system-dependent configuration necessary) +* Dynamically switch between float -> double -> long double -> multiprecision, when Matrices become ill-conditioned. + Note that this feature currenlty only works for the InterpolantDualSOSBarrier, SumBarrier and ProductBarrier. +* Dynamic step-length for predictor step and size of large neighborhood. +* Input and configuration files in JSON format + +#### Roadmap + +* Add switch to use sparse computation (via counting nonzeros in A or other existing funcionality) +* Parameter tuning +* Choose Method for QR Decompositions dynamically. Benchmarks can be found [here](https://eigen.tuxfamily.org/dox/group__DenseDecompositionBenchmark.html). +* Use Eigen funcionality to avoid dynamical heap memory is allocation. +* Implement Lagrange polynomials more efficiently (or find C++ library with sufficient precision.) +* Benchmarking with alfonso, SOSTOOLS, MOSEK (SDP and the new Nonsymmetric cone solver), SeDuMi +* Implementation/inspiration from [4]. In particular interesting are: + * Positive definite rescaling matrix for added stability and monotonicity + * Combining Predictor- and Corrector steps. +* Higher order corrector steps as in [1] +* Write more tests +* Test different central path neighborhoods, e.g. measuring \psi in \infty norm. +* Interfaces for R (and Python) +* Store the gradient and Hessian of the previous iterate (for failing next step in predictor direction) + * Even better: For all the line steps, compute the new predictor/corrector direction. This is cheap, + as we already have gradient and hessian, but might drastically improve performance. +* Fix SDP solver. It returns feasible solutions but no optimal solutions yet. + + +#### References + +[1] A. Skajaa and Y. Ye, [A homogeneous interior-point algorithm for nonsymmetric convex conic optimization](https://web.stanford.edu/~yyye/nonsymmhsdimp.pdf), Mathematical Programming Ser. A, 150 (2015), pp. 391-422. + +[2] D. Papp and S. Yildiz, On “A homogeneous interior-point algorithm for nonsymmetric convex conic optimization”. https://arxiv.org/abs/1712.00492 + +[3] D. Papp and S. Yildiz, [Sum-of-squares optimization without semidefinite programming](https://arxiv.org/abs/1712.01792). SIAM Journal on Optimization 29(1), 2019, pp. 822-851. + +[4] R. Badenbroek and J. Dahl, [An Algorithm for Nonsymmetric Conic Optimization Inspired by MOSEK](https://arxiv.org/pdf/2003.01546.pdf). https://arxiv.org/pdf/2003.01546.pdf + + diff --git a/src/volesti/include/sos/barriers/DualSOSConeStandardBarrier.h b/src/volesti/include/sos/barriers/DualSOSConeStandardBarrier.h new file mode 100644 index 00000000..d4ff9b7d --- /dev/null +++ b/src/volesti/include/sos/barriers/DualSOSConeStandardBarrier.h @@ -0,0 +1,49 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file +#ifndef SOS_DUALSOSCONESTANDARDBARRIER_H +#define SOS_DUALSOSCONESTANDARDBARRIER_H + +#include "LHSCB.h" + +//Implementation for Standard Monomial Basis for the Dual SOS cone. +template +class DualSOSConeStandardBarrier : public LHSCB { + + using LHSCB = LHSCB; + + typedef Vector Vector; + typedef Matrix Matrix; + +public: + DualSOSConeStandardBarrier() : LHSCB() {}; + + DualSOSConeStandardBarrier(unsigned max_polynomial_degree_) : _max_polynomial_degree(max_polynomial_degree_) { + }; + + Vector gradient(Vector x) override; + + Matrix hessian(Vector x) override; + + //Matrix inverse_hessian(Vector x) override; + bool in_interior(Vector x) override; + + IPMDouble concordance_parameter(Vector x) override; + + Vector initialize_x() override; + Vector initialize_s() override; + + //Lambda is the linear operator used in Proposition 1.1 in Papp & Yildiz "SOS + //without semidefinite programming. In particular, for the monomial basis it takes + //the form listed in section 3.1 of the same paper. + Matrix Lambda(Vector x); + +private: + unsigned _max_polynomial_degree; +}; + +#include "DualSOSConeStandardBarrier.hpp" + +#endif //SOS_DUALSOSCONESTANDARDBARRIER_H diff --git a/src/volesti/include/sos/barriers/DualSOSConeStandardBarrier.hpp b/src/volesti/include/sos/barriers/DualSOSConeStandardBarrier.hpp new file mode 100644 index 00000000..7e4f5106 --- /dev/null +++ b/src/volesti/include/sos/barriers/DualSOSConeStandardBarrier.hpp @@ -0,0 +1,81 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#include "DualSOSConeStandardBarrier.h" + + +template +Vector DualSOSConeStandardBarrier::gradient(Vector x) { + assert(in_interior(x)); + Matrix X = Lambda(x); + Matrix Z = X.inverse(); + Vector g(x.rows()); + for (int i = 0; i < g.rows(); ++i) { + Matrix E_i = Matrix::Zero(X.rows(), X.cols()); + for (int j = 0; j <= i; ++j) { + E_i(j, i - j) = 1; + } + g(i) = -Z.cwiseProduct(E_i).sum(); + } + return g; +} + +template +Matrix DualSOSConeStandardBarrier::hessian(Vector x) { + assert(in_interior(x)); + Matrix X = Lambda(x); + Matrix Z = X.inverse(); + Matrix H = Matrix::Zero(Z.rows(), Z.cols()); + for (int u = 0; u < H.rows(); ++u) { + for (int v = 0; v < H.cols(); ++v) { + IPMDouble H_uv = 0; + for (int a = 0; a <= u; ++a) { + for (int k = 0; k <= v; ++k) { + H_uv += Z(a, u - a) + Z(k, v - k); + } + } + } + } + return H; +} + +template +bool DualSOSConeStandardBarrier::in_interior(Vector x) { + Matrix X = Lambda(x); + CustomLLT llt_check; + llt_check.compute(X); + return llt_check.info() != Eigen::NumericalIssue; +} + +template +IPMDouble DualSOSConeStandardBarrier::concordance_parameter(Vector x) { + return x.rows(); +} + +template +Vector DualSOSConeStandardBarrier::initialize_x() { + //TODO: find centered initialization + return Vector(); +} + +template +Vector DualSOSConeStandardBarrier::initialize_s() { + //TODO: find centered initialization + return Vector(); +} + +template +Matrix DualSOSConeStandardBarrier::Lambda(Vector x) { + assert(x.rows() == _max_polynomial_degree + 1); + Matrix M(_max_polynomial_degree + 1, _max_polynomial_degree + 1); + for (unsigned i = 0; i < _max_polynomial_degree + 1; ++i) { + for (unsigned j = 0; j < _max_polynomial_degree + 1; ++j) { + M(i, j) = x(i + j); + } + } + return M; +} + diff --git a/src/volesti/include/sos/barriers/FullSpaceBarrier.h b/src/volesti/include/sos/barriers/FullSpaceBarrier.h new file mode 100644 index 00000000..c8221a88 --- /dev/null +++ b/src/volesti/include/sos/barriers/FullSpaceBarrier.h @@ -0,0 +1,46 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef SOS_FULLSPACEBARRIER_H +#define SOS_FULLSPACEBARRIER_H + +#include "LHSCB.h" + +template +class FullSpaceBarrier final : public LHSCB{ + + using LHSCB = LHSCB; + + typedef Vector Vector; + typedef Matrix Matrix; + + Vector gradient(Vector x) override; + + Matrix hessian(Vector x) override; + + Matrix inverse_hessian(Vector x) override; + + bool in_interior(Vector x) override; + + Matrix llt_solve(Vector x, const Matrix &rhs) override; + Vector llt_L_solve(Vector x, Vector rhs) override; + + + IPMDouble concordance_parameter(Vector x) override; + + Vector initialize_x() override; + + Vector initialize_s() override; + +public: + FullSpaceBarrier(unsigned num_variables_) { + this->_num_variables = num_variables_; + } +}; + +#include "FullSpaceBarrier.hpp" + +#endif //SOS_FULLSPACEBARRIER_H diff --git a/src/volesti/include/sos/barriers/FullSpaceBarrier.hpp b/src/volesti/include/sos/barriers/FullSpaceBarrier.hpp new file mode 100644 index 00000000..3e0f29cb --- /dev/null +++ b/src/volesti/include/sos/barriers/FullSpaceBarrier.hpp @@ -0,0 +1,66 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#include "FullSpaceBarrier.h" + +//This barrier function exists for convenience. +//An instance should be reformulated instead of using the barrier. + +template +Vector FullSpaceBarrier::gradient(Vector) { + return Vector::Zero(this->_num_variables); +} + +template +Matrix FullSpaceBarrier::hessian(Vector) { + return Matrix::Zero(this->_num_variables, this->_num_variables); +} + +template +Matrix FullSpaceBarrier::inverse_hessian(Vector) { + return Matrix::Zero(this->_num_variables, this->_num_variables); +} + +template +bool FullSpaceBarrier::in_interior(Vector) { + return true; +} + +template +Matrix FullSpaceBarrier::llt_solve(Vector x, const Matrix &rhs) { + if(rhs.norm() > 1e-10){ + spdlog::warn("Exit because RHS of matrix is"); + std::cout << rhs << std::endl; + assert(false); + } + return Matrix::Zero(rhs.rows(),rhs.cols()); +} + +template +Vector FullSpaceBarrier::llt_L_solve(Vector x, Vector rhs) { + if(rhs.norm() > 1e-10){ + spdlog::warn("Exit because RHS of matrix is"); + std::cout << rhs << std::endl; + assert(false); + } + return Vector::Zero(x.rows()); +} + + +template +IPMDouble FullSpaceBarrier::concordance_parameter(Vector) { + return 0; +} + +template +Vector FullSpaceBarrier::initialize_x() { + return Vector::Zero(this->_num_variables); +} + +template +Vector FullSpaceBarrier::initialize_s() { + return Vector::Zero(this->_num_variables); +} diff --git a/src/volesti/include/sos/barriers/InterpolantDualSOSBarrier.h b/src/volesti/include/sos/barriers/InterpolantDualSOSBarrier.h new file mode 100644 index 00000000..76781e52 --- /dev/null +++ b/src/volesti/include/sos/barriers/InterpolantDualSOSBarrier.h @@ -0,0 +1,113 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef SOS_INTERPOLANTDUALSOSBARRIER_H +#define SOS_INTERPOLANTDUALSOSBARRIER_H + +#include "LHSCB.h" + +template +class InterpolantDualSOSBarrier : public LHSCB { + + using LHSCB = LHSCB; + + typedef Vector Vector; + typedef Matrix Matrix; + +public: + InterpolantDualSOSBarrier() : LHSCB() {}; + + InterpolantDualSOSBarrier(unsigned max_polynomial_degree_, unsigned num_variables_ = 1) : + InterpolantDualSOSBarrier(max_polynomial_degree_, Vector::Ones(1), num_variables_) {}; + + InterpolantDualSOSBarrier(unsigned max_polynomial_degree_, Vector poly_g, unsigned num_variable_symbols_ = 1); + + template + InterpolantDualSOSBarrier * cast(){ + InterpolantDualSOSBarrier * int_bar = new InterpolantDualSOSBarrier(); + int_bar->_custom_timers.resize(10); + int_bar->_max_polynomial_degree = _max_polynomial_degree; + int_bar->_num_variable_symbols = _num_variable_symbols; + int_bar->_unisolvent_basis = _unisolvent_basis; + int_bar->_intermediate_matrix = _intermediate_matrix.template cast(); + int_bar->_preintermediate_matrix = _preintermediate_matrix.template cast(); +// int_bar->_intermediate_LLT = _intermediate_LLT.template cast(); + int_bar->_Q = _Q.template cast(); + int_bar->_V = _V.template cast(); + int_bar->_L = _L; + int_bar->_U = _U; + int_bar->_g = _g.template cast(); + int_bar->_g_g_transpose = _g_g_transpose.template cast(); + int_bar->_P = _P.template cast(); + int_bar->use_low_rank_updates = use_low_rank_updates; + + return int_bar; + }; + + bool update_gradient_hessian_LLT(Vector x, bool check_interior_only = false); + + Vector gradient(Vector x) override; + + Matrix hessian(Vector x) override; + + Eigen::LLT llt(Vector x, bool symmetrize = 0) override; + + Matrix inverse_hessian(Vector x) override; + + bool in_interior(Vector x) override; + + //TODO: better solution for implementation concordance parameter; + IPMDouble concordance_parameter(Vector x) override; + + Vector initialize_x() override; + + Vector initialize_s() override; + + std::vector > &get_basis() { + return _unisolvent_basis; + } + + Matrix get_P() { + return _P; + } + + void configure(pt::ptree & config); + + unsigned _max_polynomial_degree; + unsigned _num_variable_symbols; + //Turn _unisolvent_basis into Matrix + std::vector > _unisolvent_basis; + Matrix _intermediate_matrix; + Matrix _preintermediate_matrix; + CustomLLT _intermediate_LLT; + Matrix _Q; + Matrix _V; + unsigned _L, _U; + + //Weighted polynomials + Vector _g; + Matrix _g_g_transpose; + Matrix _P; + + //Usage of Woodburry matrix identity and simple heuristic for choice of variables + //to update. + bool use_low_rank_updates = true; + +private: + + + void compute_V_transpose_V(); + + void construct_univariate(Vector poly_g); + void construct_bivariate(Vector poly_g); + void construct_multivariate(Vector poly_g); + + +}; + +#include "InterpolantDualSOSBarrier.hpp" + +#endif //SOS_INTERPOLANTDUALSOSBARRIER_H diff --git a/src/volesti/include/sos/barriers/InterpolantDualSOSBarrier.hpp b/src/volesti/include/sos/barriers/InterpolantDualSOSBarrier.hpp new file mode 100644 index 00000000..c08046de --- /dev/null +++ b/src/volesti/include/sos/barriers/InterpolantDualSOSBarrier.hpp @@ -0,0 +1,493 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#include "InterpolantDualSOSBarrier.h" +#include +#include "Padua/padua.h" + +template +InterpolantDualSOSBarrier::InterpolantDualSOSBarrier( + unsigned max_polynomial_degree_, Vector poly_g,unsigned num_variable_symbols_) + : _max_polynomial_degree(max_polynomial_degree_), _num_variable_symbols(num_variable_symbols_) { + + //TODO: Check if still true for multivariate case. + assert(poly_g.rows() <= max_polynomial_degree_ + 1); + + this->_custom_timers.resize(10); + + //poly_g.rows() is degree + 1 of the polynomial g; + //TODO: setting _L using the number of rows only works for univariate polynomials + // + + //For now no weights for multivariate case. + assert(_num_variable_symbols == 1 or poly_g == Vector::Ones(poly_g.rows())); + + //TODO: check if type cast is safe. + _L = static_cast(boost::math::binomial_coefficient( + _num_variable_symbols + _max_polynomial_degree + 1 - (unsigned) poly_g.rows(), _num_variable_symbols)); + _U = static_cast(boost::math::binomial_coefficient( + 2 * _max_polynomial_degree + _num_variable_symbols, _num_variable_symbols)); + + _preintermediate_matrix = Matrix(_U, _L); + _intermediate_matrix = Matrix(_L, _L); + _intermediate_LLT = CustomLLT(_L); + _V = Matrix(_L, _U); + _Q = Matrix(_V.cols(), _V.cols()); + + this->_num_variables = _U; + _unisolvent_basis.resize(_U); + + if (_num_variable_symbols == 1) { + construct_univariate(poly_g); + } else if (_num_variable_symbols == 2) { + construct_bivariate(poly_g); + } else { + construct_multivariate(poly_g); + } + +}; + +template +void InterpolantDualSOSBarrier::construct_univariate(Vector poly_g) { + for (unsigned i = 0; i < _unisolvent_basis.size(); ++i) { + BoostDouble cos_i = boost::multiprecision::cos(i * boost::math::constants::pi() / (_U - 1)); + InterpolantDouble dummy_ipm; + InterpolantDouble cos_val = static_cast(cos_i); + _unisolvent_basis[i].push_back(cos_val); + } + + //TODO: Figure out how choice of P could influence condition / stability of maps. + + //Use monomial standard basis to orthogonalize + + //_P is used in the Interior Point Method. Therefore we need to convert the multi-precision + // floating-point into the IPM floating point precision + + //TODO: This interpolant Matrix only needs to be found once and can then be reused; + + //Alternative approach of finding _P via Chebyshev basis + + //TODO:Make this library more precise + + //Computing _g should be done with transformation matrix. + _g = Vector::Zero(_U); + for (int p = 0; p < _U; ++p) { + _g(p) = poly_g(0); + for (int i = 1; i < poly_g.rows(); i++) { + _g(p) += poly_g(i) * pow(_unisolvent_basis[p][0], i).template convert_to(); + } + } + _g_g_transpose = _g * _g.transpose(); + + //TODO: Option to compute cheb_P via InterpolantDouble; + + //TODO: Figure out whether orthogonalization could be done in double precision to speed up initialisation. + spdlog::info("Construct orthogonal interpolant point Matrix P..."); + cxxtimer::Timer orth_timer; + orth_timer.start(); + _P = orthogonal_P_Matrix_library.get(_L,_U); + orth_timer.stop(); + std::cout << "Orthogonalization done in " << orth_timer.count() / 1000. + << " seconds." << std::endl; +} + +//Untested +template +void InterpolantDualSOSBarrier::construct_bivariate(Vector poly_g) { + + //For now no weighted polynomials + assert(poly_g == Vector::Ones(1)); + unsigned const corrected_d = 2 * _max_polynomial_degree + 1 - poly_g.size(); + unsigned const corrected_d_plus_1 = corrected_d + 1; + + double *pd_pts = padua::padua_points(_max_polynomial_degree + 1); + + for (int i = 0; i < _U; i++) { + _unisolvent_basis[i] = {pd_pts[2 * i], pd_pts[2 * i + 1]}; + } + + //Set weight vector _g; + //TODO: do properly for weighted case. + _g = Vector::Ones(_U); + _g_g_transpose = _g * _g.transpose(); + + std::cout << "Construct Matrix P" << std::endl; + + _P.resize(_U, _L); + + //TODO: Make following loops more efficient OR find mathematical theory that simplifies expressions. + unsigned col_idx = 0; + for (int i = 0; i <= _max_polynomial_degree; i++) { + for (int j = 0; j + i <= _max_polynomial_degree; j++) { + Eigen::VectorXd vec_i = Eigen::VectorXd::Zero(i + 1); + vec_i(i) = 1; + Eigen::VectorXd vec_j = Eigen::VectorXd::Zero(j + 1); + vec_j(j) = 1; + ChebTools::ChebyshevExpansion cheb_i(vec_i); + ChebTools::ChebyshevExpansion cheb_j(vec_j); + for (int k = 0; k < _U; k++) { + double first_eval = cheb_i.y_recurrence(static_cast(_unisolvent_basis[k][0])); + double second_eval = cheb_j.y_recurrence(static_cast(_unisolvent_basis[k][1])); + _P(k, col_idx) = first_eval * second_eval; + } + col_idx++; + } + } + + std::cout << "P before orthogonolisation: " << std::endl << _P << std::endl; + + Matrix P_ortho = _P.householderQr().householderQ(); + P_ortho.colwise().hnormalized(); + _P = P_ortho.block(0, 0, _U, _L); + //end +} + +//TODO: test +template +void InterpolantDualSOSBarrier::construct_multivariate(Vector poly_g) { + assert(poly_g == Vector::Ones(1)); + + //Set weight vector _g; + //TODO: do properly for weighted case. + _g = Vector::Ones(_U); + _g_g_transpose = _g * _g.transpose(); + + _P.resize(_U, _L); + + //just for testing. + + int num_candidates = 1; + for (int i = 2 * _max_polynomial_degree + 1 + 1; + i <= 2 * _max_polynomial_degree + _num_variable_symbols + 1; i++) { + num_candidates *= i; + } + + //generate Fekete candidates + + + std::vector > candidates; + std::vector comb_bound; + for (int i = 2 * _max_polynomial_degree + 1; i <= 2 * _max_polynomial_degree + _num_variable_symbols; i++) { + comb_bound.push_back(i); + } + AllCombinationTuple comb_tuple(comb_bound); + + do { + std::vector &cheb_v = comb_tuple.get_combination(); + std::vector cand; + for (int i = 0; i < cheb_v.size(); i++) { + cand.push_back(cos((double) cheb_v[i] * boost::math::constants::pi() / (double) comb_bound[i])); + } + candidates.push_back(cand); + } while (comb_tuple.next()); + + for (auto cand : candidates) { + for (auto c : cand) { + std::cout << c << " "; + } + std::cout << std::endl; + } + + assert(num_candidates == candidates.size()); + + //generate Fekete polynomials + + Matrix candidate_matrix(_U, candidates.size()); + + DegreeTuple dt(_num_variable_symbols, 2 * _max_polynomial_degree); + unsigned tup_idx = 0; + do { + //Compute chebyshev polynomial evaluation + std::vector &tup = dt.get_tuple(); + for (int i = 0; i < candidates.size(); i++) { + double cheb_eval = 1.; + for (int j = 0; j < candidates[i].size(); j++) { + Eigen::VectorXd vec_j = Eigen::VectorXd::Zero(tup[j] + 1); + vec_j(tup[j]) = 1; + ChebTools::ChebyshevExpansion cheb_j(vec_j); + double eval_j = cheb_j.y_recurrence(static_cast(candidates[i][j])); + cheb_eval *= eval_j; + } + candidate_matrix(tup_idx, i) = cheb_eval; + } + tup_idx++; + } while (dt.next_valid()); + + + assert(tup_idx == _U); + +// std::cout << "Candidate matrix is \n"; +// std::cout << candidate_matrix << std::endl; + + + Matrix col_permutated_cand_matrix = candidate_matrix * candidate_matrix.colPivHouseholderQr().colsPermutation(); +// std::cout << "And after permutation \n"; +// std::cout << col_permutated_cand_matrix << std::endl; + _P = col_permutated_cand_matrix.block(0, 0, _U, _U).transpose(); +} + +//For profiling purposes +template +void InterpolantDualSOSBarrier::compute_V_transpose_V() { + _Q.noalias() = _V.transpose() * _V; +} + +template +void InterpolantDualSOSBarrier::configure(pt::ptree & config){ + if(config.find("use_low_rank_updates") != config.not_found()){ + use_low_rank_updates = config.get("use_low_rank_updates"); + } +} + +template +bool InterpolantDualSOSBarrier::update_gradient_hessian_LLT(Vector x, bool check_interior_only) { + + Matrix Q; + CustomLLT LLT; + IPMDouble stored_gx_norm; + Vector new_stored_x; + +// if(!this->_stored_gradients.empty()){ +// //This is to test whether lower ran updates make sense +// +// Vector stored_x = this->_stored_gradients[0].first; +// stored_gx_norm = _g.cwiseProduct(stored_x).norm(); +// Matrix aux(2, x.rows()); +// IPMDouble relative_norm = stored_x.norm()/x.norm(); +//// std::cout << "relative norm is " << relative_norm << std::endl; +// aux << stored_x.transpose(), x.transpose() * relative_norm; +//// std::cout << "aux is\n" << aux << std::endl; +// Vector rel = stored_x.cwiseProduct(x.cwiseInverse()); +// std::vector relative_vec(rel.data(), rel.data() + rel.rows()); +// sort(relative_vec.begin(), relative_vec.end()); +// IPMDouble mean = relative_vec[relative_vec.size() / 2]; +// +// std::cout << "Relative mean vector is: \n"; +// +// unsigned small_variation_count_05 = 0; +// unsigned small_variation_count_01 = 0; +// for(IPMDouble i : relative_vec){ +// i/=mean; +// std::cout << i << ", "; +// if(abs(i-1) < .05){ +// small_variation_count_05++; +// } +// if(abs(i-1) < .01){ +// small_variation_count_01++; +// } +// } +// std::cout << std::endl; +// std::cout << "small variation ratio: " << double(small_variation_count_01) / relative_vec.size() << std::endl; +// std::cout << "small variation ratio: " << double(small_variation_count_05) / relative_vec.size() << std::endl; +// } + + bool do_exact_computation = true; + + if (use_low_rank_updates and not this->_stored_gradients.empty()) { + do_exact_computation = false; + this->_custom_timers[9].start(); + Vector stored_x = this->_stored_gradients[0].first; + stored_gx_norm = _g.cwiseProduct(stored_x).norm(); + Matrix aux(2, x.rows()); + aux << stored_x.transpose(), x.transpose(); + std::cout << "aux is\n" << aux << std::endl; + Vector stored_scaled_gx = _g.cwiseProduct(stored_x).normalized(); + //TODO: Find best scaling to minimize number of adjusted variables. + Vector scaled_gx = _g.cwiseProduct(x).normalized(); + Vector relative = scaled_gx - stored_scaled_gx; + Vector relative_abs = relative.cwiseAbs(); +// TODO: Check if adding diagonal here makes sense. This corresponds to the gradient and the term occurs for the Sherman-Morrison update. + + std::vector relative_vec(relative_abs.data(), relative_abs.data() + relative_abs.rows()); + std::vector relative_vec_sorted = sort_indexes(relative_vec); + std::reverse(relative_vec_sorted.begin(), relative_vec_sorted.end()); + + Q = _Q; + LLT = _intermediate_LLT; + assert(_U == relative_vec_sorted.size()); + + new_stored_x = this->_stored_gradients[0].first; + for (int i = 0; i < _U; i++) { + + //rank one updates + + unsigned idx = relative_vec_sorted[i]; + + //TODO: find good threshold + if (relative_abs(idx) < 1e-10 * relative_abs(relative_vec_sorted[0])) { + std::cout << "break after " << i << " of " << _U << " updates" + << std::endl; + break; + } + + //Q update + new_stored_x(idx) = x(idx); + + this->_custom_timers[7].start(); + Vector P_idx = _P.row(idx).transpose(); + Vector tmp = LLT.solve(P_idx); + Vector rank_one_factor = _P * tmp; + Matrix Q_update = + -rank_one_factor * rank_one_factor.transpose() / + (1 / (stored_gx_norm * relative(idx)) + tmp.dot(P_idx)); +// std::cout << "Q_update normed is \n" << Q_update << std::endl; + + Q += Q_update; + this->_custom_timers[7].stop(); + + //L update + this->_custom_timers[8].start(); + LLT.rankUpdate(P_idx, stored_gx_norm * relative(idx)); + if (LLT.info() == Eigen::NumericalIssue) { + //This means we should either change the order in which we apply the rank-1 updates + //or revert back to exact computation. Currently we just jump to exact computation. + + do_exact_computation = true; + break; + } + this->_custom_timers[8].stop(); + } + this->_custom_timers[9].stop(); + + if (not do_exact_computation) { +// std::cout << "Old matrix LLT \n" << LLT.matrixL().toDenseMatrix() << std::endl; + _intermediate_LLT.copy_and_scale(LLT, sqrt(_g.cwiseProduct(x).norm() / stored_gx_norm)); +// std::cout << "Newly copied matrix \n" +// << _intermediate_LLT.matrixL().toDenseMatrix() / sqrt(_g.cwiseProduct(x).norm() / stored_gx_norm) << std::endl; + _Q = Q * stored_gx_norm / _g.cwiseProduct(x).norm(); + } + } + + if (do_exact_computation) { + new_stored_x = x; + + if(this->_stored_intermediate_LLT.empty()){ + this->_stored_intermediate_LLT.resize(1); + this->_stored_intermediate_LLT[0].first = Vector::Zero(new_stored_x.rows()); + } + + if(this->_stored_intermediate_LLT[0].first == new_stored_x){ + _intermediate_LLT = this->_stored_intermediate_LLT[0].second; + } else { + + this->_custom_timers[1].start(); + _preintermediate_matrix.noalias() = _g.cwiseProduct(x).asDiagonal() * _P; + _intermediate_matrix.noalias() = _P.transpose() * _preintermediate_matrix; + this->_custom_timers[1].stop(); + this->_custom_timers[2].start(); + _intermediate_LLT.compute(_intermediate_matrix); + this->_custom_timers[2].stop(); + + if (_intermediate_LLT.info() == Eigen::NumericalIssue) { + return false; + } else { + this->_stored_intermediate_LLT[0].first = new_stored_x; + this->_stored_intermediate_LLT[0].second = _intermediate_LLT; + } + } + + if (check_interior_only) { + return true; + } + + this->_custom_timers[3].start(); + _V.noalias() = _intermediate_LLT.matrixL().solve(_P.transpose()); + this->_custom_timers[3].stop(); + + //Experiments showed that using the triangularView instead would slow down the program. + //So we use the full Matrix _V to compute its product with the transpose. + + this->_custom_timers[4].start(); + compute_V_transpose_V(); + this->_custom_timers[4].stop(); + } + + //TODO: store hessian as self-adjoint + if (this->_stored_hessians.empty()) { + this->_stored_hessians.resize(1); + } + this->_stored_hessians[0].first = new_stored_x; + this->_stored_hessians[0].second.noalias() = _g_g_transpose.cwiseProduct(_Q.cwiseProduct(_Q)); + + if (this->_stored_gradients.empty()) { + this->_stored_gradients.resize(1); + } + this->_stored_gradients[0].first = new_stored_x; + this->_stored_gradients[0].second.noalias() = -_Q.diagonal().cwiseProduct(_g); + + if (this->_stored_LLT.empty()) { + this->_stored_LLT.resize(1); + } + + this->_stored_LLT[0].first = new_stored_x; + this->_custom_timers[5].start(); + this->_stored_LLT[0].second.compute(this->_stored_hessians[0].second.template selfadjointView()); + this->_custom_timers[5].stop(); + + return true; +} + +template +Vector InterpolantDualSOSBarrier::gradient(Vector x) { + auto *grad_ptr = this->find_gradient(x); + if (grad_ptr) { + return *grad_ptr; + } + update_gradient_hessian_LLT(x); + return this->_stored_gradients[0].second; +} + +template +Matrix InterpolantDualSOSBarrier::hessian(Vector x) { + auto *hess_ptr = this->find_hessian(x); + if (hess_ptr) { + return *hess_ptr; + } + update_gradient_hessian_LLT(x); + return this->_stored_hessians[0].second; +} + +template +Eigen::LLT > InterpolantDualSOSBarrier::llt(Vector x, bool) { + auto *llt_ptr = this->find_LLT(x); + if (llt_ptr) { + return *llt_ptr; + } + update_gradient_hessian_LLT(x); + return this->_stored_LLT[0].second; +} + +//Should not be invoked as it is slow. +template +Matrix InterpolantDualSOSBarrier::inverse_hessian(Vector x) { + Matrix L_inv = llt(x).matrixL().toDenseMatrix().inverse(); + Matrix inv = L_inv.transpose() * L_inv; + return inv; +} + +template +bool InterpolantDualSOSBarrier::in_interior(Vector x) { + //The computational effort to calculate whether x is in the interior is nearly as high + //as computing gradient and hessian. Therefore we just calculate them here as well. + bool check_interior_only = true; + return update_gradient_hessian_LLT(x, check_interior_only); +} + +template +IPMDouble InterpolantDualSOSBarrier::concordance_parameter(Vector) { + return _L; +} + +template +Vector InterpolantDualSOSBarrier::initialize_x() { + return Vector::Ones(_U); +} + +template +Vector InterpolantDualSOSBarrier::initialize_s() { + return -gradient(initialize_x()); +} + diff --git a/src/volesti/include/sos/barriers/LHSCB.h b/src/volesti/include/sos/barriers/LHSCB.h new file mode 100644 index 00000000..dd19d099 --- /dev/null +++ b/src/volesti/include/sos/barriers/LHSCB.h @@ -0,0 +1,89 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef SOS_LHSCB_H +#define SOS_LHSCB_H + +#include +#include +#include "utils.h" + +template +class LHSCB { + typedef Vector Vector; + typedef Matrix Matrix; +public: + LHSCB() : _num_variables(0) {}; + + template + void cast_members_from(const LHSCB & other){ + _num_variables = other._num_variables; + } + + template + LHSCB * cast(){ + LHSCB * lhscb; + (*lhscb).template cast_members_from(*this); + return lhscb; + } + + virtual ~LHSCB() {}; + + virtual Vector gradient(Vector x) = 0; + + virtual Matrix hessian(Vector x) = 0; + + virtual Eigen::LLT llt(Vector x, bool symmetrize = 0); + + virtual Matrix llt_solve(Vector x, const Matrix &rhs); + + virtual Vector llt_L_solve(Vector x, Vector rhs); + + Vector *find_gradient(Vector x); + + Matrix *find_hessian(Vector x); + + Eigen::LLT *find_LLT(Vector x); + + virtual Matrix inverse_hessian(Vector x); + + virtual bool in_interior(Vector x) = 0; + + virtual IPMDouble concordance_parameter(Vector x) = 0; + + virtual Vector initialize_x(IPMDouble parameter) { + return parameter * initialize_x(); + } + + //TODO: figure out if initializing the dual is in general just - 1/mu * g(x) (i.e. whether this is in the dual cone) + virtual Vector initialize_s(IPMDouble parameter) { + return initialize_s() / parameter; + } + + virtual Vector initialize_x() = 0; + + virtual Vector initialize_s() = 0; + + cxxtimer::Timer _in_interior_timer; + std::vector _custom_timers; + + unsigned _num_variables; + + +protected: + + std::vector > _stored_gradients; + std::vector > _stored_hessians; + std::vector > > _stored_LLT; + std::vector > > _stored_intermediate_LLT; + +public: + unsigned int getNumVariables() const; +}; + +#include "LHSCB.hpp" + +#endif //SOS_LHSCB_H diff --git a/src/volesti/include/sos/barriers/LHSCB.hpp b/src/volesti/include/sos/barriers/LHSCB.hpp new file mode 100644 index 00000000..bf209a92 --- /dev/null +++ b/src/volesti/include/sos/barriers/LHSCB.hpp @@ -0,0 +1,89 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#include +#include "LHSCB.h" + +template +Matrix LHSCB::inverse_hessian(Vector x) { + Eigen::LLT LLT = llt(x); + Matrix L_inv = LLT.matrixL().toDenseMatrix().inverse(); + return L_inv.transpose() * L_inv; +} + +template +unsigned int LHSCB::getNumVariables() const { + return _num_variables; +} + +//TODO: use short queue instead. +template +Vector *LHSCB::find_gradient(Vector x) { + for (int i = _stored_gradients.size() - 1; i >= 0; i--) { + if (x == _stored_gradients[i].first) { + return &_stored_gradients[i].second; + } + } + return nullptr; +} + +template +Matrix *LHSCB::find_hessian(Vector x) { + for (int i = _stored_hessians.size() - 1; i >= 0; i--) { + if (x == _stored_hessians[i].first) { + return &_stored_hessians[i].second; + } + } + return nullptr; +} + +template +Eigen::LLT > *LHSCB::find_LLT(Vector x) { + for (int i = _stored_LLT.size() - 1; i >= 0; i--) { + if (x == _stored_LLT[i].first) { + return &_stored_LLT[i].second; + } + } + return nullptr; +} + +template +Eigen::LLT > LHSCB::llt(Vector x, bool symmetrize) { + + Eigen::LLT *llt_ptr = nullptr; + + if (not symmetrize) { + llt_ptr = find_LLT(x); + } + + if (llt_ptr) { + return *llt_ptr; + } + + if (_stored_LLT.empty()) { + _stored_LLT.resize(1); + } + + _stored_LLT[0].first = x; + if (not symmetrize) { + _stored_LLT[0].second = Eigen::LLT(hessian(x).llt()); + } else { + Matrix hess_tmp = hessian(x); + _stored_LLT[0].second = Eigen::LLT(((hess_tmp+ hess_tmp.transpose()) / 2).llt()); + } + + return _stored_LLT[0].second; +} + +template +Matrix LHSCB::llt_solve(Vector x, const Matrix &rhs) { + return llt(x).solve(rhs); +} + +template +Vector LHSCB::llt_L_solve(Vector x, Vector rhs) { + return llt(x).matrixL().solve(rhs); +} diff --git a/src/volesti/include/sos/barriers/LPStandardBarrier.h b/src/volesti/include/sos/barriers/LPStandardBarrier.h new file mode 100644 index 00000000..a0de7a48 --- /dev/null +++ b/src/volesti/include/sos/barriers/LPStandardBarrier.h @@ -0,0 +1,47 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef SOS_LPSTANDARDBARRIER_H +#define SOS_LPSTANDARDBARRIER_H + +#include "LHSCB.h" + +template +class LPStandardBarrier final : public LHSCB { + using LHSCB = LHSCB; + + typedef Vector Vector; + typedef Matrix Matrix; + +public: + LPStandardBarrier() : LHSCB() {}; + + LPStandardBarrier(unsigned num_variables_) { + this->_num_variables = num_variables_; + }; + + Vector gradient(Vector x) override; + + Matrix hessian(Vector x) override; + + Matrix inverse_hessian(Vector x) override; + + bool in_interior(Vector x) override; + + //TODO: better solution for concordance parameter; + IPMDouble concordance_parameter(Vector x) override; + + Vector initialize_x() override; + + Vector initialize_s() override; + +private: + +}; + +#include "LPStandardBarrier.hpp" + +#endif //SOS_LPSTANDARDBARRIER_H diff --git a/src/volesti/include/sos/barriers/LPStandardBarrier.hpp b/src/volesti/include/sos/barriers/LPStandardBarrier.hpp new file mode 100644 index 00000000..9a6815a9 --- /dev/null +++ b/src/volesti/include/sos/barriers/LPStandardBarrier.hpp @@ -0,0 +1,43 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#include "LPStandardBarrier.h" + +template +bool LPStandardBarrier::in_interior(Vector x) { + return (x.minCoeff() > 0); +} + +template +Vector LPStandardBarrier::gradient(Vector x) { + assert(in_interior(x)); + return -x.array().inverse(); +} + +template +Matrix LPStandardBarrier::hessian(Vector x) { + return x.array().pow(2).inverse().matrix().asDiagonal(); +} + +template +Matrix LPStandardBarrier::inverse_hessian(Vector x) { + return hessian(x).inverse(); +} + +template +IPMDouble LPStandardBarrier::concordance_parameter(Vector x) { + return x.rows(); +} + +template +Vector LPStandardBarrier::initialize_x() { + return Vector::Ones(this->_num_variables); +} + +template +Vector LPStandardBarrier::initialize_s() { + return Vector::Ones(this->_num_variables); +} diff --git a/src/volesti/include/sos/barriers/ProductBarrier.h b/src/volesti/include/sos/barriers/ProductBarrier.h new file mode 100644 index 00000000..be5e4329 --- /dev/null +++ b/src/volesti/include/sos/barriers/ProductBarrier.h @@ -0,0 +1,113 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef SOS_PRODUCTBARRIER_H +#define SOS_PRODUCTBARRIER_H + +#include "LHSCB.h" +#include "SumBarrier.h" +#include "InterpolantDualSOSBarrier.h" + + +template +class ProductBarrier : public LHSCB { + + typedef Vector Vector; + typedef Matrix Matrix; + + typedef Vector (LHSCB::*VectorFunc)(Vector); + typedef Matrix (LHSCB::*MatrixFunc)(Vector); + typedef Vector (LHSCB::*VoidFunc)(); + +public: + ProductBarrier(unsigned num_threads = 1) : LHSCB() { + _num_threads = num_threads; + } + + ProductBarrier(std::vector *> barriers_, std::vector num_variables_, unsigned num_threads = 1) { + assert(barriers_.size() == num_variables_.size()); + _num_threads = num_threads; + for (unsigned j = 0; j < barriers_.size(); ++j) { + _barriers.push_back(barriers_[j]); + _num_vars_per_barrier.push_back(num_variables_[j]); + } + } + + template + ProductBarrier * cast(){ + ProductBarrier * pb = new ProductBarrier(); + pb->_num_threads = _num_threads; + pb->_segments = _segments; + for(LHSCB * barrier : _barriers){ + //TODO: High priority. Figure out how to undo the cast to the SumBarrier. + LHSCB * new_barr; + if(dynamic_cast*>(barrier)){ + new_barr = static_cast*>(barrier)->template cast(); + } else { + if(dynamic_cast*>(barrier)){ + new_barr = static_cast*>(barrier)->template cast(); + } else { + exit(1); + } + } + pb->add_barrier(new_barr); + } + return pb; + }; + + void add_barrier(LHSCB *lhscb) { + _barriers.push_back(lhscb); + _num_vars_per_barrier.push_back(lhscb->getNumVariables()); + this->_num_variables += lhscb->getNumVariables(); + } + + Vector gradient(Vector x) override; + + Matrix hessian(Vector x) override; + +// Matrix inverse_hessian(Vector x) override; + bool in_interior(Vector x) override; + + //TODO: better solution for concordance parameter; + IPMDouble concordance_parameter(Vector x) override; + + Vector initialize_x() override; + + Vector initialize_s() override; + + Eigen::LLT llt(Vector x, bool symmetrize = 0) override; + + Matrix llt_solve(Vector x, const Matrix &rhs) override; + + Vector llt_L_solve(Vector x, Vector rhs) override; + + Matrix inverse_hessian(Vector x) override; + + void update_segments(); + std::vector > & get_segments(){ + return _segments; + }; + + Vector evaluate(Vector x, VectorFunc func); + Matrix evaluate(Vector x, MatrixFunc func); + Vector evaluate(VoidFunc func); + + std::vector *> & get_barriers(){ + return _barriers; + } + + std::vector *> _barriers; + std::vector > _segments; + std::vector _num_vars_per_barrier; + unsigned _num_threads; + +private: + +}; + +#include "ProductBarrier.hpp" + +#endif //SOS_PRODUCTBARRIER_H diff --git a/src/volesti/include/sos/barriers/ProductBarrier.hpp b/src/volesti/include/sos/barriers/ProductBarrier.hpp new file mode 100644 index 00000000..722a0c47 --- /dev/null +++ b/src/volesti/include/sos/barriers/ProductBarrier.hpp @@ -0,0 +1,173 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + + +#include "ProductBarrier.h" + +//TODO: Figure out how to compute llt decomposition with diagonal blocks. There +// does not seem to be a straightforward way with Eigen. +template +Eigen::LLT > ProductBarrier::llt(Vector x, bool symmetrize) { + return LHSCB::llt(x, symmetrize); +} + +//TODO: figure out how these methods can be even more abstracted. +template +Vector ProductBarrier::llt_L_solve(Vector x, Vector rhs) { + update_segments(); + Vector product_llt_solve(this->_num_variables); +#ifdef PARALLELIZE_BARRIERS +#pragma omp parallel for +#endif + for (unsigned i = 0; i < _barriers.size(); ++i) { + std::pair & seg = _segments[i]; + LHSCB *barrier = _barriers[i]; + Vector x_seg = x.segment(seg.first, seg.second - seg.first); + Vector rhs_seg = rhs.segment(seg.first, seg.second - seg.first); + Vector lls_solve_seg = rhs_seg.nonZeros() ? barrier->llt_L_solve(x_seg, rhs_seg) + : Vector::Zero(rhs_seg.rows()); + product_llt_solve.segment(seg.first , seg.second - seg.first) = lls_solve_seg; + } + return product_llt_solve; +} + +template +Matrix ProductBarrier::llt_solve(Vector x, const Matrix &rhs) { + update_segments(); + Matrix product_llt_solve = Matrix::Zero(rhs.rows(), rhs.cols()); +#ifdef PARALLELIZE_BARRIERS +#pragma omp parallel for +#endif + for (unsigned i = 0; i < _barriers.size(); ++i) { + std::pair & seg = _segments[i]; + LHSCB *barrier = _barriers[i]; + Vector x_seg = x.segment(seg.first, seg.second - seg.first); + Matrix rhs_block = rhs.block(seg.first, 0, seg.second-seg.first, rhs.cols()); + Matrix lls_solve_block = barrier->llt_solve(x_seg, rhs_block); + product_llt_solve.block(seg.first, 0, seg.second - seg.first, rhs.cols()) = lls_solve_block; + } + return product_llt_solve; +} + +//Should not be used, as immense storage is needed. +template +Matrix ProductBarrier::hessian(Vector x) { + return evaluate(x, &LHSCB::hessian); +} + +template +bool ProductBarrier::in_interior(Vector x) { + this->_in_interior_timer.start(); + update_segments(); + std::vector in_interior_vec(_barriers.size()); +#ifdef PARALLELIZE_BARRIERS +#pragma omp parallel for +#endif + for (unsigned i = 0; i < _barriers.size(); ++i) { + std::pair & seg = _segments[i]; + LHSCB *barrier = _barriers[i]; + Vector x_seg = x.segment(seg.first, seg.second - seg.first); + in_interior_vec[i] = barrier->in_interior(x_seg); + } + this->_in_interior_timer.stop(); + return std::all_of(in_interior_vec.begin(), in_interior_vec.end(), [](bool b){return b;}); +} + +template +IPMDouble ProductBarrier::concordance_parameter(Vector x) { + unsigned idx = 0; + IPMDouble concordance_par = 0; + for (unsigned i = 0; i < _barriers.size(); ++i) { + LHSCB *barrier = _barriers[i]; + unsigned num_variables = _num_vars_per_barrier[i]; + Vector v_segment = x.segment(idx, num_variables); + concordance_par += barrier->concordance_parameter(v_segment); + idx += num_variables; + } + return concordance_par; +} + +template +Vector ProductBarrier::initialize_x() { + return evaluate(&LHSCB::initialize_x); +} + +template +Vector ProductBarrier::initialize_s() { + return evaluate(&LHSCB::initialize_s); +} + +template +Matrix ProductBarrier::inverse_hessian(Vector x) { + return evaluate(x, &LHSCB::inverse_hessian); +} + +template +Vector ProductBarrier::gradient(Vector x) { + return evaluate(x, &LHSCB::gradient); +} + +template +void ProductBarrier::update_segments() { + _segments.resize(0); + unsigned idx = 0; + std::vector > segments; + for (unsigned i = 0; i < _barriers.size(); ++i) { + unsigned num_variables = _num_vars_per_barrier[i]; + _segments.push_back(std::pair(idx, idx + num_variables)); + idx += num_variables; + } +} + +template +Vector ProductBarrier::evaluate(Vector x, VectorFunc func) { + update_segments(); + Vector product_vector(this->_num_variables); +#ifdef PARALLELIZE_BARRIERS +#pragma omp parallel for +#endif + for (unsigned i = 0; i < _barriers.size(); ++i) { + std::pair &seg = _segments[i]; + LHSCB *barrier = _barriers[i]; + Vector x_seg = x.segment(seg.first, seg.second - seg.first); + Vector vec_seg = (barrier->*func)(x_seg); + product_vector.segment(seg.first, seg.second - seg.first) = vec_seg; + } + return product_vector; +} + +template +Matrix ProductBarrier::evaluate(Vector x, MatrixFunc func) { + update_segments(); + Matrix product_matrix = Matrix::Zero(this->_num_variables, this->_num_variables); +#ifdef PARALLELIZE_BARRIERS +#pragma omp parallel for +#endif + for (unsigned i = 0; i < _barriers.size(); ++i) { + std::pair &seg = _segments[i]; + LHSCB *barrier = _barriers[i]; + Vector x_seg = x.segment(seg.first, seg.second - seg.first); + Matrix matrix_block = (barrier->*func)(x_seg); + product_matrix.block(seg.first, seg.first, seg.second - seg.first, seg.second - seg.first) = matrix_block; + } + return product_matrix; +} + +template +Vector ProductBarrier::evaluate(VoidFunc func) { + update_segments(); + Vector product_vector(this->_num_variables); +#ifdef PARALLELIZE_BARRIERS +#pragma omp parallel for +#endif + for (unsigned i = 0; i < _barriers.size(); ++i) { + std::pair &seg = _segments[i]; + LHSCB *barrier = _barriers[i]; + Vector vec_seg = (barrier->*func)(); + product_vector.segment(seg.first, seg.second - seg.first) = vec_seg; + } + return product_vector; +} diff --git a/src/volesti/include/sos/barriers/SumBarrier.h b/src/volesti/include/sos/barriers/SumBarrier.h new file mode 100644 index 00000000..7252cab5 --- /dev/null +++ b/src/volesti/include/sos/barriers/SumBarrier.h @@ -0,0 +1,59 @@ +// +// Created by test Bento Natura on 30/07/2020. +// + +#ifndef SOS_SUMBARRIER_H +#define SOS_SUMBARRIER_H + +#include "LHSCB.h" +#include "InterpolantDualSOSBarrier.h" + +//corresponds to intersection of cones. +template +class SumBarrier : public LHSCB { + + typedef Vector Vector; + typedef Matrix Matrix; + +public: + SumBarrier(unsigned num_variables_); + + SumBarrier(std::vector *> barriers_, unsigned num_variables_); + + template + SumBarrier * cast(){ + SumBarrier * sb = new SumBarrier(this->_num_variables); + for(LHSCB * barrier : _barriers){ + //TODO: High priority. Figure out how to undo the cast to the InterpolantBarrier. + InterpolantDualSOSBarrier * new_barr = static_cast*>(barrier)->template cast(); +// LHSCB * new_barr = (*barrier).template cast(); + sb->add_barrier(new_barr); + } + return sb; + }; + + void add_barrier(LHSCB *lhscb); + + Vector gradient(Vector x) override; + + Matrix hessian(Vector x) override; + + bool in_interior(Vector x) override; + + //TODO: better solution for concordance parameter; + IPMDouble concordance_parameter(Vector x) override; + + //TODO: verify that initialisation works for both primal and dual side in general. + Vector initialize_x() override; + + Vector initialize_s() override; + + std::vector *> & get_barriers(){ + return _barriers; + } +private: + std::vector *> _barriers; +}; + +#include "SumBarrier.hpp" +#endif //SOS_SUMBARRIER_H diff --git a/src/volesti/include/sos/barriers/SumBarrier.hpp b/src/volesti/include/sos/barriers/SumBarrier.hpp new file mode 100644 index 00000000..40cb48ba --- /dev/null +++ b/src/volesti/include/sos/barriers/SumBarrier.hpp @@ -0,0 +1,101 @@ +// +// Created by test Bento Natura on 30/07/2020. +// + +#include "SumBarrier.h" + +template +SumBarrier::SumBarrier(unsigned num_variables_) : LHSCB() { + this->_num_variables = num_variables_; +} + +template +SumBarrier::SumBarrier(std::vector *> barriers_, unsigned num_variables_) { + this->_num_variables = num_variables_; + for (unsigned j = 0; j < barriers_.size(); ++j) { + assert(barriers_[j]->getNumVariables() == num_variables_); + _barriers.push_back(barriers_[j]); + } +} + +template +void SumBarrier::add_barrier(LHSCB *lhscb) { + _barriers.push_back(lhscb); +} + +template +Vector SumBarrier::gradient(Vector x) { + Vector grad_vec = Vector::Zero(x.rows()); + std::vector barrier_vec(_barriers.size()); +#ifdef PARALLELIZE_BARRIERS +#pragma omp parallel for +#endif + for (int i = 0; i < _barriers.size(); i++){ + barrier_vec[i] = _barriers[i]->gradient(x); + } + for(int i = 0; i < _barriers.size(); i++){ + grad_vec += barrier_vec[i]; + } + return grad_vec; +} + +template +Matrix SumBarrier::hessian(Vector x) { + Matrix hess_mat = Matrix::Zero(x.rows(), x.rows()); + std::vector barrier_vec(_barriers.size()); +#ifdef PARALLELIZE_BARRIERS +#pragma omp parallel for +#endif + for (int i = 0; i < _barriers.size(); i++){ + barrier_vec[i] = _barriers[i]->hessian(x); + } + for(int i = 0; i < _barriers.size(); i++){ + hess_mat += barrier_vec[i]; + } + + return hess_mat; +} + +template +bool SumBarrier::in_interior(Vector x) { + std::vector barrier_vec(_barriers.size()); +#pragma omp parallel for + for (int i = 0; i < _barriers.size(); i++){ + barrier_vec[i] = _barriers[i]->in_interior(x); + } + return std::all_of(barrier_vec.begin(), barrier_vec.end(), [](bool b){return b;}); +} + +//TODO: better solution for concordance parameter; +template +IPMDouble SumBarrier::concordance_parameter(Vector x) { + IPMDouble conc_par = 0.; + for ( + auto barrier + : _barriers) { + conc_par += barrier-> + concordance_parameter(x); + } + return conc_par; +} + +//TODO: verify that initialisation works for both primal and dual side in general. +template +Vector SumBarrier::initialize_x() { + assert(!_barriers.empty()); + Vector rs = _barriers[0]->initialize_x(); + return rs; + +} + +template +Vector SumBarrier::initialize_s() { + Vector s_init = Vector::Zero(this->_num_variables); + for ( + auto barrier + : _barriers) { + s_init += barrier->initialize_s(); + } + return s_init; +} + diff --git a/src/volesti/include/sos/barriers/ZeroSpaceBarrier.h b/src/volesti/include/sos/barriers/ZeroSpaceBarrier.h new file mode 100644 index 00000000..e2a11929 --- /dev/null +++ b/src/volesti/include/sos/barriers/ZeroSpaceBarrier.h @@ -0,0 +1,44 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef SOS_ZEROSPACEBARRIER_H +#define SOS_ZEROSPACEBARRIER_H + +#include "LHSCB.h" + +//This class models the 0-cone. Should not be used, so reformulate your +//instance. +template +class ZeroSpaceBarrier final : public LHSCB { + + using LHSCB = LHSCB; + + typedef Vector Vector; + typedef Matrix Matrix; + + Vector gradient(Vector x) override; + + Matrix hessian(Vector x) override; + + Matrix inverse_hessian(Vector x) override; + + bool in_interior(Vector x) override; + + IPMDouble concordance_parameter(Vector x) override; + + Vector initialize_x() override; + + Vector initialize_s() override; + +public: + ZeroSpaceBarrier(unsigned num_variables_) { + this->_num_variables = num_variables_; + } +}; + +#include "ZeroSpaceBarrier.hpp" + +#endif //SOS_ZEROSPACEBARRIER_H diff --git a/src/volesti/include/sos/barriers/ZeroSpaceBarrier.hpp b/src/volesti/include/sos/barriers/ZeroSpaceBarrier.hpp new file mode 100644 index 00000000..1381fbcd --- /dev/null +++ b/src/volesti/include/sos/barriers/ZeroSpaceBarrier.hpp @@ -0,0 +1,46 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#include "ZeroSpaceBarrier.h" + +template +Vector ZeroSpaceBarrier::gradient(Vector x) { + //should not be used + assert(false); + return -std::numeric_limits::infinity() * Vector::Ones(x.cols()); +} + +template +Matrix ZeroSpaceBarrier::hessian(Vector x) { + //should not be used + assert(false); + return std::numeric_limits::infinity() * Matrix::Identity(x.cols(), x.cols()); +} + +template +Matrix ZeroSpaceBarrier::inverse_hessian(Vector) { + return Matrix::Zero(this->_num_variables, this->_num_variables); +} + +template +bool ZeroSpaceBarrier::in_interior(Vector) { + return true; +} + +template +IPMDouble ZeroSpaceBarrier::concordance_parameter(Vector) { + return 0; +} + +template +Vector ZeroSpaceBarrier::initialize_x() { + return Vector::Zero(this->_num_variables); +} + +template +Vector ZeroSpaceBarrier::initialize_s() { + return Vector::Zero(this->_num_variables); +} diff --git a/src/volesti/include/sos/config/config.json b/src/volesti/include/sos/config/config.json new file mode 100644 index 00000000..f3b150f5 --- /dev/null +++ b/src/volesti/include/sos/config/config.json @@ -0,0 +1,21 @@ +{ + "IPM": { + "epsilon": 1e-6, + "num_corrector_steps": 4, + "large_neighborhood": 5, + "small_neighborhood": 0.05, + "scale_predictor_step": 1, + "length_corrector_step": 1, + "logger_level": 2, + "use_line_search": true, + "check_centrality_in_every_segment": true, + "type_cast_if_unsuccessful": true + }, + "InterpolantBarrier": { + "use_low_rank_updates": false + }, + + "use_weighted_polynomials": true, + "input_in_interpolant_basis": true, + "plot_threshold": 20 +} diff --git a/src/volesti/include/sos/gsoc_history.md b/src/volesti/include/sos/gsoc_history.md new file mode 100644 index 00000000..df4354de --- /dev/null +++ b/src/volesti/include/sos/gsoc_history.md @@ -0,0 +1,15 @@ +#### Weekly Progress + +* Week 1: Implementation of [1,2]. +* Week 2: Testing implementation with LP and SDP barrier, Debugging, Refactoring. +* Week 3: Implementation of Interpolant-Barrier, Focus on speeding up implementation with dedicated libraries. +* Week 4: Implementation of Toy Problem: Polynomial Envelope. +* Week 5: Debugging, Refactoring Code. +* Week 6: Debugging, Refactoring Code, Speeding up initialisation of "Polynomial Envelope" Problem + (non-trivial: properties of Chebyshev polynomials, Clenshaw-Curtis algorithm) + * Week 7: Runtime speedups, more efficient (including experimentally) implementation. + * Week 8: Further speedup, Big code refactoring, Add Polynomial minimization, Create Benchmarks with alfonso. + * Week 9: Writing of documentation / report, more refactoring, add multivariate support. + * Week 10: Improve multivariate support, Added multithreading. + * Week 11: Added low-rank updates to both Hessian and intermediate Cholesky decomposition, templating algorithm. + * Week 12: MKL Support, JSON parsing of configuration, templating algorithm diff --git a/src/volesti/include/sos/include/cxxtimer.hpp b/src/volesti/include/sos/include/cxxtimer.hpp new file mode 100644 index 00000000..c3f0b722 --- /dev/null +++ b/src/volesti/include/sos/include/cxxtimer.hpp @@ -0,0 +1,184 @@ +/* + +MIT License + +Copyright (c) 2017 André L. Maravilha + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +#ifndef CXX_TIMER_HPP +#define CXX_TIMER_HPP + +#include + + +namespace cxxtimer { + +/** + * This class works as a stopwatch. + */ +class Timer { + +public: + + /** + * Constructor. + * + * @param start + * If true, the timer is started just after construction. + * Otherwise, it will not be automatically started. + */ + Timer(bool start = false); + + /** + * Copy constructor. + * + * @param other + * The object to be copied. + */ + Timer(const Timer& other) = default; + + /** + * Transfer constructor. + * + * @param other + * The object to be transfered. + */ + Timer(Timer&& other) = default; + + /** + * Destructor. + */ + virtual ~Timer() = default; + + /** + * Assignment operator by copy. + * + * @param other + * The object to be copied. + * + * @return A reference to this object. + */ + Timer& operator=(const Timer& other) = default; + + /** + * Assignment operator by transfer. + * + * @param other + * The object to be transferred. + * + * @return A reference to this object. + */ + Timer& operator=(Timer&& other) = default; + + /** + * Start/resume the timer. + */ + void start(); + + /** + * Stop/pause the timer. + */ + void stop(); + + /** + * Reset the timer. + */ + void reset(); + + /** + * Return the elapsed time. + * + * @param duration_t + * The duration type used to return the time elapsed. If not + * specified, it returns the time as represented by + * std::chrono::milliseconds. + * + * @return The elapsed time. + */ + template + typename duration_t::rep count() const; + +private: + + bool started_; + bool paused_; + std::chrono::steady_clock::time_point reference_; + std::chrono::duration accumulated_; +}; + +} + + +inline cxxtimer::Timer::Timer(bool start) : + started_(false), paused_(false), + reference_(std::chrono::steady_clock::now()), + accumulated_(std::chrono::duration(0)) { + if (start) { + this->start(); + } +} + +inline void cxxtimer::Timer::start() { + if (!started_) { + started_ = true; + paused_ = false; + accumulated_ = std::chrono::duration(0); + reference_ = std::chrono::steady_clock::now(); + } else if (paused_) { + reference_ = std::chrono::steady_clock::now(); + paused_ = false; + } +} + +inline void cxxtimer::Timer::stop() { + if (started_ && !paused_) { + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + accumulated_ = accumulated_ + std::chrono::duration_cast< std::chrono::duration >(now - reference_); + paused_ = true; + } +} + +inline void cxxtimer::Timer::reset() { + if (started_) { + started_ = false; + paused_ = false; + reference_ = std::chrono::steady_clock::now(); + accumulated_ = std::chrono::duration(0); + } +} + +template +typename duration_t::rep cxxtimer::Timer::count() const { + if (started_) { + if (paused_) { + return std::chrono::duration_cast(accumulated_).count(); + } else { + return std::chrono::duration_cast( + accumulated_ + (std::chrono::steady_clock::now() - reference_)).count(); + } + } else { + return duration_t(0).count(); + } +} + + +#endif diff --git a/src/volesti/include/sos/include/matplotlib-cpp/matplotlibcpp.h b/src/volesti/include/sos/include/matplotlib-cpp/matplotlibcpp.h new file mode 100644 index 00000000..4b2b241f --- /dev/null +++ b/src/volesti/include/sos/include/matplotlib-cpp/matplotlibcpp.h @@ -0,0 +1,2366 @@ +//The MIT License (MIT) +// +//Copyright (c) 2014 Benno Evers +// +//Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +//in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +//furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +//SOFTWARE. + +//Credit to https://github.com/lava/matplotlib-cpp/ + +#pragma once + +// Python headers must be included before any system headers, since +// they define _POSIX_C_SOURCE +#include + +#include +#include +#include +#include +#include +#include +#include +#include // requires c++11 support +#include + +#ifndef WITHOUT_NUMPY +# define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +# include + +# ifdef WITH_OPENCV +# include +# endif // WITH_OPENCV + +/* + * A bunch of constants were removed in OpenCV 4 in favour of enum classes, so + * define the ones we need here. + */ +# if CV_MAJOR_VERSION > 3 +# define CV_BGR2RGB cv::COLOR_BGR2RGB +# define CV_BGRA2RGBA cv::COLOR_BGRA2RGBA +# endif +#endif // WITHOUT_NUMPY + +#if PY_MAJOR_VERSION >= 3 +# define PyString_FromString PyUnicode_FromString +# define PyInt_FromLong PyLong_FromLong +# define PyString_FromString PyUnicode_FromString +#endif + + +namespace matplotlibcpp { +namespace detail { + +static std::string s_backend; + +struct _interpreter { + PyObject *s_python_function_show; + PyObject *s_python_function_close; + PyObject *s_python_function_draw; + PyObject *s_python_function_pause; + PyObject *s_python_function_save; + PyObject *s_python_function_figure; + PyObject *s_python_function_fignum_exists; + PyObject *s_python_function_plot; + PyObject *s_python_function_quiver; + PyObject *s_python_function_semilogx; + PyObject *s_python_function_semilogy; + PyObject *s_python_function_loglog; + PyObject *s_python_function_fill; + PyObject *s_python_function_fill_between; + PyObject *s_python_function_hist; + PyObject *s_python_function_imshow; + PyObject *s_python_function_scatter; + PyObject *s_python_function_boxplot; + PyObject *s_python_function_subplot; + PyObject *s_python_function_subplot2grid; + PyObject *s_python_function_legend; + PyObject *s_python_function_xlim; + PyObject *s_python_function_ion; + PyObject *s_python_function_ginput; + PyObject *s_python_function_ylim; + PyObject *s_python_function_title; + PyObject *s_python_function_axis; + PyObject *s_python_function_axvline; + PyObject *s_python_function_xlabel; + PyObject *s_python_function_ylabel; + PyObject *s_python_function_gca; + PyObject *s_python_function_xticks; + PyObject *s_python_function_yticks; + PyObject *s_python_function_tick_params; + PyObject *s_python_function_grid; + PyObject *s_python_function_clf; + PyObject *s_python_function_errorbar; + PyObject *s_python_function_annotate; + PyObject *s_python_function_tight_layout; + PyObject *s_python_colormap; + PyObject *s_python_empty_tuple; + PyObject *s_python_function_stem; + PyObject *s_python_function_xkcd; + PyObject *s_python_function_text; + PyObject *s_python_function_suptitle; + PyObject *s_python_function_bar; + PyObject *s_python_function_colorbar; + PyObject *s_python_function_subplots_adjust; + + + /* For now, _interpreter is implemented as a singleton since its currently not possible to have + multiple independent embedded python interpreters without patching the python source code + or starting a separate process for each. + http://bytes.com/topic/python/answers/793370-multiple-independent-python-interpreters-c-c-program + */ + + static _interpreter& get() { + static _interpreter ctx; + return ctx; + } + + PyObject* safe_import(PyObject* module, std::string fname) { + PyObject* fn = PyObject_GetAttrString(module, fname.c_str()); + + if (!fn) + throw std::runtime_error(std::string("Couldn't find required function: ") + fname); + + if (!PyFunction_Check(fn)) + throw std::runtime_error(fname + std::string(" is unexpectedly not a PyFunction.")); + + return fn; + } + +private: + +#ifndef WITHOUT_NUMPY +# if PY_MAJOR_VERSION >= 3 + + void *import_numpy() { + import_array(); // initialize C-API + return NULL; + } + +# else + + void import_numpy() { + import_array(); // initialize C-API + } + +# endif +#endif + + _interpreter() { + + // optional but recommended +#if PY_MAJOR_VERSION >= 3 + wchar_t name[] = L"plotting"; +#else + char name[] = "plotting"; +#endif + Py_SetProgramName(name); + Py_Initialize(); + +#ifndef WITHOUT_NUMPY + import_numpy(); // initialize numpy C-API +#endif + + PyObject* matplotlibname = PyString_FromString("matplotlib"); + PyObject* pyplotname = PyString_FromString("matplotlib.pyplot"); + PyObject* cmname = PyString_FromString("matplotlib.cm"); + PyObject* pylabname = PyString_FromString("pylab"); + if (!pyplotname || !pylabname || !matplotlibname || !cmname) { + throw std::runtime_error("couldnt create string"); + } + + PyObject* matplotlib = PyImport_Import(matplotlibname); + Py_DECREF(matplotlibname); + if (!matplotlib) { + PyErr_Print(); + throw std::runtime_error("Error loading module matplotlib!"); + } + + // matplotlib.use() must be called *before* pylab, matplotlib.pyplot, + // or matplotlib.backends is imported for the first time + if (!s_backend.empty()) { + PyObject_CallMethod(matplotlib, const_cast("use"), const_cast("s"), s_backend.c_str()); + } + + PyObject* pymod = PyImport_Import(pyplotname); + Py_DECREF(pyplotname); + if (!pymod) { throw std::runtime_error("Error loading module matplotlib.pyplot!"); } + + s_python_colormap = PyImport_Import(cmname); + Py_DECREF(cmname); + if (!s_python_colormap) { throw std::runtime_error("Error loading module matplotlib.cm!"); } + + PyObject* pylabmod = PyImport_Import(pylabname); + Py_DECREF(pylabname); + if (!pylabmod) { throw std::runtime_error("Error loading module pylab!"); } + + s_python_function_show = safe_import(pymod, "show"); + s_python_function_close = safe_import(pymod, "close"); + s_python_function_draw = safe_import(pymod, "draw"); + s_python_function_pause = safe_import(pymod, "pause"); + s_python_function_figure = safe_import(pymod, "figure"); + s_python_function_fignum_exists = safe_import(pymod, "fignum_exists"); + s_python_function_plot = safe_import(pymod, "plot"); + s_python_function_quiver = safe_import(pymod, "quiver"); + s_python_function_semilogx = safe_import(pymod, "semilogx"); + s_python_function_semilogy = safe_import(pymod, "semilogy"); + s_python_function_loglog = safe_import(pymod, "loglog"); + s_python_function_fill = safe_import(pymod, "fill"); + s_python_function_fill_between = safe_import(pymod, "fill_between"); + s_python_function_hist = safe_import(pymod,"hist"); + s_python_function_scatter = safe_import(pymod,"scatter"); + s_python_function_boxplot = safe_import(pymod,"boxplot"); + s_python_function_subplot = safe_import(pymod, "subplot"); + s_python_function_subplot2grid = safe_import(pymod, "subplot2grid"); + s_python_function_legend = safe_import(pymod, "legend"); + s_python_function_ylim = safe_import(pymod, "ylim"); + s_python_function_title = safe_import(pymod, "title"); + s_python_function_axis = safe_import(pymod, "axis"); + s_python_function_axvline = safe_import(pymod, "axvline"); + s_python_function_xlabel = safe_import(pymod, "xlabel"); + s_python_function_ylabel = safe_import(pymod, "ylabel"); + s_python_function_gca = safe_import(pymod, "gca"); + s_python_function_xticks = safe_import(pymod, "xticks"); + s_python_function_yticks = safe_import(pymod, "yticks"); + s_python_function_tick_params = safe_import(pymod, "tick_params"); + s_python_function_grid = safe_import(pymod, "grid"); + s_python_function_xlim = safe_import(pymod, "xlim"); + s_python_function_ion = safe_import(pymod, "ion"); + s_python_function_ginput = safe_import(pymod, "ginput"); + s_python_function_save = safe_import(pylabmod, "savefig"); + s_python_function_annotate = safe_import(pymod,"annotate"); + s_python_function_clf = safe_import(pymod, "clf"); + s_python_function_errorbar = safe_import(pymod, "errorbar"); + s_python_function_tight_layout = safe_import(pymod, "tight_layout"); + s_python_function_stem = safe_import(pymod, "stem"); + s_python_function_xkcd = safe_import(pymod, "xkcd"); + s_python_function_text = safe_import(pymod, "text"); + s_python_function_suptitle = safe_import(pymod, "suptitle"); + s_python_function_bar = safe_import(pymod,"bar"); + s_python_function_colorbar = PyObject_GetAttrString(pymod, "colorbar"); + s_python_function_subplots_adjust = safe_import(pymod,"subplots_adjust"); +#ifndef WITHOUT_NUMPY + s_python_function_imshow = safe_import(pymod, "imshow"); +#endif + s_python_empty_tuple = PyTuple_New(0); + } + + ~_interpreter() { + Py_Finalize(); + } +}; + +} // end namespace detail + +/// Select the backend +/// +/// **NOTE:** This must be called before the first plot command to have +/// any effect. +/// +/// Mainly useful to select the non-interactive 'Agg' backend when running +/// matplotlibcpp in headless mode, for example on a machine with no display. +/// +/// See also: https://matplotlib.org/2.0.2/api/matplotlib_configuration_api.html#matplotlib.use +inline void backend(const std::string& name) +{ + detail::s_backend = name; +} + +inline bool annotate(std::string annotation, double x, double y) +{ + detail::_interpreter::get(); + + PyObject * xy = PyTuple_New(2); + PyObject * str = PyString_FromString(annotation.c_str()); + + PyTuple_SetItem(xy,0,PyFloat_FromDouble(x)); + PyTuple_SetItem(xy,1,PyFloat_FromDouble(y)); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "xy", xy); + + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, str); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_annotate, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + if(res) Py_DECREF(res); + + return res; +} + +namespace detail { + +#ifndef WITHOUT_NUMPY +// Type selector for numpy array conversion +template struct select_npy_type { const static NPY_TYPES type = NPY_NOTYPE; }; //Default +template <> struct select_npy_type { const static NPY_TYPES type = NPY_DOUBLE; }; +template <> struct select_npy_type { const static NPY_TYPES type = NPY_FLOAT; }; +template <> struct select_npy_type { const static NPY_TYPES type = NPY_BOOL; }; +template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT8; }; +template <> struct select_npy_type { const static NPY_TYPES type = NPY_SHORT; }; +template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT; }; +template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; +template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT8; }; +template <> struct select_npy_type { const static NPY_TYPES type = NPY_USHORT; }; +template <> struct select_npy_type { const static NPY_TYPES type = NPY_ULONG; }; +template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; + +// Sanity checks; comment them out or change the numpy type below if you're compiling on +// a platform where they don't apply +//static_assert(sizeof(long long) == 8); +//template <> struct select_npy_type { const static NPY_TYPES type = NPY_INT64; }; +//static_assert(sizeof(unsigned long long) == 8); +//template <> struct select_npy_type { const static NPY_TYPES type = NPY_UINT64; }; +// TODO: add int, long, etc. + +template +PyObject* get_array(const std::vector& v) +{ + npy_intp vsize = v.size(); + NPY_TYPES type = select_npy_type::type; + if (type == NPY_NOTYPE) { + size_t memsize = v.size()*sizeof(double); + double* dp = static_cast(::malloc(memsize)); + for (size_t i=0; i(varray), NPY_ARRAY_OWNDATA); + return varray; + } + + PyObject* varray = PyArray_SimpleNewFromData(1, &vsize, type, (void*)(v.data())); + return varray; +} + + +template +PyObject* get_2darray(const std::vector<::std::vector>& v) +{ + if (v.size() < 1) throw std::runtime_error("get_2d_array v too small"); + + npy_intp vsize[2] = {static_cast(v.size()), + static_cast(v[0].size())}; + + PyArrayObject *varray = + (PyArrayObject *)PyArray_SimpleNew(2, vsize, NPY_DOUBLE); + + double *vd_begin = static_cast(PyArray_DATA(varray)); + + for (const ::std::vector &v_row : v) { + if (v_row.size() != static_cast(vsize[1])) + throw std::runtime_error("Missmatched array size"); + std::copy(v_row.begin(), v_row.end(), vd_begin); + vd_begin += vsize[1]; + } + + return reinterpret_cast(varray); +} + +#else // fallback if we don't have numpy: copy every element of the given vector + +template +PyObject* get_array(const std::vector& v) +{ + PyObject* list = PyList_New(v.size()); + for(size_t i = 0; i < v.size(); ++i) { + PyList_SetItem(list, i, PyFloat_FromDouble(v.at(i))); + } + return list; +} + +#endif // WITHOUT_NUMPY + +// sometimes, for labels and such, we need string arrays +inline PyObject * get_array(const std::vector& strings) +{ + PyObject* list = PyList_New(strings.size()); + for (std::size_t i = 0; i < strings.size(); ++i) { + PyList_SetItem(list, i, PyString_FromString(strings[i].c_str())); + } + return list; +} + +// not all matplotlib need 2d arrays, some prefer lists of lists +template +PyObject* get_listlist(const std::vector>& ll) +{ + PyObject* listlist = PyList_New(ll.size()); + for (std::size_t i = 0; i < ll.size(); ++i) { + PyList_SetItem(listlist, i, get_array(ll[i])); + } + return listlist; +} + +} // namespace detail + +/// Plot a line through the given x and y data points.. +/// +/// See: https://matplotlib.org/3.2.1/api/_as_gen/matplotlib.pyplot.plot.html +template +bool plot(const std::vector &x, const std::vector &y, const std::map& keywords) +{ + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; +} + +// TODO - it should be possible to make this work by implementing +// a non-numpy alternative for `detail::get_2darray()`. +#ifndef WITHOUT_NUMPY +template +void plot_surface(const std::vector<::std::vector> &x, + const std::vector<::std::vector> &y, + const std::vector<::std::vector> &z, + const std::map &keywords = + std::map()) +{ + detail::_interpreter::get(); + + // We lazily load the modules here the first time this function is called + // because I'm not sure that we can assume "matplotlib installed" implies + // "mpl_toolkits installed" on all platforms, and we don't want to require + // it for people who don't need 3d plots. + static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; + if (!mpl_toolkitsmod) { + detail::_interpreter::get(); + + PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); + PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); + if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } + + mpl_toolkitsmod = PyImport_Import(mpl_toolkits); + Py_DECREF(mpl_toolkits); + if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } + + axis3dmod = PyImport_Import(axis3d); + Py_DECREF(axis3d); + if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } + } + + assert(x.size() == y.size()); + assert(y.size() == z.size()); + + // using numpy arrays + PyObject *xarray = detail::get_2darray(x); + PyObject *yarray = detail::get_2darray(y); + PyObject *zarray = detail::get_2darray(z); + + // construct positional args + PyObject *args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); + + // Build up the kw args. + PyObject *kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "rstride", PyInt_FromLong(1)); + PyDict_SetItemString(kwargs, "cstride", PyInt_FromLong(1)); + + PyObject *python_colormap_coolwarm = PyObject_GetAttrString( + detail::_interpreter::get().s_python_colormap, "coolwarm"); + + PyDict_SetItemString(kwargs, "cmap", python_colormap_coolwarm); + + for (std::map::const_iterator it = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } + + + PyObject *fig = + PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple); + if (!fig) throw std::runtime_error("Call to figure() failed."); + + PyObject *gca_kwargs = PyDict_New(); + PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); + + PyObject *gca = PyObject_GetAttrString(fig, "gca"); + if (!gca) throw std::runtime_error("No gca"); + Py_INCREF(gca); + PyObject *axis = PyObject_Call( + gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); + + if (!axis) throw std::runtime_error("No axis"); + Py_INCREF(axis); + + Py_DECREF(gca); + Py_DECREF(gca_kwargs); + + PyObject *plot_surface = PyObject_GetAttrString(axis, "plot_surface"); + if (!plot_surface) throw std::runtime_error("No surface"); + Py_INCREF(plot_surface); + PyObject *res = PyObject_Call(plot_surface, args, kwargs); + if (!res) throw std::runtime_error("failed surface"); + Py_DECREF(plot_surface); + + Py_DECREF(axis); + Py_DECREF(args); + Py_DECREF(kwargs); + if (res) Py_DECREF(res); +} +#endif // WITHOUT_NUMPY + +template +void plot3(const std::vector &x, + const std::vector &y, + const std::vector &z, + const std::map &keywords = + std::map()) +{ + detail::_interpreter::get(); + + // Same as with plot_surface: We lazily load the modules here the first time + // this function is called because I'm not sure that we can assume "matplotlib + // installed" implies "mpl_toolkits installed" on all platforms, and we don't + // want to require it for people who don't need 3d plots. + static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; + if (!mpl_toolkitsmod) { + detail::_interpreter::get(); + + PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); + PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); + if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } + + mpl_toolkitsmod = PyImport_Import(mpl_toolkits); + Py_DECREF(mpl_toolkits); + if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } + + axis3dmod = PyImport_Import(axis3d); + Py_DECREF(axis3d); + if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } + } + + assert(x.size() == y.size()); + assert(y.size() == z.size()); + + PyObject *xarray = detail::get_array(x); + PyObject *yarray = detail::get_array(y); + PyObject *zarray = detail::get_array(z); + + // construct positional args + PyObject *args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + PyTuple_SetItem(args, 2, zarray); + + // Build up the kw args. + PyObject *kwargs = PyDict_New(); + + for (std::map::const_iterator it = keywords.begin(); + it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } + + PyObject *fig = + PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple); + if (!fig) throw std::runtime_error("Call to figure() failed."); + + PyObject *gca_kwargs = PyDict_New(); + PyDict_SetItemString(gca_kwargs, "projection", PyString_FromString("3d")); + + PyObject *gca = PyObject_GetAttrString(fig, "gca"); + if (!gca) throw std::runtime_error("No gca"); + Py_INCREF(gca); + PyObject *axis = PyObject_Call( + gca, detail::_interpreter::get().s_python_empty_tuple, gca_kwargs); + + if (!axis) throw std::runtime_error("No axis"); + Py_INCREF(axis); + + Py_DECREF(gca); + Py_DECREF(gca_kwargs); + + PyObject *plot3 = PyObject_GetAttrString(axis, "plot"); + if (!plot3) throw std::runtime_error("No 3D line plot"); + Py_INCREF(plot3); + PyObject *res = PyObject_Call(plot3, args, kwargs); + if (!res) throw std::runtime_error("Failed 3D line plot"); + Py_DECREF(plot3); + + Py_DECREF(axis); + Py_DECREF(args); + Py_DECREF(kwargs); + if (res) Py_DECREF(res); +} + +template +bool stem(const std::vector &x, const std::vector &y, const std::map& keywords) +{ + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for (std::map::const_iterator it = + keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyString_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_stem, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + if (res) + Py_DECREF(res); + + return res; +} + +template< typename Numeric > +bool fill(const std::vector& x, const std::vector& y, const std::map& keywords) +{ + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + // construct positional args + PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, yarray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_fill, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + if (res) Py_DECREF(res); + + return res; +} + +template< typename Numeric > +bool fill_between(const std::vector& x, const std::vector& y1, const std::vector& y2, const std::map& keywords) +{ + assert(x.size() == y1.size()); + assert(x.size() == y2.size()); + + detail::_interpreter::get(); + + // using numpy arrays + PyObject* xarray = detail::get_array(x); + PyObject* y1array = detail::get_array(y1); + PyObject* y2array = detail::get_array(y2); + + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, xarray); + PyTuple_SetItem(args, 1, y1array); + PyTuple_SetItem(args, 2, y2array); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_fill_between, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; +} + +template< typename Numeric> +bool hist(const std::vector& y, long bins=10,std::string color="b", + double alpha=1.0, bool cumulative=false) +{ + detail::_interpreter::get(); + + PyObject* yarray = detail::get_array(y); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); + PyDict_SetItemString(kwargs, "color", PyString_FromString(color.c_str())); + PyDict_SetItemString(kwargs, "alpha", PyFloat_FromDouble(alpha)); + PyDict_SetItemString(kwargs, "cumulative", cumulative ? Py_True : Py_False); + + PyObject* plot_args = PyTuple_New(1); + + PyTuple_SetItem(plot_args, 0, yarray); + + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_hist, plot_args, kwargs); + + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; +} + +#ifndef WITHOUT_NUMPY +namespace detail { + +inline void imshow(void *ptr, const NPY_TYPES type, const int rows, const int columns, const int colors, const std::map &keywords, PyObject** out) +{ + assert(type == NPY_UINT8 || type == NPY_FLOAT); + assert(colors == 1 || colors == 3 || colors == 4); + + detail::_interpreter::get(); + + // construct args + npy_intp dims[3] = { rows, columns, colors }; + PyObject *args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyArray_SimpleNewFromData(colors == 1 ? 2 : 3, dims, type, ptr)); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_imshow, args, kwargs); + Py_DECREF(args); + Py_DECREF(kwargs); + if (!res) + throw std::runtime_error("Call to imshow() failed"); + if (out) + *out = res; + else + Py_DECREF(res); +} + +} // namespace detail + +inline void imshow(const unsigned char *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) +{ + detail::imshow((void *) ptr, NPY_UINT8, rows, columns, colors, keywords, out); +} + +inline void imshow(const float *ptr, const int rows, const int columns, const int colors, const std::map &keywords = {}, PyObject** out = nullptr) +{ + detail::imshow((void *) ptr, NPY_FLOAT, rows, columns, colors, keywords, out); +} + +#ifdef WITH_OPENCV +void imshow(const cv::Mat &image, const std::map &keywords = {}) +{ + // Convert underlying type of matrix, if needed + cv::Mat image2; + NPY_TYPES npy_type = NPY_UINT8; + switch (image.type() & CV_MAT_DEPTH_MASK) { + case CV_8U: + image2 = image; + break; + case CV_32F: + image2 = image; + npy_type = NPY_FLOAT; + break; + default: + image.convertTo(image2, CV_MAKETYPE(CV_8U, image.channels())); + } + + // If color image, convert from BGR to RGB + switch (image2.channels()) { + case 3: + cv::cvtColor(image2, image2, CV_BGR2RGB); + break; + case 4: + cv::cvtColor(image2, image2, CV_BGRA2RGBA); + } + + detail::imshow(image2.data, npy_type, image2.rows, image2.cols, image2.channels(), keywords); +} +#endif // WITH_OPENCV +#endif // WITHOUT_NUMPY + +template +bool scatter(const std::vector& x, + const std::vector& y, + const double s=1.0, // The marker size in points**2 + const std::map & keywords = {}) +{ + detail::_interpreter::get(); + + assert(x.size() == y.size()); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "s", PyLong_FromLong(s)); + for (const auto& it : keywords) + { + PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); + } + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_scatter, plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; +} + +template +bool boxplot(const std::vector>& data, + const std::vector& labels = {}, + const std::map & keywords = {}) +{ + detail::_interpreter::get(); + + PyObject* listlist = detail::get_listlist(data); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, listlist); + + PyObject* kwargs = PyDict_New(); + + // kwargs needs the labels, if there are (the correct number of) labels + if (!labels.empty() && labels.size() == data.size()) { + PyDict_SetItemString(kwargs, "labels", detail::get_array(labels)); + } + + // take care of the remaining keywords + for (const auto& it : keywords) + { + PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_boxplot, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + if(res) Py_DECREF(res); + + return res; +} + +template +bool boxplot(const std::vector& data, + const std::map & keywords = {}) +{ + detail::_interpreter::get(); + + PyObject* vector = detail::get_array(data); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, vector); + + PyObject* kwargs = PyDict_New(); + for (const auto& it : keywords) + { + PyDict_SetItemString(kwargs, it.first.c_str(), PyString_FromString(it.second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_boxplot, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + if(res) Py_DECREF(res); + + return res; +} + +template +bool bar(const std::vector & x, + const std::vector & y, + std::string ec = "black", + std::string ls = "-", + double lw = 1.0, + const std::map & keywords = {}) +{ + detail::_interpreter::get(); + + PyObject * xarray = detail::get_array(x); + PyObject * yarray = detail::get_array(y); + + PyObject * kwargs = PyDict_New(); + + PyDict_SetItemString(kwargs, "ec", PyString_FromString(ec.c_str())); + PyDict_SetItemString(kwargs, "ls", PyString_FromString(ls.c_str())); + PyDict_SetItemString(kwargs, "lw", PyFloat_FromDouble(lw)); + + for (std::map::const_iterator it = + keywords.begin(); + it != keywords.end(); + ++it) { + PyDict_SetItemString( + kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject * plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject * res = PyObject_Call( + detail::_interpreter::get().s_python_function_bar, plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if (res) Py_DECREF(res); + + return res; +} + +template +bool bar(const std::vector & y, + std::string ec = "black", + std::string ls = "-", + double lw = 1.0, + const std::map & keywords = {}) +{ + using T = typename std::remove_reference::type::value_type; + + detail::_interpreter::get(); + + std::vector x; + for (std::size_t i = 0; i < y.size(); i++) { x.push_back(i); } + + return bar(x, y, ec, ls, lw, keywords); +} + +inline bool subplots_adjust(const std::map& keywords = {}) +{ + detail::_interpreter::get(); + + PyObject* kwargs = PyDict_New(); + for (std::map::const_iterator it = + keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), + PyFloat_FromDouble(it->second)); + } + + + PyObject* plot_args = PyTuple_New(0); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_subplots_adjust, plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; +} + +template< typename Numeric> +bool named_hist(std::string label,const std::vector& y, long bins=10, std::string color="b", double alpha=1.0) +{ + detail::_interpreter::get(); + + PyObject* yarray = detail::get_array(y); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", PyString_FromString(label.c_str())); + PyDict_SetItemString(kwargs, "bins", PyLong_FromLong(bins)); + PyDict_SetItemString(kwargs, "color", PyString_FromString(color.c_str())); + PyDict_SetItemString(kwargs, "alpha", PyFloat_FromDouble(alpha)); + + + PyObject* plot_args = PyTuple_New(1); + PyTuple_SetItem(plot_args, 0, yarray); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_hist, plot_args, kwargs); + + Py_DECREF(plot_args); + Py_DECREF(kwargs); + if(res) Py_DECREF(res); + + return res; +} + +template +bool plot(const std::vector& x, const std::vector& y, const std::string& s = "") +{ + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; +} + +template +bool quiver(const std::vector& x, const std::vector& y, const std::vector& u, const std::vector& w, const std::map& keywords = {}) +{ + assert(x.size() == y.size() && x.size() == u.size() && u.size() == w.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* uarray = detail::get_array(u); + PyObject* warray = detail::get_array(w); + + PyObject* plot_args = PyTuple_New(4); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, uarray); + PyTuple_SetItem(plot_args, 3, warray); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_quiver, plot_args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if (res) + Py_DECREF(res); + + return res; +} + +template +bool stem(const std::vector& x, const std::vector& y, const std::string& s = "") +{ + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_stem, plot_args); + + Py_DECREF(plot_args); + if (res) + Py_DECREF(res); + + return res; +} + +template +bool semilogx(const std::vector& x, const std::vector& y, const std::string& s = "") +{ + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_semilogx, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; +} + +template +bool semilogy(const std::vector& x, const std::vector& y, const std::string& s = "") +{ + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_semilogy, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; +} + +template +bool loglog(const std::vector& x, const std::vector& y, const std::string& s = "") +{ + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(s.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_loglog, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; +} + +template +bool errorbar(const std::vector &x, const std::vector &y, const std::vector &yerr, const std::map &keywords = {}) +{ + assert(x.size() == y.size()); + + detail::_interpreter::get(); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + PyObject* yerrarray = detail::get_array(yerr); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + } + + PyDict_SetItemString(kwargs, "yerr", yerrarray); + + PyObject *plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject *res = PyObject_Call(detail::_interpreter::get().s_python_function_errorbar, plot_args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + + if (res) + Py_DECREF(res); + else + throw std::runtime_error("Call to errorbar() failed."); + + return res; +} + +template +bool named_plot(const std::string& name, const std::vector& y, const std::string& format = "") +{ + detail::_interpreter::get(); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(format.c_str()); + + PyObject* plot_args = PyTuple_New(2); + + PyTuple_SetItem(plot_args, 0, yarray); + PyTuple_SetItem(plot_args, 1, pystring); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if (res) Py_DECREF(res); + + return res; +} + +template +bool named_plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") +{ + detail::_interpreter::get(); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(format.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if (res) Py_DECREF(res); + + return res; +} + +template +bool named_semilogx(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") +{ + detail::_interpreter::get(); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(format.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_semilogx, plot_args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if (res) Py_DECREF(res); + + return res; +} + +template +bool named_semilogy(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") +{ + detail::_interpreter::get(); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(format.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_semilogy, plot_args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if (res) Py_DECREF(res); + + return res; +} + +template +bool named_loglog(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") +{ + detail::_interpreter::get(); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(format.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_loglog, plot_args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + if (res) Py_DECREF(res); + + return res; +} + +template +bool plot(const std::vector& y, const std::string& format = "") +{ + std::vector x(y.size()); + for(size_t i=0; i +bool plot(const std::vector& y, const std::map& keywords) +{ + std::vector x(y.size()); + for(size_t i=0; i +bool stem(const std::vector& y, const std::string& format = "") +{ + std::vector x(y.size()); + for (size_t i = 0; i < x.size(); ++i) x.at(i) = i; + return stem(x, y, format); +} + +template +void text(Numeric x, Numeric y, const std::string& s = "") +{ + detail::_interpreter::get(); + + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(y)); + PyTuple_SetItem(args, 2, PyString_FromString(s.c_str())); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_text, args); + if(!res) throw std::runtime_error("Call to text() failed."); + + Py_DECREF(args); + Py_DECREF(res); +} + +inline void colorbar(PyObject* mappable = NULL, const std::map& keywords = {}) +{ + if (mappable == NULL) + throw std::runtime_error("Must call colorbar with PyObject* returned from an image, contour, surface, etc."); + + detail::_interpreter::get(); + + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, mappable); + + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyFloat_FromDouble(it->second)); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_colorbar, args, kwargs); + if(!res) throw std::runtime_error("Call to colorbar() failed."); + + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); +} + + +inline long figure(long number = -1) +{ + detail::_interpreter::get(); + + PyObject *res; + if (number == -1) + res = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, detail::_interpreter::get().s_python_empty_tuple); + else { + assert(number > 0); + + // Make sure interpreter is initialised + detail::_interpreter::get(); + + PyObject *args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyLong_FromLong(number)); + res = PyObject_CallObject(detail::_interpreter::get().s_python_function_figure, args); + Py_DECREF(args); + } + + if(!res) throw std::runtime_error("Call to figure() failed."); + + PyObject* num = PyObject_GetAttrString(res, "number"); + if (!num) throw std::runtime_error("Could not get number attribute of figure object"); + const long figureNumber = PyLong_AsLong(num); + + Py_DECREF(num); + Py_DECREF(res); + + return figureNumber; +} + +inline bool fignum_exists(long number) +{ + detail::_interpreter::get(); + + PyObject *args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyLong_FromLong(number)); + PyObject *res = PyObject_CallObject(detail::_interpreter::get().s_python_function_fignum_exists, args); + if(!res) throw std::runtime_error("Call to fignum_exists() failed."); + + bool ret = PyObject_IsTrue(res); + Py_DECREF(res); + Py_DECREF(args); + + return ret; +} + +inline void figure_size(size_t w, size_t h) +{ + detail::_interpreter::get(); + + const size_t dpi = 100; + PyObject* size = PyTuple_New(2); + PyTuple_SetItem(size, 0, PyFloat_FromDouble((double)w / dpi)); + PyTuple_SetItem(size, 1, PyFloat_FromDouble((double)h / dpi)); + + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "figsize", size); + PyDict_SetItemString(kwargs, "dpi", PyLong_FromSize_t(dpi)); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_figure, + detail::_interpreter::get().s_python_empty_tuple, kwargs); + + Py_DECREF(kwargs); + + if(!res) throw std::runtime_error("Call to figure_size() failed."); + Py_DECREF(res); +} + +inline void legend() +{ + detail::_interpreter::get(); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_legend, detail::_interpreter::get().s_python_empty_tuple); + if(!res) throw std::runtime_error("Call to legend() failed."); + + Py_DECREF(res); +} + +template +void ylim(Numeric left, Numeric right) +{ + detail::_interpreter::get(); + + PyObject* list = PyList_New(2); + PyList_SetItem(list, 0, PyFloat_FromDouble(left)); + PyList_SetItem(list, 1, PyFloat_FromDouble(right)); + + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, list); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_ylim, args); + if(!res) throw std::runtime_error("Call to ylim() failed."); + + Py_DECREF(args); + Py_DECREF(res); +} + +template +void xlim(Numeric left, Numeric right) +{ + detail::_interpreter::get(); + + PyObject* list = PyList_New(2); + PyList_SetItem(list, 0, PyFloat_FromDouble(left)); + PyList_SetItem(list, 1, PyFloat_FromDouble(right)); + + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, list); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_xlim, args); + if(!res) throw std::runtime_error("Call to xlim() failed."); + + Py_DECREF(args); + Py_DECREF(res); +} + + +inline double* xlim() +{ + detail::_interpreter::get(); + + PyObject* args = PyTuple_New(0); + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_xlim, args); + PyObject* left = PyTuple_GetItem(res,0); + PyObject* right = PyTuple_GetItem(res,1); + + double* arr = new double[2]; + arr[0] = PyFloat_AsDouble(left); + arr[1] = PyFloat_AsDouble(right); + + if(!res) throw std::runtime_error("Call to xlim() failed."); + + Py_DECREF(res); + return arr; +} + + +inline double* ylim() +{ + detail::_interpreter::get(); + + PyObject* args = PyTuple_New(0); + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_ylim, args); + PyObject* left = PyTuple_GetItem(res,0); + PyObject* right = PyTuple_GetItem(res,1); + + double* arr = new double[2]; + arr[0] = PyFloat_AsDouble(left); + arr[1] = PyFloat_AsDouble(right); + + if(!res) throw std::runtime_error("Call to ylim() failed."); + + Py_DECREF(res); + return arr; +} + +template +inline void xticks(const std::vector &ticks, const std::vector &labels = {}, const std::map& keywords = {}) +{ + assert(labels.size() == 0 || ticks.size() == labels.size()); + + detail::_interpreter::get(); + + // using numpy array + PyObject* ticksarray = detail::get_array(ticks); + + PyObject* args; + if(labels.size() == 0) { + // construct positional args + args = PyTuple_New(1); + PyTuple_SetItem(args, 0, ticksarray); + } else { + // make tuple of tick labels + PyObject* labelstuple = PyTuple_New(labels.size()); + for (size_t i = 0; i < labels.size(); i++) + PyTuple_SetItem(labelstuple, i, PyUnicode_FromString(labels[i].c_str())); + + // construct positional args + args = PyTuple_New(2); + PyTuple_SetItem(args, 0, ticksarray); + PyTuple_SetItem(args, 1, labelstuple); + } + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_xticks, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to xticks() failed"); + + Py_DECREF(res); +} + +template +inline void xticks(const std::vector &ticks, const std::map& keywords) +{ + xticks(ticks, {}, keywords); +} + +template +inline void yticks(const std::vector &ticks, const std::vector &labels = {}, const std::map& keywords = {}) +{ + assert(labels.size() == 0 || ticks.size() == labels.size()); + + detail::_interpreter::get(); + + // using numpy array + PyObject* ticksarray = detail::get_array(ticks); + + PyObject* args; + if(labels.size() == 0) { + // construct positional args + args = PyTuple_New(1); + PyTuple_SetItem(args, 0, ticksarray); + } else { + // make tuple of tick labels + PyObject* labelstuple = PyTuple_New(labels.size()); + for (size_t i = 0; i < labels.size(); i++) + PyTuple_SetItem(labelstuple, i, PyUnicode_FromString(labels[i].c_str())); + + // construct positional args + args = PyTuple_New(2); + PyTuple_SetItem(args, 0, ticksarray); + PyTuple_SetItem(args, 1, labelstuple); + } + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_yticks, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + if(!res) throw std::runtime_error("Call to yticks() failed"); + + Py_DECREF(res); +} + +template +inline void yticks(const std::vector &ticks, const std::map& keywords) +{ + yticks(ticks, {}, keywords); +} + +inline void tick_params(const std::map& keywords, const std::string axis = "both") +{ + detail::_interpreter::get(); + + // construct positional args + PyObject* args; + args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyString_FromString(axis.c_str())); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for (std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + } + + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_tick_params, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + if (!res) throw std::runtime_error("Call to tick_params() failed"); + + Py_DECREF(res); +} + +inline void subplot(long nrows, long ncols, long plot_number) +{ + detail::_interpreter::get(); + + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(nrows)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(ncols)); + PyTuple_SetItem(args, 2, PyFloat_FromDouble(plot_number)); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_subplot, args); + if(!res) throw std::runtime_error("Call to subplot() failed."); + + Py_DECREF(args); + Py_DECREF(res); +} + +inline void subplot2grid(long nrows, long ncols, long rowid=0, long colid=0, long rowspan=1, long colspan=1) +{ + detail::_interpreter::get(); + + PyObject* shape = PyTuple_New(2); + PyTuple_SetItem(shape, 0, PyLong_FromLong(nrows)); + PyTuple_SetItem(shape, 1, PyLong_FromLong(ncols)); + + PyObject* loc = PyTuple_New(2); + PyTuple_SetItem(loc, 0, PyLong_FromLong(rowid)); + PyTuple_SetItem(loc, 1, PyLong_FromLong(colid)); + + PyObject* args = PyTuple_New(4); + PyTuple_SetItem(args, 0, shape); + PyTuple_SetItem(args, 1, loc); + PyTuple_SetItem(args, 2, PyLong_FromLong(rowspan)); + PyTuple_SetItem(args, 3, PyLong_FromLong(colspan)); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_subplot2grid, args); + if(!res) throw std::runtime_error("Call to subplot2grid() failed."); + + Py_DECREF(shape); + Py_DECREF(loc); + Py_DECREF(args); + Py_DECREF(res); +} + +inline void title(const std::string &titlestr, const std::map &keywords = {}) +{ + detail::_interpreter::get(); + + PyObject* pytitlestr = PyString_FromString(titlestr.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pytitlestr); + + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_title, args, kwargs); + if(!res) throw std::runtime_error("Call to title() failed."); + + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); +} + +inline void suptitle(const std::string &suptitlestr, const std::map &keywords = {}) +{ + detail::_interpreter::get(); + + PyObject* pysuptitlestr = PyString_FromString(suptitlestr.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pysuptitlestr); + + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_suptitle, args, kwargs); + if(!res) throw std::runtime_error("Call to suptitle() failed."); + + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); +} + +inline void axis(const std::string &axisstr) +{ + detail::_interpreter::get(); + + PyObject* str = PyString_FromString(axisstr.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, str); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_axis, args); + if(!res) throw std::runtime_error("Call to title() failed."); + + Py_DECREF(args); + Py_DECREF(res); +} + +inline void axvline(double x, double ymin = 0., double ymax = 1., const std::map& keywords_string = std::map(), + const std::map& keywords_double = std::map()) +{ + detail::_interpreter::get(); + + // construct positional args + PyObject* args = PyTuple_New(3); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(x)); + PyTuple_SetItem(args, 1, PyFloat_FromDouble(ymin)); + PyTuple_SetItem(args, 2, PyFloat_FromDouble(ymax)); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords_string.begin(); it != keywords_string.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyString_FromString(it->second.c_str())); + } + + for(std::map::const_iterator it = keywords_double.begin(); it != keywords_double.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyFloat_FromDouble(it->second)); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_axvline, args, kwargs); + + Py_DECREF(args); + Py_DECREF(kwargs); + + if(res) Py_DECREF(res); +} + +inline void xlabel(const std::string &str, const std::map &keywords = {}) +{ + detail::_interpreter::get(); + + PyObject* pystr = PyString_FromString(str.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pystr); + + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_xlabel, args, kwargs); + if(!res) throw std::runtime_error("Call to xlabel() failed."); + + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); +} + +inline void ylabel(const std::string &str, const std::map& keywords = {}) +{ + detail::_interpreter::get(); + + PyObject* pystr = PyString_FromString(str.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pystr); + + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_ylabel, args, kwargs); + if(!res) throw std::runtime_error("Call to ylabel() failed."); + + Py_DECREF(args); + Py_DECREF(kwargs); + Py_DECREF(res); +} + +inline void set_zlabel(const std::string &str, const std::map& keywords = {}) +{ + detail::_interpreter::get(); + + // Same as with plot_surface: We lazily load the modules here the first time + // this function is called because I'm not sure that we can assume "matplotlib + // installed" implies "mpl_toolkits installed" on all platforms, and we don't + // want to require it for people who don't need 3d plots. + static PyObject *mpl_toolkitsmod = nullptr, *axis3dmod = nullptr; + if (!mpl_toolkitsmod) { + PyObject* mpl_toolkits = PyString_FromString("mpl_toolkits"); + PyObject* axis3d = PyString_FromString("mpl_toolkits.mplot3d"); + if (!mpl_toolkits || !axis3d) { throw std::runtime_error("couldnt create string"); } + + mpl_toolkitsmod = PyImport_Import(mpl_toolkits); + Py_DECREF(mpl_toolkits); + if (!mpl_toolkitsmod) { throw std::runtime_error("Error loading module mpl_toolkits!"); } + + axis3dmod = PyImport_Import(axis3d); + Py_DECREF(axis3d); + if (!axis3dmod) { throw std::runtime_error("Error loading module mpl_toolkits.mplot3d!"); } + } + + PyObject* pystr = PyString_FromString(str.c_str()); + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pystr); + + PyObject* kwargs = PyDict_New(); + for (auto it = keywords.begin(); it != keywords.end(); ++it) { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject *ax = + PyObject_CallObject(detail::_interpreter::get().s_python_function_gca, + detail::_interpreter::get().s_python_empty_tuple); + if (!ax) throw std::runtime_error("Call to gca() failed."); + Py_INCREF(ax); + + PyObject *zlabel = PyObject_GetAttrString(ax, "set_zlabel"); + if (!zlabel) throw std::runtime_error("Attribute set_zlabel not found."); + Py_INCREF(zlabel); + + PyObject *res = PyObject_Call(zlabel, args, kwargs); + if (!res) throw std::runtime_error("Call to set_zlabel() failed."); + Py_DECREF(zlabel); + + Py_DECREF(ax); + Py_DECREF(args); + Py_DECREF(kwargs); + if (res) Py_DECREF(res); +} + +inline void grid(bool flag) +{ + detail::_interpreter::get(); + + PyObject* pyflag = flag ? Py_True : Py_False; + Py_INCREF(pyflag); + + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pyflag); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_grid, args); + if(!res) throw std::runtime_error("Call to grid() failed."); + + Py_DECREF(args); + Py_DECREF(res); +} + +inline void show(const bool block = true) +{ + detail::_interpreter::get(); + + PyObject* res; + if(block) + { + res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_show, + detail::_interpreter::get().s_python_empty_tuple); + } + else + { + PyObject *kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "block", Py_False); + res = PyObject_Call( detail::_interpreter::get().s_python_function_show, detail::_interpreter::get().s_python_empty_tuple, kwargs); + Py_DECREF(kwargs); + } + + + if (!res) throw std::runtime_error("Call to show() failed."); + + Py_DECREF(res); +} + +inline void close() +{ + detail::_interpreter::get(); + + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_close, + detail::_interpreter::get().s_python_empty_tuple); + + if (!res) throw std::runtime_error("Call to close() failed."); + + Py_DECREF(res); +} + +inline void xkcd() { + detail::_interpreter::get(); + + PyObject* res; + PyObject *kwargs = PyDict_New(); + + res = PyObject_Call(detail::_interpreter::get().s_python_function_xkcd, + detail::_interpreter::get().s_python_empty_tuple, kwargs); + + Py_DECREF(kwargs); + + if (!res) + throw std::runtime_error("Call to show() failed."); + + Py_DECREF(res); +} + +inline void draw() +{ + detail::_interpreter::get(); + + PyObject* res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_draw, + detail::_interpreter::get().s_python_empty_tuple); + + if (!res) throw std::runtime_error("Call to draw() failed."); + + Py_DECREF(res); +} + +template +inline void pause(Numeric interval) +{ + detail::_interpreter::get(); + + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyFloat_FromDouble(interval)); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_pause, args); + if(!res) throw std::runtime_error("Call to pause() failed."); + + Py_DECREF(args); + Py_DECREF(res); +} + +inline void save(const std::string& filename) +{ + detail::_interpreter::get(); + + PyObject* pyfilename = PyString_FromString(filename.c_str()); + + PyObject* args = PyTuple_New(1); + PyTuple_SetItem(args, 0, pyfilename); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_save, args); + if (!res) throw std::runtime_error("Call to save() failed."); + + Py_DECREF(args); + Py_DECREF(res); +} + +inline void clf() { + detail::_interpreter::get(); + + PyObject *res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_clf, + detail::_interpreter::get().s_python_empty_tuple); + + if (!res) throw std::runtime_error("Call to clf() failed."); + + Py_DECREF(res); +} + +inline void ion() { + detail::_interpreter::get(); + + PyObject *res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_ion, + detail::_interpreter::get().s_python_empty_tuple); + + if (!res) throw std::runtime_error("Call to ion() failed."); + + Py_DECREF(res); +} + +inline std::vector> ginput(const int numClicks = 1, const std::map& keywords = {}) +{ + detail::_interpreter::get(); + + PyObject *args = PyTuple_New(1); + PyTuple_SetItem(args, 0, PyLong_FromLong(numClicks)); + + // construct keyword args + PyObject* kwargs = PyDict_New(); + for(std::map::const_iterator it = keywords.begin(); it != keywords.end(); ++it) + { + PyDict_SetItemString(kwargs, it->first.c_str(), PyUnicode_FromString(it->second.c_str())); + } + + PyObject* res = PyObject_Call( + detail::_interpreter::get().s_python_function_ginput, args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(args); + if (!res) throw std::runtime_error("Call to ginput() failed."); + + const size_t len = PyList_Size(res); + std::vector> out; + out.reserve(len); + for (size_t i = 0; i < len; i++) { + PyObject *current = PyList_GetItem(res, i); + std::array position; + position[0] = PyFloat_AsDouble(PyTuple_GetItem(current, 0)); + position[1] = PyFloat_AsDouble(PyTuple_GetItem(current, 1)); + out.push_back(position); + } + Py_DECREF(res); + + return out; +} + +// Actually, is there any reason not to call this automatically for every plot? +inline void tight_layout() { + detail::_interpreter::get(); + + PyObject *res = PyObject_CallObject( + detail::_interpreter::get().s_python_function_tight_layout, + detail::_interpreter::get().s_python_empty_tuple); + + if (!res) throw std::runtime_error("Call to tight_layout() failed."); + + Py_DECREF(res); +} + +// Support for variadic plot() and initializer lists: + +namespace detail { + +template +using is_function = typename std::is_function>>::type; + +template +struct is_callable_impl; + +template +struct is_callable_impl +{ + typedef is_function type; +}; // a non-object is callable iff it is a function + +template +struct is_callable_impl +{ + struct Fallback { void operator()(); }; + struct Derived : T, Fallback { }; + + template struct Check; + + template + static std::true_type test( ... ); // use a variadic function to make sure (1) it accepts everything and (2) its always the worst match + + template + static std::false_type test( Check* ); + +public: + typedef decltype(test(nullptr)) type; + typedef decltype(&Fallback::operator()) dtype; + static constexpr bool value = type::value; +}; // an object is callable iff it defines operator() + +template +struct is_callable +{ + // dispatch to is_callable_impl or is_callable_impl depending on whether T is of class type or not + typedef typename is_callable_impl::value, T>::type type; +}; + +template +struct plot_impl { }; + +template<> +struct plot_impl +{ + template + bool operator()(const IterableX& x, const IterableY& y, const std::string& format) + { + // 2-phase lookup for distance, begin, end + using std::distance; + using std::begin; + using std::end; + + auto xs = distance(begin(x), end(x)); + auto ys = distance(begin(y), end(y)); + assert(xs == ys && "x and y data must have the same number of elements!"); + + PyObject* xlist = PyList_New(xs); + PyObject* ylist = PyList_New(ys); + PyObject* pystring = PyString_FromString(format.c_str()); + + auto itx = begin(x), ity = begin(y); + for(size_t i = 0; i < xs; ++i) { + PyList_SetItem(xlist, i, PyFloat_FromDouble(*itx++)); + PyList_SetItem(ylist, i, PyFloat_FromDouble(*ity++)); + } + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xlist); + PyTuple_SetItem(plot_args, 1, ylist); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_CallObject(detail::_interpreter::get().s_python_function_plot, plot_args); + + Py_DECREF(plot_args); + if(res) Py_DECREF(res); + + return res; + } +}; + +template<> +struct plot_impl +{ + template + bool operator()(const Iterable& ticks, const Callable& f, const std::string& format) + { + if(begin(ticks) == end(ticks)) return true; + + // We could use additional meta-programming to deduce the correct element type of y, + // but all values have to be convertible to double anyways + std::vector y; + for(auto x : ticks) y.push_back(f(x)); + return plot_impl()(ticks,y,format); + } +}; + +} // end namespace detail + +// recursion stop for the above +template +bool plot() { return true; } + +template +bool plot(const A& a, const B& b, const std::string& format, Args... args) +{ + return detail::plot_impl::type>()(a,b,format) && plot(args...); +} + +/* + * This group of plot() functions is needed to support initializer lists, i.e. calling + * plot( {1,2,3,4} ) + */ +inline bool plot(const std::vector& x, const std::vector& y, const std::string& format = "") { + return plot(x,y,format); +} + +inline bool plot(const std::vector& y, const std::string& format = "") { + return plot(y,format); +} + +inline bool plot(const std::vector& x, const std::vector& y, const std::map& keywords) { + return plot(x,y,keywords); +} + +/* + * This class allows dynamic plots, ie changing the plotted data without clearing and re-plotting + */ +class Plot +{ +public: + // default initialization with plot label, some data and format + template + Plot(const std::string& name, const std::vector& x, const std::vector& y, const std::string& format = "") { + detail::_interpreter::get(); + + assert(x.size() == y.size()); + + PyObject* kwargs = PyDict_New(); + if(name != "") + PyDict_SetItemString(kwargs, "label", PyString_FromString(name.c_str())); + + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* pystring = PyString_FromString(format.c_str()); + + PyObject* plot_args = PyTuple_New(3); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + PyTuple_SetItem(plot_args, 2, pystring); + + PyObject* res = PyObject_Call(detail::_interpreter::get().s_python_function_plot, plot_args, kwargs); + + Py_DECREF(kwargs); + Py_DECREF(plot_args); + + if(res) + { + line= PyList_GetItem(res, 0); + + if(line) + set_data_fct = PyObject_GetAttrString(line,"set_data"); + else + Py_DECREF(line); + Py_DECREF(res); + } + } + + // shorter initialization with name or format only + // basically calls line, = plot([], []) + Plot(const std::string& name = "", const std::string& format = "") + : Plot(name, std::vector(), std::vector(), format) {} + + template + bool update(const std::vector& x, const std::vector& y) { + assert(x.size() == y.size()); + if(set_data_fct) + { + PyObject* xarray = detail::get_array(x); + PyObject* yarray = detail::get_array(y); + + PyObject* plot_args = PyTuple_New(2); + PyTuple_SetItem(plot_args, 0, xarray); + PyTuple_SetItem(plot_args, 1, yarray); + + PyObject* res = PyObject_CallObject(set_data_fct, plot_args); + if (res) Py_DECREF(res); + return res; + } + return false; + } + + // clears the plot but keep it available + bool clear() { + return update(std::vector(), std::vector()); + } + + // definitely remove this line + void remove() { + if(line) + { + auto remove_fct = PyObject_GetAttrString(line,"remove"); + PyObject* args = PyTuple_New(0); + PyObject* res = PyObject_CallObject(remove_fct, args); + if (res) Py_DECREF(res); + } + decref(); + } + + ~Plot() { + decref(); + } +private: + + void decref() { + if(line) + Py_DECREF(line); + if(set_data_fct) + Py_DECREF(set_data_fct); + } + + + PyObject* line = nullptr; + PyObject* set_data_fct = nullptr; +}; + +} // end namespace matplotlibcpp diff --git a/src/volesti/include/sos/plot_saved.png b/src/volesti/include/sos/plot_saved.png new file mode 100644 index 00000000..4bf495f6 Binary files /dev/null and b/src/volesti/include/sos/plot_saved.png differ diff --git a/src/volesti/include/sos/utils.cpp b/src/volesti/include/sos/utils.cpp new file mode 100644 index 00000000..bb1265e7 --- /dev/null +++ b/src/volesti/include/sos/utils.cpp @@ -0,0 +1,6 @@ +#include "utils.h" + +std::string getEnvVar(std::string const &key) { + char *val = getenv(key.c_str()); + return val == NULL ? std::string("") : std::string(val); +} \ No newline at end of file diff --git a/src/volesti/include/sos/utils.h b/src/volesti/include/sos/utils.h new file mode 100644 index 00000000..09285ec9 --- /dev/null +++ b/src/volesti/include/sos/utils.h @@ -0,0 +1,331 @@ +// VolEsti (volume computation and sampling library) +// +// Copyright (c) 2020 Bento Natura +// +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef SOS_UTILS_H +#define SOS_UTILS_H + +//Preprocessor directive allows us to forbid Eigen to allocate memory. Temporariliy helps to debug where allocation might slow down the program. +#define EIGEN_RUNTIME_NO_MALLOC + +//Note MKL Macro is set in CmakeLists file. + +//#define EIGEN_USE_MKL_ALL +//#define EIGEN_USE_BLAS +//#define EIGEN_USE_LAPACKE + +#include +#include + +#include + +#include +#include +#include +#include "spdlog/spdlog.h" +#include +#include +#include + +#include "ChebTools/ChebTools.h" + + +#ifndef DIGITS_PRECISION +#define DIGITS_PRECISION 50 +#endif + +typedef boost::multiprecision::cpp_dec_float mp_backend; +typedef boost::multiprecision::number BoostDouble; +//typedef boost::multiprecision::number > BoostDouble; +typedef BoostDouble InterpolantDouble; + +typedef long double long_double; + +#ifdef IPM_DOUBLE +typedef IPM_DOUBLE Double; +#else +typedef double Double; +#endif + +//Change typedef here to use different double type in interior point method. +#ifdef IPM_USE_DOUBLE +//typedef Double IPMDouble; +#else +typedef BoostDouble IPMDouble; +#endif + +typedef Eigen::Matrix BoostMatrix; +typedef Eigen::Matrix BoostVector; + +typedef Eigen::Matrix InterpolantMatrix; +typedef Eigen::Matrix InterpolantVector; + +typedef Eigen::Matrix DoubleMatrix; +typedef Eigen::Matrix DoubleVector; + +//Note: Boost Dependency +namespace pt = boost::property_tree; + +template +using Matrix = Eigen::Matrix; + +template +using Vector = Eigen::Matrix; + +//Stack the columns of a square m x m matrix to a vector of length m x m. +template +Vector StackMatrixToVector(Matrix M) { + assert(M.rows() == M.cols()); + Eigen::Map> x(M.data(), M.rows() * M.cols(), 1); + return x; +} + +//Unstack vector +template +Matrix UnstackVectorToMatrix(Vector v, unsigned matrix_dimension) { + assert(v.rows() == matrix_dimension * matrix_dimension); + Eigen::Map > M(v.data(), matrix_dimension, matrix_dimension); + return M; +} + +//template +//class Constraints; + +template +class Solution { +public: + template + Solution cast() { + Solution sol; + sol.x = x.template cast(); + sol.s = s.template cast(); + //Note: Boost Dependency. + sol.centrality = boost::numeric_cast(centrality); + sol.gap= boost::numeric_cast(gap); + return sol; + } + + Vector x; + Vector s; + T centrality; + T gap; +}; + +//TODO: Need full row rank matrices for IPM. Also, is preprocessing A, e.g. row-echelon form useful? +template +class Constraints { +public: + Matrix A; + Vector b; + Vector c; + + Constraints() {}; + + Constraints(Matrix A_, Vector b_, Vector c_) : A(A_), b(b_), c(c_) {}; + + void print() { + std::cout << "Constraints are as follows. Constraint matrix is A: " << std::endl; + std::cout << A << std::endl; + std::cout << "Objective c is " << std::endl; + std::cout << c.transpose() << std::endl; + std::cout << "RHS b is " << std::endl; + std::cout << b.transpose() << std::endl; + } + + //TODO: use optional argument to indicate sparseness. + Constraints dual_system() { + std::cout << "Create dual system... " << std::endl; + Constraints dual_constraints; +// dual_constraints.c = A.colPivHouseholderQr().solve(b); + + //TODO: use proper tolerance / reference. + + Eigen::SparseMatrix A_top_sparse = A.transpose().sparseView(0, 1e-10); + Eigen::SparseMatrix A_sparse = A.sparseView(0, 1e-10); + + A_top_sparse.makeCompressed(); + A_sparse.makeCompressed(); + + Eigen::SparseQR, Eigen::COLAMDOrdering > QR_top_sparse; + Eigen::SparseQR, Eigen::COLAMDOrdering > QR_sparse; + + QR_top_sparse.compute(A_top_sparse); + QR_sparse.compute(A_sparse); + + dual_constraints.c = QR_sparse.solve(b); + + Matrix QR_from_sparse(QR_top_sparse.matrixQ()); +// Matrix QR = A.transpose().householderQr().householderQ(); + + dual_constraints.A = QR_from_sparse.block(0, A.rows(), QR_from_sparse.rows(), + QR_from_sparse.cols() - A.rows()).transpose(); + dual_constraints.b = dual_constraints.A * c; + + std::cout << "Done." << std::endl; + + //TODO: use different measure to calculate centrality error + assert((dual_constraints.A * A.transpose()).norm() < 10e-5); + return dual_constraints; + } +}; + +//Naive implementation +class DegreeTuple { +public: + DegreeTuple(const int num_vars, const unsigned max_degree_) { + max_degree = max_degree_; + v.resize(num_vars, 0); + } + + bool next() { + for (int i = v.size() - 1; i >= 0; i--) { + if (v[i] < max_degree) { + v[i]++; + return true; + } + v[i] = 0; + } + return false; + } + + bool valid() { + int sum = 0; + for (auto el : v) { + sum += el; + } + return sum <= max_degree; + } + + bool next_valid() { + if (!next()) { + return false; + } + while (not valid()) { + if (not next()) { + return false; + } + } + return true; + } + + std::vector &get_tuple() { + return v; + } + + void print_tuple() { + for (auto el : v) { + std::cout << el << " "; + } + std::cout << std::endl; + } + +private: + unsigned max_degree; + std::vector v; +}; + +class AllCombinationTuple { +public: + AllCombinationTuple(std::vector const bounds_) { + + bounds = bounds_; + v.resize(bounds.size(), 0); + } + + bool next() { + for (int i = v.size() - 1; i >= 0; i--) { + if (bounds[i] > v[i]) { + v[i]++; + return true; + } + v[i] = 0; + } + return false; + } + + std::vector &get_combination() { + return v; + } + + std::vector bounds; + std::vector v; +}; + + +//Implementation copied from https://stackoverflow.com/questions/1577475/c-sorting-and-keeping-track-of-indexes + +template +std::vector sort_indexes(const std::vector &v) { + + // initialize original index locations + std::vector idx(v.size()); + std::iota(idx.begin(), idx.end(), 0); + + // sort indexes based on comparing values in v + // using std::stable_sort instead of std::sort + // to avoid unnecessary index re-orderings + // when v contains elements of equal values + stable_sort(idx.begin(), idx.end(), + [&v](size_t i1, size_t i2) { return v[i1] < v[i2]; }); + + return idx; +} + +//Access environment variables + +std::string getEnvVar(std::string const &key); + +template +class OrthogonaPMatrixLibrary { +private: + std::map, Matrix > matrices; + + void build(int L, int U) { + Eigen::MatrixXd cheb_P = ChebTools::u_matrix_library.get(U - 1).block(0, 0, U, L); + Matrix P_tmp = cheb_P.cast(); + Matrix P_ortho = P_tmp.householderQr().householderQ(); + P_ortho.colwise().hnormalized(); + matrices[std::pair(L, U)] = P_ortho.block(0, 0, U, L); + } + +public: + /// Get the \f$\mathbf{U}\f$ matrix of degree N + const Matrix &get(int L, int U) { + auto it = matrices.find(std::pair(L, U)); + if (it != matrices.end()) { + return it->second; + } else { + build(L, U); + return matrices.find(std::pair(L, U))->second; + } + } +}; + +template +static OrthogonaPMatrixLibrary orthogonal_P_Matrix_library; + + +template class CustomLLT : public Eigen::LLT<_MatrixType, _UpLo> { +public: + + typedef _MatrixType MatrixType; + typedef typename MatrixType::Scalar Scalar; + + CustomLLT<_MatrixType, _UpLo>() : Eigen::LLT<_MatrixType, _UpLo>() {}; + + CustomLLT<_MatrixType, _UpLo>(unsigned int n) : Eigen::LLT<_MatrixType, _UpLo>(n) {}; + + void copy_and_scale(const CustomLLT<_MatrixType, _UpLo> & other, Scalar scalar){ + this->m_matrix = other.m_matrix * scalar; + this->m_l1_norm = other.m_l1_norm; + this->m_isInitialized = other.m_isInitialized; + this->m_info = other.m_info; + } +}; + +#endif //SOS_UTILS_H + + + diff --git a/src/volesti/include/volume/copulas.h b/src/volesti/include/volume/copulas.h new file mode 100644 index 00000000..11a89492 --- /dev/null +++ b/src/volesti/include/volume/copulas.h @@ -0,0 +1,244 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2018 Vissarion Fisikopoulos, Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. + +// VolEsti is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or (at +// your option) any later version. +// +// VolEsti is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// See the file COPYING.LESSER for the text of the GNU Lesser General +// Public License. If you did not receive this file along with HeaDDaCHe, +// see . + +#ifndef COPULAS_H +#define COPULAS_H + + +template +std::vector > twoParHypFam(const int dim, + const int num, + const int num_slices, + const std::vector &pl1, const std::vector &pl2, + double seed = std::numeric_limits::signaling_NaN()) +{ + + int i,j,col,row; + std::vector vec1,vec2,Zs1,Zs2; + NT sum1,sum2; + std::list points; + typename std::list::iterator rpit; + std::pair< std::vector,std::vector > result; + Point p; + + Sam_Canon_Unit (dim, num, points, seed); + + std::vector > Matrix(num_slices); + std::vector > pos_Matrix(num_slices); + for (i=0; i +std::vector > hypfam_ellfam(int dim, + int num, + int num_slices, + std::vector pl, + ellipsoid G, + double seed = std::numeric_limits::signaling_NaN()) +{ + + int i,j,col,row; + std::vector vec1,vec2,Zs1,Cs; + NT sum1,sum2; + std::list points; + typename std::list::iterator rpit; + std::pair< std::vector,std::vector > result; + Point p; + + Sam_Canon_Unit (dim, num, points, seed); + + std::vector > Matrix(num_slices); + std::vector > pos_Matrix(num_slices); + for (i=0; i +#include +#include + +// From rosetta code at http://rosettacode.org/wiki/Combinations#C.2B.2B +// We made some adjustments to vectorize the output +// Compute all the N combinations from N elements +inline std::vector< std::vector > comb(int N, int K) +{ + std::string bitmask(K, 1); // K leading 1's + bitmask.resize(N, 0); // N-K trailing 0's + std::vector iter_comb(K,0); + std::vector > combs; + int count; + + // print integers and permute bitmask + do { + count = 0; + for (int i = 0; i < N; ++i) // [0..N-1] integers + { + if (bitmask[i]){ + iter_comb[count] = i; + count++; + } + } + combs.push_back(iter_comb); + } while (std::prev_permutation(bitmask.begin(), bitmask.end())); + return combs; +} + + +template +NT exact_zonotope_vol(const Polytope &ZP){ + + typedef typename Polytope::MT MT; + typedef std::vector< std::vector >::iterator IntMatIt; + typedef std::vector::iterator IntIt; + + int n = ZP.dimension(), col, k = ZP.num_of_generators(); + MT V1 = ZP.get_mat().transpose(), SubV(n,n), V(n, 2*k); + V << V1, -V1; + NT vol = 0.0; + + std::vector< std::vector > combs = comb(2*k, n); + IntMatIt iter_combs; + IntIt it; + + iter_combs = combs.begin(); + for ( ; iter_combs!=combs.end(); ++iter_combs) { + it = (*iter_combs).begin(); + col = 0; + // construct all the nxn submatrices + for ( ; it!=(*iter_combs).end(); ++it, ++col) { + SubV.col(col) = V.col(*it); + } + vol += std::abs(SubV.determinant()); + } + return vol; +} + +template +NT vol_Ali(std::vector &plane, const NT &zit, const unsigned int dim) { + + unsigned int i, J = 0, K = 0, k; + std::vector Y(dim, 0.0), X(dim, 0.0), a(dim + 1, 0.0); + + for (i = 0; i < dim; i++) { + + if (plane[i] + zit < 0) { + X[J] = plane[i] + zit; + J++; + } else { + Y[K] = plane[i] + zit; + K++; + } + } + + a[0] = 1.0; + if (J>0) { + for (i = 0; i < J; i++) { + for (k = 0; k < K; k++) { + a[k+1] = (Y[k] * a[k+1] - X[i] * a[k]) / (Y[k] - X[i]); + } + } + } + return a[K]; +} + +#endif diff --git a/src/volesti/include/volume/math_helpers.hpp b/src/volesti/include/volume/math_helpers.hpp new file mode 100644 index 00000000..11e2742b --- /dev/null +++ b/src/volesti/include/volume/math_helpers.hpp @@ -0,0 +1,46 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis +// Copyright (c) 2021 Vaibhav Thakkar + +// Contributed and/or modified by Vaibhav Thakkar, as part of Google Summer of Code 2021 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef MATH_HELPERS_HPP +#define MATH_HELPERS_HPP + +#include +#include + + +//An implementation of Welford's algorithm for mean and variance. +template +std::pair get_mean_variance(std::vector& vec) +{ + NT mean = 0; + NT M2 = 0; + NT variance = 0; + NT delta; + + unsigned int i=0; + for (auto vecit = vec.begin(); vecit!=vec.end(); vecit++, i++) + { + delta = *vecit - mean; + mean += delta / (i + 1); + M2 += delta * (*vecit - mean); + variance = M2 / (i + 1); + } + return std::pair (mean, variance); +} + + +template +static NT log_gamma_function(NT x) +{ + if (x <= NT(100)) return std::log(tgamma(x)); + return (std::log(x - NT(1)) + log_gamma_function(x - NT(1))); +} + +#endif // MATH_HELPERS_HPP \ No newline at end of file diff --git a/src/volesti/include/volume/rotating.hpp b/src/volesti/include/volume/rotating.hpp new file mode 100644 index 00000000..64c2198b --- /dev/null +++ b/src/volesti/include/volume/rotating.hpp @@ -0,0 +1,64 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2019 Vissarion Fisikopoulos +// Copyright (c) 2019 Apostolos Chalkis + + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef ROTATING_H +#define ROTATING_H + +#include + +template +MT rotating(Polytope &P){ + + typedef boost::mt19937 RNGType; + unsigned rng_seed = std::chrono::system_clock::now().time_since_epoch().count(); + RNGType rng(rng_seed); + boost::random::uniform_real_distribution<> urdist(-1.0, 1.0); + unsigned int n = P.dimension(); + + // pick a random rotation + MT R(n,n); + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + R(i,j) = urdist(rng); + } + } + + Eigen::JacobiSVD svd(R, Eigen::ComputeFullU | Eigen::ComputeFullV); + + // apply rotation to the polytope P + P.linear_transformIt(svd.matrixU()); + + return svd.matrixU().inverse(); +} + + +template +MT rotating(Polytope &P, unsigned seed){ + + typedef boost::mt19937 RNGType; + RNGType rng(seed); + boost::random::uniform_real_distribution<> urdist(-1.0, 1.0); + unsigned int n = P.dimension(); + + // pick a random rotation + MT R(n,n); + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + R(i,j) = urdist(rng); + } + } + + Eigen::JacobiSVD svd(R, Eigen::ComputeFullU | Eigen::ComputeFullV); + + // apply rotation to the polytope P + P.linear_transformIt(svd.matrixU()); + + return svd.matrixU().inverse(); +} + +#endif diff --git a/src/volesti/include/volume/sampling_policies.hpp b/src/volesti/include/volume/sampling_policies.hpp new file mode 100644 index 00000000..6645fcb6 --- /dev/null +++ b/src/volesti/include/volume/sampling_policies.hpp @@ -0,0 +1,50 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef SAMPLING_POLICIES_HPP +#define SAMPLING_POLICIES_HPP + +struct PushBackWalkPolicy +{ + template + void apply(PointList &randPoints, + Point &p) const + { + randPoints.push_back(p); + } +}; + +template +struct CountingWalkPolicy +{ + CountingWalkPolicy(unsigned int const& nump_PBSmall, BallPoly const& PBSmall) + : _nump_PBSmall(nump_PBSmall) + , _PBSmall(PBSmall) + {} + + template + void apply(PointList &randPoints, + Point &p) + { + if (_PBSmall.second().is_in(p) == -1)//is in + { + randPoints.push_back(p); + ++_nump_PBSmall; + } + } + + unsigned int get_nump_PBSmall() const + { + return _nump_PBSmall; + } + +private : + unsigned int _nump_PBSmall; + BallPoly _PBSmall; +}; + +#endif // SAMPLING_POLICIES_HPP diff --git a/src/volesti/include/volume/volume_cooling_balls.hpp b/src/volesti/include/volume/volume_cooling_balls.hpp new file mode 100644 index 00000000..d90604fa --- /dev/null +++ b/src/volesti/include/volume/volume_cooling_balls.hpp @@ -0,0 +1,853 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Repouskos Panagiotis, as part of Google Summer of Code 2019 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLUME_COOLING_BALLS_HPP +#define VOLUME_COOLING_BALLS_HPP + +#include +#include + +#include "cartesian_geom/cartesian_kernel.h" +#include "convex_bodies/hpolytope.h" +#ifndef DISABLE_LPSOLVE + #include "convex_bodies/vpolytope.h" + #include "convex_bodies/zpolytope.h" + #include "convex_bodies/vpolyintersectvpoly.h" +#endif +#include "random_walks/uniform_cdhr_walk.hpp" +#include "convex_bodies/ball.h" +#include "convex_bodies/ballintersectconvex.h" +#include "sampling/random_point_generators.hpp" +#include "volume/math_helpers.hpp" + + +//////////////////////////////////// +// ball annealing + +template +struct cooling_ball_parameters +{ + cooling_ball_parameters(unsigned int win_len) + : lb(0.1) + , ub(0.15) + , p(0.75) + , rmax(0) + , alpha(0.2) + , win_len(win_len) + , N(125) + , nu(10) + , window2(false) + {} + + NT lb; + NT ub; + NT p; + NT rmax; + NT alpha; + int win_len; + int N; + int nu; + bool window2; +}; + +/// Helpers + +template +bool check_convergence(ConvexBody &P, + PointList const& randPoints, + bool& too_few, + NT& ratio, + int const& nu, + bool const& precheck, + bool const& lastball, + cooling_ball_parameters const& parameters) +{ + NT alpha = parameters.alpha; + std::vector ratios; + std::pair mv; + int m = randPoints.size()/nu; + int i = 1; + NT T; + NT rs; + NT alpha_check = 0.01; + size_t countsIn = 0; + + for (auto pit=randPoints.begin(); pit!=randPoints.end(); ++pit, i++) + { + if (P.is_in(*pit)==-1) countsIn++; + if (i % m == 0) + { + ratios.push_back(NT(countsIn)/m); + countsIn = 0; + if (ratios.size()>1 && precheck) + { + boost::math::students_t dist(ratios.size() - 1); + mv = get_mean_variance(ratios); + ratio = mv.first; + rs = std::sqrt(mv.second); + T = rs * (boost::math::quantile + (boost::math::complement(dist, alpha_check / 2.0)) + / std::sqrt(NT(ratios.size()))); + if (ratio + T < parameters.lb) + { + too_few = true; + return false; + } else if (ratio - T > parameters.ub) return false; + } + } + } + + if (precheck) alpha *= 0.5; + mv = get_mean_variance(ratios); + ratio = mv.first; + rs = std::sqrt(mv.second); + boost::math::students_t dist(nu - 1); + T = rs * (boost::math::quantile(boost::math::complement(dist, alpha)) + / std::sqrt(NT(nu))); + if (ratio > parameters.lb + T) + { + if (lastball) return true; + if ((precheck && ratio < parameters.ub - T) + || (!precheck && ratio < parameters.ub + T)) return true; + return false; + } + too_few = true; + return false; +} + + +template +bool get_first_ball(Polytope &P, + Ball& B0, + NT& ratio, + NT const& radius_input, + cooling_ball_parameters const& parameters, + RNG& rng) { + const unsigned max_iterarions = 20; + NT tolerance = 0.00000000001; + typedef typename Polytope::PointType Point; + int n = P.dimension(); + int iter = 1; + bool bisection_int = false; + bool pass = false; + bool too_few = false; + std::list randPoints; + NT rmax = parameters.rmax; + NT sqrt_n = std::sqrt(NT(n)); + NT radius1 = radius_input; + + if (rmax > 0.0) { + for (int i = 0; i < 1200; ++i) { + randPoints.push_back(GetPointInDsphere::apply(n, rmax, rng)); + } + pass = check_convergence(P, randPoints, too_few, ratio, + 10, true, false, parameters); + if (pass || !too_few) { + B0 = Ball(Point(n), rmax * rmax); + return true; + } + bisection_int = true; + } else { + rmax = 2 * sqrt_n * radius1; + } + NT radius = radius1; + + while (!bisection_int) { + randPoints.clear(); + too_few = false; + + for (int i = 0; i < 1200; ++i) { + randPoints.push_back(GetPointInDsphere::apply(n, rmax, rng)); + } + + if (check_convergence(P, randPoints, too_few, ratio, 10, + true, false, parameters)) { + B0 = Ball(Point(n), rmax * rmax); + return true; + } + + if (too_few) break; + radius1 = rmax; + rmax = rmax + 2 * sqrt_n * radius; + } + + NT rad_med; + NT rad0 = radius1; + NT rad_m = rmax; + + while (iter <= max_iterarions) { + rad_med = 0.5 * (radius1 + rmax); + randPoints.clear(); + too_few = false; + + for (int i = 0; i < 1200; ++i) { + randPoints.push_back(GetPointInDsphere::apply(n, rad_med, rng)); + } + + if (check_convergence(P, randPoints, too_few, ratio, 10, + true, false, parameters)) { + B0 = Ball(Point(n), rad_med * rad_med); + return true; + } + + if (too_few) { + rmax = rad_med; + } else { + radius1 = rad_med; + } + + if (rmax - radius1 < tolerance) { + radius1 = rad0; + rmax = rad_m; + iter++; + } + } + return false; +} + + +template +bool get_next_zonotopeball(std::vector& BallSet, + PointList const& randPoints, + NT const& rad_min, + std::vector& ratios, + cooling_ball_parameters const& parameters) +{ + const unsigned max_iterarions = 20; + NT tolerance = 0.00000000001; + int n = (*randPoints.begin()).dimension(); + int iter = 1; + bool too_few; + NT radmax = NT(0); + NT radmin = rad_min; + + for (auto rpit = randPoints.begin(); rpit!=randPoints.end(); ++rpit) + { + NT pnorm = (*rpit).squared_length(); + if (pnorm > radmax) radmax = pnorm; + } + ball Biter; + radmax = std::sqrt(radmax); + NT radmin_init = radmin; + NT radmax_init = radmax; + + while (iter <= max_iterarions) + { + NT rad = 0.5 * (radmin + radmax); + Biter = ball(Point(n), rad * rad); + too_few = false; + + NT ratio; + if (check_convergence(Biter, randPoints, too_few, ratio, + parameters.nu, false, false, parameters)) + { + BallSet.push_back(Biter); + ratios.push_back(ratio); + return true; + } + + if (too_few) + { + radmin = rad; + } else + { + radmax = rad; + } + + if (radmax-radmin < tolerance) + { + radmin = radmin_init; + radmax = radmax_init; + iter++; + } + } + return false; +} + + +template +< + typename RandomPointGenerator, + typename PolyBall, + typename ball, + typename Polytope, + typename NT, + typename RNG +> +bool get_sequence_of_polytopeballs(Polytope &P, + std::vector& BallSet, + std::vector& ratios, + int const& Ntot, + NT const& radius, + unsigned int const& walk_length, + cooling_ball_parameters const& parameters, + RNG& rng) +{ + typedef typename Polytope::PointType Point; + bool fail; + int n = P.dimension(); + NT ratio; + NT ratio0; + std::list randPoints; + ball B0; + Point q(n); + + if ( !get_first_ball(P, B0, ratio, radius, parameters, rng) ) + { + return false; + } + + ratio0 = ratio; + + PushBackWalkPolicy push_back_policy; + RandomPointGenerator::apply(P, q, Ntot, walk_length, + randPoints, push_back_policy, rng); + + if (check_convergence(B0, randPoints, + fail, ratio, parameters.nu, + false, true, parameters)) + { + ratios.push_back(ratio); + BallSet.push_back(B0); + ratios.push_back(ratio0); + return true; + } + if ( !get_next_zonotopeball(BallSet, randPoints, B0.radius(), ratios, + parameters) ) + { + return false; + } + + while (true) + { + PolyBall zb_it(P, BallSet[BallSet.size()-1]); + q.set_to_origin(); + randPoints.clear(); + + RandomPointGenerator::apply(zb_it, q, Ntot, walk_length, + randPoints, push_back_policy, rng); + if (check_convergence(B0, randPoints, fail, ratio, parameters.nu, + false, true, parameters)) + { + ratios.push_back(ratio); + BallSet.push_back(B0); + ratios.push_back(ratio0); + return true; + } + if ( !get_next_zonotopeball(BallSet, randPoints, B0.radius(), + ratios, parameters) ) + { + return false; + } + } +} + + +//////////////////////////////////// +/// +/// ratio estimation + +template +bool is_max_error(NT const& a, NT const& b, NT const& error) +{ + return ((b-a)/a +struct estimate_ratio_parameters +{ +public: + + estimate_ratio_parameters(unsigned int W_len, unsigned int N, NT ratio) + : min_val(std::numeric_limits::lowest()) + , max_val(std::numeric_limits::max()) + , max_iterations_estimation(10000000) + , min_index(W_len-1) + , max_index(W_len-1) + , W(W_len) + , index(0) + , tot_count(N) + , count_in(N * ratio) + , iter(0) + , last_W(std::vector(W_len)) + , minmaxIt(last_W.begin()) + {} + + NT min_val; + NT max_val; + const unsigned int max_iterations_estimation; + unsigned int min_index; + unsigned int max_index; + unsigned int W; + unsigned int index; + size_t tot_count; + size_t count_in; + unsigned int iter; + std::vector last_W; + typename std::vector::iterator minmaxIt; +}; + +template +bool estimate_ratio_generic(Pollyball &Pb2, Point const& p, NT const& error, + estimate_ratio_parameters &ratio_parameters) +{ + if (ratio_parameters.iter++ <= ratio_parameters.max_iterations_estimation) + { + if (Pb2.is_in(p) == -1) ratio_parameters.count_in = ratio_parameters.count_in + 1.0; + + ratio_parameters.tot_count = ratio_parameters.tot_count + 1.0; + NT val = NT(ratio_parameters.count_in) / NT(ratio_parameters.tot_count); + ratio_parameters.last_W[ratio_parameters.index] = val; + + if (val <= ratio_parameters.min_val) + { + ratio_parameters.min_val = val; + ratio_parameters.min_index = ratio_parameters.index; + } else if (ratio_parameters.min_index == ratio_parameters.index) + { + ratio_parameters.minmaxIt = std::min_element(ratio_parameters.last_W.begin(), ratio_parameters.last_W.end()); + ratio_parameters.min_val = (*ratio_parameters.minmaxIt); + ratio_parameters.min_index = std::distance(ratio_parameters.last_W.begin(), ratio_parameters.minmaxIt); + } + + if (val >= ratio_parameters.max_val) + { + ratio_parameters.max_val = val; + ratio_parameters.max_index = ratio_parameters.index; + } else if (ratio_parameters.max_index == ratio_parameters.index) + { + ratio_parameters.minmaxIt = std::max_element(ratio_parameters.last_W.begin(), ratio_parameters.last_W.end()); + ratio_parameters.max_val = (*ratio_parameters.minmaxIt); + ratio_parameters.max_index = std::distance(ratio_parameters.last_W.begin(), ratio_parameters.minmaxIt); + } + + if ( (ratio_parameters.max_val - ratio_parameters.min_val) / ratio_parameters.max_val <= error/2.0 ) + { + return true; + } + + ratio_parameters.index = ratio_parameters.index % ratio_parameters.W + 1; + if (ratio_parameters.index == ratio_parameters.W) ratio_parameters.index = 0; + + return false; + } + return true; +} + + +template +< + typename WalkType, + typename Point, + typename PolyBall1, + typename PolyBall2, + typename NT, + typename RNG +> +NT estimate_ratio(PolyBall1 &Pb1, + PolyBall2 &Pb2, + NT const& ratio, + NT const& error, + unsigned int const& W, + unsigned int const& Ntot, + unsigned int const& walk_length, + RNG& rng) +{ + estimate_ratio_parameters ratio_parameters(W, Ntot, ratio); + unsigned int n = Pb1.dimension(); + Point p(n); + WalkType walk(Pb1, p, rng); + + do + { + walk.template apply(Pb1, p, walk_length, rng); + } while(!estimate_ratio_generic(Pb2, p, error, ratio_parameters)); + + return NT(ratio_parameters.count_in) / NT(ratio_parameters.tot_count); +} + +template +< typename Point, + typename ball, + typename PolyBall, + typename NT, + typename RNG +> +NT estimate_ratio(ball const& B, + PolyBall &Pb2, + NT const& ratio, + NT const& error, + int const& W, + int const& Ntot, + RNG& rng) +{ + estimate_ratio_parameters ratio_parameters(W, Ntot, ratio); + unsigned int n = B.dimension(); + Point p(n); + NT radius = B.radius(); + + do + { + p = GetPointInDsphere::apply(n, radius, rng); + } while(!estimate_ratio_generic(Pb2, p, error, ratio_parameters)); + + return NT(ratio_parameters.count_in) / NT(ratio_parameters.tot_count); +} + +//-------------------------------------------------------------------------- + +template +struct estimate_ratio_interval_parameters +{ +public: + estimate_ratio_interval_parameters(unsigned int W_len, + unsigned int N, + NT ratio) + : mean(0) + , sum_sq(0) + , sum(0) + , s(0) + , max_iterations_estimation(10000000) + , W(W_len) + , index(0) + , tot_count(N) + , count_in(N * ratio) + , iter(0) + , last_W(std::vector(W_len)) + {} + + NT mean; + NT sum_sq; + NT sum; + NT s; + const unsigned int max_iterations_estimation; + unsigned int W; + unsigned int index; + size_t tot_count; + size_t count_in; + unsigned int iter; + std::vector last_W; +}; + +template +void full_sliding_window(Pollyball &Pb2, + Point const& p, + estimate_ratio_interval_parameters& ratio_parameters) +{ + if (Pb2.is_in(p) == -1) ratio_parameters.count_in = ratio_parameters.count_in + 1.0; + + ratio_parameters.tot_count = ratio_parameters.tot_count + 1.0; + NT val = NT(ratio_parameters.count_in) / NT(ratio_parameters.tot_count); + ratio_parameters.sum += val; + ratio_parameters.sum_sq += val * val; + ratio_parameters.last_W[ratio_parameters.index] = val; + ratio_parameters.index = ratio_parameters.index % ratio_parameters.W + 1; + if (ratio_parameters.index == ratio_parameters.W) ratio_parameters.index = 0; +} + +template +bool estimate_ratio_interval_generic(Pollyball &Pb2, + Point const& p, + NT const& error, + NT const& zp, + estimate_ratio_interval_parameters + & ratio_parameters) +{ + if (ratio_parameters.iter++ <= ratio_parameters.max_iterations_estimation) + { + if (Pb2.is_in(p) == -1) ratio_parameters.count_in = ratio_parameters.count_in + 1.0; + + ratio_parameters.tot_count = ratio_parameters.tot_count + 1.0; + NT val = NT(ratio_parameters.count_in) / NT(ratio_parameters.tot_count); + + ratio_parameters.mean = (ratio_parameters.mean + - ratio_parameters.last_W[ratio_parameters.index] / + NT(ratio_parameters.W)) + val / NT(ratio_parameters.W); + + ratio_parameters.sum_sq = (ratio_parameters.sum_sq - + ratio_parameters.last_W[ratio_parameters.index] + * ratio_parameters.last_W[ratio_parameters.index]) + + val * val; + + ratio_parameters.sum = (ratio_parameters.sum + - ratio_parameters.last_W[ratio_parameters.index]) + + val; + + ratio_parameters.s = std::sqrt((ratio_parameters.sum_sq + NT(ratio_parameters.W) * + ratio_parameters.mean * ratio_parameters.mean - NT(2) + * ratio_parameters.mean + * ratio_parameters.sum) / + NT(ratio_parameters.W)); + + ratio_parameters.last_W[ratio_parameters.index] = val; + + ratio_parameters.index = ratio_parameters.index % ratio_parameters.W + 1; + if (ratio_parameters.index == ratio_parameters.W) + { + ratio_parameters.index = 0; + } + + if (is_max_error(val - zp * ratio_parameters.s, + val + zp * ratio_parameters.s, + error)) + { + return true; + } + return false; + } + return true; +} + +template +< + typename Point, + typename ball, + typename PolyBall2, + typename NT, + typename RNG +> +NT estimate_ratio_interval(ball const& B, + PolyBall2 &Pb2, + NT const& ratio, + NT const& error, + int const& W, + int const& Ntot, + NT const& prob, + RNG& rng) +{ + estimate_ratio_interval_parameters ratio_parameters(W, Ntot, ratio); + boost::math::normal dist(0.0, 1.0); + NT zp = boost::math::quantile(boost::math::complement(dist, (1.0 - prob)/2.0)); + NT radius = B.radius(); + + unsigned int n = Pb2.dimension(); + Point p(n); + + for (int i = 0; i < ratio_parameters.W; ++i) + { + p = GetPointInDsphere::apply(n, radius, rng); + full_sliding_window(Pb2, p, ratio_parameters); + } + + ratio_parameters.mean = ratio_parameters.sum / NT(ratio_parameters.W); + + do { + p = GetPointInDsphere::apply(n, radius, rng); + } while (!estimate_ratio_interval_generic(Pb2, p, error, zp, ratio_parameters)); + + return NT(ratio_parameters.count_in) / NT(ratio_parameters.tot_count); +} + + +template +< + typename WalkType, + typename Point, + typename PolyBall1, + typename PolyBall2, + typename NT, + typename RNG +> +NT estimate_ratio_interval(PolyBall1 &Pb1, + PolyBall2 &Pb2, + NT const& ratio, + NT const& error, + int const& W, + int const& Ntot, + NT const& prob, + unsigned int const& walk_length, + RNG& rng) +{ + estimate_ratio_interval_parameters ratio_parameters(W, Ntot, ratio); + boost::math::normal dist(0.0, 1.0); + NT zp = boost::math::quantile(boost::math::complement(dist, (1.0 - prob)/2.0)); + + unsigned int n = Pb1.dimension(); + Point p(n); + WalkType walk(Pb1, p, rng); + + for (int i = 0; i < ratio_parameters.W; ++i) + { + walk.template apply(Pb1, p, walk_length, rng); + full_sliding_window(Pb2, p, ratio_parameters); + } + + ratio_parameters.mean = ratio_parameters.sum / NT(ratio_parameters.W); + + do { + walk.template apply(Pb1, p, walk_length, rng); + } while (!estimate_ratio_interval_generic(Pb2, p, error, zp, ratio_parameters)); + + return NT(ratio_parameters.count_in) / NT(ratio_parameters.tot_count); +} + +template +< + typename WalkTypePolicy, + typename Polytope, + typename RandomNumberGenerator = BoostRandomNumberGenerator +> +std::pair volume_cooling_balls(Polytope& Pin, + RandomNumberGenerator &rng, + double const& error = 0.1, + unsigned int const& walk_length = 1, + unsigned int const& win_len = 300) +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + typedef Ball BallType; + typedef BallIntersectPolytope PolyBall; + typedef typename Polytope::VT VT; + typedef std::list PointList; + + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > WalkType; + typedef RandomPointGenerator RandomPointGenerator; + + auto P(Pin); + cooling_ball_parameters parameters(win_len); + + int n = P.dimension(); + NT prob = parameters.p; + int N_times_nu = parameters.N * parameters.nu; + + auto InnerBall = P.ComputeInnerBall(); + if (InnerBall.second < 0.0) return std::pair (-1.0, 0.0); + + NT radius = InnerBall.second; + Point c = InnerBall.first; + + std::vector BallSet; + std::vector ratios; + + // move the chebychev center to the origin + // and apply the same shifting to the polytope + P.shift(c.getCoefficients()); + + if ( !get_sequence_of_polytopeballs + < + RandomPointGenerator, + PolyBall + >(P, BallSet, ratios, + N_times_nu, radius, walk_length, + parameters, rng) ) + { + return std::pair (-1.0, 0.0); + } + + NT vol = (NT(n)/NT(2) * std::log(M_PI)) + NT(n)*std::log((*(BallSet.end() - 1)).radius()) - log_gamma_function(NT(n) / NT(2) + 1); + + int mm = BallSet.size() + 1; + prob = std::pow(prob, 1.0 / NT(mm)); + NT er0 = error / (2.0 * std::sqrt(NT(mm))); + NT er1 = (error * std::sqrt(4.0 * NT(mm) - 1)) / (2.0 * std::sqrt(NT(mm))); + + vol += (parameters.window2) ? + std::log(estimate_ratio(*(BallSet.end() - 1), + P, *(ratios.end() - 1), + er0, parameters.win_len, 1200, rng)) + : std::log(estimate_ratio_interval(*(BallSet.end() - 1), + P, *(ratios.end() - 1), + er0, parameters.win_len, 1200, + prob, rng)); + auto balliter = BallSet.begin(); + auto ratioiter = ratios.begin(); + + er1 = er1 / std::sqrt(NT(mm) - 1.0); + + if (*ratioiter != 1) + { + vol += (!parameters.window2) ? + std::log(NT(1) / estimate_ratio_interval + (P, + *balliter, + *ratioiter, + er1, + parameters.win_len, + N_times_nu, + prob, + walk_length, + rng)) + : std::log(NT(1) / estimate_ratio + (P, + *balliter, + *ratioiter, + er1, + parameters.win_len, + N_times_nu, + walk_length, + rng)); + } + + for ( ; balliter < BallSet.end() - 1; ++balliter, ++ratioiter) + { + PolyBall Pb(P, *balliter); + vol += (!parameters.window2) ? + std::log(NT(1) / estimate_ratio_interval + (Pb, + *(balliter + 1), + *(ratioiter + 1), + er1, parameters.win_len, + N_times_nu, + prob, walk_length, + rng)) + : std::log(NT(1) / estimate_ratio + (Pb, + *balliter, + *ratioiter, + er1, + parameters.win_len, + N_times_nu, + walk_length, + rng)); + } + + return std::pair (vol, std::exp(vol)); +} + + + +template +< + typename WalkTypePolicy = CDHRWalk, + typename RandomNumberGenerator = BoostRandomNumberGenerator, + typename Polytope +> +std::pair volume_cooling_balls(Polytope &Pin, + double const& error = 0.1, + unsigned int const& walk_length = 1) +{ + RandomNumberGenerator rng(Pin.dimension()); + return volume_cooling_balls(Pin, rng, error, walk_length); +} + + +template +< + typename WalkTypePolicy = CDHRWalk, + typename RandomNumberGenerator = BoostRandomNumberGenerator, + typename Polytope +> +std::pair volume_cooling_balls(Polytope &Pin, + Cartesian::Point const& interior_point, + unsigned int const& walk_length = 1, + double const& error = 0.1) +{ + RandomNumberGenerator rng(Pin.dimension()); + Pin.set_interior_point(interior_point); + return volume_cooling_balls(Pin, rng, error, walk_length); +} + +#endif // VOLUME_COOLING_BALLS_HPP diff --git a/src/volesti/include/volume/volume_cooling_gaussians.hpp b/src/volesti/include/volume/volume_cooling_gaussians.hpp new file mode 100644 index 00000000..ca06ce31 --- /dev/null +++ b/src/volesti/include/volume/volume_cooling_gaussians.hpp @@ -0,0 +1,490 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLUME_COOLING_GAUSSIANS_HPP +#define VOLUME_COOLING_GAUSSIANS_HPP + +//#define VOLESTI_DEBUG + +#include +#include +#include +#include +#include + +#include "cartesian_geom/cartesian_kernel.h" +#include "random_walks/gaussian_helpers.hpp" +#include "random_walks/gaussian_ball_walk.hpp" +#include "random_walks/gaussian_cdhr_walk.hpp" +#include "sampling/random_point_generators.hpp" +#include "volume/math_helpers.hpp" + + +/////////////////// Helpers for random walks + +template +struct update_delta +{ + template + static void apply(WalkType, NT) {} +}; + +template +struct update_delta> +{ + template + static void apply(GaussianBallWalk::Walk walk, + NT delta) + { + walk.update_delta(delta); + } +}; + + +////////////////////////////// Algorithms + +// Gaussian Anealling + +// Implementation is based on algorithm from paper "A practical volume algorithm", +// Springer-Verlag Berlin Heidelberg and The Mathematical Programming Society 2015 +// Ben Cousins, Santosh Vempala + +// Compute the first variance a_0 for the starting gaussian +template +void get_first_gaussian(Polytope& P, + NT const& frac, + NT const& chebychev_radius, + NT const& error, + std::vector & a_vals) +{ + // if tol is smaller than 1e-6 no convergence can be obtained when float is used + NT tol = std::is_same::value ? 0.001 : 0.0000001; + + std::vector dists = P.get_dists(chebychev_radius); + NT lower = 0.0; + NT upper = 1.0; + + // Compute an upper bound for a_0 + unsigned int i; + const unsigned int maxiter = 10000; + for (i= 1; i <= maxiter; ++i) { + NT sum = 0.0; + for (auto it = dists.begin(); it != dists.end(); ++it) + { + sum += std::exp(-upper * std::pow(*it, 2.0)) + / (2.0 * (*it) * std::sqrt(M_PI * upper)); + } + + if (sum > frac * error) + { + upper = upper * 10; + } else { + break; + } + } + + if (i == maxiter) { +#ifdef VOLESTI_DEBUG + std::cout << "Cannot obtain sharp enough starting Gaussian" << std::endl; +#endif + return; + } + + //get a_0 with binary search + while (upper - lower > tol) + { + NT mid = (upper + lower) / 2.0; + NT sum = 0.0; + for (auto it = dists.begin(); it != dists.end(); ++it) { + sum += std::exp(-mid * std::pow(*it, 2.0)) + / (2.0 * (*it) * std::sqrt(M_PI * mid)); + } + + if (sum < frac * error) { + upper = mid; + } else { + lower = mid; + } + } + a_vals.push_back((upper + lower) / NT(2.0)); +} + + +// Compute a_{i+1} when a_i is given +template +< + typename RandomPointGenerator, + typename Polytope, + typename Point, + typename NT, + typename RandomNumberGenerator +> +NT get_next_gaussian(Polytope& P, + Point &p, + NT const& a, + const unsigned int &N, + const NT &ratio, + const NT &C, + const unsigned int& walk_length, + RandomNumberGenerator& rng) +{ + + NT last_a = a; + NT last_ratio = 0.1; + //k is needed for the computation of the next variance a_{i+1} = a_i * (1-1/d)^k + NT k = 1.0; + const NT tol = 0.00001; + bool done=false; + std::vector fn(N,NT(0.0)); + std::list randPoints; + typedef typename std::vector::iterator viterator; + + //sample N points + PushBackWalkPolicy push_back_policy; + RandomPointGenerator::apply(P, p, last_a, N, walk_length, randPoints, + push_back_policy, rng); + + while (!done) + { + NT new_a = last_a * std::pow(ratio,k); + + auto fnit = fn.begin(); + for (auto pit=randPoints.begin(); pit!=randPoints.end(); ++pit, fnit++) + { + *fnit = eval_exp(*pit, new_a)/eval_exp(*pit, last_a); + } + std::pair mv = get_mean_variance(fn); + + // Compute a_{i+1} + if (mv.second/(mv.first * mv.first)>=C || mv.first/last_ratio<1.0+tol) + { + if (k != 1.0) + { + k = k / 2; + } + done = true; + } else { + k = 2 * k; + } + last_ratio = mv.first; + } + return last_a * std::pow(ratio, k); +} + +// Compute the sequence of spherical gaussians +template +< + typename WalkType, + typename RandomPointGenerator, + typename Polytope, + typename NT, + typename RandomNumberGenerator +> +void compute_annealing_schedule(Polytope& P, + NT const& ratio, + NT const& C, + NT const& frac, + unsigned int const& N, + unsigned int const& walk_length, + NT const& chebychev_radius, + NT const& error, + std::vector& a_vals, + RandomNumberGenerator& rng) +{ + typedef typename Polytope::PointType Point; + typedef typename Polytope::VT VT; + + // Compute the first gaussian + get_first_gaussian(P, frac, chebychev_radius, error, a_vals); + +#ifdef VOLESTI_DEBUG + std::cout<<"first gaussian computed\n"< + (P, p, a_vals[it], N, ratio, C, walk_length, rng); + + NT curr_fn = 0; + NT curr_its = 0; + auto steps = totalSteps; + + WalkType walk(P, p, a_vals[it], rng); + //TODO: test update delta here? + + update_delta + ::apply(walk, 4.0 * chebychev_radius + / std::sqrt(std::max(NT(1.0), a_vals[it]) * NT(n))); + + // Compute some ratios to decide if this is the last gaussian + for (unsigned int j = 0; j < steps; j++) + { + walk.template apply(P, p, a_vals[it], walk_length, rng); + curr_its += 1.0; + curr_fn += eval_exp(p, next_a) / eval_exp(p, a_vals[it]); + } + + // Remove the last gaussian. + // Set the last a_i equal to zero + if (next_a>0 && curr_fn/curr_its>(1.0+tol)) + { + a_vals.push_back(next_a); + it++; + } else if (next_a <= 0) + { + a_vals.push_back(a_stop); + it++; + break; + } else { + a_vals[it] = a_stop; + break; + } + } +} + +template +struct gaussian_annealing_parameters +{ + gaussian_annealing_parameters(unsigned int d) + : frac(0.1) + , ratio(NT(1)-NT(1)/(NT(d))) + , C(NT(2)) + , N(500 * ((int) C) + ((int) (d * d / 2))) + , W(6*d*d+800) + {} + + NT frac; + NT ratio; + NT C; + unsigned int N; + unsigned int W; +}; + +template +< + typename WalkTypePolicy, + typename Polytope, + typename RandomNumberGenerator + +> +double volume_cooling_gaussians(Polytope& Pin, + RandomNumberGenerator& rng, + double const& error = 0.1, + unsigned int const& walk_length = 1) +{ + typedef typename Polytope::PointType Point; + typedef typename Point::FT NT; + typedef typename Polytope::VT VT; + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > WalkType; + typedef GaussianRandomPointGenerator RandomPointGenerator; + + //const NT maxNT = std::numeric_limits::max();//1.79769e+308; + //const NT minNT = std::numeric_limits::min();//-1.79769e+308; + + auto P(Pin); //copy and work with P because we are going to shift + unsigned int n = P.dimension(); + unsigned int m = P.num_of_hyperplanes(); + gaussian_annealing_parameters parameters(P.dimension()); + //RandomNumberGenerator rng(n); + + // Consider Chebychev center as an internal point + auto InnerBall = P.ComputeInnerBall(); + if (InnerBall.second < 0.0) return -1.0; + + Point c = InnerBall.first; + NT radius = InnerBall.second; + + // Move the chebychev center to the origin and apply the same shifting to the polytope + P.shift(c.getCoefficients()); + + // Computing the sequence of gaussians +#ifdef VOLESTI_DEBUG + std::cout<<"\n\nComputing annealing...\n"< a_vals; + NT ratio = parameters.ratio; + NT C = parameters.C; + unsigned int N = parameters.N; + + compute_annealing_schedule + < + WalkType, + RandomPointGenerator + >(P, ratio, C, parameters.frac, N, walk_length, radius, error, a_vals, rng); + +#ifdef VOLESTI_DEBUG + std::cout<<"All the variances of schedule_annealing computed in = " + << (double)clock()/(double)CLOCKS_PER_SEC-tstart2<<" sec"< last_W2(W,0); + std::vector fn(mm,0); + std::vector its(mm,0); + VT lamdas; + lamdas.setZero(m); + NT vol = std::pow(M_PI/a_vals[0], (NT(n))/2.0); + Point p(n); // The origin is the Chebychev center of the Polytope + unsigned int i=0; + + typedef typename std::vector::iterator viterator; + viterator itsIt = its.begin(); + viterator avalsIt = a_vals.begin(); + viterator minmaxIt; + +#ifdef VOLESTI_DEBUG + std::cout<<"volume of the first gaussian = "<::min(); + NT max_val = std::numeric_limits::max(); + unsigned int min_index = W-1; + unsigned int max_index = W-1; + unsigned int index = 0; + unsigned int min_steps = 0; + std::vector last_W = last_W2; + + // Set the radius for the ball walk + WalkType walk(P, p, *avalsIt, rng); + + update_delta + ::apply(walk, 4.0 * radius + / std::sqrt(std::max(NT(1.0), *avalsIt) * NT(n))); + + while (!done || (*itsIt)= max_val) + { + max_val = val; + max_index = index; + } else if (max_index == index) + { + minmaxIt = std::max_element(last_W.begin(), last_W.end()); + max_val = *minmaxIt; + max_index = std::distance(last_W.begin(), minmaxIt); + } + + if ( (max_val-min_val)/max_val <= curr_eps/2.0 ) + { + done=true; + } + + index = index%W + 1; + if (index == W) index = 0; + } +#ifdef VOLESTI_DEBUG + std::cout << "ratio " << i << " = " << (*fnIt) / (*itsIt) + << " N_" << i << " = " << *itsIt << std::endl; +#endif + vol *= ((*fnIt) / (*itsIt)); + } + +#ifdef VOLESTI_DEBUG + NT sum_of_steps = 0.0; + for(viterator it = its.begin(); it != its.end(); ++it) { + sum_of_steps += *it; + } + auto steps= int(sum_of_steps); + std::cout<<"\nTotal number of steps = "<, + typename Polytope +> +double volume_cooling_gaussians(Polytope &Pin, + double const& error = 0.1, + unsigned int const& walk_length = 1) +{ + RandomNumberGenerator rng(Pin.dimension()); + return volume_cooling_gaussians(Pin, rng, error, walk_length); +} + + +template +< + typename WalkTypePolicy = GaussianCDHRWalk, + typename RandomNumberGenerator = BoostRandomNumberGenerator, + typename Polytope +> +double volume_cooling_gaussians(Polytope &Pin, + Cartesian::Point const& interior_point, + unsigned int const& walk_length = 1, + double const& error = 0.1) +{ + RandomNumberGenerator rng(Pin.dimension()); + Pin.set_interior_point(interior_point); + + return volume_cooling_gaussians(Pin, rng, error, walk_length); +} + +#endif // VOLUME_COOLING_GAUSSIANS_HPP diff --git a/src/volesti/include/volume/volume_cooling_hpoly.hpp b/src/volesti/include/volume/volume_cooling_hpoly.hpp new file mode 100644 index 00000000..c8183384 --- /dev/null +++ b/src/volesti/include/volume/volume_cooling_hpoly.hpp @@ -0,0 +1,385 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLUME_COOLING_HPOLY_HPP +#define VOLUME_COOLING_HPOLY_HPP + +#include "volume/volume_cooling_gaussians.hpp" +#include "sampling/random_point_generators.hpp" +#include "preprocess/min_sampling_covering_ellipsoid_rounding.hpp" + + +template +< + typename RandomPointGenerator, + typename Zonotope, + typename HPolytope, + typename NT, + typename RNG, + typename VT +> +bool get_first_poly(Zonotope &P, + HPolytope &HP, + NT &ratio, + cooling_ball_parameters const& parameters, + RNG& rng, VT &b_max) +{ + + typedef typename Zonotope::PointType Point; + typedef typename Zonotope::MT MT; + + PushBackWalkPolicy push_back_policy; + + const unsigned max_iterarions = 20; + const NT tolerance = 0.00000000001; + + MT G = P.get_mat().transpose(), A = HP.get_mat(); + b_max = (A*G).cwiseAbs().rowwise().sum(); + VT b_min = HP.get_vec(); + HPolytope HPiter(HP); + + int n = P.dimension(), m = b_max.size(), N = 1200, iter = 1, count = 0; + Point q(n); + bool too_few, print = false; + std::list randPoints; + + NT l=0.0, u=1.0, med; + VT b_med(m); + + while(iter <= max_iterarions) { + + q=Point(n); + med = (u + l) * 0.5; + b_med = b_min + (b_max-b_min)*med; + HPiter.set_vec(b_med); + + randPoints.clear(); + RandomPointGenerator::apply(HPiter, q, 1200, 10+10*n, + randPoints, push_back_policy, rng); + too_few = false; + + if(check_convergence(P, randPoints, + too_few, ratio, parameters.nu, + true, false, parameters)){ + HP.set_vec(b_med); + return true; + } + + if (too_few) { + u = med; + } else { + l = med; + } + if(med>0.9) { + HP.set_vec(b_med); + return true; + } + if(u-l < tolerance) { + u=1.0; + l=0.0; + iter++; + } + } + return false; +} + + +template +bool get_next_zonoball(std::vector &HPolySet, + HPolytope &HP2, + const VT &b_max, + const VT &b_min, + PointList &randPoints, + std::vector &ratios, + cooling_ball_parameters const& parameters) +{ + + typedef typename Zonotope::PointType Point; + + const unsigned max_iterarions = 20; + const NT tolerance = 0.00000000001; + + int n = HP2.dimension(), iter = 1; + bool too_few; + VT b_med(b_max.size()); + NT ratio, med, u = 1.0, l = 0.0; + + while (iter <= max_iterarions) { + med = (u + l) * 0.5; + b_med = b_min + (b_max-b_min) * med; + HP2.set_vec(b_med); + too_few = false; + + if(check_convergence(HP2, randPoints, + too_few, ratio, parameters.nu, + false, false, parameters)){ + HPolySet.push_back(HP2); + ratios.push_back(ratio); + return true; + } + if(too_few) { + l = med; + } else { + u = med; + } + if(u-l < tolerance) { + u=1.0; + l=0.0; + iter++; + } + } + return false; +} + +template +< + typename RandomPointGenerator, + typename ZonoHP, + typename Zonotope, + typename HPolytope, + typename VT, + typename NT, + typename RNG +> +bool get_sequence_of_zonopolys(Zonotope &Z, + const HPolytope &HP, + std::vector &HPolySet, + std::vector &ratios, + const VT &b_max, + unsigned int const& N_times_nu, + unsigned int const& walk_length, + cooling_ball_parameters const& parameters, + RNG& rng) +{ + + bool too_few=false; + typedef typename Zonotope::PointType Point; + typedef typename Zonotope::MT MT; + + PushBackWalkPolicy push_back_policy; + + int n = Z.dimension(); + MT G = Z.get_mat().transpose(); + MT AG = HP.get_mat()*G; + NT ratio; + std::list randPoints; + Point q(n); + + RandomPointGenerator::apply(Z, q, N_times_nu, walk_length, + randPoints, push_back_policy, rng); + HPolytope HP2(HP); + if (check_convergence(HP, randPoints, + too_few, ratio, parameters.nu, + false, true, parameters)) { + ratios.push_back(ratio); + return true; + } + if ( !get_next_zonoball(HPolySet, HP2, b_max, HP.get_vec(), + randPoints, ratios, parameters)) + { + return false; + } + + VT Zs_min = HP.get_vec(); + + while (true) { + + ZonoHP ZHP2(Z,HP2); + q=Point(n); + randPoints.clear(); + RandomPointGenerator::apply(ZHP2, q, N_times_nu, walk_length, + randPoints, push_back_policy, rng); + + if (check_convergence(HP, randPoints, + too_few, ratio, parameters.nu, + false, true, parameters)) + { + ratios.push_back(ratio); + return true; + } + if ( !get_next_zonoball(HPolySet, HP2, HP2.get_vec(), + Zs_min, randPoints, ratios, parameters) ) + { + return false; + } + } +} + +// TODO: Rewrite to avoid some matrix operations and improve the signature +template + < + typename Zonotope, + typename HPolytope + > +HPolytope compute_hpoly_for_mmc(Zonotope const& P) { + typedef typename Zonotope::PointType Point; + typedef typename Zonotope::NT NT; + typedef typename Zonotope::VT VT; + typedef typename Zonotope::MT MT; + + MT V = P.get_mat(); + MT G = V.transpose(); + int m = G.cols(); + std::list randPoints; + + MT XX(m, 2*m); + XX << MT::Identity(m,m), -MT::Identity(m,m); + MT AA = XX.transpose(); VT b = VT::Ones(2*m); + MT T = P.get_T(); + MT Tt = T.transpose(); + MT A2 = AA * Tt, B = G * Tt; + MT A3 = A2 * B.inverse(); + + NT row_norm; + for (int i = 0; i < A3.rows(); ++i) { + row_norm = A3.row(i).norm(); + A3.row(i) = A3.row(i) / row_norm; + b(i) = b(i) / row_norm; + } + + return HPolytope(P.dimension(), A3, b); +} + + +template +< + typename WalkTypePolicy, + typename HPolytope, + typename Zonotope, + typename RandomNumberGenerator +> +double volume_cooling_hpoly (Zonotope const& Pin, + RandomNumberGenerator &rng, + double const& error = 1.0, + unsigned int const& walk_length = 1, + unsigned int const& win_len = 200) +{ + + typedef typename Zonotope::PointType Point; + typedef typename Point::FT NT; + typedef ZonoIntersectHPoly ZonoHP; + typedef typename Zonotope::VT VT; + typedef typename Zonotope::MT MT; + typedef std::list PointList; + + typedef typename WalkTypePolicy::template Walk WalkType; + typedef RandomPointGenerator ZonoRandomPointGenerator; + + typedef typename CDHRWalk::template Walk CdhrWalk; + typedef RandomPointGenerator CdhrRandomPointGenerator; + + auto P(Pin); + cooling_ball_parameters parameters(win_len); + + int n = P.dimension(); + NT prob = parameters.p, ratio; + int N_times_nu = parameters.N * parameters.nu; + + HPolytope HP(compute_hpoly_for_mmc(P)); + VT b_max(2 * P.num_of_generators()); + if ( !get_first_poly(P, HP, ratio, parameters, rng, b_max) ) + { + return -1.0; + } + + std::vector HPolySet; + std::vector ratios; + std::vector diams_inter; + + if ( !get_sequence_of_zonopolys + (P, HP, HPolySet, ratios, + b_max, N_times_nu, walk_length, parameters, rng) ) + { + return -1.0; + } + + int mm = HPolySet.size() + 2; + int mm2 = mm + 1; + prob = std::pow(prob, 1.0/NT(mm2)); + NT er0 = error/(2.0*std::sqrt(NT(mm2))); + NT er1 = (error*std::sqrt(2.0*NT(mm2)-1))/(std::sqrt(2.0*NT(mm2))); + NT Her = error/(2.0*std::sqrt(NT(mm2))); + + HPolytope HP2(HP); + std::pair InnerBall = HP2.ComputeInnerBall(); + std::tuple res = min_sampling_covering_ellipsoid_rounding(HP2, InnerBall, + 10 + 10 * n, rng); + NT vol = std::get<2>(res) * volume_cooling_gaussians(HP2, rng, Her/2.0, 1); + + if (!parameters.window2) { + vol *= estimate_ratio_interval(HP, P, ratio, er0, parameters.win_len, 1200, + prob, 10 + 10 * n, rng); + } else { + vol *= estimate_ratio(HP, P, ratio, er0, parameters.win_len, 1200, 10 + 10 * n, rng); + } + + HPolytope b1, b2; + if (HPolySet.size()==0) { + if (ratios[0]!=1) { + if(!parameters.window2) { + vol = vol / estimate_ratio_interval(P, HP, ratios[0], er1, parameters.win_len, N_times_nu, + prob, walk_length, rng); + } else { + vol = vol / estimate_ratio(P, HP, ratios[0], er1, parameters.win_len, N_times_nu, + walk_length, rng); + } + } + } else { + er1 = er1 / std::sqrt(NT(mm)-1.0); + b1 = HPolySet[0]; + if(!parameters.window2) { + vol = vol / estimate_ratio_interval(P, b1, ratios[0], er1, parameters.win_len, N_times_nu, + prob, walk_length, rng); + } else { + vol = vol / estimate_ratio(P, b1, ratios[0], er1, parameters.win_len, N_times_nu, + walk_length, rng); + } + + for (int i = 0; i < HPolySet.size()-1; ++i) { + ZonoHP zb1(P,HPolySet[i]); + b2 = HPolySet[i+1]; + if(!parameters.window2) { + vol = vol / estimate_ratio_interval(zb1, b2, ratios[i], er1, parameters.win_len, + N_times_nu, prob, walk_length, rng); + } else { + vol = vol / estimate_ratio(zb1, b2, ratios[i], er1, parameters.win_len, N_times_nu, + walk_length, rng); + } + } + + ZonoHP zb1(P, HPolySet[HPolySet.size() - 1]); + if (!parameters.window2) { + vol = vol / estimate_ratio_interval(zb1, HP, ratios[ratios.size() - 1], er1, + parameters.win_len, N_times_nu, prob, walk_length, + rng); + } else { + vol = vol / estimate_ratio(zb1, HP, ratios[ratios.size() - 1], er1, parameters.win_len, + N_times_nu, walk_length, rng); + } + } + + return vol; + +} + + +template +< + typename WalkTypePolicy, + typename RandomNumberGenerator, + typename HPolytope, + typename Polytope +> +double volume_cooling_hpoly(Polytope& Pin, + double const& error = 0.1, + unsigned int const& walk_length = 1) +{ + RandomNumberGenerator rng(Pin.dimension()); + return volume_cooling_hpoly(Pin, rng, error, walk_length); +} + +#endif // VOLUME_COOLING_HPOLY_HPP diff --git a/src/volesti/include/volume/volume_sequence_of_balls.hpp b/src/volesti/include/volume/volume_sequence_of_balls.hpp new file mode 100644 index 00000000..b79358a6 --- /dev/null +++ b/src/volesti/include/volume/volume_sequence_of_balls.hpp @@ -0,0 +1,266 @@ +// VolEsti (volume computation and sampling library) + +// Copyright (c) 2012-2020 Vissarion Fisikopoulos +// Copyright (c) 2018-2020 Apostolos Chalkis + +//Contributed and/or modified by Apostolos Chalkis, as part of Google Summer of Code 2018 program. + +// Licensed under GNU LGPL.3, see LICENCE file + +#ifndef VOLUME_SEQUENCE_OF_BALLS_HPP +#define VOLUME_SEQUENCE_OF_BALLS_HPP + +#include +#include +#include +#include +#include + +#include "cartesian_geom/cartesian_kernel.h" +#include "generators/boost_random_number_generator.hpp" +#include "convex_bodies/hpolytope.h" +#ifndef DISABLE_LPSOLVE + #include "convex_bodies/vpolytope.h" + #include "convex_bodies/zpolytope.h" + #include "convex_bodies/zonoIntersecthpoly.h" + #include "convex_bodies/vpolyintersectvpoly.h" +#endif +#include "convex_bodies/ball.h" +#include "convex_bodies/ballintersectconvex.h" +#include "random_walks/uniform_cdhr_walk.hpp" +#include "sampling/random_point_generators.hpp" +#include "volume/sampling_policies.hpp" + + +////////////////////////////// Algorithms + + +// ----- VOLUME ------ // + +template +< + typename WalkTypePolicy, + typename Polytope, + typename RandomNumberGenerator + +> +double volume_sequence_of_balls(Polytope& Pin, + RandomNumberGenerator &rng, + double const& error = 1.0, + unsigned int const& walk_length = 1, + unsigned int const& n_threads = 1) +{ + typedef typename Polytope::PointType Point; + typedef typename Polytope::VT VT; + typedef typename Polytope::MT MT; + typedef typename Point::FT NT; + typedef Ball Ball; + typedef BallIntersectPolytope BallPoly; + + typedef typename WalkTypePolicy::template Walk + < + Polytope, + RandomNumberGenerator + > walk; + + typedef RandomPointGenerator RandomPointGenerator; + + auto P(Pin); //copy and work with P because we are going to shift + unsigned int n = P.dimension(); + unsigned int rnum = std::pow(error, -2) * 400 * n * std::log(n); + //RandomNumberGenerator rng(P.dimension()); + + //Compute the Chebychev ball (largest inscribed ball) with center and radius + auto InnerBall = P.ComputeInnerBall(); + if (InnerBall.second < 0.0) return -1.0; + + Point c = InnerBall.first; + NT radius = InnerBall.second; + + // Move the chebychev center to the origin and apply the same shifting to the polytope + P.shift(c.getCoefficients()); + c = Point(n); + + // Scale by number of threads and prevent edge case rnum=0 from producing overflow later + rnum = rnum >= 2*n_threads ? rnum/n_threads : 2u; + NT vol = NT(0); + + // Perform the procedure for a number of threads and then take the average + for (auto t=0u; t::apply(P.dimension(), radius, rng); + std::list randPoints; //ds for storing rand points + + PushBackWalkPolicy push_back_policy; + RandomPointGenerator::apply(P, p, 1, 50*n, randPoints, push_back_policy, rng); + +#ifdef VOLESTI_DEBUG + double tstart2 = (double)clock()/(double)CLOCKS_PER_SEC; + std::cout<<"\nCompute "< balls; + + for (auto i=nb1; i<=nb2; ++i) + { + if (i == nb1) + { + balls.push_back(Ball(c,radius*radius)); + vol = (std::pow(M_PI,n/2.0)*(std::pow(balls[0].radius(), n) ) ) + / (tgamma(n/2.0+1)); + } else { + balls.push_back(Ball(c,std::pow(std::pow(2.0,NT(i)/NT(n)),2))); + } + } + assert(!balls.empty()); + + // Estimate Vol(P) + typename std::vector::iterator bit2=balls.end(); + bit2--; + + while (bit2!=balls.begin()) + { + //each step starts with some random points in PBLarge stored + //in list "randPoints", these points have been generated in a + //previous step + + BallPoly PBLarge(P,*bit2); + --bit2; + BallPoly PBSmall(P,*bit2); + +#ifdef VOLESTI_DEBUG + std::cout<<"("< counting_policy(nump_PBSmall, PBSmall); + RandomPointGenerator::apply(PBLarge, p_gen, rnum-nump_PBLarge, + walk_length, randPoints, + counting_policy, rng); + + nump_PBSmall = counting_policy.get_nump_PBSmall(); + + vol *= NT(rnum)/NT(nump_PBSmall); + +#ifdef VOLESTI_DEBUG + std::cout<, + typename Polytope +> +double volume_sequence_of_balls(Polytope &Pin, + double const& error = 1.0, + unsigned int const& walk_length = 1, + unsigned int const& n_threads = 1) +{ + RandomNumberGenerator rng(Pin.dimension()); + return volume_sequence_of_balls(Pin, rng, error, + walk_length, n_threads); +} + + +template +< + typename WalkTypePolicy = CDHRWalk, + typename RandomNumberGenerator = BoostRandomNumberGenerator, + typename Polytope +> +double volume_sequence_of_balls(Polytope &Pin, + Cartesian::Point const& interior_point, + unsigned int const& walk_length = 1, + double const& error = 1.0, + unsigned int const& n_threads = 1) +{ + RandomNumberGenerator rng(Pin.dimension()); + Pin.set_interior_point(interior_point); + + return volume_sequence_of_balls(Pin, rng, error, + walk_length, n_threads); +} + +#endif // VOLUME_SEQUENCE_OF_BALLS_HPP