├── .gitignore ├── LICENSE ├── README.md ├── custom.css ├── fv_grid.png ├── riemann-mol.png ├── riemann-waves.png ├── riemann.py ├── riemann_exact.py ├── riemann_state.png ├── simplegrid_gc.png ├── sod-exact.out └── write_a_hydrocode.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints/ 2 | __pycache__/ 3 | *~ 4 | write_a_hydrocode.slides.html 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, python-hydro 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How To Write A Hydro Code 2 | 3 | This is a simple Jupyter/python tutorial on writing a 2nd order 4 | finite-volume hydro code for the compressible Euler equations. 5 | 6 | To play as slides, do: 7 | ``` 8 | jupyter nbconvert write_a_hydrocode.ipynb --to slides --post serve 9 | ``` 10 | -------------------------------------------------------------------------------- /custom.css: -------------------------------------------------------------------------------- 1 | .reveal pre code { 2 | padding: 0.5em; 3 | } 4 | 5 | 6 | .reveal p { 7 | margin-bottom: 0.25em; 8 | margin-top: 0.5em; 9 | } 10 | 11 | .reveal ul { 12 | padding: 0em; 13 | margin-top: 0.5em; 14 | } 15 | 16 | .reveal { 17 | font-size: 110; 18 | font-family: carlito; 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /fv_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-hydro/how_to_write_a_hydro_code/337a066405b47f9a31c8dd00ec833c3ba6742c3c/fv_grid.png -------------------------------------------------------------------------------- /riemann-mol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-hydro/how_to_write_a_hydro_code/337a066405b47f9a31c8dd00ec833c3ba6742c3c/riemann-mol.png -------------------------------------------------------------------------------- /riemann-waves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-hydro/how_to_write_a_hydro_code/337a066405b47f9a31c8dd00ec833c3ba6742c3c/riemann-waves.png -------------------------------------------------------------------------------- /riemann.py: -------------------------------------------------------------------------------- 1 | r"""Solve riemann shock tube problem for a general equation of state 2 | using the method of Colella, Glaz, and Ferguson (this is the main 3 | solver used in Castro). Use a two shock approximation, and linearly 4 | interpolation between the head and tail of a rarefaction to treat 5 | rarefactions. 6 | 7 | The Riemann problem for the Euler's equation produces 4 states, 8 | separated by the three characteristics (u - cs, u, u + cs): 9 | 10 | 11 | l_1 t l_2 l_3 12 | \ ^ . / 13 | \ *L | . *R / 14 | \ | . / 15 | \ | . / 16 | L \ | . / R 17 | \ | . / 18 | \ |. / 19 | \|./ 20 | ----------+----------------> x 21 | 22 | l_1 = u - cs eigenvalue 23 | l_2 = u eigenvalue (contact) 24 | l_3 = u + cs eigenvalue 25 | 26 | only density jumps across l_2 27 | 28 | References: 29 | 30 | CG: Colella & Glaz 1985, JCP, 59, 264. 31 | 32 | CW: Colella & Woodward 1984, JCP, 54, 174. 33 | 34 | Fry: Fryxell et al. 2000, ApJS, 131, 273. 35 | 36 | Toro: Toro 1999, ``Riemann Solvers and Numerical Methods for Fluid 37 | Dynamcs: A Practical Introduction, 2nd Ed.'', Springer-Verlag 38 | 39 | """ 40 | 41 | import numpy as np 42 | 43 | URHO = 0 44 | UMX = 1 45 | UENER = 2 46 | 47 | QRHO = 0 48 | QU = 1 49 | QP = 2 50 | 51 | NVAR = 3 52 | 53 | 54 | def riemann(q_l, q_r, gamma): 55 | """solve the Riemann problem given left and right primitive variable 56 | states. We return the flux""" 57 | 58 | flux = np.zeros(NVAR) 59 | 60 | small_rho = 1.e-10 61 | small_p = 1.e-10 62 | small_u = 1.e-10 63 | 64 | rho_l = max(q_l[QRHO], small_rho) 65 | u_l = q_l[QU] 66 | p_l = max(q_l[QP], small_p) 67 | 68 | rho_r = max(q_r[QRHO], small_rho) 69 | u_r = q_r[QU] 70 | p_r = max(q_r[QP], small_p) 71 | 72 | # wave speeds (Lagrangian sound speed) 73 | wsmall = small_rho * small_u 74 | w_l = max(np.sqrt(abs(gamma*p_l*rho_l)), wsmall) 75 | w_r = max(np.sqrt(abs(gamma*p_r*rho_r)), wsmall) 76 | 77 | # construct our guess at pstar and ustar 78 | wwinv = 1.0/(w_l + w_r) 79 | p_star = ((w_r*p_l + w_l*p_r) + w_l*w_r*(u_l - u_r))*wwinv 80 | u_star = ((w_l*u_l + w_r*u_r) + (p_l - p_r))*wwinv 81 | 82 | p_star = max(p_star, small_p) 83 | 84 | if abs(u_star) < small_u*0.5*(abs(u_l) + abs(u_r)): 85 | u_star = 0.0 86 | 87 | if u_star > 0.0: 88 | rho_o = rho_l 89 | u_o = u_l 90 | p_o = p_l 91 | 92 | elif u_star < 0: 93 | rho_o = rho_r 94 | u_o = u_r 95 | p_o = p_r 96 | 97 | else: 98 | rho_o = 0.5*(rho_l + rho_r) 99 | u_o = 0.5*(u_l + u_r) 100 | p_o = 0.5*(p_l + p_r) 101 | 102 | rho_o = max(rho_o, small_rho) 103 | 104 | c_o = np.sqrt(abs(gamma*p_o/rho_o)) 105 | c_o = max(c_o, small_u) 106 | 107 | # compute the rest of the star state 108 | drho = (p_star - p_o)/c_o**2 109 | rho_star = rho_o + drho 110 | rho_star = max(rho_star, small_rho) 111 | 112 | c_star = np.sqrt(gamma * p_star/rho_star) 113 | c_star = max(c_star, small_u) 114 | 115 | # sample the solution 116 | sgn = np.sign(u_star) 117 | spout = c_o - sgn*u_o 118 | spin = c_star - sgn*u_star 119 | 120 | ushock = 0.5*(spin + spout) 121 | 122 | if p_star > p_o: 123 | # compression -- we are a shock 124 | spin = ushock 125 | spout = ushock 126 | 127 | if spout-spin == 0.0: 128 | cavg = 0.5 * (np.sqrt(abs(gamma*p_l/rho_l)) + np.sqrt(abs(gamma*p_r/rho_r))) 129 | scr = small_u*0.5*cavg 130 | else: 131 | scr = spout - spin 132 | 133 | # interpolate for the case we have a rarefaction 134 | frac = 0.5*(1.0 + (spout + spin)/scr) 135 | frac = max(0.0, min(1.0, frac)) 136 | 137 | rho_int = frac*rho_star + (1.0 - frac)*rho_o 138 | u_int = frac*u_star + (1.0 - frac)*u_o 139 | p_int = frac*p_star + (1.0 - frac)*p_o 140 | 141 | # here we are assuming that the rarefaction spans the interface. Correct that now 142 | if spout < 0.0: 143 | rho_int = rho_o 144 | u_int = u_o 145 | p_int = p_o 146 | 147 | if spin >= 0.0: 148 | rho_int = rho_star 149 | u_int = u_star 150 | p_int = p_star 151 | 152 | # now compute the fluxes 153 | flux[URHO] = rho_int*u_int 154 | flux[UMX] = rho_int*u_int**2 + p_int 155 | flux[UENER] = u_int*(p_int/(gamma - 1.0) + 0.5*rho_int*u_int**2 + p_int) 156 | 157 | return flux 158 | 159 | if __name__ == "__main__": 160 | q_l = np.array([1.0, 0.0, 1.0]) 161 | q_r = np.array([0.125, 0.0, 0.1]) 162 | gamma = 1.4 163 | 164 | F = riemann(q_l, q_r, gamma) 165 | print(F) 166 | -------------------------------------------------------------------------------- /riemann_exact.py: -------------------------------------------------------------------------------- 1 | """An exact Riemann solver for the Euler equations with a gamma-law 2 | gas. The left and right states are stored as State objects. We then 3 | create a RiemannProblem object with the left and right state: 4 | 5 | > rp = RiemannProblem(left_state, right_state) 6 | 7 | Next we solve for the star state: 8 | 9 | > rp.find_star_state() 10 | 11 | Finally, we sample the solution to find the interface state, which 12 | is returned as a State object: 13 | 14 | > q_int = rp.sample_solution() 15 | """ 16 | 17 | import numpy as np 18 | import scipy.optimize as optimize 19 | 20 | class State(object): 21 | """ a simple object to hold a primitive variable state """ 22 | 23 | def __init__(self, p=1.0, u=0.0, rho=1.0): 24 | self.p = p 25 | self.u = u 26 | self.rho = rho 27 | 28 | def __str__(self): 29 | return "rho: {}; u: {}; p: {}".format(self.rho, self.u, self.p) 30 | 31 | class RiemannProblem(object): 32 | """ a class to define a Riemann problem. It takes a left 33 | and right state. Note: we assume a constant gamma """ 34 | 35 | def __init__(self, left_state, right_state, gamma=1.4): 36 | self.left = left_state 37 | self.right = right_state 38 | self.gamma = gamma 39 | 40 | self.ustar = None 41 | self.pstar = None 42 | 43 | def u_hugoniot(self, p, side): 44 | """define the Hugoniot curve, u(p).""" 45 | 46 | if side == "left": 47 | state = self.left 48 | s = 1.0 49 | elif side == "right": 50 | state = self.right 51 | s = -1.0 52 | 53 | c = np.sqrt(self.gamma*state.p/state.rho) 54 | 55 | if p < state.p: 56 | # rarefaction 57 | u = state.u + s*(2.0*c/(self.gamma-1.0))* \ 58 | (1.0 - (p/state.p)**((self.gamma-1.0)/(2.0*self.gamma))) 59 | else: 60 | # shock 61 | beta = (self.gamma+1.0)/(self.gamma-1.0) 62 | u = state.u + s*(2.0*c/np.sqrt(2.0*self.gamma*(self.gamma-1.0)))* \ 63 | (1.0 - p/state.p)/np.sqrt(1.0 + beta*p/state.p) 64 | 65 | return u 66 | 67 | def find_star_state(self, p_min=0.001, p_max=1000.0): 68 | """ root find the Hugoniot curve to find ustar, pstar """ 69 | 70 | # we need to root-find on 71 | self.pstar = optimize.brentq( 72 | lambda p: self.u_hugoniot(p, "left") - self.u_hugoniot(p, "right"), 73 | p_min, p_max) 74 | self.ustar = self.u_hugoniot(self.pstar, "left") 75 | 76 | 77 | def shock_solution(self, sgn, state): 78 | """return the interface solution considering a shock""" 79 | 80 | p_ratio = self.pstar/state.p 81 | c = np.sqrt(self.gamma*state.p/state.rho) 82 | 83 | # Toro, eq. 4.52 / 4.59 84 | S = state.u + sgn*c*np.sqrt(0.5*(self.gamma + 1.0)/self.gamma*p_ratio + 85 | 0.5*(self.gamma - 1.0)/self.gamma) 86 | 87 | # are we to the left or right of the shock? 88 | if (self.ustar < 0 and S < 0) or (self.ustar > 0 and S > 0): 89 | # R/L region 90 | solution = state 91 | else: 92 | # * region -- get rhostar from Toro, eq. 4.50 / 4.57 93 | gam_fac = (self.gamma - 1.0)/(self.gamma + 1.0) 94 | rhostar = state.rho * (p_ratio + gam_fac)/(gam_fac * p_ratio + 1.0) 95 | solution = State(rho=rhostar, u=self.ustar, p=self.pstar) 96 | 97 | return solution 98 | 99 | def rarefaction_solution(self, sgn, state): 100 | """return the interface solution considering a rarefaction wave""" 101 | 102 | # find the speed of the head and tail of the rarefaction fan 103 | 104 | # isentropic (Toro eq. 4.54 / 4.61) 105 | p_ratio = self.pstar/state.p 106 | c = np.sqrt(self.gamma*state.p/state.rho) 107 | cstar = c*p_ratio**((self.gamma-1.0)/(2*self.gamma)) 108 | 109 | lambda_head = state.u + sgn*c 110 | lambda_tail = self.ustar + sgn*cstar 111 | 112 | gam_fac = (self.gamma - 1.0)/(self.gamma + 1.0) 113 | 114 | if (sgn > 0 and lambda_head < 0) or (sgn < 0 and lambda_head > 0): 115 | # R/L region 116 | solution = state 117 | 118 | elif (sgn > 0 and lambda_tail > 0) or (sgn < 0 and lambda_tail < 0): 119 | # * region, we use the isentropic density (Toro 4.53 / 4.60) 120 | solution = State(rho = state.rho*p_ratio**(1.0/self.gamma), 121 | u = self.ustar, p = self.pstar) 122 | 123 | else: 124 | # we are in the fan -- Toro 4.56 / 4.63 125 | rho = state.rho * (2/(self.gamma + 1.0) - 126 | sgn*gam_fac*state.u/c)**(2.0/(self.gamma-1.0)) 127 | u = 2.0/(self.gamma + 1.0) * ( -sgn*c + 0.5*(self.gamma - 1.0)*state.u) 128 | p = state.p * (2/(self.gamma + 1.0) - 129 | sgn*gam_fac*state.u/c)**(2.0*self.gamma/(self.gamma-1.0)) 130 | solution = State(rho=rho, u=u, p=p) 131 | 132 | return solution 133 | 134 | def sample_solution(self): 135 | """given the star state (ustar, pstar), find the state on the interface""" 136 | 137 | if self.ustar < 0: 138 | # we are in the R* or R region 139 | state = self.right 140 | sgn = 1.0 141 | else: 142 | # we are in the L* or L region 143 | state = self.left 144 | sgn = -1.0 145 | 146 | # is the non-contact wave a shock or rarefaction? 147 | if self.pstar > state.p: 148 | # compression! we are a shock 149 | solution = self.shock_solution(sgn, state) 150 | 151 | else: 152 | # rarefaction 153 | solution = self.rarefaction_solution(sgn, state) 154 | 155 | return solution 156 | 157 | def cons_flux(state, v): 158 | """ given an interface state, return the conservative flux""" 159 | flux = np.zeros((v.nvar), dtype=np.float64) 160 | 161 | flux[v.urho] = state.rho * state.u 162 | flux[v.umx] = flux[v.urho] * state.u + state.p 163 | flux[v.uener] = (0.5 * state.rho * state.u**2 + 164 | state.p/(v.gamma - 1.0) + state.p) * state.u 165 | return flux 166 | 167 | 168 | if __name__ == "__main__": 169 | 170 | q_l = State(rho=1.0, u=0.0, p=1.0) 171 | q_r = State(rho=0.125, u=0.0, p=0.1) 172 | 173 | rp = RiemannProblem(q_l, q_r, gamma=1.4) 174 | 175 | rp.find_star_state() 176 | q_int = rp.sample_solution() 177 | print(q_int) 178 | -------------------------------------------------------------------------------- /riemann_state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-hydro/how_to_write_a_hydro_code/337a066405b47f9a31c8dd00ec833c3ba6742c3c/riemann_state.png -------------------------------------------------------------------------------- /simplegrid_gc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-hydro/how_to_write_a_hydro_code/337a066405b47f9a31c8dd00ec833c3ba6742c3c/simplegrid_gc.png -------------------------------------------------------------------------------- /sod-exact.out: -------------------------------------------------------------------------------- 1 | # Exact solution for the Sod problem at t = 0.2 s, using Toro's exact Riemann 2 | # solver (Ch. 4) and gamma = 1.4 3 | x rho u p e 4 | 0.003906 1.000000 0.000000 1.000000 2.500000 5 | 0.011719 1.000000 0.000000 1.000000 2.500000 6 | 0.019531 1.000000 0.000000 1.000000 2.500000 7 | 0.027344 1.000000 0.000000 1.000000 2.500000 8 | 0.035156 1.000000 0.000000 1.000000 2.500000 9 | 0.042969 1.000000 0.000000 1.000000 2.500000 10 | 0.050781 1.000000 0.000000 1.000000 2.500000 11 | 0.058594 1.000000 0.000000 1.000000 2.500000 12 | 0.066406 1.000000 0.000000 1.000000 2.500000 13 | 0.074219 1.000000 0.000000 1.000000 2.500000 14 | 0.082031 1.000000 0.000000 1.000000 2.500000 15 | 0.089844 1.000000 0.000000 1.000000 2.500000 16 | 0.097656 1.000000 0.000000 1.000000 2.500000 17 | 0.105469 1.000000 0.000000 1.000000 2.500000 18 | 0.113281 1.000000 0.000000 1.000000 2.500000 19 | 0.121094 1.000000 0.000000 1.000000 2.500000 20 | 0.128906 1.000000 0.000000 1.000000 2.500000 21 | 0.136719 1.000000 0.000000 1.000000 2.500000 22 | 0.144531 1.000000 0.000000 1.000000 2.500000 23 | 0.152344 1.000000 0.000000 1.000000 2.500000 24 | 0.160156 1.000000 0.000000 1.000000 2.500000 25 | 0.167969 1.000000 0.000000 1.000000 2.500000 26 | 0.175781 1.000000 0.000000 1.000000 2.500000 27 | 0.183594 1.000000 0.000000 1.000000 2.500000 28 | 0.191406 1.000000 0.000000 1.000000 2.500000 29 | 0.199219 1.000000 0.000000 1.000000 2.500000 30 | 0.207031 1.000000 0.000000 1.000000 2.500000 31 | 0.214844 1.000000 0.000000 1.000000 2.500000 32 | 0.222656 1.000000 0.000000 1.000000 2.500000 33 | 0.230469 1.000000 0.000000 1.000000 2.500000 34 | 0.238281 1.000000 0.000000 1.000000 2.500000 35 | 0.246094 1.000000 0.000000 1.000000 2.500000 36 | 0.253906 1.000000 0.000000 1.000000 2.500000 37 | 0.261719 1.000000 0.000000 1.000000 2.500000 38 | 0.269531 0.978445 0.025727 0.969954 2.478304 39 | 0.277344 0.951706 0.058279 0.933048 2.450988 40 | 0.285156 0.925555 0.090831 0.897353 2.423823 41 | 0.292969 0.899982 0.123383 0.862834 2.396810 42 | 0.300781 0.874977 0.155935 0.829460 2.369948 43 | 0.308594 0.850532 0.188487 0.797199 2.343237 44 | 0.316406 0.826635 0.221039 0.766019 2.316678 45 | 0.324219 0.803279 0.253591 0.735890 2.290270 46 | 0.332031 0.780454 0.286144 0.706783 2.264013 47 | 0.339844 0.758151 0.318696 0.678668 2.237908 48 | 0.347656 0.736360 0.351248 0.651518 2.211954 49 | 0.355469 0.715073 0.383800 0.625304 2.186152 50 | 0.363281 0.694282 0.416352 0.599999 2.160501 51 | 0.371094 0.673977 0.448904 0.575577 2.135001 52 | 0.378906 0.654150 0.481456 0.552012 2.109653 53 | 0.386719 0.634792 0.514008 0.529278 2.084456 54 | 0.394531 0.615896 0.546560 0.507353 2.059410 55 | 0.402344 0.597452 0.579112 0.486210 2.034516 56 | 0.410156 0.579452 0.611664 0.465827 2.009773 57 | 0.417969 0.561889 0.644216 0.446181 1.985182 58 | 0.425781 0.544755 0.676769 0.427249 1.960742 59 | 0.433594 0.528041 0.709321 0.409010 1.936453 60 | 0.441406 0.511739 0.741873 0.391443 1.912316 61 | 0.449219 0.495843 0.774425 0.374526 1.888330 62 | 0.457031 0.480345 0.806977 0.358240 1.864495 63 | 0.464844 0.465236 0.839529 0.342565 1.840812 64 | 0.472656 0.450510 0.872081 0.327481 1.817280 65 | 0.480469 0.436160 0.904633 0.312971 1.793900 66 | 0.488281 0.426319 0.927453 0.303130 1.777600 67 | 0.496094 0.426319 0.927453 0.303130 1.777600 68 | 0.503906 0.426319 0.927453 0.303130 1.777600 69 | 0.511719 0.426319 0.927453 0.303130 1.777600 70 | 0.519531 0.426319 0.927453 0.303130 1.777600 71 | 0.527344 0.426319 0.927453 0.303130 1.777600 72 | 0.535156 0.426319 0.927453 0.303130 1.777600 73 | 0.542969 0.426319 0.927453 0.303130 1.777600 74 | 0.550781 0.426319 0.927453 0.303130 1.777600 75 | 0.558594 0.426319 0.927453 0.303130 1.777600 76 | 0.566406 0.426319 0.927453 0.303130 1.777600 77 | 0.574219 0.426319 0.927453 0.303130 1.777600 78 | 0.582031 0.426319 0.927453 0.303130 1.777600 79 | 0.589844 0.426319 0.927453 0.303130 1.777600 80 | 0.597656 0.426319 0.927453 0.303130 1.777600 81 | 0.605469 0.426319 0.927453 0.303130 1.777600 82 | 0.613281 0.426319 0.927453 0.303130 1.777600 83 | 0.621094 0.426319 0.927453 0.303130 1.777600 84 | 0.628906 0.426319 0.927453 0.303130 1.777600 85 | 0.636719 0.426319 0.927453 0.303130 1.777600 86 | 0.644531 0.426319 0.927453 0.303130 1.777600 87 | 0.652344 0.426319 0.927453 0.303130 1.777600 88 | 0.660156 0.426319 0.927453 0.303130 1.777600 89 | 0.667969 0.426319 0.927453 0.303130 1.777600 90 | 0.675781 0.426319 0.927453 0.303130 1.777600 91 | 0.683594 0.426319 0.927453 0.303130 1.777600 92 | 0.691406 0.265574 0.927453 0.303130 2.853541 93 | 0.699219 0.265574 0.927453 0.303130 2.853541 94 | 0.707031 0.265574 0.927453 0.303130 2.853541 95 | 0.714844 0.265574 0.927453 0.303130 2.853541 96 | 0.722656 0.265574 0.927453 0.303130 2.853541 97 | 0.730469 0.265574 0.927453 0.303130 2.853541 98 | 0.738281 0.265574 0.927453 0.303130 2.853541 99 | 0.746094 0.265574 0.927453 0.303130 2.853541 100 | 0.753906 0.265574 0.927453 0.303130 2.853541 101 | 0.761719 0.265574 0.927453 0.303130 2.853541 102 | 0.769531 0.265574 0.927453 0.303130 2.853541 103 | 0.777344 0.265574 0.927453 0.303130 2.853541 104 | 0.785156 0.265574 0.927453 0.303130 2.853541 105 | 0.792969 0.265574 0.927453 0.303130 2.853541 106 | 0.800781 0.265574 0.927453 0.303130 2.853541 107 | 0.808594 0.265574 0.927453 0.303130 2.853541 108 | 0.816406 0.265574 0.927453 0.303130 2.853541 109 | 0.824219 0.265574 0.927453 0.303130 2.853541 110 | 0.832031 0.265574 0.927453 0.303130 2.853541 111 | 0.839844 0.265574 0.927453 0.303130 2.853541 112 | 0.847656 0.265574 0.927453 0.303130 2.853541 113 | 0.855469 0.125000 0.000000 0.100000 2.000000 114 | 0.863281 0.125000 0.000000 0.100000 2.000000 115 | 0.871094 0.125000 0.000000 0.100000 2.000000 116 | 0.878906 0.125000 0.000000 0.100000 2.000000 117 | 0.886719 0.125000 0.000000 0.100000 2.000000 118 | 0.894531 0.125000 0.000000 0.100000 2.000000 119 | 0.902344 0.125000 0.000000 0.100000 2.000000 120 | 0.910156 0.125000 0.000000 0.100000 2.000000 121 | 0.917969 0.125000 0.000000 0.100000 2.000000 122 | 0.925781 0.125000 0.000000 0.100000 2.000000 123 | 0.933594 0.125000 0.000000 0.100000 2.000000 124 | 0.941406 0.125000 0.000000 0.100000 2.000000 125 | 0.949219 0.125000 0.000000 0.100000 2.000000 126 | 0.957031 0.125000 0.000000 0.100000 2.000000 127 | 0.964844 0.125000 0.000000 0.100000 2.000000 128 | 0.972656 0.125000 0.000000 0.100000 2.000000 129 | 0.980469 0.125000 0.000000 0.100000 2.000000 130 | 0.988281 0.125000 0.000000 0.100000 2.000000 131 | 0.996094 0.125000 0.000000 0.100000 2.000000 132 | -------------------------------------------------------------------------------- /write_a_hydrocode.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "slideshow": { 7 | "slide_type": "slide" 8 | } 9 | }, 10 | "source": [ 11 | "# How To Write A Hydro Code\n", 12 | "\n", 13 | "Michael Zingale" 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "metadata": { 19 | "slideshow": { 20 | "slide_type": "slide" 21 | } 22 | }, 23 | "source": [ 24 | "There are _many_ methods for solving the equations of hydrodynamics. We will make some choices right from the start:\n", 25 | "\n", 26 | " * We will consider **finite-volume methods**. These are popular in astrophysics because they are based on the integral form of the conservative equations and properly conserve mass, momentum, and energy.\n", 27 | " \n", 28 | " * We will consider an **Eulerian** grid: the grid is fixed and the fluid moves through it.\n", 29 | " \n", 30 | " * We will be **explicit in time**: the new solution depends only on the previous state.\n", 31 | " \n", 32 | " * We will look at a simple 2nd order **method-of-lines** integration. We do this for simplicity here, and will point out where things are commonly done differently. This scheme has a much simpler spatial reconstruction than methods that do characteristic tracing and relies on an integrator (like a Runge-Kutta method) to advance in time.\n", 33 | " \n", 34 | " * We will work in 1-d.\n", 35 | " \n", 36 | " * We won't cover in detail how to write a Riemann solver (that's a math exercise as much as anything else and beyond the scope of this notebook).\n", 37 | " \n", 38 | " * We'll assume a gamma-law equation of state—this is often not the case in astrophysics." 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": { 44 | "slideshow": { 45 | "slide_type": "slide" 46 | } 47 | }, 48 | "source": [ 49 | " \n", 50 | "Much more in-depth details and derivations are given in my hydro notes available online: https://github.com/Open-Astrophysics-Bookshelf/numerical_exercises\n", 51 | "\n", 52 | "For a greater variety of methods, in 2-d, see the pyro code: https://github.com/python-hydro/pyro2 (ref: [Harpole et al. JOSS](http://joss.theoj.org/papers/10.21105/joss.01265))" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": { 58 | "slideshow": { 59 | "slide_type": "slide" 60 | } 61 | }, 62 | "source": [ 63 | "## Overview\n", 64 | "\n", 65 | "We'll focus on the Euler equations. In 1-d, these are:\n", 66 | "\n", 67 | "\\begin{align*}\n", 68 | " \\frac{\\partial \\rho}{\\partial t} + \\frac{\\partial (\\rho u)}{\\partial x} & = 0 \\\\\n", 69 | " \\frac{\\partial (\\rho u)}{\\partial t} + \\frac{\\partial (\\rho u^2 + p)}{\\partial x} &= 0 \\\\\n", 70 | " \\frac{\\partial (\\rho E)}{\\partial t} + \\frac{\\partial (u(\\rho E + p))}{\\partial x} &= 0 \\\\\n", 71 | " \\end{align*}\n", 72 | "\n", 73 | "This is a set of (hyperbolic) partial differential equations. To close the system, we need an equation of state, relating the specific internal energy, $e$, to the pressure:\n", 74 | "\\begin{align*}\n", 75 | "e &= E - \\frac{1}{2}u^2 \\\\\n", 76 | "p &= \\rho e (\\gamma - 1)\n", 77 | "\\end{align*}" 78 | ] 79 | }, 80 | { 81 | "cell_type": "markdown", 82 | "metadata": { 83 | "slideshow": { 84 | "slide_type": "slide" 85 | } 86 | }, 87 | "source": [ 88 | "To solve these, we need to discretize the equations in both space and time. We'll use grid-based methods (in addition to the finite-volume method we'll consider, this can include finite-difference and finite-element methods). \n", 89 | "\n", 90 | "Our system of equations can be expressed in conservative form:\n", 91 | "$$ \\frac{\\partial U}{\\partial t} + \\frac{\\partial F(U)}{\\partial x} = 0$$\n", 92 | "where $U = (\\rho, \\rho u, \\rho E)^\\intercal$ and\n", 93 | "$$\n", 94 | "F(U) = \\left ( \\begin{array}{c} \\rho u \\\\ \\rho u^2 + p \\\\ u (\\rho E + p) \\end{array} \\right )$$" 95 | ] 96 | }, 97 | { 98 | "cell_type": "markdown", 99 | "metadata": { 100 | "slideshow": { 101 | "slide_type": "slide" 102 | } 103 | }, 104 | "source": [ 105 | "In a finite-volume method, we store the state of the fluid in discrete volumes in space, and we can refer to this discretized state with an index. To see this, we integrate the conservative law system in space over a volume $[x_{i-1/2},x_{i+1/2}]$:\n", 106 | "$$\\frac{\\partial \\langle U\\rangle_i}{\\partial t} = - \\frac{F_{i+1/2} - F_{i-1/2}}{\\Delta x}$$\n", 107 | "\n", 108 | "This is the form of the equations we will solve. Here, $\\langle U\\rangle_i$ represents the average state of the fluid in a volume:\n", 109 | "$$\\langle U\\rangle_i = \\frac{1}{\\Delta x} \\int_{x_{i-1/2}}^{x_{i+1/2}} U(x) dx$$\n", 110 | "Visually, we usually think of this grid as:\n", 111 | "\n", 112 | "![FV grid](fv_grid.png)" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": { 118 | "slideshow": { 119 | "slide_type": "slide" 120 | } 121 | }, 122 | "source": [ 123 | "The state on the grid represents an instance in time. We evolve the state by computing the fluxes through the volumes. These fluxes tell us how much the state changes in each volume over some small timestep, $\\Delta t$. \n", 124 | "\n", 125 | "Our code will have the following structure:\n", 126 | "\n", 127 | " * Create our numerical grid\n", 128 | " \n", 129 | " * Set the initial conditions\n", 130 | " \n", 131 | " * Main timestep evolution loop\n", 132 | " \n", 133 | " * Compute the timestep\n", 134 | " \n", 135 | " * Loop to advance one step (count depends on the number of stages in the integrator)\n", 136 | " \n", 137 | " * Reconstruct the state to interfaces\n", 138 | " \n", 139 | " * Solve Riemann problem to find the fluxes through the interface\n", 140 | " \n", 141 | " * Do a conservative update of the state to the stage\n", 142 | " \n", 143 | " * Output" 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "metadata": { 149 | "slideshow": { 150 | "slide_type": "slide" 151 | } 152 | }, 153 | "source": [ 154 | "## Grid\n", 155 | "\n", 156 | "We'll manage our 1-d grid via a class `FVGrid`. We will divide the domain into a number of zones (or volumes) that will store the state. To implement boundary conditions, we traditionally use ghost cells--extra cells added to each end of the domain. We'll consider a grid that looks like this:\n", 157 | "\n", 158 | "![grid w ghostcells](simplegrid_gc.png)\n", 159 | "\n", 160 | "We'll use the names `lo` and `hi` to refer to the first and last zone in our domain. The domain boundaries are the bold lines shown above, and beyond that, on each end, we have ghost cells.\n", 161 | "\n", 162 | "The main information we need to setup the grid are the number of zones in the interior and the number of ghost cells." 163 | ] 164 | }, 165 | { 166 | "cell_type": "code", 167 | "execution_count": 1, 168 | "metadata": { 169 | "slideshow": { 170 | "slide_type": "skip" 171 | } 172 | }, 173 | "outputs": [], 174 | "source": [ 175 | "import numpy as np" 176 | ] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "metadata": { 181 | "slideshow": { 182 | "slide_type": "slide" 183 | } 184 | }, 185 | "source": [ 186 | "To make life easier, we'll have a simple class with indices that we use to index the fluid state arrays. We can pass this around and be sure that we are always accessing the correct fluid state." 187 | ] 188 | }, 189 | { 190 | "cell_type": "code", 191 | "execution_count": 2, 192 | "metadata": { 193 | "slideshow": { 194 | "slide_type": "fragment" 195 | } 196 | }, 197 | "outputs": [], 198 | "source": [ 199 | "class FluidVars:\n", 200 | " \"\"\"A simple container that holds the integer indicies we will use to\n", 201 | " refer to the different fluid components\"\"\"\n", 202 | " def __init__(self, gamma=1.4, C=0.8):\n", 203 | " self.nvar = 3\n", 204 | " \n", 205 | " # conserved variables\n", 206 | " self.urho = 0\n", 207 | " self.umx = 1\n", 208 | " self.uener = 2\n", 209 | " \n", 210 | " # primitive variables\n", 211 | " self.qrho = 0\n", 212 | " self.qu = 1\n", 213 | " self.qp = 2\n", 214 | " \n", 215 | " # EOS gamma\n", 216 | " self.gamma = gamma\n", 217 | " \n", 218 | " # CFL number\n", 219 | " self.C = C" 220 | ] 221 | }, 222 | { 223 | "cell_type": "markdown", 224 | "metadata": { 225 | "slideshow": { 226 | "slide_type": "slide" 227 | } 228 | }, 229 | "source": [ 230 | "This is the main class for managing the finite-volume grid. In addition to holding coordinate information and knowing the bounds of the domain, it also can fill the ghost cells and give you a scratch array that lives on the same grid." 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": 3, 236 | "metadata": { 237 | "slideshow": { 238 | "slide_type": "slide" 239 | } 240 | }, 241 | "outputs": [], 242 | "source": [ 243 | "class FVGrid:\n", 244 | " \"\"\"The main finite-volume grid class for holding our fluid state.\"\"\"\n", 245 | " \n", 246 | " def __init__(self, nx, ng, xmin=0.0, xmax=1.0):\n", 247 | "\n", 248 | " self.xmin = xmin\n", 249 | " self.xmax = xmax\n", 250 | " self.ng = ng\n", 251 | " self.nx = nx\n", 252 | "\n", 253 | " self.lo = ng\n", 254 | " self.hi = ng+nx-1\n", 255 | "\n", 256 | " # physical coords -- cell-centered\n", 257 | " self.dx = (xmax - xmin)/(nx)\n", 258 | " self.x = xmin + (np.arange(nx+2*ng)-ng+0.5)*self.dx\n", 259 | "\n", 260 | " def scratch_array(self, nc=1):\n", 261 | " \"\"\" return a scratch array dimensioned for our grid \"\"\"\n", 262 | " return np.squeeze(np.zeros((self.nx+2*self.ng, nc), dtype=np.float64))\n", 263 | "\n", 264 | " def fill_BCs(self, atmp):\n", 265 | " \"\"\" fill all ghost cells with zero-gradient boundary conditions \"\"\"\n", 266 | " if atmp.ndim == 2:\n", 267 | " for n in range(atmp.shape[-1]):\n", 268 | " atmp[0:self.lo, n] = atmp[self.lo, n]\n", 269 | " atmp[self.hi+1:, n] = atmp[self.hi, n] \n", 270 | " else:\n", 271 | " atmp[0:self.lo] = atmp[self.lo]\n", 272 | " atmp[self.hi+1:] = atmp[self.hi]" 273 | ] 274 | }, 275 | { 276 | "cell_type": "markdown", 277 | "metadata": { 278 | "slideshow": { 279 | "slide_type": "slide" 280 | } 281 | }, 282 | "source": [ 283 | "## Reconstruction\n", 284 | "\n", 285 | "We need to use the cell-averages to figure out what the fluid state is on the interfaces. We'll _reconstruct_ the cell-averages as piecewise lines that give us the same average in the zone. We then follow these lines to the interfaces to define the left and right state at each interface." 286 | ] 287 | }, 288 | { 289 | "cell_type": "markdown", 290 | "metadata": { 291 | "slideshow": { 292 | "slide_type": "slide" 293 | } 294 | }, 295 | "source": [ 296 | "Usually we work in terms of the **primitive variables**, $q = (\\rho, u, p)$. So we first write a routine to do the algebraic transformation from conservative to primitive variables:\n", 297 | "\\begin{align}\n", 298 | "\\rho &= \\rho \\\\\n", 299 | "u &= \\frac{(\\rho u)}{\\rho} \\\\\n", 300 | "p &= \\left ( (\\rho E) - \\frac{1}{2} \\frac{(\\rho u)^2}{\\rho}\\right )(\\gamma - 1)\n", 301 | "\\end{align}" 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": 4, 307 | "metadata": { 308 | "slideshow": { 309 | "slide_type": "fragment" 310 | } 311 | }, 312 | "outputs": [], 313 | "source": [ 314 | "def cons_to_prim(grid, U):\n", 315 | " \"\"\"take a conservative state U and return the corresponding primitive\n", 316 | " variable state as a new array.\"\"\"\n", 317 | " v = FluidVars()\n", 318 | " q = grid.scratch_array(nc=v.nvar)\n", 319 | "\n", 320 | " q[:, v.qrho] = U[:, v.urho]\n", 321 | " q[:, v.qu] = U[:, v.umx]/U[:, v.urho]\n", 322 | " rhoe = U[:, v.uener] - 0.5*q[:, v.qrho]*q[:, v.qu]**2\n", 323 | " q[:, v.qp] = rhoe*(v.gamma - 1.0)\n", 324 | " return q" 325 | ] 326 | }, 327 | { 328 | "cell_type": "markdown", 329 | "metadata": { 330 | "slideshow": { 331 | "slide_type": "slide" 332 | } 333 | }, 334 | "source": [ 335 | "Next we need a routine to create the interface states. Here's well construct a slope for each zone, $\\Delta q$ based on the average state in the neighboring zones. This gives us a line representing the value of the fluid state as a function of position in each zone:\n", 336 | "$$q_i(x) = \\langle q\\rangle_i + \\frac{\\Delta q_i}{\\Delta x} (x - x_i)$$\n", 337 | "\n", 338 | "Note that there is a unique $q_i(x)$ for each zone—this is usually called _piecewise linear reconstruction_. By design, the average of $q_i(x)$ over the zone is the cell-average, so it is conservative." 339 | ] 340 | }, 341 | { 342 | "cell_type": "markdown", 343 | "metadata": { 344 | "slideshow": { 345 | "slide_type": "slide" 346 | } 347 | }, 348 | "source": [ 349 | "We use this equation for a line to find the fluid state right at the interface. For zone $i$, the line $q_i(x)$ gives you the right state on the left interface, $q_{i-1/2,R}$, and the left state on the right interface, $q_{i+1/2,L}$. Visually this looks like:\n", 350 | "\n", 351 | "![finding interface states](riemann-mol.png)" 352 | ] 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "metadata": { 357 | "slideshow": { 358 | "slide_type": "fragment" 359 | } 360 | }, 361 | "source": [ 362 | "There's one additional wrinkle—2nd order codes tend to produce oscillations near discontinuities, so we usually need to _limit_ the slopes, $\\Delta q_i$, so we don't introduce new minima or maxima in the evolution. We'll use the minmod limiter:\n", 363 | "\\begin{equation} \n", 364 | "\\left . \\frac{\\partial a}{\\partial x} \\right |_i = \\mathtt{minmod} \\left ( \n", 365 | " \\frac{a_i - a_{i-1}}{\\Delta x}, \\frac{a_{i+1} - a_i}{\\Delta x} \\right ) \n", 366 | "\\end{equation}\n", 367 | "with \n", 368 | "\\begin{equation} \n", 369 | "\\mathtt{minmod}(a,b) = \\left \\{ \n", 370 | " \\begin{array}{ll} \n", 371 | " a & \\mathit{if~} |a| < |b| \\mathrm{~and~} a\\cdot b > 0 \\\\ \n", 372 | " b & \\mathit{if~} |b| < |a| \\mathrm{~and~} a\\cdot b > 0 \\\\ \n", 373 | " 0 & \\mathit{otherwise} \n", 374 | " \\end{array} \n", 375 | " \\right . \n", 376 | "\\end{equation} " 377 | ] 378 | }, 379 | { 380 | "cell_type": "code", 381 | "execution_count": 5, 382 | "metadata": { 383 | "slideshow": { 384 | "slide_type": "slide" 385 | } 386 | }, 387 | "outputs": [], 388 | "source": [ 389 | "def states(grid, U):\n", 390 | " v = FluidVars()\n", 391 | " q = cons_to_prim(grid, U)\n", 392 | "\n", 393 | " # construct the slopes\n", 394 | " dq = grid.scratch_array(nc=v.nvar)\n", 395 | "\n", 396 | " for n in range(v.nvar): \n", 397 | " dl = grid.scratch_array()\n", 398 | " dr = grid.scratch_array()\n", 399 | "\n", 400 | " dl[grid.lo-1:grid.hi+2] = q[grid.lo:grid.hi+3,n] - q[grid.lo-1:grid.hi+2,n]\n", 401 | " dr[grid.lo-1:grid.hi+2] = q[grid.lo-1:grid.hi+2,n] - q[grid.lo-2:grid.hi+1,n]\n", 402 | "\n", 403 | " # these where's do a minmod()\n", 404 | " d1 = np.where(np.fabs(dl) < np.fabs(dr), dl, dr)\n", 405 | " dq[:, n] = np.where(dl*dr > 0.0, d1, 0.0)\n", 406 | "\n", 407 | " # now make the states\n", 408 | " q_l = grid.scratch_array(nc=v.nvar)\n", 409 | " q_l[grid.lo:grid.hi+2, :] = q[grid.lo-1:grid.hi+1, :] + 0.5*dq[grid.lo-1:grid.hi+1, :]\n", 410 | "\n", 411 | " q_r = grid.scratch_array(nc=v.nvar)\n", 412 | " q_r[grid.lo:grid.hi+2, :] = q[grid.lo:grid.hi+2, :] - 0.5*dq[grid.lo:grid.hi+2, :]\n", 413 | " \n", 414 | " return q_l, q_r" 415 | ] 416 | }, 417 | { 418 | "cell_type": "markdown", 419 | "metadata": { 420 | "slideshow": { 421 | "slide_type": "slide" 422 | } 423 | }, 424 | "source": [ 425 | "## Riemann problem and conservative update\n", 426 | "\n", 427 | "After doing our reconstruction, we are left with a left and right state on an interface. To find the unique fluid state on the interface, we solve a _Riemann problem_, \n", 428 | "$$q_{i+1/2} = \\mathcal{R}(q_{i+1/2,L},q_{i+1/2,R})$$\n", 429 | "\n", 430 | "We could spend an entire day talking about how to solve the Riemann problem. Well just summarize things here. " 431 | ] 432 | }, 433 | { 434 | "cell_type": "markdown", 435 | "metadata": { 436 | "slideshow": { 437 | "slide_type": "slide" 438 | } 439 | }, 440 | "source": [ 441 | "At each interface, we have a left and right state. Information about the jump across this interface will be carried away from the interface by the 3 hydrodynamic waves ($u$ and $u\\pm c$).\n", 442 | "\n", 443 | "![Riemann solution structure](riemann-waves.png)\n", 444 | "The solution to the Riemann problem that we need is the state on the interface--with that we can evaluate the flux through the interface. " 445 | ] 446 | }, 447 | { 448 | "cell_type": "markdown", 449 | "metadata": { 450 | "slideshow": { 451 | "slide_type": "slide" 452 | } 453 | }, 454 | "source": [ 455 | "To solve the Riemann problem, we need to know how much each variable changes across each of the three waves. To complicate matters, the left and right waves can be either shocks or rarefactions. The middle wave ($u$) is always a contact discontinuity (and of our primitive variables, only $\\rho$ jumps across it).\n", 456 | "\n", 457 | "For a gamma-law gas, we can write down analytic expressions for the change in the primitive variables across both a rarefaction and shock. We can then solve these to find the state inbetween the left and right waves (the star state) and then compute the wave speeds." 458 | ] 459 | }, 460 | { 461 | "cell_type": "markdown", 462 | "metadata": { 463 | "slideshow": { 464 | "slide_type": "slide" 465 | } 466 | }, 467 | "source": [ 468 | "Finally, we can find the solution on the interface by determining which region we are in.\n", 469 | "![Riemann state](riemann_state.png)" 470 | ] 471 | }, 472 | { 473 | "cell_type": "markdown", 474 | "metadata": { 475 | "slideshow": { 476 | "slide_type": "slide" 477 | } 478 | }, 479 | "source": [ 480 | "We'll use an exact Riemann solver to find the solution on the interface. There a lot of algebra involved in finding the expressions for the jumps across the waves and the wave speeds, which we'll skip (by see my notes). Instead we'll just use this solver to give us the state." 481 | ] 482 | }, 483 | { 484 | "cell_type": "markdown", 485 | "metadata": {}, 486 | "source": [ 487 | "One we have the interface state, we can compute the fluxes using this state:" 488 | ] 489 | }, 490 | { 491 | "cell_type": "code", 492 | "execution_count": 6, 493 | "metadata": {}, 494 | "outputs": [], 495 | "source": [ 496 | "def cons_flux(state, v):\n", 497 | " \"\"\" given an interface state, return the conservative flux\"\"\"\n", 498 | " flux = np.zeros((v.nvar), dtype=np.float64)\n", 499 | "\n", 500 | " flux[v.urho] = state.rho * state.u\n", 501 | " flux[v.umx] = flux[v.urho] * state.u + state.p\n", 502 | " flux[v.uener] = (0.5 * state.rho * state.u**2 +\n", 503 | " state.p/(v.gamma - 1.0) + state.p) * state.u\n", 504 | " return flux" 505 | ] 506 | }, 507 | { 508 | "cell_type": "code", 509 | "execution_count": 7, 510 | "metadata": { 511 | "slideshow": { 512 | "slide_type": "slide" 513 | } 514 | }, 515 | "outputs": [ 516 | { 517 | "name": "stdout", 518 | "output_type": "stream", 519 | "text": [ 520 | "Help on module riemann_exact:\n", 521 | "\n", 522 | "NAME\n", 523 | " riemann_exact\n", 524 | "\n", 525 | "DESCRIPTION\n", 526 | " An exact Riemann solver for the Euler equations with a gamma-law\n", 527 | " gas. The left and right states are stored as State objects. We then\n", 528 | " create a RiemannProblem object with the left and right state:\n", 529 | " \n", 530 | " > rp = RiemannProblem(left_state, right_state)\n", 531 | " \n", 532 | " Next we solve for the star state:\n", 533 | " \n", 534 | " > rp.find_star_state()\n", 535 | " \n", 536 | " Finally, we sample the solution to find the interface state, which\n", 537 | " is returned as a State object:\n", 538 | " \n", 539 | " > q_int = rp.sample_solution()\n", 540 | "\n", 541 | "CLASSES\n", 542 | " builtins.object\n", 543 | " RiemannProblem\n", 544 | " State\n", 545 | " \n", 546 | " class RiemannProblem(builtins.object)\n", 547 | " | RiemannProblem(left_state, right_state, gamma=1.4)\n", 548 | " | \n", 549 | " | a class to define a Riemann problem. It takes a left\n", 550 | " | and right state. Note: we assume a constant gamma\n", 551 | " | \n", 552 | " | Methods defined here:\n", 553 | " | \n", 554 | " | __init__(self, left_state, right_state, gamma=1.4)\n", 555 | " | Initialize self. See help(type(self)) for accurate signature.\n", 556 | " | \n", 557 | " | find_star_state(self, p_min=0.001, p_max=1000.0)\n", 558 | " | root find the Hugoniot curve to find ustar, pstar\n", 559 | " | \n", 560 | " | rarefaction_solution(self, sgn, state)\n", 561 | " | return the interface solution considering a rarefaction wave\n", 562 | " | \n", 563 | " | sample_solution(self)\n", 564 | " | given the star state (ustar, pstar), find the state on the interface\n", 565 | " | \n", 566 | " | shock_solution(self, sgn, state)\n", 567 | " | return the interface solution considering a shock\n", 568 | " | \n", 569 | " | u_hugoniot(self, p, side)\n", 570 | " | define the Hugoniot curve, u(p).\n", 571 | " | \n", 572 | " | ----------------------------------------------------------------------\n", 573 | " | Data descriptors defined here:\n", 574 | " | \n", 575 | " | __dict__\n", 576 | " | dictionary for instance variables (if defined)\n", 577 | " | \n", 578 | " | __weakref__\n", 579 | " | list of weak references to the object (if defined)\n", 580 | " \n", 581 | " class State(builtins.object)\n", 582 | " | State(p=1.0, u=0.0, rho=1.0)\n", 583 | " | \n", 584 | " | a simple object to hold a primitive variable state\n", 585 | " | \n", 586 | " | Methods defined here:\n", 587 | " | \n", 588 | " | __init__(self, p=1.0, u=0.0, rho=1.0)\n", 589 | " | Initialize self. See help(type(self)) for accurate signature.\n", 590 | " | \n", 591 | " | __str__(self)\n", 592 | " | Return str(self).\n", 593 | " | \n", 594 | " | ----------------------------------------------------------------------\n", 595 | " | Data descriptors defined here:\n", 596 | " | \n", 597 | " | __dict__\n", 598 | " | dictionary for instance variables (if defined)\n", 599 | " | \n", 600 | " | __weakref__\n", 601 | " | list of weak references to the object (if defined)\n", 602 | "\n", 603 | "FUNCTIONS\n", 604 | " cons_flux(state, v)\n", 605 | " given an interface state, return the conservative flux\n", 606 | "\n", 607 | "FILE\n", 608 | " /home/zingale/classes/how_to_write_a_hydro_code/riemann_exact.py\n", 609 | "\n", 610 | "\n" 611 | ] 612 | } 613 | ], 614 | "source": [ 615 | "import riemann_exact as re\n", 616 | "help(re)" 617 | ] 618 | }, 619 | { 620 | "cell_type": "markdown", 621 | "metadata": { 622 | "slideshow": { 623 | "slide_type": "slide" 624 | } 625 | }, 626 | "source": [ 627 | "For a method-of-lines approach, we want to just compute the righthand side, $A = -\\partial F/\\partial x$. Then we will turn our PDE into an ODE for time:\n", 628 | "$$\\frac{\\partial \\langle U\\rangle_i}{\\partial t} = -A_i = - \\frac{F_{i+1/2} - F_{i-1/2}}{\\Delta x}$$\n", 629 | "\n", 630 | "We can then use any ODE integration method, like Runge-Kutta to solve the system." 631 | ] 632 | }, 633 | { 634 | "cell_type": "markdown", 635 | "metadata": { 636 | "slideshow": { 637 | "slide_type": "slide" 638 | } 639 | }, 640 | "source": [ 641 | "This routine will take the conserved state, $U$, construct the left and right states at all interfaces, solve the Riemann problem to get the unique state on the boundary, and then compute the advective term and return it." 642 | ] 643 | }, 644 | { 645 | "cell_type": "code", 646 | "execution_count": 8, 647 | "metadata": { 648 | "slideshow": { 649 | "slide_type": "fragment" 650 | } 651 | }, 652 | "outputs": [], 653 | "source": [ 654 | "def make_flux_divergence(grid, U):\n", 655 | " \n", 656 | " v = FluidVars()\n", 657 | " \n", 658 | " # get the states\n", 659 | " q_l, q_r = states(grid, U)\n", 660 | "\n", 661 | " # now solve the Riemann problem\n", 662 | " flux = grid.scratch_array(nc=v.nvar)\n", 663 | " for i in range(grid.lo, grid.hi+2):\n", 664 | " sl = re.State(rho=q_l[i,v.qrho], u=q_l[i,v.qu], p=q_l[i,v.qp])\n", 665 | " sr = re.State(rho=q_r[i,v.qrho], u=q_r[i,v.qu], p=q_r[i,v.qp])\n", 666 | " rp = re.RiemannProblem(sl, sr, gamma=v.gamma)\n", 667 | " rp.find_star_state()\n", 668 | " q_int = rp.sample_solution()\n", 669 | " flux[i, :] = cons_flux(q_int, v)\n", 670 | "\n", 671 | " A = grid.scratch_array(nc=v.nvar)\n", 672 | " for n in range(v.nvar):\n", 673 | " A[grid.lo:grid.hi+1, n] = (flux[grid.lo:grid.hi+1, n] -\n", 674 | " flux[grid.lo+1:grid.hi+2, n])/grid.dx\n", 675 | "\n", 676 | " return A" 677 | ] 678 | }, 679 | { 680 | "cell_type": "markdown", 681 | "metadata": { 682 | "slideshow": { 683 | "slide_type": "slide" 684 | } 685 | }, 686 | "source": [ 687 | "## Timestep\n", 688 | "\n", 689 | "Explicit hydro codes have a restriction on the size of the timestep. We cannot allow information to move more than one zone per step. For the hydro equations, the speeds at which information travels are $u$ and $u \\pm c$, so we use the largest speed here to compute the timestep." 690 | ] 691 | }, 692 | { 693 | "cell_type": "code", 694 | "execution_count": 9, 695 | "metadata": { 696 | "slideshow": { 697 | "slide_type": "fragment" 698 | } 699 | }, 700 | "outputs": [], 701 | "source": [ 702 | "def timestep(grid, U):\n", 703 | "\n", 704 | " v = FluidVars()\n", 705 | " \n", 706 | " # compute the sound speed\n", 707 | " q = cons_to_prim(grid, U)\n", 708 | " c = grid.scratch_array()\n", 709 | " c[grid.lo:grid.hi+1] = np.sqrt(v.gamma *\n", 710 | " q[grid.lo:grid.hi+1,v.qp] /\n", 711 | " q[grid.lo:grid.hi+1,v.qrho])\n", 712 | "\n", 713 | " dt = v.C * grid.dx / (np.abs(q[grid.lo:grid.hi+1, v.qu]) +\n", 714 | " c[grid.lo:grid.hi+1]).max()\n", 715 | " return dt" 716 | ] 717 | }, 718 | { 719 | "cell_type": "markdown", 720 | "metadata": { 721 | "slideshow": { 722 | "slide_type": "slide" 723 | } 724 | }, 725 | "source": [ 726 | "## Main driver\n", 727 | "\n", 728 | "This is the main driver. For simplicity, I've hardcoded the initial conditions here for the standard Sod problem. Usually those would be a separate routine.\n", 729 | "\n", 730 | "This does 2nd-order RK (or Euler's method) for the integration, and requires that we compute the advection terms twice to advance the solution by $\\Delta t$. The update looks like:\n", 731 | "\\begin{align*}\n", 732 | "U^\\star &= U^n + \\frac{\\Delta t}{2} A(U^n) \\\\\n", 733 | "U^{n+1} &= U^n + \\Delta t A(U^\\star)\n", 734 | "\\end{align*}" 735 | ] 736 | }, 737 | { 738 | "cell_type": "code", 739 | "execution_count": 10, 740 | "metadata": { 741 | "slideshow": { 742 | "slide_type": "slide" 743 | } 744 | }, 745 | "outputs": [], 746 | "source": [ 747 | "def mol_solve(nx, tmax=1.0, init_cond=None):\n", 748 | " \"\"\"Perform 2nd order MOL integration of the Euler equations.\n", 749 | " You need to pass in a function foo(grid) that returns the \n", 750 | " initial conserved fluid state.\"\"\"\n", 751 | "\n", 752 | " grid = FVGrid(nx, 2)\n", 753 | " v = FluidVars()\n", 754 | " \n", 755 | " U = init_cond(grid)\n", 756 | " \n", 757 | " t = 0.0\n", 758 | " \n", 759 | " while t < tmax:\n", 760 | " dt = timestep(grid, U)\n", 761 | " if t + dt > tmax:\n", 762 | " dt = tmax - t\n", 763 | "\n", 764 | " grid.fill_BCs(U)\n", 765 | " k1 = make_flux_divergence(grid, U)\n", 766 | "\n", 767 | " U_tmp = grid.scratch_array(nc=v.nvar)\n", 768 | " for n in range(v.nvar):\n", 769 | " U_tmp[:, n] = U[:, n] + 0.5 * dt * k1[:, n]\n", 770 | "\n", 771 | " grid.fill_BCs(U_tmp)\n", 772 | " k2 = make_flux_divergence(grid, U_tmp)\n", 773 | "\n", 774 | " for n in range(v.nvar):\n", 775 | " U[:, n] += dt * k2[:, n]\n", 776 | "\n", 777 | " t += dt\n", 778 | "\n", 779 | " return grid, U" 780 | ] 781 | }, 782 | { 783 | "cell_type": "markdown", 784 | "metadata": { 785 | "slideshow": { 786 | "slide_type": "slide" 787 | } 788 | }, 789 | "source": [ 790 | "## Example: Sod's problem\n", 791 | "\n", 792 | "The Sod problem is a standard test problem, consisting of a left and right state separated by an initial discontinuity. As time evolves, a rightward moving shock and contact and leftward moving rarefaction form.\n", 793 | "\n", 794 | "One reason this problem is so popular is that you can find the exact solution (it's just the Riemann problem) and compare the performance of your code to the exact solution." 795 | ] 796 | }, 797 | { 798 | "cell_type": "code", 799 | "execution_count": 11, 800 | "metadata": { 801 | "slideshow": { 802 | "slide_type": "slide" 803 | } 804 | }, 805 | "outputs": [], 806 | "source": [ 807 | "def sod(grid):\n", 808 | " \n", 809 | " v = FluidVars()\n", 810 | " U = grid.scratch_array(nc=v.nvar)\n", 811 | " \n", 812 | " # setup initial conditions -- this is Sod's problem\n", 813 | " rho_l = 1.0\n", 814 | " u_l = 0.0\n", 815 | " p_l = 1.0\n", 816 | " rho_r = 0.125\n", 817 | " u_r = 0.0\n", 818 | " p_r = 0.1\n", 819 | "\n", 820 | " idx_l = grid.x < 0.5\n", 821 | " idx_r = grid.x >= 0.5\n", 822 | "\n", 823 | " U[idx_l, v.urho] = rho_l\n", 824 | " U[idx_l, v.umx] = rho_l * u_l\n", 825 | " U[idx_l, v.uener] = p_l/(v.gamma - 1.0) + 0.5 * rho_l * u_l**2\n", 826 | "\n", 827 | " U[idx_r, v.urho] = rho_r\n", 828 | " U[idx_r, v.umx] = rho_r * u_r\n", 829 | " U[idx_r, v.uener] = p_r/(v.gamma - 1.0) + 0.5 * rho_r * u_r**2\n", 830 | " \n", 831 | " return U" 832 | ] 833 | }, 834 | { 835 | "cell_type": "code", 836 | "execution_count": 12, 837 | "metadata": { 838 | "slideshow": { 839 | "slide_type": "slide" 840 | } 841 | }, 842 | "outputs": [], 843 | "source": [ 844 | "g, U = mol_solve(128, tmax=0.2, init_cond=sod)" 845 | ] 846 | }, 847 | { 848 | "cell_type": "code", 849 | "execution_count": 13, 850 | "metadata": { 851 | "slideshow": { 852 | "slide_type": "fragment" 853 | } 854 | }, 855 | "outputs": [], 856 | "source": [ 857 | "import matplotlib.pyplot as plt\n", 858 | "plt.rcParams['figure.dpi'] = 100\n", 859 | "plt.rcParams['figure.figsize'] = [8, 6]" 860 | ] 861 | }, 862 | { 863 | "cell_type": "code", 864 | "execution_count": 14, 865 | "metadata": { 866 | "slideshow": { 867 | "slide_type": "fragment" 868 | } 869 | }, 870 | "outputs": [], 871 | "source": [ 872 | "sod = np.genfromtxt(\"sod-exact.out\", skip_header=2, names=True)" 873 | ] 874 | }, 875 | { 876 | "cell_type": "code", 877 | "execution_count": 15, 878 | "metadata": { 879 | "slideshow": { 880 | "slide_type": "slide" 881 | } 882 | }, 883 | "outputs": [ 884 | { 885 | "data": { 886 | "text/plain": [ 887 | "[]" 888 | ] 889 | }, 890 | "execution_count": 15, 891 | "metadata": {}, 892 | "output_type": "execute_result" 893 | }, 894 | { 895 | "data": { 896 | "image/png": "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\n", 897 | "text/plain": [ 898 | "
" 899 | ] 900 | }, 901 | "metadata": { 902 | "needs_background": "light" 903 | }, 904 | "output_type": "display_data" 905 | } 906 | ], 907 | "source": [ 908 | "v = FluidVars()\n", 909 | "plt.scatter(g.x, U[:,v.urho], marker=\"x\", color=\"C0\")\n", 910 | "plt.plot(sod[\"x\"], sod[\"rho\"], color=\"C1\")" 911 | ] 912 | }, 913 | { 914 | "cell_type": "markdown", 915 | "metadata": {}, 916 | "source": [ 917 | "## Exercises" 918 | ] 919 | }, 920 | { 921 | "cell_type": "markdown", 922 | "metadata": { 923 | "slideshow": { 924 | "slide_type": "slide" 925 | } 926 | }, 927 | "source": [ 928 | "1. Run the problem without limiting the slopes to see how it compares\n", 929 | "\n", 930 | "2. Try a higher-order Runge-Kutta time integration methods to see how the problem changes\n", 931 | "\n", 932 | "3. Implement periodic boundary conditions and create a new set of initial conditions that just puts a low amplitude Gaussian pulse—this will create an acoustic wave that propagates through the domain." 933 | ] 934 | }, 935 | { 936 | "cell_type": "code", 937 | "execution_count": null, 938 | "metadata": {}, 939 | "outputs": [], 940 | "source": [] 941 | } 942 | ], 943 | "metadata": { 944 | "kernelspec": { 945 | "display_name": "Python 3", 946 | "language": "python", 947 | "name": "python3" 948 | }, 949 | "language_info": { 950 | "codemirror_mode": { 951 | "name": "ipython", 952 | "version": 3 953 | }, 954 | "file_extension": ".py", 955 | "mimetype": "text/x-python", 956 | "name": "python", 957 | "nbconvert_exporter": "python", 958 | "pygments_lexer": "ipython3", 959 | "version": "3.7.3" 960 | } 961 | }, 962 | "nbformat": 4, 963 | "nbformat_minor": 4 964 | } 965 | --------------------------------------------------------------------------------