Coverage for pyxfem/fluidmechanics/exact_riemann_solver.py: 0%

248 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-24 07:17 +0000

1""" 

2@author: chevaugeon-n 

31D Exact Riemann Solver for the Euler Equations. 

4 

5This module provides a robust, high-performance implementation of the exact 

6Riemann solver for the one-dimensional Euler equations of gas dynamics, 

7based on the methodologies described in E.F. Toro's textbook. 

8 

9It is designed for High-Performance Computing (HPC) applications and features: 

10 - A `PerfectGaz` thermodynamic closure model. 

11 - Hardware-accelerated JIT-compiled critical paths using Numba (`@njit` and `@cfunc`), 

12 allowing zero-overhead integration into broader finite volume schemes. 

13 - An `ExactRiemannSolver` that computes the exact *star region* state using a 

14 Newton-Raphson iterative scheme with robust initial guessing (PVRS, Two-Shock, 

15 Two-Rarefaction). 

16 - Full wave sampling across rarefaction fans, contact discontinuities, and shock waves. 

17 - Built-in classical Riemann test cases (e.g., Sod shock tube). 

18 

19Dependencies: 

20 - numpy 

21 - numba (optional but strongly recommended for performance) 

22 

23Example: 

24 from pyxfem.core.riemann import ExactRiemannSolver 

25 solver = ExactRiemannSolver(rho0=1.0, u0=0.0, p0=1.0, 

26 ... rho1=0.125, u1=0.0, p1=0.1) 

27 x, rho, u, p = solver.solution_sample(x0=0.5, t=0.2, l=1.0, n=100) 

28 

29References: 

30 Toro, E. F. (2009). Riemann Solvers and Numerical Methods for Fluid Dynamics: 

31 A Practical Introduction. Springer Science & Business Media. 

32""" 

33 

34from typing import Tuple, Optional, Any 

35import numpy as np 

36from pyxfem.core.config import USE_NUMBA, FloatArray 

37 

38if USE_NUMBA : 

39 import numba 

40 @numba.njit(cache=True, fastmath=True) 

41 def pg_soundspeed_numba(p: float, rho: float, gamma: float) -> float: 

42 """ 

43 Numba-compiled function to compute the sound speed of a perfect gas. 

44 

45 Args: 

46 p (float): Pressure. 

47 rho (float): Density. 

48 gamma (float): Heat capacity ratio (adiabatic index). 

49 

50 Returns: 

51 float: Sound speed. 

52 """ 

53 return np.sqrt(gamma*p/rho) 

54 

55 @numba.njit(cache=True, fastmath=True) 

56 def pg_pressure_numba(e: float, rho: float, gamma: float) -> float: 

57 """ 

58 Numba-compiled function to compute the pressure of a perfect gas. 

59 

60 Args: 

61 e (float): Specific internal energy. 

62 rho (float): Density. 

63 gamma (float): Heat capacity ratio (adiabatic index). 

64 

65 Returns: 

66 float: Pressure. 

67 """ 

68 return (gamma-1.)*e*rho 

69 

70 nbfloat = numba.types.float64 

71 c_sig = nbfloat(nbfloat, nbfloat, nbfloat) 

72 

73 @numba.cfunc(c_sig, nopython=True, cache=True, fastmath=True) 

74 def pg_pressure_c(e: float, rho: float, gamma: float) -> float: 

75 """ 

76 C-callable Numba function to compute the pressure of a perfect gas. 

77 

78 Args: 

79 e (float): Specific internal energy. 

80 rho (float): Density. 

81 gamma (float): Heat capacity ratio (adiabatic index). 

82 

83 Returns: 

84 float: Pressure. 

85 """ 

86 return (gamma - 1.0) * e * rho 

87 

88 @numba.cfunc(c_sig, nopython=True, cache=True, fastmath=True) 

89 def pg_soundspeed_c(p: float, rho: float, gamma: float) -> float: 

90 """ 

91 C-callable Numba function to compute the sound speed of a perfect gas. 

92 

93 Args: 

94 p (float): Pressure. 

95 rho (float): Density. 

96 gamma (float): Heat capacity ratio (adiabatic index). 

97 

98 Returns: 

99 float: Sound speed. 

100 """ 

101 return np.sqrt(gamma * p / rho) 

102 

103class PerfectGaz: 

104 """ 

105 Thermodynamic model for a perfect gas. 

106 

107 Attributes: 

108 gamma (float): Heat capacity ratio (adiabatic index). 

109 """ 

110 def __init__(self, gamma: float = 1.4): 

111 """ 

112 Initializes the PerfectGaz model. 

113 

114 Args: 

115 gamma (float, optional): Heat capacity ratio. Defaults to 1.4. 

116 """ 

117 self.gamma: float = gamma 

118 

119 def soundspeed(self, p: FloatArray, rho: FloatArray, out: Optional[FloatArray] = None)\ 

120 -> FloatArray: 

121 """ 

122 Computes the sound speed as an array operation. 

123 

124 Args: 

125 p (FloatVars): Pressure array or float 

126 rho (FloatVars): Density array or float 

127 out (Optional[FloatArray], optional): Output array to store results. Defaults to None. 

128 

129 Raises: 

130 ValueError: If pressure or density lead to an invalid (NaN) sound speed calculation. 

131 

132 Returns: 

133 FloatVars: Sound speed array (or float). 

134 """ 

135 gamma = self.gamma 

136 if out is not None: 

137 cc = np.divide(p, rho, out=out) 

138 cc *= gamma 

139 cc = np.sqrt(cc, out=out) 

140 else: 

141 cc = np.sqrt(gamma * (p / rho)) 

142 if np.any(np.isnan(cc)): 

143 print("Error : Invalid pressure or density in cells :") 

144 ibad = np.argwhere(np.isnan(cc)) 

145 print(ibad) 

146 print("pressure :", np.asarray(p)[ibad]) 

147 print("density :", np.asarray(rho)[ibad]) 

148 raise ValueError("invalid pressure or density when computing sound speed") 

149 return cc 

150 

151 def soundspeed_scalar(self, p:float, rho: float) -> float: 

152 """ 

153 Computes the sound speed as an array operation. 

154 

155 Args: 

156 p (FloatVars): Pressure array or float 

157 rho (FloatVars): Density array or float 

158 out (Optional[FloatArray], optional): Output array to store results. Defaults to None. 

159 

160 Raises: 

161 ValueError: If pressure or density lead to an invalid (NaN) sound speed calculation. 

162 

163 Returns: 

164 FloatVars: Sound speed array (or float). 

165 """ 

166 gamma = self.gamma 

167 cc = np.sqrt(gamma * (p / rho)) 

168 # Vérification robuste (np.any fonctionne sur scalaires et tableaux) 

169 if np.any(np.isnan(cc)): 

170 print("Error : Invalid pressure or density in cells :") 

171 print(f"pressure : {p}") 

172 print(f"density : {rho}") 

173 raise ValueError("invalid pressure or density when computing sound speed") 

174 return cc 

175 

176 def internal_specific_energy(self, p: FloatArray, rho: FloatArray) -> FloatArray: 

177 """ 

178 Returns the specific internal energy per unit of mass. 

179 

180 Args: 

181 p (FloatArray): Pressure array. 

182 rho (FloatArray): Density array. 

183 

184 Returns: 

185 FloatArray: Specific internal energy array. 

186 """ 

187 return p/rho/(self.gamma-1.) 

188 

189 def pressure(self, e: FloatArray, rho: FloatArray, out: Optional[FloatArray] = None)\ 

190 -> FloatArray: 

191 """ 

192 Returns pressure as a function of internal energy and density. 

193 

194 Args: 

195 e (FloatArray): Specific internal energy array. 

196 rho (FloatArray): Density array. 

197 out (Optional[FloatArray], optional): Output array to store results. Defaults to None. 

198 

199 Returns: 

200 FloatArray: Pressure array. 

201 """ 

202 p = np.multiply(e,rho, out = out) 

203 p *=(self.gamma-1) 

204 return p 

205 

206 if USE_NUMBA : 

207 def get_numba_c_funcs(self) -> Tuple[Any, Any, float]: 

208 """ 

209 Returns ctypes pointers for the Numba C functions and the gamma parameter. 

210 

211 Returns: 

212 Tuple[ctypes._CFuncPtr, ctypes._CFuncPtr, float]: 

213 Tuple containing ctypes pointers for pressure and sound speed functions, and gamma. 

214 """ 

215 return pg_pressure_c.ctypes, pg_soundspeed_c.ctypes, self.gamma 

216 

217 # def make_numba_pressure_func(self): 

218 # gamma = self.gamma 

219 # @numba.njit(cache =True) 

220 # def numba_pressure_func(e, rho): 

221 # return (gamma-1.)*e*rho 

222 # return numba_pressure_func 

223 

224 # def make_numba_soundspeed_func(self): 

225 # gamma = self.gamma 

226 # @numba.njit(cache =True) 

227 # def numba_soundspeed_func(p, rho): 

228 # return np.sqrt(gamma*p/rho) 

229 # return numba_soundspeed_func 

230 

231 # def get_numba_funcs(self): 

232 # # On retourne les pointeurs de fonction ET le tuple de paramètres 

233 # return pg_pressure_numba, pg_soundspeed_numba, self.gamma 

234 

235 

236 

237class ExactRiemannSolver(): 

238 """ Implementation of the exact Riemann solver for the 1D Euler equations, 

239 following E.F. Toro's textbook. 

240 

241 Attributes: 

242 gamma (float): Heat capacity ratio. 

243 gs (FloatArray): Array of constant gamma-dependent thermodynamic factors. 

244 rho0 (float): Density of the left state. 

245 rho1 (float): Density of the right state. 

246 u0 (float): Velocity of the left state. 

247 u1 (float): Velocity of the right state. 

248 p0 (float): Pressure of the left state. 

249 p1 (float): Pressure of the right state. 

250 gaz (PerfectGaz): Perfect gas model instance. 

251 c0 (float): Sound speed of the left state. 

252 c1 (float): Sound speed of the right state. 

253 pm (float): Resolved pressure in the star region. 

254 um (float): Resolved velocity in the star region. 

255 """ 

256 def __init__(self, gamma: float = 1.4, rho0: float = 1.0, rho1: float = 0.125, 

257 u0: float = 0.0, u1: float = 0.0, p0: float = 1.0, p1: float = 0.1): 

258 """ 

259 Initializes the exact Riemann solver and computes the star state (pm, um). 

260 

261 Args: 

262 gamma (float, optional): Heat capacity ratio. Defaults to 1.4. 

263 rho0 (float, optional): Left state density. Defaults to 1.0. 

264 rho1 (float, optional): Right state density. Defaults to 0.125. 

265 u0 (float, optional): Left state velocity. Defaults to 0.0. 

266 u1 (float, optional): Right state velocity. Defaults to 0.0. 

267 p0 (float, optional): Left state pressure. Defaults to 1.0. 

268 p1 (float, optional): Right state pressure. Defaults to 0.1. 

269 

270 Raises: 

271 ValueError: If initial data generates a vacuum. 

272 """ 

273 self.gamma = gamma 

274 g = self.gamma 

275 # 0 1 2 3 4 

276 self.gs = np.array([g, (g-1)/2./g, (g+1)/2./g, 2*g/(g-1), 2./(g-1), \ 

277 # 5 6 7 8 

278 2./(g+1), (g-1)/(g+1), (g-1)/2, g-1]) 

279 self.rho0 = rho0 

280 self.rho1 = rho1 

281 self.u0 = u0 

282 self.u1 = u1 

283 self.p0 = p0 

284 self.p1 = p1 

285 self.gaz = PerfectGaz(gamma) 

286 self.c0 = self.gaz.soundspeed_scalar(p0, rho0) 

287 self.c1 = self.gaz.soundspeed_scalar(p1, rho1) 

288 if 2./(gamma-1.)*(self.c0+self.c1) < (u1-u0) : 

289 print("Generation of vaccum with initial data !") 

290 raise ValueError() 

291 self.pm, self.um = self.star_pu() 

292 

293 def guess_p(self) -> float: 

294 """ 

295 Provides an initial guess for the pressure in the star region using 

296 approximate Riemann solvers (PVRS, Two-Rarefaction, Two-Shock). 

297 

298 Returns: 

299 float: Initial guess for pressure. 

300 """ 

301 p0, p1 = self.p0, self.p1 

302 u0, u1 = self.u0, self.u1 

303 rho0, rho1 = self.rho0, self.rho1 

304 c0, c1 = self.c0, self.c1 

305 cup = 0.25*(rho0+rho1)*(c0+c1) 

306 tmp = 0.5*(p0+p1) + 0.5*(u0-u1)*cup 

307 ppv = max(tmp, 0.) 

308 pmin = min(p0,p1) 

309 pmax = max(p0,p1) 

310 qmax = pmax/pmin 

311 quser = 2. 

312 if qmax <= quser and pmin <= ppv and ppv<= pmax : 

313 # PVRS Riemann Solver 

314 return ppv 

315 if ppv <= pmin : 

316 # two rarefaction rieman Solver 

317 g1, g3, g4, g7 = self.gs[[1,3,4,7]] 

318 pq = (p0/p1)**g1 

319 um = (pq*u0/c0 + u1/c1 + g4*(pq-1.))/(pq/c0 + 1./c1) 

320 pt0 = 1. + g7*(u0-um)/c0 

321 pt1 = 1. + g7*(um-u1)/c1 

322 return 0.5*(p0*pt0**g3 + p1*pt1**g3) 

323 # two shock estimate 

324 g5, g6 = self.gs[[5,6]] 

325 ge0 = np.sqrt( (g5/rho0)/(g6*p0 + ppv) ) 

326 ge1 = np.sqrt( (g5/rho1)/(g6*p1 + ppv) ) 

327 return (ge0*p0+ge1*p1 - (u1 -u0))/(ge0+ge1) 

328 

329 def prefun(self, p: float, dk: float, pk: float, ck: float) -> Tuple[float, float]: 

330 """ 

331 Computes the Riemann function and its derivative for a given state. 

332 

333 Args: 

334 p (float): Current guessed pressure. 

335 dk (float): State density (left or right). 

336 pk (float): State pressure (left or right). 

337 ck (float): State sound speed (left or right). 

338 

339 Returns: 

340 Tuple[float, float]: Riemann function value (f) and its derivative (fd). 

341 """ 

342 g1,g2,g4,g5,g6 = self.gs[[1,2,4, 5, 6]] 

343 if p <= pk : 

344 # rarefaction 

345 rp = p/pk 

346 f = g4*ck*(rp**g1 - 1.) 

347 fd = (1./(dk*ck))*rp**(-g2) 

348 else : 

349 # shockwave 

350 ak = g5/dk 

351 bk = g6*pk 

352 qrt = np.sqrt(ak/(bk + p)) 

353 f = (p-pk)*qrt 

354 fd = (1.- 0.5*(p-pk)/(bk+p))*qrt 

355 return f, fd 

356 

357 def star_pu(self) -> Tuple[float, float]: 

358 """ 

359 Solves for pressure and velocity in the star region using Newton-Raphson iteration. 

360 

361 Raises: 

362 ValueError: If the Newton scheme fails to converge within nitermax. 

363 

364 Returns: 

365 Tuple[float, float]: Pressure (p) and velocity (u) in the star region. 

366 """ 

367 p0, p1 = self.p0, self.p1 

368 u0, u1 = self.u0, self.u1 

369 rho0, rho1 = self.rho0, self.rho1 

370 c0, c1 = self.c0, self.c1 

371 pold = self.guess_p() 

372 du = u1 - u0 

373 nitermax = 100 

374 tol = 1.e-6 

375 for i in range(1, nitermax+1): 

376 f0, f0d = self.prefun( pold, rho0, p0, c0) 

377 f1, f1d = self.prefun( pold, rho1, p1, c1) 

378 p = pold - (f0 + f1 + du)/( f0d+f1d) 

379 change = 2.*np.abs( (p - pold)/(p+pold)) 

380 if change <= tol : 

381 break 

382 #print(i, p) 

383 pold = p 

384 if i >= nitermax: 

385 print("divergence of newton scheme !") 

386 raise ValueError() 

387 u = 0.5*(u0+u1 + f1 -f0) 

388 return p, u 

389 

390 def sample(self, pm: float, um: float, s: FloatArray)\ 

391 -> Tuple[FloatArray, FloatArray, FloatArray]: 

392 """ 

393 Samples the complete Riemann solution across a range of wave speeds. 

394 

395 Args: 

396 pm (float): Resolved star pressure. 

397 um (float): Resolved star velocity. 

398 s (FloatArray): Array of wave speeds (x/t) to sample. 

399 

400 Returns: 

401 Tuple[FloatArray, FloatArray, FloatArray]: 

402 Sampled density (rho), velocity (u), 

403 and pressure (p) arrays corresponding to input speeds. 

404 """ 

405 s = np.asarray(s) 

406 p0, p1 = self.p0, self.p1 

407 u0, u1 = self.u0, self.u1 

408 rho0, rho1 = self.rho0, self.rho1 

409 c0, c1 = self.c0, self.c1 

410 g, g1, g2, g3, g4, g5, g6, g7, _ = self.gs[:] 

411 rho = np.zeros(s.shape) 

412 u = np.zeros(s.shape) 

413 p = np.zeros(s.shape) 

414 s_le_um = s<=um 

415 #indices = np.where(s<=um) 

416 # point left to the contact discontinuity 

417 if pm <= p0: 

418 # in left rarefaction 

419 sh0 = u0 - c0 

420 #lefts tate 

421 s_le_sh0 = s <= sh0 

422 indices = np.where(np.logical_and(s_le_um, s_le_sh0)) 

423 rho[indices] = rho0 

424 u[indices] = u0 

425 p[indices] = p0 

426 cm0 = c0*(pm/p0)**g1 

427 st0 = um - cm0 

428 s_le_st0 = s <= st0 

429 s_le_um_and_s_gt_sh0 = np.logical_and(s_le_um, np.logical_not(s_le_sh0)) 

430 indices = np.where(np.logical_and( s_le_um_and_s_gt_sh0, np.logical_not(s_le_st0))) 

431 #point in star left 

432 rho[indices] = rho0*(pm/p0)**(1./g) 

433 u[indices] = um 

434 p[indices] = pm 

435 indices = np.where(np.logical_and(s_le_um_and_s_gt_sh0, s_le_st0)) 

436 # point inside left fan 

437 c = g5*(c0+ g7*(u0 -s[indices])) 

438 u[indices]= g5*(c0+ g7*u0 +s[indices]) 

439 rho[indices] = rho0*(c/c0)**g4 

440 p[indices]= p0*(c/c0)**g3 

441 #left shock 

442 pm0 = pm/p0 

443 s0 = u0 - c0*np.sqrt(g2*pm0+g1) 

444 else : #pm > p0 

445 pm0 = pm/p0 

446 s0 = u0 - c0*np.sqrt(g2*pm0+g1) 

447 indice = np.where(np.logical_and(s_le_um, s<=s0)) 

448 # point is left data state 

449 rho[indice], u[indice], p[indice] = rho0, u0, p0 

450 indice = np.where(np.logical_and(s_le_um, s>s0)) 

451 # point is star left state 

452 rho[indice], u[indice], p[indice] = rho0*(pm0 + g6)/(pm0*g6 + 1.), um, pm 

453 

454 #indices where s>um 

455 #sampig point is to the right of the contact discontinuity 

456 if pm > p1 : 

457 #right shock 

458 pm1 = pm/p1 

459 s1 = u1 + c1*np.sqrt(g2*pm1 + g1) 

460 indice = np.where(np.logical_and(s > um, s >= s1)) 

461 rho[indice], u[indice], p[indice] = rho1, u1, p1 

462 indice = np.where(np.logical_and(s > um, s < s1)) 

463 # sampled point is star right state 

464 rho[indice], u[indice], p[indice] = rho1*(pm1 + g6)/(pm1*g6 + 1.), um, pm 

465 else : #pm<= p1 

466 #right rarefaction 

467 sh1 = u1 +c1 

468 indice = np.where( np.logical_and(s > um, s >= sh1)) 

469 # right state ! 

470 rho[indice], u[indice], p[indice] = rho1, u1, p1 

471 cm1 = c1*(pm/p1)**g1 

472 st1 = um + cm1 

473 s_gt_um_and_s_lt_sh1 = np.logical_and(s > um, s < sh1) 

474 indice = np.where(np.logical_and(s_gt_um_and_s_lt_sh1, s<= st1)) 

475 #point in star right 

476 rho[indice], u[indice], p[indice]= rho1*(pm/p1)**(1./g), um, pm 

477 indice = np.where(np.logical_and(s_gt_um_and_s_lt_sh1, s > st1)) 

478 # point inside right fan 

479 c = g5*(c1 - g7*(u1 -s[indice])) 

480 rho[indice] = rho1*(c/c1)**g4 

481 u[indice] = g5*(-c1 + g7*u1 +s[indice]) 

482 p[indice] = p1*(c/c1)**g3 

483 return rho, u, p 

484 

485 def solution_sample(self, x0: float, t: float, l: float, n: int) \ 

486 -> Tuple[FloatArray, FloatArray, FloatArray, FloatArray]: 

487 """ 

488 Samples the complete solution profile over physical space at a specific time. 

489 

490 Args: 

491 x0 (float): Initial position of the discontinuity. 

492 t (float): Evaluation time. 

493 l (float): Domain length (from 0 to l). 

494 n (int): Number of spatial intervals. 

495 

496 Returns: 

497 Tuple[FloatArray, FloatArray, FloatArray, FloatArray]: 

498 Spatial grid (x), Density (rho), Velocity (u), and Pressure (p) arrays. 

499 """ 

500 x = np.linspace(0,l,n+1) 

501 if t != 0: 

502 s = (x - x0)/t 

503 rho = np.zeros(s.shape) 

504 u = np.zeros(s.shape) 

505 p = np.zeros(s.shape) 

506 rho, u, p = self.sample(self.pm, self.um, s) 

507 else: 

508 p0, p1 = self.p0, self.p1 

509 u0, u1 = self.u0, self.u1 

510 rho0, rho1 = self.rho0, self.rho1 

511 rho = np.where(x <= x0, rho0, rho1) 

512 u = np.where(x <= x0, u0, u1) 

513 p = np.where(x <= x0, p0, p1) 

514 return x, rho, u, p 

515 

516def init_shock_tube(pbname: str = 'sod') \ 

517 -> Tuple[Tuple[float, float, float], Tuple[float, float, float], float]: 

518 """ 

519 Returns initial conditions for classical 1D shock tube tests. 

520 

521 Args: 

522 pbname (str, optional): Name of the problem ('sod', 'toro1', 'toro2', 'toro3'). 

523 Defaults to 'sod'. 

524 

525 Raises: 

526 ValueError: If an unknown problem name is provided. 

527 

528 Returns: 

529 Tuple[Tuple[float, float, float], Tuple[float, float, float], float]: 

530 (rho_L, u_L, p_L), (rho_R, u_R, p_R), and end time (tend). 

531 """ 

532 if pbname == "sod": 

533 rhol, rhor = 1., 0.125 

534 ul, ur = 0. , 0. 

535 pl, pr = 1., 0.1 

536 tend = 0.2 

537 elif pbname == "toro1": 

538 rhol, rhor = 1., 0.125 

539 ul, ur = 0.75, 0. 

540 pl, pr = 1., 0.1 

541 tend = 0.2 

542 elif pbname =="toro2": 

543 rhol, rhor = 1., 1. 

544 ul, ur = -2. , 2. 

545 pl, pr = 0.4, 0.4 

546 tend = 0.3 

547 elif pbname =="toro3": 

548 rhol, rhor = 1., 1. 

549 ul, ur = 0. , 0. 

550 pl, pr = 100., 0.1 

551 tend = 0.03 

552 else : 

553 print(f"pb {pbname} unknown") 

554 raise ValueError() 

555 # elif pb =="toro4": 

556 # rhol, rhor = 1., 1. 

557 # ul, ur = 0. , 0. 

558 # pl, pr = 1000., 0.01 

559 return (rhol, ul, pl), (rhor, ur, pr), tend 

560 

561def test_Riemann_Sod(plot = False) -> None: #pylint: disable=invalid-name 

562 """ 

563 Tests the exact Riemann solver on the standard Sod shock tube problem and plots results. 

564 """ 

565 import matplotlib.pyplot as plt #pylint: disable=import-outside-toplevel 

566 # revert sod : 

567 rs = ExactRiemannSolver(rho0 = 0.125, p0 = 0.1, rho1 =1., p1 = 1.) 

568 # sod : 

569 rs = ExactRiemannSolver() 

570 x, rho, u, p = rs.solution_sample(0.5, 0.1, 1., 50) 

571 if plot : 

572 plt.plot(x, rho) 

573 plt.ylabel('rho') 

574 plt.figure() 

575 plt.plot(x, u ) 

576 plt.ylabel('u') 

577 plt.figure() 

578 plt.plot(x, p ) 

579 plt.ylabel('p') 

580 plt.show() 

581 

582if __name__ == "__main__": 

583 test_Riemann_Sod(plot = True)