ApprPolynomialSpecial2.h 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. // David Eberly, Geometric Tools, Redmond WA 98052
  2. // Copyright (c) 1998-2020
  3. // Distributed under the Boost Software License, Version 1.0.
  4. // https://www.boost.org/LICENSE_1_0.txt
  5. // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
  6. // Version: 4.0.2019.08.13
  7. #pragma once
  8. #include <Mathematics/ApprQuery.h>
  9. #include <Mathematics/GMatrix.h>
  10. #include <array>
  11. // Fit the data with a polynomial of the form
  12. // w = sum_{i=0}^{n-1} c[i]*x^{p[i]}
  13. // where p[i] are distinct nonnegative powers provided by the caller. A
  14. // least-squares fitting algorithm is used, but the input data is first
  15. // mapped to (x,w) in [-1,1]^2 for numerical robustness.
  16. namespace WwiseGTE
  17. {
  18. template <typename Real>
  19. class ApprPolynomialSpecial2 : public ApprQuery<Real, std::array<Real, 2>>
  20. {
  21. public:
  22. // Initialize the model parameters to zero. The degrees must be
  23. // nonnegative and strictly increasing.
  24. ApprPolynomialSpecial2(std::vector<int> const& degrees)
  25. :
  26. mDegrees(degrees),
  27. mParameters(degrees.size(), (Real)0)
  28. {
  29. #if !defined(GTE_NO_LOGGER)
  30. LogAssert(mDegrees.size() > 0, "The input array must have elements.");
  31. int lastDegree = -1;
  32. for (auto degree : mDegrees)
  33. {
  34. LogAssert(degree > lastDegree, "Degrees must be increasing.");
  35. lastDegree = degree;
  36. }
  37. #endif
  38. mXDomain[0] = std::numeric_limits<Real>::max();
  39. mXDomain[1] = -mXDomain[0];
  40. mWDomain[0] = std::numeric_limits<Real>::max();
  41. mWDomain[1] = -mWDomain[0];
  42. mScale[0] = (Real)0;
  43. mScale[1] = (Real)0;
  44. mInvTwoWScale = (Real)0;
  45. // Powers of x are computed up to twice the powers when
  46. // constructing the fitted polynomial. Powers of x are computed
  47. // up to the powers for the evaluation of the fitted polynomial.
  48. mXPowers.resize(2 * mDegrees.back() + 1);
  49. mXPowers[0] = (Real)1;
  50. }
  51. // Basic fitting algorithm. See ApprQuery.h for the various Fit(...)
  52. // functions that you can call.
  53. virtual bool FitIndexed(
  54. size_t numObservations, std::array<Real, 2> const* observations,
  55. size_t numIndices, int const* indices) override
  56. {
  57. if (this->ValidIndices(numObservations, observations, numIndices, indices))
  58. {
  59. // Transform the observations to [-1,1]^2 for numerical
  60. // robustness.
  61. std::vector<std::array<Real, 2>> transformed;
  62. Transform(observations, numIndices, indices, transformed);
  63. // Fit the transformed data using a least-squares algorithm.
  64. return DoLeastSquares(transformed);
  65. }
  66. std::fill(mParameters.begin(), mParameters.end(), (Real)0);
  67. return false;
  68. }
  69. // Get the parameters for the best fit.
  70. std::vector<Real> const& GetParameters() const
  71. {
  72. return mParameters;
  73. }
  74. virtual size_t GetMinimumRequired() const override
  75. {
  76. return mParameters.size();
  77. }
  78. // Compute the model error for the specified observation for the
  79. // current model parameters. The returned value for observation
  80. // (x0,w0) is |w(x0) - w0|, where w(x) is the fitted polynomial.
  81. virtual Real Error(std::array<Real, 2> const& observation) const override
  82. {
  83. Real w = Evaluate(observation[0]);
  84. Real error = std::fabs(w - observation[1]);
  85. return error;
  86. }
  87. virtual void CopyParameters(ApprQuery<Real, std::array<Real, 2>> const* input) override
  88. {
  89. auto source = dynamic_cast<ApprPolynomialSpecial2 const*>(input);
  90. if (source)
  91. {
  92. *this = *source;
  93. }
  94. }
  95. // Evaluate the polynomial. The domain interval is provided so you can
  96. // interpolate (x in domain) or extrapolate (x not in domain).
  97. std::array<Real, 2> const& GetXDomain() const
  98. {
  99. return mXDomain;
  100. }
  101. Real Evaluate(Real x) const
  102. {
  103. // Transform x to x' in [-1,1].
  104. x = (Real)-1 + (Real)2 * mScale[0] * (x - mXDomain[0]);
  105. // Compute relevant powers of x.
  106. int jmax = mDegrees.back();
  107. for (int j = 1; j <= jmax; ++j)
  108. {
  109. mXPowers[j] = mXPowers[j - 1] * x;
  110. }
  111. Real w = (Real)0;
  112. int isup = static_cast<int>(mDegrees.size());
  113. for (int i = 0; i < isup; ++i)
  114. {
  115. Real xp = mXPowers[mDegrees[i]];
  116. w += mParameters[i] * xp;
  117. }
  118. // Transform w from [-1,1] back to the original space.
  119. w = (w + (Real)1) * mInvTwoWScale + mWDomain[0];
  120. return w;
  121. }
  122. private:
  123. // Transform the (x,w) values to (x',w') in [-1,1]^2.
  124. void Transform(std::array<Real, 2> const* observations, size_t numIndices,
  125. int const* indices, std::vector<std::array<Real, 2>>& transformed)
  126. {
  127. int numSamples = static_cast<int>(numIndices);
  128. transformed.resize(numSamples);
  129. std::array<Real, 2> omin = observations[indices[0]];
  130. std::array<Real, 2> omax = omin;
  131. std::array<Real, 2> obs;
  132. int s, i;
  133. for (s = 1; s < numSamples; ++s)
  134. {
  135. obs = observations[indices[s]];
  136. for (i = 0; i < 2; ++i)
  137. {
  138. if (obs[i] < omin[i])
  139. {
  140. omin[i] = obs[i];
  141. }
  142. else if (obs[i] > omax[i])
  143. {
  144. omax[i] = obs[i];
  145. }
  146. }
  147. }
  148. mXDomain[0] = omin[0];
  149. mXDomain[1] = omax[0];
  150. mWDomain[0] = omin[1];
  151. mWDomain[1] = omax[1];
  152. for (i = 0; i < 2; ++i)
  153. {
  154. mScale[i] = (Real)1 / (omax[i] - omin[i]);
  155. }
  156. for (s = 0; s < numSamples; ++s)
  157. {
  158. obs = observations[indices[s]];
  159. for (i = 0; i < 2; ++i)
  160. {
  161. transformed[s][i] = (Real)-1 + (Real)2 * mScale[i] * (obs[i] - omin[i]);
  162. }
  163. }
  164. mInvTwoWScale = (Real)0.5 / mScale[1];
  165. }
  166. // The least-squares fitting algorithm for the transformed data.
  167. bool DoLeastSquares(std::vector<std::array<Real, 2>> & transformed)
  168. {
  169. // Set up a linear system A*X = B, where X are the polynomial
  170. // coefficients.
  171. int size = static_cast<int>(mDegrees.size());
  172. GMatrix<Real> A(size, size);
  173. A.MakeZero();
  174. GVector<Real> B(size);
  175. B.MakeZero();
  176. int numSamples = static_cast<int>(transformed.size());
  177. int twoMaxXDegree = 2 * mDegrees.back();
  178. int row, col;
  179. for (int i = 0; i < numSamples; ++i)
  180. {
  181. // Compute relevant powers of x.
  182. Real x = transformed[i][0];
  183. Real w = transformed[i][1];
  184. for (int j = 0; j <= twoMaxXDegree; ++j)
  185. {
  186. mXPowers[j] = mXPowers[j - 1] * x;
  187. }
  188. for (row = 0; row < size; ++row)
  189. {
  190. // Update the upper-triangular portion of the symmetric
  191. // matrix.
  192. for (col = row; col < size; ++col)
  193. {
  194. A(row, col) += mXPowers[mDegrees[row] + mDegrees[col]];
  195. }
  196. // Update the right-hand side of the system.
  197. B[row] += mXPowers[mDegrees[row]] * w;
  198. }
  199. }
  200. // Copy the upper-triangular portion of the symmetric matrix to
  201. // the lower-triangular portion.
  202. for (row = 0; row < size; ++row)
  203. {
  204. for (col = 0; col < row; ++col)
  205. {
  206. A(row, col) = A(col, row);
  207. }
  208. }
  209. // Precondition by normalizing the sums.
  210. Real invNumSamples = (Real)1 / (Real)numSamples;
  211. A *= invNumSamples;
  212. B *= invNumSamples;
  213. // Solve for the polynomial coefficients.
  214. GVector<Real> coefficients = Inverse(A) * B;
  215. bool hasNonzero = false;
  216. for (int i = 0; i < size; ++i)
  217. {
  218. mParameters[i] = coefficients[i];
  219. if (coefficients[i] != (Real)0)
  220. {
  221. hasNonzero = true;
  222. }
  223. }
  224. return hasNonzero;
  225. }
  226. std::vector<int> mDegrees;
  227. std::vector<Real> mParameters;
  228. // Support for evaluation. The coefficients were generated for the
  229. // samples mapped to [-1,1]^2. The Evaluate() function must transform
  230. // x to x' in [-1,1], compute w' in [-1,1], then transform w' to w.
  231. std::array<Real, 2> mXDomain, mWDomain;
  232. std::array<Real, 2> mScale;
  233. Real mInvTwoWScale;
  234. // This array is used by Evaluate() to avoid reallocation of the
  235. // 'vector' for each call. The member is mutable because, to the
  236. // user, the call to Evaluate does not modify the polynomial.
  237. mutable std::vector<Real> mXPowers;
  238. };
  239. }