Two shear layers roll up into the iconic billows; the mesh tracks the growing vortices.
ρ0.887 to 2.18
3 mesh levels46,279 cells61 frames
When two fluid layers slide past each other, the shear interface is unstable:
any small ripple grows and rolls up into a train of vortices - the
Kelvin-Helmholtz billows seen in clouds, ocean currents and countless
astrophysical flows. It is a favorite showcase for adaptive solvers because the
action is confined to thin, evolving shear layers.
This case is a new scenario contributed tosamurai-euler; the code shown is
its definition (initial shear profile and the seeded perturbation).
Equations
Compressible Euler for an ideal gas (γ=1.4). The domain [0,1]2 has a
central layer (0.25<y<0.75) with density ρ=2 moving right, and
outer layers with ρ=1 moving left, at uniform pressure. A small vertical
velocity localized at each interface seeds the instability.
Numerical method
Flux: HLLC approximate Riemann solver.
Time: explicit Euler.
Adaptation: multiresolution with a positivity-preserving prediction.
The image shows the density ρ.
What to look for
Two rows of billows form along the interfaces and roll the light and heavy
fluids into interlocking spirals. The adaptive mesh refines precisely along the
braided shear layers, leaving the uniform interiors coarse.
Source
scenario.hpp · 72 lines
This case runs on the samurai-euler solver. The code below is its scenario: initial and
boundary conditions. Fluxes, time integration and mesh adaptation come from the shared solver.
// Copyright 2025 the samurai team// SPDX-License-Identifier: BSD-3-Clause#pragma once#include <cmath>#include <numbers>#include <samurai/bc.hpp>#include "../variables.hpp"#include "registry.hpp"// Kelvin-Helmholtz instability: two horizontal layers in shear. A small// vertical-velocity perturbation localized at the two interfaces grows into// the characteristic rolled-up billows.namespace test_case::kelvin_helmholtz{ constexpr double pi = std::numbers::pi; // Layer states double rho_in = 2.0; // inner layer (0.25 < y < 0.75) double rho_out = 1.0; // outer layers double v_shear = 0.5; // horizontal shear velocity (+/-) double p0 = 2.5; // uniform pressure // Perturbation double amp = 0.1; // amplitude of the seeded vertical velocity double sigma = 0.05; // interface thickness of the seed int mode = 2; // number of billows (wavenumber = 2*mode) auto init_fn = [](auto& u, auto& cell) { auto c = cell.center(); const double x = c[0]; const double y = c[1]; const bool inner = (y > 0.25 && y < 0.75); const double rho = inner ? rho_in : rho_out; const double vx = inner ? v_shear : -v_shear; const double seed = std::exp(-(y - 0.25) * (y - 0.25) / (2 * sigma * sigma)) + std::exp(-(y - 0.75) * (y - 0.75) / (2 * sigma * sigma)); const double vy = amp * std::sin(2 * mode * pi * x) * seed; PrimState<2> state{ rho, p0, xt::xtensor_fixed<double, xt::xshape<2>>{vx, vy} }; u[cell] = prim2cons<2>(state); }; void bc_fn(auto& u, double /*t*/) { samurai::make_bc<samurai::Neumann<1>>(u, 0., 0., 0., 0.); } template <std::size_t dim> auto box_fn() { xt::xtensor_fixed<double, xt::xshape<dim>> min_corner = {0., 0.}; xt::xtensor_fixed<double, xt::xshape<dim>> max_corner = {1., 1.}; return samurai::Box<double, dim>(min_corner, max_corner); }}REGISTER_TEST_CASE(kelvin_helmholtz, test_case::kelvin_helmholtz::box_fn, test_case::kelvin_helmholtz::init_fn, test_case::kelvin_helmholtz::bc_fn)