PolynomialUtils.hpp
Go to the documentation of this file.
1 // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 // vi: set et ts=4 sw=4 sts=4:
3 /*
4  Copyright (C) 2010-2013 by Andreas Lauser
5 
6  This file is part of the Open Porous Media project (OPM).
7 
8  OPM is free software: you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation, either version 2 of the License, or
11  (at your option) any later version.
12 
13  OPM is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  GNU General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with OPM. If not, see <http://www.gnu.org/licenses/>.
20 */
25 #ifndef OPM_POLYNOMIAL_UTILS_HH
26 #define OPM_POLYNOMIAL_UTILS_HH
27 
28 #include <cmath>
29 #include <algorithm>
30 
32 
33 namespace Opm {
48 template <class Scalar, class SolContainer>
49 int invertLinearPolynomial(SolContainer &sol,
50  Scalar a,
51  Scalar b)
52 {
53  typedef MathToolbox<Scalar> Toolbox;
54  if (std::abs(Toolbox::value(a)) < 1e-30)
55  return 0;
56 
57  sol[0] = -b/a;
58  return 1;
59 }
60 
77 template <class Scalar, class SolContainer>
78 int invertQuadraticPolynomial(SolContainer &sol,
79  Scalar a,
80  Scalar b,
81  Scalar c)
82 {
83  typedef MathToolbox<Scalar> Toolbox;
84 
85  // check for a line
86  if (std::abs(Toolbox::value(a)) < 1e-30)
87  return invertLinearPolynomial(sol, b, c);
88 
89  // discriminant
90  Scalar Delta = b*b - 4*a*c;
91  if (Delta < 0)
92  return 0; // no real roots
93 
94  Delta = Toolbox::sqrt(Delta);
95  sol[0] = (- b + Delta)/(2*a);
96  sol[1] = (- b - Delta)/(2*a);
97 
98  // sort the result
99  if (sol[0] > sol[1])
100  std::swap(sol[0], sol[1]);
101  return 2; // two real roots
102 }
103 
105 template <class Scalar, class SolContainer>
106 void invertCubicPolynomialPostProcess_(SolContainer &sol,
107  int numSol,
108  Scalar a,
109  Scalar b,
110  Scalar c,
111  Scalar d)
112 {
113  typedef MathToolbox<Scalar> Toolbox;
114 
115  // do one Newton iteration on the analytic solution if the
116  // precision is increased
117  for (int i = 0; i < numSol; ++i) {
118  Scalar x = sol[i];
119  Scalar fOld = d + x*(c + x*(b + x*a));
120 
121  Scalar fPrime = c + x*(2*b + x*3*a);
122  if (std::abs(Toolbox::value(fPrime)) < 1e-30)
123  continue;
124  x -= fOld/fPrime;
125 
126  Scalar fNew = d + x*(c + x*(b + x*a));
127  if (std::abs(Toolbox::value(fNew)) < std::abs(Toolbox::value(fOld)))
128  sol[i] = x;
129  }
130 }
132 
150 template <class Scalar, class SolContainer>
151 int invertCubicPolynomial(SolContainer *sol,
152  Scalar a,
153  Scalar b,
154  Scalar c,
155  Scalar d)
156 {
157  typedef MathToolbox<Scalar> Toolbox;
158 
159  // reduces to a quadratic polynomial
160  if (std::abs(Toolbox::value(a)) < 1e-30)
161  return invertQuadraticPolynomial(sol, b, c, d);
162 
163  // normalize the polynomial
164  b /= a;
165  c /= a;
166  d /= a;
167  a = 1;
168 
169  // get rid of the quadratic term by subsituting x = t - b/3
170  Scalar p = c - b*b/3;
171  Scalar q = d + (2*b*b*b - 9*b*c)/27;
172 
173  if (std::abs(Toolbox::value(p)) > 1e-30 && std::abs(Toolbox::value(q)) > 1e-30) {
174  // At this point
175  //
176  // t^3 + p*t + q = 0
177  //
178  // with p != 0 and q != 0 holds. Introducing the variables u and v
179  // with the properties
180  //
181  // u + v = t and 3*u*v + p = 0
182  //
183  // leads to
184  //
185  // u^3 + v^3 + q = 0 .
186  //
187  // multiplying both sides with u^3 and taking advantage of the
188  // fact that u*v = -p/3 leads to
189  //
190  // u^6 + q*u^3 - p^3/27 = 0
191  //
192  // Now, substituting u^3 = w yields
193  //
194  // w^2 + q*w - p^3/27 = 0
195  //
196  // This is a quadratic equation with the solutions
197  //
198  // w = -q/2 + sqrt(q^2/4 + p^3/27) and
199  // w = -q/2 - sqrt(q^2/4 + p^3/27)
200  //
201  // Since w is equivalent to u^3 it is sufficient to only look at
202  // one of the two cases. Then, there are still 2 cases: positive
203  // and negative discriminant.
204  Scalar wDisc = q*q/4 + p*p*p/27;
205  if (wDisc >= 0) { // the positive discriminant case:
206  // calculate the cube root of - q/2 + sqrt(q^2/4 + p^3/27)
207  Scalar u = - q/2 + Toolbox::sqrt(wDisc);
208  if (u < 0) u = - Toolbox::pow(-u, 1.0/3);
209  else u = Toolbox::pow(u, 1.0/3);
210 
211  // at this point, u != 0 since p^3 = 0 is necessary in order
212  // for u = 0 to hold, so
213  sol[0] = u - p/(3*u) - b/3;
214  // does not produce a division by zero. the remaining two
215  // roots of u are rotated by +- 2/3*pi in the complex plane
216  // and thus not considered here
217  invertCubicPolynomialPostProcess_(sol, 1, a, b, c, d);
218  return 1;
219  }
220  else { // the negative discriminant case:
221  Scalar uCubedRe = - q/2;
222  Scalar uCubedIm = Toolbox::sqrt(-wDisc);
223  // calculate the cube root of - q/2 + sqrt(q^2/4 + p^3/27)
224  Scalar uAbs = Toolbox::pow(Toolbox::sqrt(uCubedRe*uCubedRe + uCubedIm*uCubedIm), 1.0/3);
225  Scalar phi = Toolbox::atan2(uCubedIm, uCubedRe)/3;
226 
227  // calculate the length and the angle of the primitive root
228 
229  // with the definitions from above it follows that
230  //
231  // x = u - p/(3*u) - b/3
232  //
233  // where x and u are complex numbers. Rewritten in polar form
234  // this is equivalent to
235  //
236  // x = |u|*e^(i*phi) - p*e^(-i*phi)/(3*|u|) - b/3 .
237  //
238  // Factoring out the e^ terms and subtracting the additional
239  // terms, yields
240  //
241  // x = (e^(i*phi) + e^(-i*phi))*(|u| - p/(3*|u|)) - y - b/3
242  //
243  // with
244  //
245  // y = - |u|*e^(-i*phi) + p*e^(i*phi)/(3*|u|) .
246  //
247  // The crucial observation is the fact that y is the conjugate
248  // of - x + b/3. This means that after taking advantage of the
249  // relation
250  //
251  // e^(i*phi) + e^(-i*phi) = 2*cos(phi)
252  //
253  // the equation
254  //
255  // x = 2*cos(phi)*(|u| - p / (3*|u|)) - conj(x) - 2*b/3
256  //
257  // holds. Since |u|, p, b and cos(phi) are real numbers, it
258  // follows that Im(x) = - Im(x) and thus Im(x) = 0. This
259  // implies
260  //
261  // Re(x) = x = cos(phi)*(|u| - p / (3*|u|)) - b/3 .
262  //
263  // Considering the fact that u is a cubic root, we have three
264  // values for phi which differ by 2/3*pi. This allows to
265  // calculate the three real roots of the polynomial:
266  for (int i = 0; i < 3; ++i) {
267  sol[i] = Toolbox::cos(phi)*(uAbs - p/(3*uAbs)) - b/3;
268  phi += 2*M_PI/3;
269  }
270 
271  // post process the obtained solution to increase numerical
272  // precision
273  invertCubicPolynomialPostProcess_(sol, 3, a, b, c, d);
274 
275  // sort the result
276  std::sort(sol, sol + 3);
277 
278  return 3;
279  }
280  }
281  // Handle some (pretty unlikely) special cases required to avoid
282  // divisions by zero in the code above...
283  else if (std::abs(Toolbox::value(p)) < 1e-30 && std::abs(Toolbox::value(q)) < 1e-30) {
284  // t^3 = 0, i.e. triple root at t = 0
285  sol[0] = sol[1] = sol[2] = 0.0 - b/3;
286  return 3;
287  }
288  else if (std::abs(Toolbox::value(p)) < 1e-30 && std::abs(Toolbox::value(q)) > 1e-30) {
289  // t^3 + q = 0,
290  //
291  // i. e. single real root at t=curt(q)
292  Scalar t;
293  if (-q > 0) t = Toolbox::pow(-q, 1./3);
294  else t = - Toolbox::pow(q, 1./3);
295  sol[0] = t - b/3;
296 
297  return 1;
298  }
299 
300  assert(std::abs(Toolbox::value(p)) > 1e-30 && std::abs(Toolbox::value(q)) > 1e-30);
301 
302  // t^3 + p*t = 0 = t*(t^2 + p),
303  //
304  // i. e. roots at t = 0, t^2 + p = 0
305  if (p > 0) {
306  sol[0] = 0.0 - b/3;
307  return 1; // only a single real root at t=0
308  }
309 
310  // two additional real roots at t = sqrt(-p) and t = -sqrt(-p)
311  sol[0] = -Toolbox::sqrt(-p) - b/3;;
312  sol[1] = 0.0 - b/3;
313  sol[2] = Toolbox::sqrt(-p) - b/3;
314 
315  return 3;
316 }
317 }
318 
319 #endif
Definition: MathToolbox.hpp:39
Definition: Air_Mesitylene.hpp:31
Evaluation< Scalar, VarSetTag, numVars > sqrt(const Evaluation< Scalar, VarSetTag, numVars > &x)
Definition: Math.hpp:278
Evaluation< Scalar, VarSetTag, numVars > cos(const Evaluation< Scalar, VarSetTag, numVars > &x)
Definition: Math.hpp:248
Evaluation< Scalar, VarSetTag, numVars > atan2(const Evaluation< Scalar, VarSetTag, numVars > &x, const Evaluation< Scalar, VarSetTag, numVars > &y)
Definition: Math.hpp:198
int invertCubicPolynomial(SolContainer *sol, Scalar a, Scalar b, Scalar c, Scalar d)
Invert a cubic polynomial analytically.
Definition: PolynomialUtils.hpp:151
int invertLinearPolynomial(SolContainer &sol, Scalar a, Scalar b)
Invert a linear polynomial analytically.
Definition: PolynomialUtils.hpp:49
Evaluation< Scalar, VarSetTag, numVars > abs(const Evaluation< Scalar, VarSetTag, numVars > &)
Definition: Math.hpp:41
Evaluation< Scalar, VarSetTag, numVars > pow(const Evaluation< Scalar, VarSetTag, numVars > &base, Scalar exp)
Definition: Math.hpp:312
int invertQuadraticPolynomial(SolContainer &sol, Scalar a, Scalar b, Scalar c)
Invert a quadratic polynomial analytically.
Definition: PolynomialUtils.hpp:78
A traits class which provides basic mathematical functions for arbitrary scalar floating point values...