REMORA
Regional Modeling of Oceans Refined Adaptively
Loading...
Searching...
No Matches
REMORA_set_2d_cf_bcs.cpp
Go to the documentation of this file.
1#include <iomanip>
2#include <REMORA.H>
4
5using namespace amrex;
6
7namespace {
9
10/** \brief Replace each group of nr faces along the interface by their mean.
11 *
12 * ROMS gives every fine face under one parent face the same parent flux: get_persisted2d
13 * copies DU_avg2 at the donor face picked by integer division of the fine index, and
14 * put_refine2d and u2dbc_im scale it by the edge-length ratio. AMReX's face interpolator gives
15 * the flux a linear variation along the interface instead. Both make the fine fluxes sum to
16 * the parent's; they differ in how the total is shared out. That interpolation being
17 * conservative, the group mean is the parent value, so averaging turns the linear profile back
18 * into ROMS's constant one.
19 *
20 * Only groups lying wholly inside a box are touched, and only faces the set mask covers, so
21 * faces the parent never wrote keep their zero.
22 */
23void group_average_faces (MultiFab& mf, const iMultiFab& mask, int set_mask, int tdir, int nr)
24{
25 if (nr <= 1) { return; }
26 for (MFIter mfi(mf); mfi.isValid(); ++mfi)
27 {
28 const Box& bx = mfi.validbox();
29 const auto& a = mf.array(mfi);
30 const auto& m = mask.const_array(mfi);
31 const int lo_t = bx.smallEnd(tdir);
32 const int hi_t = bx.bigEnd(tdir);
33 ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k)
34 {
35 const int t = (tdir == 0) ? i : j;
36 const int t0 = amrex::coarsen(t, nr) * nr;
37 // one thread per group, and only where the whole group is in this box
38 if (t != t0 || t0 < lo_t || t0 + nr - 1 > hi_t) { return; }
39 Real sum = Real(0.0);
40 int cnt = 0;
41 for (int s = t0; s < t0 + nr; ++s) {
42 const int si = (tdir == 0) ? s : i;
43 const int sj = (tdir == 0) ? j : s;
44 if (m(si,sj,k) != set_mask) { continue; }
45 sum += a(si,sj,k); ++cnt;
46 }
47 if (cnt == 0) { return; }
48 const Real avg = sum / Real(cnt);
49 for (int s = t0; s < t0 + nr; ++s) {
50 const int si = (tdir == 0) ? s : i;
51 const int sj = (tdir == 0) ? j : s;
52 if (m(si,sj,k) != set_mask) { continue; }
53 a(si,sj,k) = avg;
54 }
55 });
56 }
57}
58}
59
60/**
61 * Store this level's barotropic mass flux per unit cell edge length, for a finer level to
62 * interpolate.
63 *
64 * @param[in] lev level of refinement
65 */
66void
68{
69 // The previous step's end-of-step value is this step's start-of-step value: DU_avg2 is
70 // accumulated during a step and only meaningful once it is over.
71 MultiFab::Copy(*vec_Dubar_old[lev], *vec_Dubar_new[lev], 0, 0, 1,
73 MultiFab::Copy(*vec_Dvbar_old[lev], *vec_Dvbar_new[lev], 0, 0, 1,
75
76#ifdef _OPENMP
77#pragma omp parallel if (Gpu::notInLaunchRegion())
78#endif
79 for ( MFIter mfi(*vec_Dubar_new[lev], TilingIfNotGPU()); mfi.isValid(); ++mfi )
80 {
81 Array4<Real const> const& pn = vec_pn[lev]->const_array(mfi);
82 Array4<Real const> const& DU_avg2 = vec_DU_avg2[lev]->const_array(mfi);
83 Array4<Real > const& Dubar = vec_Dubar_new[lev]->array(mfi);
84
85 Box ubx = mfi.grownnodaltilebox(0,IntVect(NGROW,NGROW,0));
86 ubx.makeSlab(2,0);
87
88 // DU_avg2 is the flux through a whole face, m^3/s. Dividing by on_u leaves D*ubar,
89 // so a finer face can multiply its own edge length back in and the fine fluxes sum
90 // to the coarse one. Passing ubar across, or the raw flux, loses that.
91 ParallelFor(ubx, [=] AMREX_GPU_DEVICE (int i, int j, int)
92 {
93 Real on_u = two / (pn(i,j,0) + pn(i-1,j,0));
94 Dubar(i,j,0) = DU_avg2(i,j,0) / on_u;
95 });
96 }
97
98#ifdef _OPENMP
99#pragma omp parallel if (Gpu::notInLaunchRegion())
100#endif
101 for ( MFIter mfi(*vec_Dvbar_new[lev], TilingIfNotGPU()); mfi.isValid(); ++mfi )
102 {
103 Array4<Real const> const& pm = vec_pm[lev]->const_array(mfi);
104 Array4<Real const> const& DV_avg2 = vec_DV_avg2[lev]->const_array(mfi);
105 Array4<Real > const& Dvbar = vec_Dvbar_new[lev]->array(mfi);
106
107 Box vbx = mfi.grownnodaltilebox(1,IntVect(NGROW,NGROW,0));
108 vbx.makeSlab(2,0);
109
110 ParallelFor(vbx, [=] AMREX_GPU_DEVICE (int i, int j, int)
111 {
112 Real om_v = two / (pm(i,j,0) + pm(i,j-1,0));
113 Dvbar(i,j,0) = DV_avg2(i,j,0) / om_v;
114 });
115 }
116
117 // There is no previous step on the first one, so extrapolate rather than leave old at
118 // the zero it was initialised with. Only remora.time_interp_flux reads old at all.
119 if (istep[lev] == 0) {
120 MultiFab::Copy(*vec_Dubar_old[lev], *vec_Dubar_new[lev], 0, 0, 1,
122 MultiFab::Copy(*vec_Dvbar_old[lev], *vec_Dvbar_new[lev], 0, 0, 1,
124 }
125}
126
127/**
128 * Set the normal barotropic velocity on this level's coarse-fine interface from the parent's
129 * time-averaged mass flux, as ROMS does in put_refine2d (nesting.F):
130 *
131 * ubar_f = Dubar_c / D_f, D_f = 0.5*(h + zeta)_{i-1} + 0.5*(h + zeta)_i
132 *
133 * D comes from know; ROMS instead uses one time index for both sides: u2dbc_im.F's nested branch
134 * builds D from zeta(kout) and writes ubar(kout), and put_refine2d uses indx1 for both. Since
135 * the next half-step forms DUon = ubar(krhs)*D(krhs) with krhs equal to this knew, ROMS
136 * recovers Dubar_parent exactly while this carries an extra D(knew)/D(know). Measured at most
137 * 1.2e-07 on the step-1 Channel_Test interface velocity, against a 2.4e-03 artifact, with no
138 * change to Dogbone volume drift or the Channel_Test blow-up.
139 *
140 * Momentum only. setup_step resets all three zeta components to Zt_avg1, so what a finer
141 * level interpolates for the free surface is already the parent's fast-time average -- as in
142 * ROMS, where set_zeta runs ahead of put_refine2d.
143 *
144 * @param[in] lev level of refinement
145 * @param[in] time simulation time to interpolate the parent's flux to
146 * @param[in] know zeta time component to take the sea surface height from
147 * @param[in] knew ubar/vbar time component to set
148 */
149void
151{
152 if (lev == 0 || cf_set_width < 0) { return; }
153
154 BL_PROFILE("REMORA::set_2d_cf_bcs()");
155
156 // Overwrites what the fill patchers just set from the parent's ubar, which would not
157 // conserve mass. Every fast step: unlike a ROMS contact point on a physical perimeter,
158 // these faces are interior and the barotropic solver rewrites them.
159 const int set_mask = FPr_Dubar[lev-1].GetSetMaskVal();
160
165 Dubar_cf.setVal(zero);
166 Dvbar_cf.setVal(zero);
167
170
171 // an x-face varies along y across the interface, and a y-face along x
172 if (cf_flux_pc) {
173 const IntVect rr = refRatio(lev-1);
174 group_average_faces(Dubar_cf, *FPr_Dubar[lev-1].GetMask(), set_mask, 1, rr[1]);
175 group_average_faces(Dvbar_cf, *FPr_Dvbar[lev-1].GetMask(), set_mask, 0, rr[0]);
176 }
177
178
179#ifdef _OPENMP
180#pragma omp parallel if (Gpu::notInLaunchRegion())
181#endif
182 for ( MFIter mfi(*vec_ubar[lev], TilingIfNotGPU()); mfi.isValid(); ++mfi )
183 {
184 Array4<Real > const& ubar = vec_ubar[lev]->array(mfi);
185 Array4<Real const> const& Dubar = Dubar_cf.const_array(mfi);
186 Array4<int const> const& cmask = FPr_Dubar[lev-1].GetMask()->const_array(mfi);
187 Array4<Real const> const& zeta = vec_zeta[lev]->const_array(mfi);
188 Array4<Real const> const& h = vec_h[lev]->const_array(mfi);
189 Array4<Real const> const& msku = vec_msku[lev]->const_array(mfi);
190
191 // The mask carries no ghost cells, so this is exactly the region it covers.
192 ParallelFor(mfi.tilebox(), [=] AMREX_GPU_DEVICE (int i, int j, int)
193 {
194 if (cmask(i,j,0) != set_mask) { return; }
195
196 Real D = Real(0.5) * (h(i-1,j,0,0) + zeta(i-1,j,0,know) +
197 h(i ,j,0,0) + zeta(i ,j,0,know));
198 if (D <= zero) { return; }
199
200 ubar(i,j,0,knew) = Dubar(i,j,0) / D * msku(i,j,0);
201 });
202 }
203
204 // Diagnostic: the fine level is not written to NetCDF, so dump the imposed u-face values
205 // here. The last block printed for a step is the one the step ends on.
206 // one block per baroclinic step: only the first fast step, from cf_print_iface onward.
207 // cf_print_iface = N lines up with ROMS child history record N.
208 if (cf_print_iface >= 0 && istep[0] >= cf_print_iface) {
209#ifdef AMREX_USE_GPU
210 // Host loops over Array4s that live in device memory, so they would fault here.
211 // Staging every fab through the pinned arena is not worth it for a diagnostic.
212 static bool warned = false;
213 if (!warned) {
214 amrex::Warning("remora.cf_print_iface is host-only; ignored on a GPU build");
215 warned = true;
216 }
217#else
218 const auto dx_p = geom[lev].CellSizeArray();
219 const auto lo_p = geom[lev].ProbLoArray();
220 for (MFIter mfi(*vec_ubar[lev]); mfi.isValid(); ++mfi)
221 {
222 const Box& bx = mfi.tilebox();
223 auto const& ubar = vec_ubar[lev]->const_array(mfi);
224 auto const& cmask = FPr_Dubar[lev-1].GetMask()->const_array(mfi);
225 const auto lo = lbound(bx); const auto hi = ubound(bx);
226 for (int j = lo.y; j <= hi.y; ++j) {
227 for (int i = lo.x; i <= hi.x; ++i) {
228 if (cmask(i,j,0) != set_mask) { continue; }
229 amrex::AllPrint() << "[IFACE] lev " << lev
230 << " t " << std::setprecision(12) << time
231 << " told0 " << t_old[0] << " tnew0 " << t_new[0]
232 << " i " << i << " j " << j
233 << " x " << lo_p[0] + i * dx_p[0]
234 << " y " << lo_p[1] + (j + Real(0.5)) * dx_p[1]
235 << " ubar " << std::setprecision(12) << ubar(i,j,0,knew) << "\n";
236 }
237 }
238 }
239
240 // The neighbourhood of the western interface at mid-channel: what the fine level
241 // actually holds in and around its ghost region, which no output file carries.
242 for (MFIter mfi(*vec_zeta[lev]); mfi.isValid(); ++mfi)
243 {
244 const Box& bx = mfi.tilebox();
245 const auto lo = lbound(bx); const auto hi = ubound(bx);
246 auto const& zeta = vec_zeta[lev]->const_array(mfi);
247 auto const& ubar = vec_ubar[lev]->const_array(mfi);
248 auto const& cmask = FPr_Dubar[lev-1].GetMask()->const_array(mfi);
249 const int jmid = (lo.y + hi.y) / 2;
250 int iface = -1;
251 for (int i = lo.x; i <= hi.x; ++i) {
252 if (cmask(i,jmid,0) == set_mask) { iface = i; break; }
253 }
254 if (iface < 0) { continue; }
255 for (int off = -3; off <= 3; ++off) {
256 const int i = iface + off;
257 amrex::AllPrint() << "[HALO] lev " << lev
258 << " t " << std::setprecision(12) << time
259 << " j " << jmid << " off " << off << " i " << i
260 << " x " << lo_p[0] + i * dx_p[0]
261 << " zeta_know " << zeta(i,jmid,0,know)
262 << " zeta_knew " << zeta(i,jmid,0,knew)
263 << " ubar_know " << ubar(i,jmid,0,know)
264 << " ubar_knew " << ubar(i,jmid,0,knew)
265 << " mask " << cmask(i,jmid,0) << "\n";
266 }
267 break; // one box is enough for this diagnostic
268 }
269#endif
270 }
271
272#ifdef _OPENMP
273#pragma omp parallel if (Gpu::notInLaunchRegion())
274#endif
275 for ( MFIter mfi(*vec_vbar[lev], TilingIfNotGPU()); mfi.isValid(); ++mfi )
276 {
277 Array4<Real > const& vbar = vec_vbar[lev]->array(mfi);
278 Array4<Real const> const& Dvbar = Dvbar_cf.const_array(mfi);
279 Array4<int const> const& cmask = FPr_Dvbar[lev-1].GetMask()->const_array(mfi);
280 Array4<Real const> const& zeta = vec_zeta[lev]->const_array(mfi);
281 Array4<Real const> const& h = vec_h[lev]->const_array(mfi);
282 Array4<Real const> const& mskv = vec_mskv[lev]->const_array(mfi);
283
284 ParallelFor(mfi.tilebox(), [=] AMREX_GPU_DEVICE (int i, int j, int)
285 {
286 if (cmask(i,j,0) != set_mask) { return; }
287
288 Real D = Real(0.5) * (h(i,j-1,0,0) + zeta(i,j-1,0,know) +
289 h(i,j ,0,0) + zeta(i,j ,0,know));
290 if (D <= zero) { return; }
291
292 vbar(i,j,0,knew) = Dvbar(i,j,0) / D * mskv(i,j,0);
293 });
294 }
295}
296
297/**
298 * Impose the parent's barotropic mass flux directly on the coarse-fine interface faces.
299 *
300 * set_2d_cf_bcs writes a velocity, which the solver then turns back into a flux using its own
301 * depth at a different time index, so the flux it actually carries is not the one imposed.
302 * Writing the flux itself makes the transport exact whatever the depth does.
303 *
304 * @param[in] lev level of refinement
305 * @param[in] time simulation time to interpolate the parent's flux to
306 * @param[inout] mf_DUon barotropic u-flux
307 * @param[inout] mf_DVom barotropic v-flux
308 */
309void
310REMORA::set_2d_cf_flux (int lev, Real time, MultiFab& mf_DUon, MultiFab& mf_DVom)
311{
312 if (lev == 0 || cf_set_width < 0) { return; }
313
314 BL_PROFILE("REMORA::set_2d_cf_flux()");
315
316 const int set_mask = FPr_Dubar[lev-1].GetSetMaskVal();
317
322 Dubar_cf.setVal(zero);
323 Dvbar_cf.setVal(zero);
324
327
328 // an x-face varies along y across the interface, and a y-face along x
329 if (cf_flux_pc) {
330 const IntVect rr = refRatio(lev-1);
331 group_average_faces(Dubar_cf, *FPr_Dubar[lev-1].GetMask(), set_mask, 1, rr[1]);
332 group_average_faces(Dvbar_cf, *FPr_Dvbar[lev-1].GetMask(), set_mask, 0, rr[0]);
333 }
334
335#ifdef _OPENMP
336#pragma omp parallel if (Gpu::notInLaunchRegion())
337#endif
338 for ( MFIter mfi(mf_DUon, TilingIfNotGPU()); mfi.isValid(); ++mfi )
339 {
340 Array4<Real > const& DUon = mf_DUon.array(mfi);
341 Array4<Real const> const& Dubar = Dubar_cf.const_array(mfi);
342 Array4<int const> const& cmask = FPr_Dubar[lev-1].GetMask()->const_array(mfi);
343 Array4<Real const> const& pn = vec_pn[lev]->const_array(mfi);
344 Array4<Real const> const& msku = vec_msku[lev]->const_array(mfi);
345
346 ParallelFor(mfi.tilebox(), [=] AMREX_GPU_DEVICE (int i, int j, int)
347 {
348 if (cmask(i,j,0) != set_mask) { return; }
349
350 Real on_u = two / (pn(i,j,0) + pn(i-1,j,0));
351 DUon(i,j,0) = Dubar(i,j,0) * on_u * msku(i,j,0);
352 });
353 }
354
355#ifdef _OPENMP
356#pragma omp parallel if (Gpu::notInLaunchRegion())
357#endif
358 for ( MFIter mfi(mf_DVom, TilingIfNotGPU()); mfi.isValid(); ++mfi )
359 {
360 Array4<Real > const& DVom = mf_DVom.array(mfi);
361 Array4<Real const> const& Dvbar = Dvbar_cf.const_array(mfi);
362 Array4<int const> const& cmask = FPr_Dvbar[lev-1].GetMask()->const_array(mfi);
363 Array4<Real const> const& pm = vec_pm[lev]->const_array(mfi);
364 Array4<Real const> const& mskv = vec_mskv[lev]->const_array(mfi);
365
366 ParallelFor(mfi.tilebox(), [=] AMREX_GPU_DEVICE (int i, int j, int)
367 {
368 if (cmask(i,j,0) != set_mask) { return; }
369
370 Real om_v = two / (pm(i,j,0) + pm(i,j-1,0));
371 DVom(i,j,0) = Dvbar(i,j,0) * om_v * mskv(i,j,0);
372 });
373 }
374}
constexpr amrex::Real two
constexpr amrex::Real zero
#define NGROW
mf_h setVal(geomdata.ProbHi(2))
amrex::Vector< amrex::BCRec > domain_bcs_type
vector (over BCVars) of BCRecs
Definition REMORA.H:1728
int cf_print_iface
print the imposed coarse-fine interface velocity at this baroclinic step (-1 = off)....
Definition REMORA.H:1900
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_h
multilevel data container for current step's z velocities (largely unused; W stored separately)
Definition REMORA.H:413
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_pm
horizontal scaling factor: 1 / dx (2D)
Definition REMORA.H:603
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_DU_avg2
correct time average of barotropic x velocity flux for coupling (2D)
Definition REMORA.H:547
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Dvbar_new
see vec_Dvbar_old (2D)
Definition REMORA.H:560
void set_2d_cf_bcs(int lev, amrex::Real time, int know, int knew)
set this level's barotropic contact points from its parent, mass-conservingly
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_msku
land/sea mask at x-faces (2D)
Definition REMORA.H:586
int cf_flux_pc
distribute the parent's interface mass flux piecewise-constantly over the fine faces under each paren...
Definition REMORA.H:1887
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Dubar_old
DU_avg2 per unit cell edge length, at the start and end of this level's step. What a subcycled finer ...
Definition REMORA.H:554
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_mskv
land/sea mask at y-faces (2D)
Definition REMORA.H:588
amrex::Vector< int > istep
which step?
Definition REMORA.H:1689
amrex::Vector< REMORAFillPatcher > FPr_Dubar
Vector over levels of FillPatchers for Dubar (2D)
Definition REMORA.H:1656
void store_2d_flux(int lev)
store this level's barotropic mass flux per unit cell edge length
amrex::Vector< REMORAFillPatcher > FPr_Dvbar
Vector over levels of FillPatchers for Dvbar (2D)
Definition REMORA.H:1658
amrex::Vector< amrex::Real > t_new
new time at each level, in seconds since start_time
Definition REMORA.H:1700
int cf_set_width
Width for fixing values at coarse-fine interface.
Definition REMORA.H:1639
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_zeta
free surface height (2D)
Definition REMORA.H:578
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_vbar
barotropic y velocity (2D)
Definition REMORA.H:576
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_ubar
barotropic x velocity (2D)
Definition REMORA.H:574
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_DV_avg2
correct time average of barotropic y velocity flux for coupling (2D)
Definition REMORA.H:551
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Dvbar_old
DV_avg2 per unit cell edge length, see vec_Dubar_old (2D)
Definition REMORA.H:558
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_pn
horizontal scaling factor: 1 / dy (2D)
Definition REMORA.H:605
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Dubar_new
see vec_Dubar_old (2D)
Definition REMORA.H:556
amrex::Vector< amrex::Real > t_old
old time at each level, in seconds since start_time
Definition REMORA.H:1702
void set_2d_cf_flux(int lev, amrex::Real time, amrex::MultiFab &mf_DUon, amrex::MultiFab &mf_DVom)
impose the parent's barotropic mass flux on the coarse-fine interface faces