Recycling plane for turbulence generation#655
Recycling plane for turbulence generation#655drummerdoc wants to merge 3 commits intoAMReX-Combustion:developmentfrom
Conversation
There was a problem hiding this comment.
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_meanMultiFab 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.
| // 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); |
There was a problem hiding this comment.
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).
| // 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); | |
| } |
| // 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); |
There was a problem hiding this comment.
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.
| // 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); | |
| } |
| 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); |
There was a problem hiding this comment.
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.
|
@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 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? |
|
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 |
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:
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:
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.