/******************************************************************************* * This file is part of SWIFT. * Copyright (c) 2015 Bert Vandenbroucke (bert.vandenbroucke@ugent.be) * 2018 Bert Vandenbroucke (bert.vandenbroucke@gmail.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * ******************************************************************************/ /****************************************************************************** * The Riemann solver in this file was written by Bert Vandenbroucke as part of * the moving mesh code Shadowfax and adapted for use with SWIFT. It consists * of an exact Riemann solver as described in * Toro, Eleuterio F., Riemann Solvers and Numerical Methods for Fluid * Dynamics, Springer (2009, 3rd edition) * ******************************************************************************/ #ifndef SWIFT_RIEMANN_EXACT_H #define SWIFT_RIEMANN_EXACT_H /* Some standard headers. */ #include #include #include #include /* Local headers. */ #include "adiabatic_index.h" #include "error.h" #include "minmax.h" #include "riemann_checks.h" #include "riemann_vacuum.h" /** * @brief Functions (4.6) and (4.7) in Toro. * * @param p The current guess for the pressure * @param W The left or right state vector * @param a The left or right sound speed */ __attribute__((always_inline)) INLINE static float riemann_fb(float p, const float* W, float a) { float fval; if (p > W[4]) { const float A = hydro_two_over_gamma_plus_one / W[0]; const float B = hydro_gamma_minus_one_over_gamma_plus_one * W[4]; fval = (p - W[4]) * sqrtf(A / (p + B)); } else { fval = hydro_two_over_gamma_minus_one * a * (pow_gamma_minus_one_over_two_gamma(p / W[4]) - 1.0f); } return fval; } /** * @brief Function (4.5) in Toro * * @param p The current guess for the pressure * @param WL The left state vector * @param WR The right state vector * @param vL The left velocity along the interface normal * @param vR The right velocity along the interface normal * @param aL The left sound speed * @param aR The right sound speed */ __attribute__((always_inline)) INLINE static float riemann_f( float p, const float* WL, const float* WR, float vL, float vR, float aL, float aR) { return riemann_fb(p, WL, aL) + riemann_fb(p, WR, aR) + (vR - vL); } /** * @brief Function (4.37) in Toro * * @param p The current guess for the pressure * @param W The left or right state vector * @param a The left or right sound speed */ __attribute__((always_inline)) INLINE static float riemann_fprimeb( float p, const float* W, float a) { float fval; if (p > W[4]) { const float A = hydro_two_over_gamma_plus_one / W[0]; const float B = hydro_gamma_minus_one_over_gamma_plus_one * W[4]; fval = (1.0f - 0.5f * (p - W[4]) / (B + p)) * sqrtf(A / (p + B)); } else { fval = 1.0f / W[0] / a * pow_minus_gamma_plus_one_over_two_gamma(p / W[4]); } return fval; } /** * @brief The derivative of riemann_f w.r.t. p * * @param p The current guess for the pressure * @param WL The left state vector * @param WR The right state vector * @param aL The left sound speed * @param aR The right sound speed */ __attribute__((always_inline)) INLINE static float riemann_fprime( float p, const float* WL, const float* WR, float aL, float aR) { return riemann_fprimeb(p, WL, aL) + riemann_fprimeb(p, WR, aR); } /** * @brief Bottom function of (4.48) in Toro * * @param p The current guess for the pressure * @param W The left or right state vector */ __attribute__((always_inline)) INLINE static float riemann_gb(float p, const float* W) { const float A = hydro_two_over_gamma_plus_one / W[0]; const float B = hydro_gamma_minus_one_over_gamma_plus_one * W[4]; return sqrtf(A / (p + B)); } /** * @brief Get a good first guess for the pressure in the iterative scheme * * This function is based on (4.47) and (4.48) in Toro and on the * FORTRAN code provided in Toro p.156-157 * * @param WL The left state vector * @param WR The right state vector * @param vL The left velocity along the interface normal * @param vR The right velocity along the interface normal * @param aL The left sound speed * @param aR The right sound speed */ __attribute__((always_inline)) INLINE static float riemann_guess_p( const float* WL, const float* WR, float vL, float vR, float aL, float aR) { float pguess, pmin, pmax, qmax; float ppv; pmin = min(WL[4], WR[4]); pmax = max(WL[4], WR[4]); qmax = pmax / pmin; ppv = 0.5f * (WL[4] + WR[4]) - 0.125f * (vR - vL) * (WL[0] + WR[0]) * (aL + aR); ppv = max(1.e-8f, ppv); if (qmax <= 2.0f && pmin <= ppv && ppv <= pmax) { pguess = ppv; } else { if (ppv < pmin) { /* two rarefactions */ pguess = pow_two_gamma_over_gamma_minus_one( (aL + aR - hydro_gamma_minus_one_over_two * (vR - vL)) / (aL / pow_gamma_minus_one_over_two_gamma(WL[4]) + aR / pow_gamma_minus_one_over_two_gamma(WR[4]))); } else { /* two shocks */ pguess = (riemann_gb(ppv, WL) * WL[4] + riemann_gb(ppv, WR) * WR[4] - vR + vL) / (riemann_gb(ppv, WL) + riemann_gb(ppv, WR)); } } /* Toro: "Not that approximate solutions may predict, incorrectly, a negative value for pressure (...). Thus in order to avoid negative guess values we introduce the small positive constant _tolerance" */ pguess = max(1.e-8f, pguess); return pguess; } /** * @brief Find the zeropoint of riemann_f(p) using Brent's method * * @param lower_limit Lower limit for the method (riemann_f(lower_limit) < 0) * @param upper_limit Upper limit for the method (riemann_f(upper_limit) > 0) * @param lowf Function value riemann_f(lower_limit) * @param upf Function value riemann_f(upper_limit) * @param error_tol Tolerance used to decide if the solution is converged * @param WL Left state vector * @param WR Right state vector * @param vL The left velocity along the interface normal * @param vR The right velocity along the interface normal * @param aL The left sound speed * @param aR The right sound speed */ __attribute__((always_inline)) INLINE static float riemann_solve_brent( float lower_limit, float upper_limit, float lowf, float upf, float error_tol, const float* WL, const float* WR, float vL, float vR, float aL, float aR) { float a, b, c, d, e, s; float fa, fb, fc, fs; float tmp, tmp2; int mflag; a = lower_limit; b = upper_limit; c = 0.0f; // previous value of b: b_{n-1} d = FLT_MAX; // value of b from two iterations ago: b_{n-2} e = FLT_MAX; // previous value of a: a_{n-1} fa = lowf; fb = upf; fc = 0.0f; s = 0.0f; fs = 0.0f; /* if f(a) f(b) >= 0 then error-exit */ if (fa * fb >= 0.0f) { error( "Brent's method called with equal sign function values!\n" "f(%g) = %g, f(%g) = %g\n", a, fa, b, fb); /* return NaN */ return 0.0f / 0.0f; } /* if |f(a)| < |f(b)| then swap (a,b) */ if (fabs(fa) < fabs(fb)) { tmp = a; a = b; b = tmp; tmp = fa; fa = fb; fb = tmp; } c = a; fc = fa; mflag = 1; /* Loop until convergence, i.e. until an exact zero point is found, or the * interval is sufficiently small, or the interval is unchanged since the * previous iteration */ int counter = 0; while ((fb != 0.0f) && (fabs(a - b) > error_tol * 0.5f * (a + b)) && (a != e || b != c)) { counter++; if (counter > 1000) error("Brent's method did not converge!\n"); if ((fa != fc) && (fb != fc)) /* Inverse quadratic interpolation */ s = a * fb * fc / (fa - fb) / (fa - fc) + b * fa * fc / (fb - fa) / (fb - fc) + c * fa * fb / (fc - fa) / (fc - fb); else /* Secant Rule */ s = b - fb * (b - a) / (fb - fa); tmp2 = 0.25f * (3.0f * a + b); if (!(((s > tmp2) && (s < b)) || ((s < tmp2) && (s > b))) || (mflag && (fabs(s - b) >= (0.5f * fabs(b - c)))) || (!mflag && (fabs(s - b) >= (0.5f * fabs(c - d)))) || (mflag && (fabs(b - c) < error_tol)) || (!mflag && (fabs(c - d) < error_tol))) { s = 0.5f * (a + b); mflag = 1; } else { mflag = 0; } fs = riemann_f(s, WL, WR, vL, vR, aL, aR); d = c; c = b; e = a; fc = fb; if (fa * fs < 0.) { b = s; fb = fs; } else { a = s; fa = fs; } /* if |f(a)| < |f(b)| then swap (a,b) */ if (fabs(fa) < fabs(fb)) { tmp = a; a = b; b = tmp; tmp = fa; fa = fb; fb = tmp; } } return b; } /* Solve the Riemann problem between the states WL and WR and store the result * in Whalf * The Riemann problem is solved in the x-direction; the velocities in the y- * and z-direction * are simply advected. */ /** * @brief Solve the Riemann problem between the given left and right state and * along the given interface normal * * Based on chapter 4 in Toro * * @param WL The left state vector * @param WR The right state vector * @param Whalf Empty state vector in which the result will be stored * @param n_unit Normal vector of the interface */ __attribute__((always_inline)) INLINE static void riemann_solver_solve( const float* WL, const float* WR, float* Whalf, const float* n_unit) { /* velocity of the left and right state in a frame aligned with n_unit */ float vL, vR, vhalf; /* sound speeds */ float aL, aR; /* variables used for finding pstar */ float p, pguess, fp, fpguess; /* variables used for sampling the solution */ float u; float pdpR, SR; float SHR, STR; float pdpL, SL; float SHL, STL; /* calculate velocities in interface frame */ vL = WL[1] * n_unit[0] + WL[2] * n_unit[1] + WL[3] * n_unit[2]; vR = WR[1] * n_unit[0] + WR[2] * n_unit[1] + WR[3] * n_unit[2]; /* calculate sound speeds */ aL = sqrtf(hydro_gamma * WL[4] / WL[0]); aR = sqrtf(hydro_gamma * WR[4] / WR[0]); /* check vacuum (generation) condition */ if (riemann_is_vacuum(WL, WR, vL, vR, aL, aR)) { riemann_solve_vacuum(WL, WR, vL, vR, aL, aR, Whalf, n_unit); return; } /* values are ok: let's find pstar (riemann_f(pstar) = 0)! */ /* We normally use a Newton-Raphson iteration to find the zeropoint of riemann_f(p), but if pstar is close to 0, we risk negative p values. Since riemann_f(p) is undefined for negative pressures, we don't want this to happen. We therefore use Brent's method if riemann_f(0) is larger than some value. -5 makes the iteration fail safe while almost never invoking the expensive Brent solver. */ p = 0.; /* obtain a first guess for p */ pguess = riemann_guess_p(WL, WR, vL, vR, aL, aR); fp = riemann_f(p, WL, WR, vL, vR, aL, aR); fpguess = riemann_f(pguess, WL, WR, vL, vR, aL, aR); /* ok, pstar is close to 0, better use Brent's method... */ /* we use Newton-Raphson until we find a suitable interval */ if (fp * fpguess >= 0.0f) { /* Newton-Raphson until convergence or until suitable interval is found to use Brent's method */ unsigned int counter = 0; while (fabs(p - pguess) > 1.e-6f * 0.5f * (p + pguess) && fpguess < 0.0f) { p = pguess; pguess = pguess - fpguess / riemann_fprime(pguess, WL, WR, aL, aR); fpguess = riemann_f(pguess, WL, WR, vL, vR, aL, aR); counter++; if (counter > 1000) { error("Stuck in Newton-Raphson!\n"); } } } /* As soon as there is a suitable interval: use Brent's method */ if (1.e6 * fabs(p - pguess) > 0.5f * (p + pguess) && fpguess > 0.0f) { p = 0.0f; fp = riemann_f(p, WL, WR, vL, vR, aL, aR); /* use Brent's method to find the zeropoint */ p = riemann_solve_brent(p, pguess, fp, fpguess, 1.e-6, WL, WR, vL, vR, aL, aR); } else { p = pguess; } /* calculate the velocity in the intermediate state */ u = 0.5f * (vL + vR) + 0.5f * (riemann_fb(p, WR, aR) - riemann_fb(p, WL, aL)); /* sample the solution */ /* This corresponds to the flow chart in Fig. 4.14 in Toro */ if (u < 0.0f) { /* advect velocity components */ Whalf[1] = WR[1]; Whalf[2] = WR[2]; Whalf[3] = WR[3]; pdpR = p / WR[4]; if (p > WR[4]) { /* shockwave */ SR = vR + aR * sqrtf(hydro_gamma_plus_one_over_two_gamma * pdpR + hydro_gamma_minus_one_over_two_gamma); if (SR > 0.0f) { Whalf[0] = WR[0] * (pdpR + hydro_gamma_minus_one_over_gamma_plus_one) / (hydro_gamma_minus_one_over_gamma_plus_one * pdpR + 1.0f); vhalf = u - vR; Whalf[4] = p; } else { Whalf[0] = WR[0]; vhalf = 0.0f; Whalf[4] = WR[4]; } } else { /* rarefaction wave */ SHR = vR + aR; if (SHR > 0.0f) { STR = u + aR * pow_gamma_minus_one_over_two_gamma(pdpR); if (STR <= 0.0f) { Whalf[0] = WR[0] * pow_two_over_gamma_minus_one( hydro_two_over_gamma_plus_one - hydro_gamma_minus_one_over_gamma_plus_one / aR * vR); vhalf = hydro_two_over_gamma_plus_one * (-aR + hydro_gamma_minus_one_over_two * vR) - vR; Whalf[4] = WR[4] * pow_two_gamma_over_gamma_minus_one( hydro_two_over_gamma_plus_one - hydro_gamma_minus_one_over_gamma_plus_one / aR * vR); } else { Whalf[0] = WR[0] * pow_one_over_gamma(pdpR); vhalf = u - vR; Whalf[4] = p; } } else { Whalf[0] = WR[0]; vhalf = 0.0f; Whalf[4] = WR[4]; } } } else { Whalf[1] = WL[1]; Whalf[2] = WL[2]; Whalf[3] = WL[3]; pdpL = p / WL[4]; if (p > WL[4]) { /* shockwave */ SL = vL - aL * sqrtf(hydro_gamma_plus_one_over_two_gamma * pdpL + hydro_gamma_minus_one_over_two_gamma); if (SL < 0.0f) { Whalf[0] = WL[0] * (pdpL + hydro_gamma_minus_one_over_gamma_plus_one) / (hydro_gamma_minus_one_over_gamma_plus_one * pdpL + 1.0f); vhalf = u - vL; Whalf[4] = p; } else { Whalf[0] = WL[0]; vhalf = 0.0f; Whalf[4] = WL[4]; } } else { /* rarefaction wave */ SHL = vL - aL; if (SHL < 0.0f) { STL = u - aL * pow_gamma_minus_one_over_two_gamma(pdpL); if (STL > 0.0f) { Whalf[0] = WL[0] * pow_two_over_gamma_minus_one( hydro_two_over_gamma_plus_one + hydro_gamma_minus_one_over_gamma_plus_one / aL * vL); vhalf = hydro_two_over_gamma_plus_one * (aL + hydro_gamma_minus_one_over_two * vL) - vL; Whalf[4] = WL[4] * pow_two_gamma_over_gamma_minus_one( hydro_two_over_gamma_plus_one + hydro_gamma_minus_one_over_gamma_plus_one / aL * vL); } else { Whalf[0] = WL[0] * pow_one_over_gamma(pdpL); vhalf = u - vL; Whalf[4] = p; } } else { Whalf[0] = WL[0]; vhalf = 0.0f; Whalf[4] = WL[4]; } } } /* add the velocity solution along the interface normal to the velocities */ Whalf[1] += vhalf * n_unit[0]; Whalf[2] += vhalf * n_unit[1]; Whalf[3] += vhalf * n_unit[2]; } /** * @brief Solve the Riemann problem between the given left and right state and * return the velocity and pressure in the middle state * * Based on chapter 4 in Toro * * @param WL Left state. * @param vL Left state velocity. * @param WR Right state. * @param vR Right state velocity. * @param vM Middle state velocity. * @param PM Middle state pressure. */ __attribute__((always_inline)) INLINE static void riemann_solver_solve_middle_state(const float* WL, const float vL, const float* WR, const float vR, float* vM, float* PM) { /* sound speeds */ float aL, aR; /* variables used for finding pstar */ float p, pguess, fp, fpguess; /* calculate sound speeds */ aL = sqrtf(hydro_gamma * WL[4] / WL[0]); aR = sqrtf(hydro_gamma * WR[4] / WR[0]); /* vacuum */ /* check vacuum (generation) condition */ if (riemann_is_vacuum(WL, WR, vL, vR, aL, aR)) { *vM = 0.0f; *PM = 0.0f; return; } /* values are ok: let's find pstar (riemann_f(pstar) = 0)! */ /* We normally use a Newton-Raphson iteration to find the zeropoint of riemann_f(p), but if pstar is close to 0, we risk negative p values. Since riemann_f(p) is undefined for negative pressures, we don't want this to happen. We therefore use Brent's method if riemann_f(0) is larger than some value. -5 makes the iteration fail safe while almost never invoking the expensive Brent solver. */ p = 0.0f; /* obtain a first guess for p */ pguess = riemann_guess_p(WL, WR, vL, vR, aL, aR); fp = riemann_f(p, WL, WR, vL, vR, aL, aR); fpguess = riemann_f(pguess, WL, WR, vL, vR, aL, aR); /* ok, pstar is close to 0, better use Brent's method... */ /* we use Newton-Raphson until we find a suitable interval */ if (fp * fpguess >= 0.0f) { /* Newton-Raphson until convergence or until suitable interval is found to use Brent's method */ unsigned int counter = 0; while (fabs(p - pguess) > 1.e-6f * 0.5f * (p + pguess) && fpguess < 0.0f) { p = pguess; pguess = pguess - fpguess / riemann_fprime(pguess, WL, WR, aL, aR); fpguess = riemann_f(pguess, WL, WR, vL, vR, aL, aR); counter++; if (counter > 1000) { error("Stuck in Newton-Raphson!\n"); } } } /* As soon as there is a suitable interval: use Brent's method */ if (1.e6 * fabs(p - pguess) > 0.5f * (p + pguess) && fpguess > 0.0f) { p = 0.0f; fp = riemann_f(p, WL, WR, vL, vR, aL, aR); /* use Brent's method to find the zeropoint */ p = riemann_solve_brent(p, pguess, fp, fpguess, 1.e-6, WL, WR, vL, vR, aL, aR); } else { p = pguess; } *PM = p; /* calculate the velocity in the intermediate state */ *vM = 0.5f * (vL + vR) + 0.5f * (riemann_fb(p, WR, aR) - riemann_fb(p, WL, aL)); } __attribute__((always_inline)) INLINE static void riemann_solve_for_flux( const float* Wi, const float* Wj, const float* n_unit, const float* vij, float* totflux) { #ifdef SWIFT_DEBUG_CHECKS riemann_check_input(Wi, Wj, n_unit, vij); #endif float Whalf[5]; float flux[5][3]; float vtot[3]; float rhoe; riemann_solver_solve(Wi, Wj, Whalf, n_unit); flux[0][0] = Whalf[0] * Whalf[1]; flux[0][1] = Whalf[0] * Whalf[2]; flux[0][2] = Whalf[0] * Whalf[3]; vtot[0] = Whalf[1] + vij[0]; vtot[1] = Whalf[2] + vij[1]; vtot[2] = Whalf[3] + vij[2]; flux[1][0] = Whalf[0] * vtot[0] * Whalf[1] + Whalf[4]; flux[1][1] = Whalf[0] * vtot[0] * Whalf[2]; flux[1][2] = Whalf[0] * vtot[0] * Whalf[3]; flux[2][0] = Whalf[0] * vtot[1] * Whalf[1]; flux[2][1] = Whalf[0] * vtot[1] * Whalf[2] + Whalf[4]; flux[2][2] = Whalf[0] * vtot[1] * Whalf[3]; flux[3][0] = Whalf[0] * vtot[2] * Whalf[1]; flux[3][1] = Whalf[0] * vtot[2] * Whalf[2]; flux[3][2] = Whalf[0] * vtot[2] * Whalf[3] + Whalf[4]; /* eqn. (15) */ /* F_P = \rho e ( \vec{v} - \vec{v_{ij}} ) + P \vec{v} */ /* \rho e = P / (\gamma-1) + 1/2 \rho \vec{v}^2 */ rhoe = Whalf[4] / hydro_gamma_minus_one + 0.5f * Whalf[0] * (vtot[0] * vtot[0] + vtot[1] * vtot[1] + vtot[2] * vtot[2]); flux[4][0] = rhoe * Whalf[1] + Whalf[4] * vtot[0]; flux[4][1] = rhoe * Whalf[2] + Whalf[4] * vtot[1]; flux[4][2] = rhoe * Whalf[3] + Whalf[4] * vtot[2]; totflux[0] = flux[0][0] * n_unit[0] + flux[0][1] * n_unit[1] + flux[0][2] * n_unit[2]; totflux[1] = flux[1][0] * n_unit[0] + flux[1][1] * n_unit[1] + flux[1][2] * n_unit[2]; totflux[2] = flux[2][0] * n_unit[0] + flux[2][1] * n_unit[1] + flux[2][2] * n_unit[2]; totflux[3] = flux[3][0] * n_unit[0] + flux[3][1] * n_unit[1] + flux[3][2] * n_unit[2]; totflux[4] = flux[4][0] * n_unit[0] + flux[4][1] * n_unit[1] + flux[4][2] * n_unit[2]; #ifdef SWIFT_DEBUG_CHECKS riemann_check_output(Wi, Wj, n_unit, vij, totflux); #endif } __attribute__((always_inline)) INLINE static void riemann_solve_for_middle_state_flux(const float* Wi, const float* Wj, const float* n_unit, const float* vij, float* totflux) { #ifdef SWIFT_DEBUG_CHECKS riemann_check_input(Wi, Wj, n_unit, vij); #endif /* vacuum? */ if (Wi[0] == 0.0f || Wj[0] == 0.0f) { totflux[0] = 0.0f; totflux[1] = 0.0f; totflux[2] = 0.0f; totflux[3] = 0.0f; totflux[4] = 0.0f; return; } const float vL = Wi[1] * n_unit[0] + Wi[2] * n_unit[1] + Wi[3] * n_unit[2]; const float vR = Wj[1] * n_unit[0] + Wj[2] * n_unit[1] + Wj[3] * n_unit[2]; float vM, PM; riemann_solver_solve_middle_state(Wi, vL, Wj, vR, &vM, &PM); const float vface = vij[0] * n_unit[0] + vij[1] * n_unit[1] + vij[2] * n_unit[2]; totflux[0] = 0.0f; totflux[1] = PM * n_unit[0]; totflux[2] = PM * n_unit[1]; totflux[3] = PM * n_unit[2]; totflux[4] = (vM + vface) * PM; #ifdef SWIFT_DEBUG_CHECKS riemann_check_output(Wi, Wj, n_unit, vij, totflux); #endif } #endif /* SWIFT_RIEMANN_EXACT_H */