ApprPolynomialSpecial4.h 13 KB

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