3D backward facing step with recycling-plane inflow
Introduction
This tutorial sets up a non-reactive 3D turbulent backward-facing step (BFS)
flow using the recycling-plane inflow capability described in
PeleLMeX controls. The mean inflow is prescribed in the usual way through
the Inflow boundary condition; the new machinery samples the velocity
field on a cross-section in the interior of the domain and re-injects the
fluctuating part onto the inflow ghost cells every timestep, providing the
inlet with self-consistent turbulent forcing without resorting to a
synthetic-turbulence library or a precomputed plotfile.
The case demonstrates:
setting up a 3D BFS in PeleLMeX with embedded boundaries and a spanwise periodic direction,
enabling the recycling-plane inflow,
seeding the initial condition with a small-amplitude perturbation to trigger the shear-layer instability,
monitoring domain-integrated kinetic energy and enstrophy via the
do_temporalsmachinery to detect statistically stationary turbulence,the practical effect of the source-plane position on convergence speed and statistical quality.
Note that the results here are colored strongly by the fact that the low spatial resolution is unable to produce any significant turbulence that would otherwise be generated by the boundary along the top of the step. The resolved turbulence source in this case is rather produced by the domain expansion past the step, which generates a large-scale coherent vortex that sheds KH fluctuations. If wall boundary-layer turbulence is instead of interest, dramatically higher resolution in space and time is required along the boundary and the sample plane should be located on the step.
The case files referenced below are located in:
Exec/RegTests/EB_BFS_Recycling
Setting-up your environment
Getting a functioning environment in which to compile and run PeleLMeX is the first step of this tutorial. Follow the steps listed below to get to this point:
The first step is to get PeleLMeX and its dependencies. To do so, use a recursive git clone:
git clone --recursive --shallow-submodules --single-branch https://github.com/AMReX-Combustion/PeleLMeX.git
The
--shallow-submodulesand--single-branchflags are recommended for most users as they substantially reduce the size of the download by skipping extraneous parts of the git history. Developers may wish to omit these flags in order download the complete git history of PeleLMeX and its submodules, though standardgitcommands may also be used after a shallow clone to obtain the skipped portions if needed.Move into the Exec folder containing your tutorial. To do so:
cd PeleLMeX/Exec/RegTests/<CaseName>
where <CaseName> is the name of your tutorial, e.g.
HotBubble,FlameSheet,EB_BackwardStepFlame,EB_FlowPastCylinder, orTripleFlame.
You’re good to go!
Note
The makefile system is set up such that default paths are automatically set to the submodules obtained with the recursive git clone, however advanced users can set their own dependencies in the GNUmakefile for each case by updating the top-most lines as follows:
PELE_HOME = <path_to_PeleLMeX>
AMREX_HOME = <path_to_MyAMReX>
AMREX_HYDRO_HOME = <path_to_MyAMReXHydro>
PELE_PHYSICS_HOME = <path_to_MyPelePhysics>
SUNDIALS_HOME = <path_to_MySUNDIALS>
or directly through shell environment variables (using bash for instance):
export PELE_HOME=<path_to_PeleLMeX>
export AMREX_HOME=<path_to_MyAMReX>
export AMREX_HYDRO_HOME=<path_to_MyAMReXHydro>
export PELE_PHYSICS_HOME=<path_to_MyPelePhysics>
export SUNDIALS_HOME=<path_to_MySUNDIALS>
Note that using the first option will overwrite any environment variables you might have previously defined when using this GNUmakefile.
Case setup
Geometry, grid and boundary conditions
The computational domain is a 0.08 × 0.02 × 0.02 \(m^3\) box with the
streamwise direction (\(x\)) aligned with the bulk flow and a periodic
spanwise direction (\(z\)). The step occupies the lower-left quarter
of the \(x\)-\(y\) plane and is extruded across the full periodic
\(z\) range. The physical setup here is identical that used in the
BFSFlame tutorial. Boundary conditions are Inflow on \(x_{lo}\),
Outflow on \(x_{hi}\), no-slip walls on \(y\), and Interior
(periodic) on \(z\).
#---------------------- DOMAIN DEFINITION ------------------------
geometry.is_periodic = 0 0 1
geometry.coord_sys = 0
geometry.prob_lo = -0.01 -0.01 0.00
geometry.prob_hi = 0.07 0.01 0.02
#---------------------- BC FLAGS ---------------------------------
peleLM.lo_bc = Inflow NoSlipWallAdiab Interior
peleLM.hi_bc = Outflow NoSlipWallAdiab Interior
The base grid is 256 × 64 × 64 with isotropic cell size
\(\Delta x = 3.125 \times 10^{-4}\) m. AMR is disabled in this tutorial;
max_grid_size = 32 is used to obtain enough boxes to scale onto 32 MPI
ranks.
#---------------------- AMR CONTROL ------------------------------
amr.n_cell = 256 64 64
amr.max_level = 0
amr.blocking_factor = 16
amr.max_grid_size = 32
The step is described as an axis-aligned box, extruded across the spanwise direction:
#---------------------- EB SETUP ---------------------------------
eb2.geom_type = box
eb2.box_lo = -0.01 -0.01 0.00
eb2.box_hi = 0.01 0.00 0.02
eb2.box_has_fluid_inside = 0
eb2.small_volfrac = 1.0e-4
The step height is \(H = 0.01\) m, equal to the post-step channel half- height, giving an expansion ratio of 2:1. The bulk inlet velocity is \(U_{\text{bulk}} = 10\) m/s, so the flow-through time is \(\tau = L_x / U_{\text{bulk}} = 8\) ms and the step Reynolds number is \(\mathrm{Re}_H = U_{\text{bulk}} H / \nu \approx 6700\).
Problem specifications and initial-condition seeding
The user parameters are gathered in MyProbParm:
struct MyProbParm : public ProbParmDefault
{
amrex::Real T_mean = 298.0;
amrex::Real meanFlowMag = 10.0;
amrex::Real turb_seed_amplitude = 0.05;
};
T_mean: inlet and initial gas temperaturemeanFlowMag: inlet \(x\)-velocity magnitudeturb_seed_amplitude: amplitude of the initial-condition velocity perturbation, expressed as a fraction ofmeanFlowMag
Recycling on its own does not produce turbulence: if the velocity at the source plane has no fluctuation around its running mean, the injected contribution is zero and the inlet stays steady. To break this trivial fixed point the initial condition adds a small-amplitude, deterministic multi-mode perturbation to all three velocity components, tapered to zero at the wall-normal boundaries:
const amrex::Real ywall = 1.0 - std::pow(2.0 * y / Ly, 2.0);
const amrex::Real wy = std::max(amrex::Real(0.0), ywall);
state(i, j, k, VELX) = U + A * wy * pu * norm;
state(i, j, k, VELY) = 0.0 + A * wy * pv * norm;
state(i, j, k, VELZ) = 0.0 + A * wy * pw * norm;
where A = turb_seed_amplitude * U and pu, pv, pw are sums of
three sinusoidal triads with incommensurate wavenumbers and phase offsets
(see pelelmex_prob.H for the full expression). The shear-layer
instability behind the step amplifies the perturbation into developed
turbulence within a few flow-through times; once that turbulence reaches
the source plane, the recycling loop closes.
The standard Inflow boundary condition supplies the prescribed mean
profile through bcnormal (uniform \(U_{\text{bulk}}\) here); the
recycling layer adds the zero-mean fluctuation on top of it.
Recycling-plane inflow controls
The recycling feature is enabled with five inputs (see PeleLMeX controls for the full reference):
#---------------------- Recycling-plane inflow --------------------
peleLM.use_inlet_from_plane = 1
peleLM.inlet_plane_dir = 0
peleLM.inlet_plane_position = 0.065
peleLM.inlet_plane_warmup_steps = 500
# peleLM.inlet_plane_avg_window = 1.0e-4 # uncomment for EMA
The source plane is normal to \(x\) and located at \(x_{\text{plane}} = 0.065\) m, which is \(5.5\,H\) downstream of the step end and \(0.5\,H\) from the outflow boundary. The choice of plane position has a strong effect on convergence and statistical quality; this is discussed below.
The 500-sample warmup gives the running mean roughly one flow-through to
settle before any fluctuation is injected. With the cumulative mean (no
avg_window set) the running average is \(\langle u \rangle_n =
\frac{1}{n}\sum_i u_i\); specifying inlet_plane_avg_window switches to
an exponential moving average with relaxation timescale equal to the
window. The cumulative form is appropriate for one-shot stationary-
statistics studies; the EMA form is preferred for cases where the bulk
flow is slowly evolving.
Note
The fluctuation is injected on every velocity component on the inflow
ghost layer. The implementation requires that all components share the
same ext_dir boundary status on the inflow face; mixed BCs on
different velocity components are not supported and will abort the
simulation.
Numerical parameters
#---------------------- PeleLM CONTROL ---------------------------
peleLM.v = 1
peleLM.sdc_iterMax = 2
peleLM.num_init_iter = 1
peleLM.chem_integrator = "ReactorNull"
amr.cfl = 0.4
amr.dt_shrink = 0.01
The case is non-reactive (ReactorNull skips chemistry source
evaluation entirely) but the full low-Mach algorithm is otherwise active,
so the recycling machinery exercises the same code path it would in a
reacting simulation.
num_init_iter = 1 is used because successive initial pressure
iterations on this 3D EB geometry can drive the MAC projection divergent
on the first real step; a single initial iteration is sufficient to
establish a consistent pressure field.
Building the executable
The case uses the non-reactive air chemistry model with Fuego EOS
and Simple transport:
DIM = 3
USE_EB = TRUE
USE_MPI = TRUE
Chemistry_Model = air
Eos_Model = Fuego
Transport_Model = Simple
Build third-party libraries first, then the executable:
make -j4 TPL
make -j4
This produces PeleLMeX3d.gnu.MPI.ex.
Running the simulation
The simulation is non-trivial in cost: each timestep takes around 1.6 s on 32 MPI ranks (~0.05 s/rank/step at this resolution), and reaching fully converged stationary statistics requires of order \(20\,\tau \approx 25\,000\) timesteps, so plan for roughly 8–11 hours of wall time on 32 ranks for a single case.
Launch the run, redirecting stdout to a log file:
mpiexec -n 32 ./PeleLMeX3d.gnu.MPI.ex inputs.3d-recycle peleLM.inlet_plane_position=0.065 > run.log 2>&1
Three things in particular are worth watching during the run:
The dt-shrink ramp during the first ~50 steps:
dtstarts at \(10^{-2}\) of the convective CFL estimate and grows by thedt_change_maxfactor each step until it reaches the CFL limit (\(\sim\!8\times 10^{-6}\) s).The first 500 steps where the warmup gate is active: the snapshot path is exercised every step (and the running mean accumulates) but no fluctuation is yet injected.
The transition to active recycling once the warmup completes: this is visible in the
temporals/tempStatelog as a change of slope in the integrated kinetic energy.
Diagnostics and stationarity detection
The case enables PeleLMeX’s temporal-output machinery to produce a time history of the domain-integrated kinetic energy and enstrophy:
peleLM.do_temporals = 1
peleLM.temporal_int = 10
peleLM.do_extremas = 1
peleLM.do_mass_balance = 1
This writes one CSV row every 10 steps to temporals/tempState with
columns iter, time, dt, kinEnergy, enstrophy, pressure, fuelConsumption,
heatRelease (the last two are zero for this non-reactive case).
A simple stationarity check is to compare the mean of kinEnergy over
two adjacent windows of width \(W\tau\) ending at candidate time
\(t^*\): if the trailing window
\([t^* - W\tau,\, t^*]\) and leading window
\([t^*,\, t^* + W\tau]\) agree within a tolerance, declare onset at
\(t^*\). With \(W=4\) and a 10% tolerance, this case typically
identifies onset at \(t^* \approx 4\tau\) and shows
post-onset robustness (fraction of subsequent window-pairs that pass the
same test) above 50%. The script
analyze_stationarity.py shipped with the case reports both numbers,
along with the running mean and standard deviation of kinEnergy and
enstrophy over the stationary regime.
A representative final-run summary for the \(x_{\text{plane}} = 0.065\) configuration on 25,000 timesteps (\(\sim 23\,\tau\)) is:
stationary onset at \(4\,\tau\), robustness 16 / 31 (52%),
\(\langle \mathrm{KE} \rangle = 1.04 \times 10^{-3}\) J with CoV 37%,
\(\langle \mathrm{enstrophy} \rangle = 4.0 \times 10^{2}\) with CoV 18%.
The kinetic-energy CoV reflects coherent shear-layer shedding in the post-step recovery zone; the enstrophy CoV is much tighter because the small-scale vorticity averages more uniformly across the domain.
Choice of source-plane position
The source-plane location is the single most influential parameter for
this feature. The tutorial input shipped in-tree uses
\(x_{\text{plane}} = 0.065\). The trends summarized below come from
four additional out-of-tree runs in which only
peleLM.inlet_plane_position was changed, taking values from
{0.05, 0.035, 0.005, 0.0095} to sweep the plane from near the step
to farther downstream. The main trends observed in those separate runs are:
Plane upstream of the step edge (e.g. \(x = 0.005,\, 0.0095\) ). The flow at the source plane is laminar boundary-layer development of the inflow itself: the running mean equals the instantaneous field to within numerical noise, no fluctuation is injected, and the recycling effectively disables itself. Domain- integrated KE has a coefficient of variation of order 1%, but the flow simply runs on whatever turbulence the IC perturbation seeded through the natural BFS shedding — recycling is not contributing.
Plane near the step end (e.g. \(x = 0.035,\, 2.5\,H\) downstream). The plane samples the strongly coherent shear-layer roll-up. Recycling that signal closes a tight feedback loop with the shear-layer mode and produces a large amplitude transient that overshoots and oscillates, with stationary onset pushed out to \(8.5\,\tau\) and a post-onset window-pair pass rate of only 5%.
Plane far downstream (e.g. \(x = 0.05,\, 0.065\) ). The plane samples post-transition broadband turbulence with weak large-scale coherence. The recycling loop delay \((x_{\text{plane}} - x_{\text{inlet}})/U_{\text{bulk}}\) is now comparable to a flow-through time, so the injected perturbation is decorrelated from the resulting shear-layer dynamics. Stationary onset is reached around \(4\,\tau\), with
runP25(\(x = 0.065\), \(5.5\,H\) downstream) giving the highest post-onset robustness of the five cases.
The general guidance, then, is to place the source plane in a region of
developed broadband turbulence rather than near a region of strong
coherent shedding, and to ensure the loop delay is on the order of an
integral timescale of the flow. The geometry-specific bounds (sufficient
distance from the geometric feature, but not so close to the outflow
that the BC interferes with the recycled signal) are the practical
constraints; in this case runP25 lands on a usable balance.
Note
Even at \(x_{\text{plane}} = 0.065\) (only 5 mm from the outflow at \(x = 0.07\)) the outflow boundary condition does not detectably contaminate the recycled signal; the gentle outflow extrapolation in fact mildly de-correlates the streamwise structure, which in this setup is helpful. With more reactive or compressibility-sensitive physics the user should re-evaluate that conclusion.
Restarting and continuing the simulation
The running mean of the source plane is written into the checkpoint
header (the line beginning with RecyclingPlane:) and the per-level
mean fields are written under recycle_mean in each level’s
checkpoint directory. On restart, the storage is rebuilt against the
current run’s grids and the previously-accumulated mean is
ParallelCopy’d back into place, so a checkpoint/restart of an
already-stationary simulation continues without reseeding the running
mean.
Restart is triggered the usual way:
amr.restart = chk20000
amr.max_step = 30000
Older checkpoints written before the recycling feature was added do not
contain the RecyclingPlane block; the read path silently falls back
to “no saved mean” and the running mean reseeds from the next
snapshot. This makes the feature backwards-compatible with pre-existing
checkpoints.