ApprPolynomialSpecial3.h 12 KB

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