Skip to article frontmatterSkip to article content

In this final part of the practical session, you will implement a complete compressible gas dynamics solver using the Euler equations. This builds upon everything you have learned: mesh creation, field manipulation, flux computation, and adaptive mesh refinement.

The Euler equations describe the motion of inviscid (non-viscous) compressible fluids. They are fundamental in aerodynamics, astrophysics, and shock physics. Unlike Burgers, the Euler system couples four conservation laws (mass, momentum in x and y, energy), requiring more sophisticated numerical schemes.

Euler equations

The Euler equations in 2D read:

t(ρρuρvρE)+x(ρuρu2+pρuvu(ρE+p))+y(ρvρuvρv2+pv(ρE+p))=0\frac{\partial }{\partial t} \begin{pmatrix} \rho \\\\ \rho u \\\\ \rho v \\\\ \rho E \end{pmatrix} + \frac{\partial }{\partial x} \begin{pmatrix} \rho u \\\\ \rho u^2 + p \\\\ \rho uv \\\\ u(\rho E + p) \end{pmatrix} + \frac{\partial }{\partial y} \begin{pmatrix} \rho v \\\\ \rho uv \\\\ \rho v^2 + p \\\\ v(\rho E + p) \end{pmatrix} = 0

where:

where E=e+12(u2+v2)E = e + \frac{1}{2}(u^2 + v^2) is the specific total energy (energy per unit mass), with ee the internal energy per unit mass.

The pressure is given by the equation of state for an ideal gas:

p=(γ1)ρep = (\gamma - 1) \rho e

where γ\gamma is the ratio of specific heats (typically 1.4 for air).

Sound Speed

The sound speed (or speed of sound) is defined as:

c=γpρc = \sqrt{\frac{\gamma p}{\rho}}

Physical meaning: This is the speed at which small pressure disturbances (acoustic waves) propagate through the fluid. It is crucial for:

Explanation of the provided code

The Euler code becomes more complex due to the number of equations and the coupling between them. To save time, we provide you with starter code that you can find in the euler folder. We will now explain the different parts of the code that will help you in the next steps.

The configuration of the mesh and fields is defined in config.hpp.

Naming convention of the variables

We have defined the numbering of the components of the vector field as follows:

Once you have created the vector field conserved containing the conservative variables, you can access each component using:

// For cells
conserved[cell][EulerConsVar::rho]
conserved[cell][EulerConsVar::rhoE]
conserved[cell][EulerConsVar::mom(d)] // d = 0, 1 for x, y

// For intervals
conserved(EulerConsVar::rho, level, i, j)
conserved(EulerConsVar::rhoE, level, i, j)
conserved(EulerConsVar::mom(d), level, i, j)

Conversion between conservative and primitive variables

Next, you need to be able to convert the primitive variables from the conservative ones and vice versa. The primitive variables are:

where the notation (ρu)(\rho u) and (ρv)(\rho v) represent the momentum components.

The functions cons2prim and prim2cons defined in variables.hpp allow you to convert between these two sets of variables.

Equation of state

The object eos defined in eos.hpp allows you to compute the pressure and sound speed from the primitive variables. Here is an example of its usage:

auto p = eos::stiffened_gas::p(rho, e);
auto c = eos::stiffened_gas::c(rho, p);
auto e = eos::stiffened_gas::e(rho, p);

where e is the internal energy per unit mass.

Time step computation

For explicit time integration, the CFL condition must be satisfied:

ΔtCFLΔxmax(u+c,v+c)\Delta t \leq \text{CFL} \cdot \frac{\Delta x}{\max(|u| + c, |v| + c)}

where the maximum is taken over all cells. A default value is set to CFL=0.4\text{CFL} = 0.4 in the main function. We provide the get_max_lambda function in utils.hpp that computes the maximum wave speed in the domain:

auto get_max_lambda(const auto& u)
{
    static constexpr std::size_t dim = std::decay_t<decltype(u)>::dim;
    double res                       = 0.;

    const auto& mesh = u.mesh();

    samurai::for_each_cell(mesh,
                           [&](const auto& cell)
                           {
                               auto prim = cons2prim<dim>(u[cell]);

                               auto c = EOS::stiffened_gas::c(prim.rho, prim.p);
                               for (std::size_t d = 0; d < dim; ++d)
                               {
                                   res = std::max(std::abs(prim.v[d]) + c, res);
                               }
                           });
    return res;
}

Numerical schemes

We propose implementing three different schemes for the Euler equations: Rusanov, HLL, and HLLC. Each of these schemes is described below.

Rusanov scheme

The Rusanov scheme is a simple and robust approximate Riemann solver. The flux function for the Rusanov scheme can be defined as follows:

FRusanov(QL,QR)=12(F(QL)+F(QR))12smax(QRQL)\mathbf{F}_{\text{Rusanov}}(\mathbf{Q}_L, \mathbf{Q}_R) = \frac{1}{2} \left( \mathbf{F}(\mathbf{Q}_L) + \mathbf{F}(\mathbf{Q}_R) \right) - \frac{1}{2} s_{\max} (\mathbf{Q}_R - \mathbf{Q}_L)

where smaxs_{\max} is the maximum wave speed in the direction normal to the interface.

where cL=γpL/ρLc_L = \sqrt{\gamma p_L / \rho_L} and cR=γpR/ρRc_R = \sqrt{\gamma p_R / \rho_R} are the sound speeds on the left and right states.

HLL scheme

The HLL (Harten-Lax-van Leer) scheme is another approximate Riemann solver that considers only the fastest left-going and right-going waves. The flux function for the HLL scheme is given by:

FHLL(QL,QR)={F(QL)if sL0F(QR)if sR0sRF(QL)sLF(QR)+sLsR(QRQL)sRsLotherwise\mathbf{F}_{\text{HLL}}(\mathbf{Q}_L, \mathbf{Q}_R) = \begin{cases} \mathbf{F}(\mathbf{Q}_L) & \text{if } s_L \geq 0 \\\\ \mathbf{F}(\mathbf{Q}_R) & \text{if } s_R \leq 0 \\\\ \frac{s_R \mathbf{F}(\mathbf{Q}_L) - s_L \mathbf{F}(\mathbf{Q}_R) + s_L s_R (\mathbf{Q}_R - \mathbf{Q}_L)}{s_R - s_L} & \text{otherwise} \end{cases}

where sLs_L and sRs_R are the estimated speeds of the left-going and right-going waves, respectively.

sL=min(uLcL,uRcR)s_L = \min(u_L - c_L, u_R - c_R)
sR=max(uL+cL,uR+cR)s_R = \max(u_L + c_L, u_R + c_R)

HLLC scheme

The HLLC (Harten-Lax-van Leer-Contact) scheme is an extension of the HLL scheme that also captures the contact discontinuity. The flux function for the HLLC scheme is more complex and involves additional wave speed estimates.

Contact wave speed

The contact wave speed sMs_M (also called star region velocity) is computed as:

sM=pRpL+ρLuL(sLuL)ρRuR(sRuR)ρL(sLuL)ρR(sRuR)s_M = \frac{p_R - p_L + \rho_L u_L (s_L - u_L) - \rho_R u_R (s_R - u_R)}{\rho_L (s_L - u_L) - \rho_R (s_R - u_R)}

Star region states

Let’s introduce the intermediate states uL\mathbf{u}_L^* and uR\mathbf{u}_R^* in the star region. We first compute the density in the star region:

ρK=ρKsKuKsKsM\rho_K^* = \rho_K \frac{s_K - u_K}{s_K - s_M}

where K{L,R}K \in \{L, R\}.

The velocity components in the star region remain the same as in the original state, except for the normal velocity which is set to sMs_M.

The fourth component (total energy per unit volume) in the star state is:

(ρE)K=ρK[EK+(sMuK)(sM+pKρK(sKuK))](\rho E)_K^* = \rho_K^* \left[ E_K + (s_M - u_K)\left(s_M + \frac{p_K}{\rho_K(s_K - u_K)}\right) \right]

where EK=eK+12(uK2+vK2)E_K = e_K + \frac{1}{2}(u_K^2 + v_K^2) is the specific total energy.

Thus, for the x-direction flux, the star region states are given by:

QK=ρK(1sMvKeK+12(uK2+vK2)+(sMuK)(sM+pKρK(sKuK)))\mathbf{Q}_K^* = \rho_K^* \begin{pmatrix} 1 \\\\ s_M \\\\ v_K \\\\ e_K + \frac{1}{2}(u_K^2 + v_K^2) + (s_M - u_K)\left(s_M + \frac{p_K}{\rho_K(s_K - u_K)}\right) \end{pmatrix}

and for the y-direction flux, they are given by:

QK=ρK(1uKsMeK+12(uK2+vK2)+(sMvK)(sM+pKρK(sKvK)))\mathbf{Q}_K^* = \rho_K^* \begin{pmatrix} 1 \\\\ u_K \\\\ s_M \\\\ e_K + \frac{1}{2}(u_K^2 + v_K^2) + (s_M - v_K)\left(s_M + \frac{p_K}{\rho_K(s_K - v_K)}\right) \end{pmatrix}

Star region fluxes

The fluxes in the star region are computed as:

FK=F(QK)+sK(QKQK)\mathbf{F}_K^* = \mathbf{F}(\mathbf{Q}_K) + s_K (\mathbf{Q}_K^* - \mathbf{Q}_K)

Final HLLC flux

The HLLC flux is then given by:

FHLLC(QL,QR)={F(QL)if sL0FLif sL<0sMFRif sM<0<sRF(QR)if sR0\mathbf{F}_{\text{HLLC}}(\mathbf{Q}_L, \mathbf{Q}_R) = \begin{cases} \mathbf{F}(\mathbf{Q}_L) & \text{if } s_L \geq 0 \\ \mathbf{F}_L^* & \text{if } s_L < 0 \leq s_M \\ \mathbf{F}_R^* & \text{if } s_M < 0 < s_R \\ \mathbf{F}(\mathbf{Q}_R) & \text{if } s_R \leq 0 \end{cases}

This four-wave structure allows the HLLC scheme to resolve contact discontinuities more accurately than the HLL scheme while maintaining robustness.

Initial conditions

The physical domain is [0,1]×[0,1][0,1] \times [0,1]. The initial condition consists of a Riemann problem with four constant states separated by discontinuities:

(ρ,u,v,p)(x,y,0)={(1.5,0,0,1.5)if x<0.5 and y<0.5(0.5323,1.206,0,0.3)if x0.5 and y<0.5(0.5323,0,1.206,0.3)if x<0.5 and y0.5(0.138,1.206,1.206,0.029)if x0.5 and y0.5(\rho, u, v, p)(x,y,0) = \begin{cases} (1.5, 0, 0, 1.5) & \text{if } x < 0.5 \text{ and } y < 0.5 \\\\ (0.5323, 1.206, 0, 0.3) & \text{if } x \geq 0.5 \text{ and } y < 0.5 \\\\ (0.5323, 0, 1.206, 0.3) & \text{if } x < 0.5 \text{ and } y \geq 0.5 \\\\ (0.138, 1.206, 1.206, 0.029) & \text{if } x \geq 0.5 \text{ and } y \geq 0.5 \end{cases}

The boundary conditions are homogeneous Neumann conditions.

Riemann Config 3 Setup

The initial condition is provided in the initial_condition.hpp file.

Exercises

To implement and simulate the Euler equations in 2D using samurai, you will need to follow the steps introduced in the previous sections. Here is a summary:

If everything is working correctly, you can add adaptive mesh refinement using the multiresolution capabilities of samurai.

Conclusion

In this first part on the Euler equations, you have successfully implemented a complete solver for compressible gas dynamics. You learned to:

In Part 2, you will tackle an even more challenging problem: the double Mach reflection. This classical benchmark will require you to implement custom boundary conditions (reflecting walls, inflow/outflow) and develop a robust prediction operator to maintain physical positivity during mesh adaptation. This next step will demonstrate how samurai’s flexible framework can handle complex, realistic shock physics problems.