opm-common
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  This file is part of the Open Porous Media project (OPM).
5 
6  OPM is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 2 of the License, or
9  (at your option) any later version.
10 
11  OPM is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with OPM. If not, see <http://www.gnu.org/licenses/>.
18 
19  Consult the COPYING file in the top-level source directory of this
20  module for the precise wording of the license and the list of
21  copyright holders.
22 */
27 #ifndef OPM_POLYNOMIAL_UTILS_HH
28 #define OPM_POLYNOMIAL_UTILS_HH
29 
31 
32 #include <algorithm>
33 #include <cmath>
34 #include <numbers>
35 
36 namespace Opm {
51 template <class Scalar, class SolContainer>
52 unsigned invertLinearPolynomial(SolContainer& sol,
53  Scalar a,
54  Scalar b)
55 {
56  if (std::abs(scalarValue(a)) < 1e-30)
57  return 0;
58 
59  sol[0] = -b/a;
60  return 1;
61 }
62 
79 template <class Scalar, class SolContainer>
80 unsigned invertQuadraticPolynomial(SolContainer& sol,
81  Scalar a,
82  Scalar b,
83  Scalar c)
84 {
85  // check for a line
86  if (std::abs(scalarValue(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 = 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  // do one Newton iteration on the analytic solution if the
114  // precision is increased
115  for (int i = 0; i < numSol; ++i) {
116  Scalar x = sol[i];
117  Scalar fOld = d + x*(c + x*(b + x*a));
118 
119  Scalar fPrime = c + x*(2*b + x*3*a);
120  if (std::abs(scalarValue(fPrime)) < 1e-30)
121  continue;
122  x -= fOld/fPrime;
123 
124  Scalar fNew = d + x*(c + x*(b + x*a));
125  if (std::abs(scalarValue(fNew)) < std::abs(scalarValue(fOld)))
126  sol[i] = x;
127  }
128 }
130 
148 template <class Scalar, class SolContainer>
149 unsigned invertCubicPolynomial(SolContainer* sol,
150  Scalar a,
151  Scalar b,
152  Scalar c,
153  Scalar d)
154 {
155  // reduces to a quadratic polynomial
156  if (std::abs(scalarValue(a)) < 1e-30)
157  return invertQuadraticPolynomial(sol, b, c, d);
158 
159  // normalize the polynomial
160  b /= a;
161  c /= a;
162  d /= a;
163  a = 1;
164 
165  // get rid of the quadratic term by subsituting x = t - b/3
166  Scalar p = c - b*b/3;
167  Scalar q = d + (2*b*b*b - 9*b*c)/27;
168 
169  if (std::abs(scalarValue(p)) > 1e-30 && std::abs(scalarValue(q)) > 1e-30) {
170  // At this point
171  //
172  // t^3 + p*t + q = 0
173  //
174  // with p != 0 and q != 0 holds. Introducing the variables u and v
175  // with the properties
176  //
177  // u + v = t and 3*u*v + p = 0
178  //
179  // leads to
180  //
181  // u^3 + v^3 + q = 0 .
182  //
183  // multiplying both sides with u^3 and taking advantage of the
184  // fact that u*v = -p/3 leads to
185  //
186  // u^6 + q*u^3 - p^3/27 = 0
187  //
188  // Now, substituting u^3 = w yields
189  //
190  // w^2 + q*w - p^3/27 = 0
191  //
192  // This is a quadratic equation with the solutions
193  //
194  // w = -q/2 + sqrt(q^2/4 + p^3/27) and
195  // w = -q/2 - sqrt(q^2/4 + p^3/27)
196  //
197  // Since w is equivalent to u^3 it is sufficient to only look at
198  // one of the two cases. Then, there are still 2 cases: positive
199  // and negative discriminant.
200  Scalar wDisc = q*q/4 + p*p*p/27;
201  if (wDisc >= 0) { // the positive discriminant case:
202  // calculate the cube root of - q/2 + sqrt(q^2/4 + p^3/27)
203  Scalar u = - q/2 + sqrt(wDisc);
204  if (u < 0) u = - pow(-u, 1.0/3);
205  else u = pow(u, 1.0/3);
206 
207  // at this point, u != 0 since p^3 = 0 is necessary in order
208  // for u = 0 to hold, so
209  sol[0] = u - p/(3*u) - b/3;
210  // does not produce a division by zero. the remaining two
211  // roots of u are rotated by +- 2/3*pi in the complex plane
212  // and thus not considered here
213  invertCubicPolynomialPostProcess_(sol, 1, a, b, c, d);
214  return 1;
215  }
216  else { // the negative discriminant case:
217  Scalar uCubedRe = - q/2;
218  Scalar uCubedIm = sqrt(-wDisc);
219  // calculate the cube root of - q/2 + sqrt(q^2/4 + p^3/27)
220  Scalar uAbs = pow(sqrt(uCubedRe*uCubedRe + uCubedIm*uCubedIm), 1.0/3);
221  Scalar phi = atan2(uCubedIm, uCubedRe)/3;
222 
223  // calculate the length and the angle of the primitive root
224 
225  // with the definitions from above it follows that
226  //
227  // x = u - p/(3*u) - b/3
228  //
229  // where x and u are complex numbers. Rewritten in polar form
230  // this is equivalent to
231  //
232  // x = |u|*e^(i*phi) - p*e^(-i*phi)/(3*|u|) - b/3 .
233  //
234  // Factoring out the e^ terms and subtracting the additional
235  // terms, yields
236  //
237  // x = (e^(i*phi) + e^(-i*phi))*(|u| - p/(3*|u|)) - y - b/3
238  //
239  // with
240  //
241  // y = - |u|*e^(-i*phi) + p*e^(i*phi)/(3*|u|) .
242  //
243  // The crucial observation is the fact that y is the conjugate
244  // of - x + b/3. This means that after taking advantage of the
245  // relation
246  //
247  // e^(i*phi) + e^(-i*phi) = 2*cos(phi)
248  //
249  // the equation
250  //
251  // x = 2*cos(phi)*(|u| - p / (3*|u|)) - conj(x) - 2*b/3
252  //
253  // holds. Since |u|, p, b and cos(phi) are real numbers, it
254  // follows that Im(x) = - Im(x) and thus Im(x) = 0. This
255  // implies
256  //
257  // Re(x) = x = cos(phi)*(|u| - p / (3*|u|)) - b/3 .
258  //
259  // Considering the fact that u is a cubic root, we have three
260  // values for phi which differ by 2/3*pi. This allows to
261  // calculate the three real roots of the polynomial:
262  for (int i = 0; i < 3; ++i) {
263  sol[i] = cos(phi)*(uAbs - p/(3*uAbs)) - b/3;
264  phi += 2*std::numbers::pi/3;
265  }
266 
267  // post process the obtained solution to increase numerical
268  // precision
269  invertCubicPolynomialPostProcess_(sol, 3, a, b, c, d);
270 
271  // sort the result
272  std::sort(sol, sol + 3);
273 
274  return 3;
275  }
276  }
277  // Handle some (pretty unlikely) special cases required to avoid
278  // divisions by zero in the code above...
279  else if (std::abs(scalarValue(p)) < 1e-30 && std::abs(scalarValue(q)) < 1e-30) {
280  // t^3 = 0, i.e. triple root at t = 0
281  sol[0] = sol[1] = sol[2] = 0.0 - b/3;
282  return 3;
283  }
284  else if (std::abs(scalarValue(p)) < 1e-30 && std::abs(scalarValue(q)) > 1e-30) {
285  // t^3 + q = 0,
286  //
287  // i. e. single real root at t=curt(q)
288  Scalar t;
289  if (-q > 0) t = pow(-q, 1./3);
290  else t = - pow(q, 1./3);
291  sol[0] = t - b/3;
292 
293  return 1;
294  }
295 
296  assert(std::abs(scalarValue(p)) > 1e-30 && std::abs(scalarValue(q)) > 1e-30);
297 
298  // t^3 + p*t = 0 = t*(t^2 + p),
299  //
300  // i. e. roots at t = 0, t^2 + p = 0
301  if (p > 0) {
302  sol[0] = 0.0 - b/3;
303  return 1; // only a single real root at t=0
304  }
305 
306  // two additional real roots at t = sqrt(-p) and t = -sqrt(-p)
307  sol[0] = -sqrt(-p) - b/3;;
308  sol[1] = 0.0 - b/3;
309  sol[2] = sqrt(-p) - b/3;
310 
311  return 3;
312 }
313 
331 template <class Scalar, class SolContainer>
332 unsigned cubicRoots(SolContainer* sol,
333  Scalar a,
334  Scalar b,
335  Scalar c,
336  Scalar d)
337 {
338  // reduces to a quadratic polynomial
339  if (std::abs(scalarValue(a)) < 1e-30)
340  return invertQuadraticPolynomial(sol, b, c, d);
341 
342  // We need to reduce the cubic equation to its "depressed cubic" form (however strange that sounds)
343  // Depressed cubic form: t^3 + p*t + q, where x = t - b/3*a is the transform we use when we have
344  // roots for t. p and q are defined below.
345  // Formula for p and q:
346  Scalar p = (3.0 * a * c - b * b) / (3.0 * a * a);
347  Scalar q = (2.0 * b * b * b - 9.0 * a * b * c + 27.0 * d * a * a) / (27.0 * a * a * a);
348 
349  // Check if we have three or one real root by looking at the discriminant, and solve accordingly with
350  // correct formula
351  Scalar discr = 4.0 * p * p * p + 27.0 * q * q;
352  if (discr < 0.0) {
353  // Find three real roots of a depressed cubic, using the trigonometric method
354  // Help calculation
355  Scalar theta = (1.0 / 3.0) * acos( ((3.0 * q) / (2.0 * p)) * sqrt(-3.0 / p) );
356 
357  // Calculate the three roots
358  sol[0] = 2.0 * sqrt(-p / 3.0) * cos( theta ) - b / (3.0 * a);
359  sol[1] = 2.0 * sqrt(-p / 3.0) * cos( theta - ((2.0 * std::numbers::pi) / 3.0) ) - b / (3.0 * a);
360  sol[2] = 2.0 * sqrt(-p / 3.0) * cos( theta - ((4.0 * std::numbers::pi) / 3.0) ) - b / (3.0 * a);
361 
362  // Sort in ascending order
363  std::sort(sol, sol + 3);
364 
365  // Return confirmation of three roots
366 
367  return 3;
368  }
369  else if (discr > 0.0) {
370 
371  // Find one real root of a depressed cubic using hyperbolic method. Different solutions depending on
372  // sign of p
373  Scalar t = 0;
374  if (p < 0) {
375  // Help calculation
376  Scalar theta = (1.0 / 3.0) * acosh( ((-3.0 * abs(q)) / (2.0 * p)) * sqrt(-3.0 / p) );
377 
378  // Root
379  t = ( (-2.0 * abs(q)) / q ) * sqrt(-p / 3.0) * cosh(theta);
380  }
381  else if (p > 0) {
382  // Help calculation
383  Scalar theta = (1.0 / 3.0) * asinh( ((3.0 * q) / (2.0 * p)) * sqrt(3.0 / p) );
384  // Root
385  t = -2.0 * sqrt(p / 3.0) * sinh(theta);
386 
387  }
388  else {
389  throw std::runtime_error(" p = 0 in cubic root solver!");
390  }
391 
392  // Transform t to output solution
393  sol[0] = t - b / (3.0 * a);
394  return 1;
395 
396  }
397  else {
398  // The discriminant, 4*p^3 + 27*q^2 = 0, thus we have simple (real) roots
399  // If p = 0 then also q = 0, and t = 0 is a triple root
400  if (p == 0) {
401  sol[0] = sol[1] = sol[2] = 0.0 - b / (3.0 * a);
402  }
403  // If p != 0, the we have a simple root and a double root
404  else {
405  sol[0] = (3.0 * q / p) - b / (3.0 * a);
406  sol[1] = sol[2] = (-3.0 * q) / (2.0 * p) - b / (3.0 * a);
407  std::sort(sol, sol + 3);
408  }
409  return 3;
410  }
411 }
412 } // end Opm
413 
414 #endif
A traits class which provides basic mathematical functions for arbitrary scalar floating point values...
This class implements a small container which holds the transmissibility mulitpliers for all the face...
Definition: Exceptions.hpp:30
unsigned invertCubicPolynomial(SolContainer *sol, Scalar a, Scalar b, Scalar c, Scalar d)
Invert a cubic polynomial analytically.
Definition: PolynomialUtils.hpp:149
unsigned invertQuadraticPolynomial(SolContainer &sol, Scalar a, Scalar b, Scalar c)
Invert a quadratic polynomial analytically.
Definition: PolynomialUtils.hpp:80
unsigned cubicRoots(SolContainer *sol, Scalar a, Scalar b, Scalar c, Scalar d)
Invert a cubic polynomial analytically.
Definition: PolynomialUtils.hpp:332
unsigned invertLinearPolynomial(SolContainer &sol, Scalar a, Scalar b)
Invert a linear polynomial analytically.
Definition: PolynomialUtils.hpp:52