Skip to content

Recycling plane for turbulence generation#655

Open
drummerdoc wants to merge 3 commits intoAMReX-Combustion:developmentfrom
drummerdoc:recyclingPlane
Open

Recycling plane for turbulence generation#655
drummerdoc wants to merge 3 commits intoAMReX-Combustion:developmentfrom
drummerdoc:recyclingPlane

Conversation

@drummerdoc
Copy link
Copy Markdown

As an alternative to using precursor simulations to generate flow perturbations into a system, "recycling" injects zero-mean fluctuations derived from a sample plane downstream WITHIN THE SAME SIMULATION:

 u_inlet(t) = u_inlet_target_mean + [u_src(t) - <u_src>]

The standard ext_dir BC continues to set u_inlet_target_mean; this machinery only supplies the bracketed fluctuation, and does so whenever needed, but AFTER the call to the user bc_normal filler call. Note that <u_src> is updated on the fly during the simulation as the time average of the velocity in the sampling plane. The feature is managed via a struct holding u_src, u_mean and the fluctuations, u_src-u_mean, to inject...on each AMR level. The planes of mean velocity are written to the plot and checkpoint files for analysis and smooth restarts. Controls include:

  • ues_inlet_from_plane: [0,1] turn on this machinery
  • inlet_plane_dir: [0-2] direction normal to sample plane
  • inlet_plane_position: physical location of plane
  • inlet_plane_average_window: length of averaging time for <u_src>
  • inlet_plane_warmup_steps: number of time steps to accumulate <u_src> before injecting fluctuations

Note that in order for this to reach a nontrivial quasi-steady injection, there MUST be a source of fluctuations between the inlet and the source plane. This can be driven by boundary geometries (e.g., backward facing step, or wall-generated fluctuations) or volumetric forcing. In the most efficient cases, the source plane is placed far enough downstream that generated fluctuations are decorrelated from the inlet plane - some experimentation is likely to be needed for each case.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “recycling plane” mechanism to generate and inject zero-mean inlet velocity fluctuations derived from a downstream sampling plane within the same simulation, including restart/plotfile persistence of the running mean.

Changes:

  • Adds new peleLM.* runtime parameters to enable/configure recycling-plane sampling and averaging.
  • Implements per-level slab storage, snapshot sampling, running mean/fluctuation updates, and fluctuation injection into ext_dir inflow ghosts.
  • Persists/restores recycling-plane running means through checkpoint headers and per-level recycle_mean MultiFab files; triggers rebuilds on AMR level changes.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Source/PeleLMeX_Setup.cpp Reads recycling-plane enable/config parameters and validates direction.
Source/PeleLMeX_Regrid.cpp Flags recycling-plane storage for rebuild when AMR levels change.
Source/PeleLMeX_Plot.cpp Writes/reads checkpoint header marker and per-level running-mean recycle_mean data.
Source/PeleLMeX_BC.cpp Adds fluctuation injection hook points and implements storage build/snapshot/update/injection logic.
Source/PeleLMeX_Advance.cpp Calls snapshot/update once per real timestep.
Source/PeleLMeX.H Declares new APIs and stores recycling-plane configuration/state.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Source/PeleLMeX_BC.cpp
Comment on lines +1568 to +1585
// Shift the cached fluctuation MultiFab onto the inflow ghost layer and
// ADD it to the destination. The standard ext_dir fill has already set
// the inlet mean profile; this only contributes the zero-mean fluctuation.
amrex::MultiFab& fluct = *m_inlet_recycling.fluct_src[lev];

if (need_lo) {
const int nshift = srcIndex - domain.smallEnd(planeDir) + 1;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(-shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(+shift);
}
if (need_hi) {
const int nshift = domain.bigEnd(planeDir) - srcIndex + 1;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(+shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(-shift);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recycling-plane fluctuations are only shifted onto the first ghost layer adjacent to the domain (dom.smallEnd-1). When a_vel has multiple ghost cells (e.g., m_nGrowState is 3/4), the outer ext_dir ghost layers keep the mean-only values, which can break consistency for higher-order stencils that read deeper ghost cells. Consider applying the same fluctuation to every ext_dir ghost layer (loop over g=1..nGrowDest and shift/add for each layer).

Suggested change
// Shift the cached fluctuation MultiFab onto the inflow ghost layer and
// ADD it to the destination. The standard ext_dir fill has already set
// the inlet mean profile; this only contributes the zero-mean fluctuation.
amrex::MultiFab& fluct = *m_inlet_recycling.fluct_src[lev];
if (need_lo) {
const int nshift = srcIndex - domain.smallEnd(planeDir) + 1;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(-shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(+shift);
}
if (need_hi) {
const int nshift = domain.bigEnd(planeDir) - srcIndex + 1;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(+shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(-shift);
// Shift the cached fluctuation MultiFab onto each inflow ghost layer and
// ADD it to the destination. The standard ext_dir fill has already set
// the inlet mean profile; this only contributes the zero-mean fluctuation.
amrex::MultiFab& fluct = *m_inlet_recycling.fluct_src[lev];
if (need_lo) {
for (int g = 1; g <= nGrowDest; ++g) {
const int nshift = srcIndex - domain.smallEnd(planeDir) + g;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(-shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(+shift);
}
}
if (need_hi) {
for (int g = 1; g <= nGrowDest; ++g) {
const int nshift = domain.bigEnd(planeDir) - srcIndex + g;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(+shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(-shift);
}

Copilot uses AI. Check for mistakes.
Comment thread Source/PeleLMeX_BC.cpp
Comment on lines +1568 to +1585
// Shift the cached fluctuation MultiFab onto the inflow ghost layer and
// ADD it to the destination. The standard ext_dir fill has already set
// the inlet mean profile; this only contributes the zero-mean fluctuation.
amrex::MultiFab& fluct = *m_inlet_recycling.fluct_src[lev];

if (need_lo) {
const int nshift = srcIndex - domain.smallEnd(planeDir) + 1;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(-shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(+shift);
}
if (need_hi) {
const int nshift = domain.bigEnd(planeDir) - srcIndex + 1;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(+shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(-shift);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue on the high side: the fluctuation slab is shifted only to dom.bigEnd+1, so only the first high-side ghost layer gets the fluctuation while additional ext_dir ghost layers remain mean-only. If more than one ghost cell is used, apply the fluctuation across all required ghost layers to keep the boundary condition consistent for stencils that extend beyond one cell.

Suggested change
// Shift the cached fluctuation MultiFab onto the inflow ghost layer and
// ADD it to the destination. The standard ext_dir fill has already set
// the inlet mean profile; this only contributes the zero-mean fluctuation.
amrex::MultiFab& fluct = *m_inlet_recycling.fluct_src[lev];
if (need_lo) {
const int nshift = srcIndex - domain.smallEnd(planeDir) + 1;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(-shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(+shift);
}
if (need_hi) {
const int nshift = domain.bigEnd(planeDir) - srcIndex + 1;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(+shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(-shift);
// Shift the cached fluctuation MultiFab onto the inflow ghost layers and
// ADD it to the destination. The standard ext_dir fill has already set
// the inlet mean profile; this only contributes the zero-mean fluctuation.
amrex::MultiFab& fluct = *m_inlet_recycling.fluct_src[lev];
if (need_lo) {
for (int ig = 1; ig <= nGrowDest; ++ig) {
const int nshift = srcIndex - domain.smallEnd(planeDir) + ig;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(-shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(+shift);
}
}
if (need_hi) {
for (int ig = 1; ig <= nGrowDest; ++ig) {
const int nshift = domain.bigEnd(planeDir) - srcIndex + ig;
const auto shift = amrex::BASISV(planeDir) * nshift;
fluct.shift(+shift);
a_vel.ParallelAdd(fluct, 0, vel_comp, AMREX_SPACEDIM, 0, nGrowDest);
fluct.shift(-shift);
}

Copilot uses AI. Check for mistakes.
Comment thread Source/PeleLMeX_Setup.cpp
Comment on lines +288 to +292
pp.query("use_inlet_from_plane", m_use_inlet_from_plane);
pp.query("inlet_plane_dir", m_inlet_plane_dir);
pp.query("inlet_plane_position", m_inlet_plane_position);
pp.query("inlet_plane_avg_window", m_inlet_plane_avg_window);
pp.query("inlet_plane_warmup_steps", m_inlet_plane_warmup_steps);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description lists inlet_plane_average_window, but the code reads peleLM.inlet_plane_avg_window. This mismatch can lead to user inputs being silently ignored. Either update the documented parameter name or support both keys (e.g., query the long name as a fallback) to maintain consistency.

Copilot uses AI. Check for mistakes.
@baperry2
Copy link
Copy Markdown
Collaborator

@drummerdoc thanks for submitting this: can you add the controls and description of this capability to the relevant documentation page: https://amrex-combustion.github.io/PeleLMeX/manual/html/LMeXControls.html#turbulent-forcing-and-velocity-plotfile

Is there a simple test case we could use to see this in action and maybe even run in CI? I think setting this up for the convecting vortex in PeriodicCases, which we also did to test TurbInflow, might be a good test case?

Why is filling from recycling plane done after bcnormal rather than before? It seems like before might be better if the user wants to recycle on only part of the inlet face or some other currently unanticipated use case. I'd generally prefer to err on the side of more customizability.

I could foresee use cases where rather than adding fluctuations, the user might want to take the full profile form the recycle plane (e.g., to get fully developed pipe flow). Could we add a flag that completely bypasses the mean flow part?

@baperry2
Copy link
Copy Markdown
Collaborator

It looks like you used a different version of clang-format so that test was failing, I redid it with version 20, which is what we use in the CI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants