REMORA
Regional Modeling of Oceans Refined Adaptively
Loading...
Searching...
No Matches
REMORA_Coupling.cpp
Go to the documentation of this file.
1#include <REMORA.H>
2
3#include <AMReX_BCRec.H>
4#include <AMReX_Box.H>
5#include <AMReX_FillPatchUtil.H>
6#include <AMReX_Geometry.H>
7#include <AMReX_Interpolater.H>
8#include <AMReX_MFIter.H>
9#include <AMReX_MultiFabUtil.H>
10#include <AMReX_iMultiFab.H>
11#include <AMReX_Print.H>
12#include <AMReX_Reduce.H>
13
14#include <limits>
15
16using namespace amrex;
17
18/*
19 Coupling reference context (implementation-side):
20
21 1) Legacy state-passing contract:
22 Warner et al. (2010), COAWST, Fig. 5 / Block B.
23 REMORA receives atmospheric states and computes surface fluxes internally
24 (bulk-physics/COARE-style path).
25
26 2) Future direct flux-passing roadmap:
27 COAWST's ATM2OCN_FLUXES pathway (documented in COAWST manuals/workshops
28 and exercised in Zambon et al., 2014) motivates direct flux exchange
29 (tau_x, tau_y, heat/moisture) instead of state-only exchange.
30 COAWST code anchors:
31 - Master/mct_roms_wrf.h
32 - ROMS/Nonlinear/atm2ocn_flux.F
33 - ROMS/Nonlinear/bulk_flux.F
34*/
35
36namespace {
37constexpr int SSTIndex = 0;
38
39// -------------------------------------------------------------------------
40// NEW: Conservative Sparse Matrix Remap Engine (Reverse: OCN -> ATM)
41// -------------------------------------------------------------------------
42
43// Source index region each destination box's stencils actually reference.
44//
45// The entries in index_mf are *source* indices, and for non-conformal grids they
46// routinely fall far outside the destination box's own index range, so staging the
47// source on `dst.boxArray()` plus a fixed ghost halo leaves those reads outside
48// the valid region - zero at best, out of bounds at worst. Mirror of the same
49// helper on the ERF side of the coupling.
50//
51// The result becomes a BoxArray, which must be globally consistent, hence the
52// reduction.
53amrex::BoxArray
54StagedSourceBoxArray (const amrex::MultiFab& src,
55 const amrex::MultiFab& dst,
56 const amrex::iMultiFab& index_mf,
57 int max_stencil_size)
58{
59 using namespace amrex;
60
61 const Box src_domain = src.boxArray().minimalBox();
62 const int nboxes = static_cast<int>(dst.boxArray().size());
63 constexpr int int_big = std::numeric_limits<int>::max();
64
65 Vector<int> lo(2 * nboxes, int_big);
66 Vector<int> hi(2 * nboxes, -int_big);
67
68 for (MFIter mfi(dst); mfi.isValid(); ++mfi) {
69 const int b = mfi.index();
70 const Box bx = mfi.validbox();
71 auto const& idx = index_mf.const_array(mfi);
72
73 ReduceOps<ReduceOpMin, ReduceOpMin, ReduceOpMax, ReduceOpMax> reduce_op;
74 ReduceData<int, int, int, int> reduce_data(reduce_op);
75 using ReduceTuple = typename decltype(reduce_data)::Type;
76
77 reduce_op.eval(bx, reduce_data,
78 [=] AMREX_GPU_DEVICE (int i, int j, int k) -> ReduceTuple
79 {
80 int i_min = int_big, j_min = int_big, i_max = -int_big, j_max = -int_big;
81 for (int m = 0; m < max_stencil_size; ++m) {
82 const int si = idx(i, j, k, m * 3);
83 const int sj = idx(i, j, k, m * 3 + 1);
84 if (si < 0 || sj < 0) { continue; } // -1 marks an unused slot
85 i_min = amrex::min(i_min, si); i_max = amrex::max(i_max, si);
86 j_min = amrex::min(j_min, sj); j_max = amrex::max(j_max, sj);
87 }
88 return {i_min, j_min, i_max, j_max};
89 });
90
91 auto const& hv = reduce_data.value(reduce_op);
92 lo[2*b ] = amrex::get<0>(hv); lo[2*b+1] = amrex::get<1>(hv);
93 hi[2*b ] = amrex::get<2>(hv); hi[2*b+1] = amrex::get<3>(hv);
94 }
95
96 ParallelDescriptor::ReduceIntMin(lo.dataPtr(), static_cast<int>(lo.size()));
97 ParallelDescriptor::ReduceIntMax(hi.dataPtr(), static_cast<int>(hi.size()));
98
99 // The staged boxes must carry the source's index type from the start:
100 // intersecting a cell-centered box with a staggered one trips
101 // Box::operator&='s sameType assertion.
102 const IndexType src_ixtype = src.boxArray().ixType();
103
104 BoxList bl(src_ixtype);
105 for (int b = 0; b < nboxes; ++b) {
106 Box need;
107 if (lo[2*b] > hi[2*b] || lo[2*b+1] > hi[2*b+1]) {
108 // No stencil anywhere in this destination box. A BoxArray cannot hold
109 // an empty box, so stage a single cell; nothing reads it.
110 need = Box(src_domain.smallEnd(), src_domain.smallEnd(), src_ixtype);
111 } else {
112 need = Box(IntVect(lo[2*b], lo[2*b+1], src_domain.smallEnd(2)),
113 IntVect(hi[2*b], hi[2*b+1], src_domain.bigEnd(2)),
114 src_ixtype);
115 need &= src_domain;
116 }
117 bl.push_back(need);
118 }
119 return BoxArray(std::move(bl));
120}
121
122void
123ApplyConservativeRemap (const amrex::MultiFab& src,
124 amrex::MultiFab& dst,
125 const amrex::MultiFab& weight_mf,
126 const amrex::iMultiFab& index_mf,
127 int max_stencil_size,
128 const amrex::MultiFab* dst_mask = nullptr,
129 const amrex::iMultiFab* dst_land_mask = nullptr)
130{
131 using namespace amrex;
132
133 // 1. Data Routing: Route REMORA SST data onto the ERF atmospheric layout,
134 // staging exactly the source region the stencils reference.
135 MultiFab src_on_dst(StagedSourceBoxArray(src, dst, index_mf, max_stencil_size),
136 dst.DistributionMap(), src.nComp(), 0);
137 src_on_dst.setVal(0.0);
138 src_on_dst.ParallelCopy(src);
139
140 dst.setVal(0.0);
141
142 // 2. Stencil Application: Execute the local sparse dot product
143 for (MFIter mfi(dst, TilingIfNotGPU()); mfi.isValid(); ++mfi) {
144 Box bx = mfi.tilebox();
145
146 auto const& w_arr = weight_mf.const_array(mfi);
147 auto const& idx_arr = index_mf.const_array(mfi);
148 auto const& src_arr = src_on_dst.const_array(mfi);
149 auto dst_arr = dst.array(mfi);
150 const bool has_mask = (dst_mask != nullptr);
151 auto const& mask_arr = has_mask ? dst_mask->const_array(mfi) : Array4<const Real>{};
152 // ERF land mask: 1 = land, 0 = water. Destination cells over land are
153 // zeroed out (wet/dry masking only, no vector rotation).
154 const bool has_land_mask = (dst_land_mask != nullptr);
155 auto const& land_arr = has_land_mask ? dst_land_mask->const_array(mfi) : Array4<const int>{};
156
157 ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) {
158 Real sum = 0.0;
159 for (int m = 0; m < max_stencil_size; ++m) {
160 Real w = w_arr(i, j, k, m);
161 if (w > 0.0) {
162 int src_i = idx_arr(i, j, k, m * 3);
163 int src_j = idx_arr(i, j, k, m * 3 + 1);
164 int src_k = idx_arr(i, j, k, m * 3 + 2);
165
166 sum += w * src_arr(src_i, src_j, src_k);
167 }
168 }
169 if (has_mask) { sum *= mask_arr(i, j, k); }
170 if (has_land_mask && land_arr(i, j, k) == 1) { sum = Real(0.0); }
171 dst_arr(i, j, k) = sum;
172 });
173 }
174}
175}
176
177amrex::Real
178REMORA::EvolveOneStep (amrex::Real /*time*/, amrex::Real /*dt_request*/)
179{
180 Real cur_time = t_new[0];
181 const int step = istep[0];
182
183 if (cur_time >= stop_time) {
184 return zero;
185 }
186
187 ComputeDt();
188
189 int lev = 0;
190 int iteration = 1;
191 if (max_level == 0) {
192 timeStep(lev, cur_time, iteration);
193 } else {
194 timeStepML(cur_time, iteration);
195 }
196
197 cur_time += dt[0];
198
199 WriteAtIntermediateTime(step, cur_time);
200
201 post_timestep(step, cur_time, dt[0]);
202
203 return dt[0];
204}
205
206void
208 bool use_two_way_coupling,
209 DriverAtmosForcingMode active_mode)
210{
211 running_with_coupling_driver = use_coupling_driver;
212 driver_uses_two_way_coupling = use_two_way_coupling;
213 driver_atmos_forcing_mode = active_mode;
214}
215
216void
221
222void
224 amrex::DistributionMapping& dm) const
225{
226 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(
227 !vec_srflx.empty() && vec_srflx[0] != nullptr,
228 "REMORA::GetAtmosToOceanRhoLayout requires post-InitData rho-point forcing storage.");
229 ba = vec_srflx[0]->boxArray();
230 dm = vec_srflx[0]->DistributionMap();
231}
232
233void
235 amrex::DistributionMapping& dm) const
236{
237 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(
238 !vec_sustr.empty() && vec_sustr[0] != nullptr,
239 "REMORA::GetAtmosToOceanUFaceLayout requires post-InitData u-face forcing storage.");
240 ba = vec_sustr[0]->boxArray();
241 dm = vec_sustr[0]->DistributionMap();
242}
243
244void
246 amrex::DistributionMapping& dm) const
247{
248 AMREX_ALWAYS_ASSERT_WITH_MESSAGE(
249 !vec_svstr.empty() && vec_svstr[0] != nullptr,
250 "REMORA::GetAtmosToOceanVFaceLayout requires post-InitData v-face forcing storage.");
251 ba = vec_svstr[0]->boxArray();
252 dm = vec_svstr[0]->DistributionMap();
253}
254
255void
256REMORA::GetAtmosToOceanPsiCoordinates (const amrex::MultiFab*& x_psi,
257 const amrex::MultiFab*& y_psi) const
258{
259 if (vec_xp.empty() || vec_yp.empty() ||
260 vec_xp[0] == nullptr || vec_yp[0] == nullptr) {
261 x_psi = nullptr;
262 y_psi = nullptr;
263 return;
264 }
265 x_psi = vec_xp[0].get();
266 y_psi = vec_yp[0].get();
267}
268
269void
270REMORA::GetAtmosToOceanPsiLonLat (const amrex::MultiFab*& lon_psi,
271 const amrex::MultiFab*& lat_psi) const
272{
273 if (vec_lonp.empty() || vec_latp.empty() ||
274 vec_lonp[0] == nullptr || vec_latp[0] == nullptr) {
275 lon_psi = nullptr;
276 lat_psi = nullptr;
277 return;
278 }
279 lon_psi = vec_lonp[0].get();
280 lat_psi = vec_latp[0].get();
281}
282
283void
284REMORA::GetLandSeaMasks (const amrex::MultiFab*& mskr,
285 const amrex::MultiFab*& msku,
286 const amrex::MultiFab*& mskv) const
287{
288 mskr = (!vec_mskr.empty() && vec_mskr[0]) ? vec_mskr[0].get() : nullptr;
289 msku = (!vec_msku.empty() && vec_msku[0]) ? vec_msku[0].get() : nullptr;
290 mskv = (!vec_mskv.empty() && vec_mskv[0]) ? vec_mskv[0].get() : nullptr;
291}
292
293/*
294 * \brief Extracts SST from the 3D conservative state for the atmospheric driver.
295 *
296 * Reads Temp_comp at the top water-column cell (k_sfc), converts from
297 * Celsius to Kelvin, and conservatively remaps the result into state[SSTIndex].
298 */
299void
300REMORA::PackSurfaceState (Vector<MultiFab*>& state,
301 Real /*time*/,
302 const amrex::MultiFab* weight_o2a_mf,
303 const amrex::iMultiFab* index_o2a_mf,
304 int max_stencil_size,
305 const amrex::iMultiFab* dst_land_mask)
306{
307 if (state.empty() || state[SSTIndex] == nullptr) { return; }
308 const int lev = 0;
309
310 // REMORA stores temperature in Celsius. Surface is at k=N (top of water column).
311 const int k_sfc = cons_new[lev]->boxArray().minimalBox().bigEnd(2);
312
313 // Build a temp MultiFab on REMORA's ba2d (k=0) derived from cons_new's BoxArray.
314 BoxList bl2d = cons_new[lev]->boxArray().boxList();
315 for (auto& b : bl2d) { b.setRange(2, 0); }
316 BoxArray ba2d(std::move(bl2d));
317 MultiFab tmp(ba2d, cons_new[lev]->DistributionMap(), 1, 0);
318
319 for (MFIter mfi(*cons_new[lev]); mfi.isValid(); ++mfi) {
320 auto const& c = cons_new[lev]->const_array(mfi);
321 auto t = tmp.array(mfi);
322 Box bx = makeSlab(mfi.validbox(), 2, k_sfc);
323 ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int) {
324 // Write to k=0 in tmp (ba2d range); convert Celsius → Kelvin.
325 t(i, j, 0) = c(i, j, k_sfc, Temp_comp) + Real(273.15);
326 });
327 }
328
329 MultiFab& dst = *state[SSTIndex];
330
331 if (weight_o2a_mf != nullptr && index_o2a_mf != nullptr) {
332 // Execute sparse conservative remap from REMORA SST to ERF layout
333 ApplyConservativeRemap(tmp, dst, *weight_o2a_mf, *index_o2a_mf, max_stencil_size,
334 nullptr, dst_land_mask);
335 } else {
336 // Fallback for un-stenciled or synthetic runs
337 dst.setVal(zero);
338 dst.ParallelCopy(tmp, 0, 0, 1);
339 }
340}
341
342/*
343 * \brief Receives atmospheric states from the driver and applies unit conversions.
344 */
345void
346REMORA::ApplyAtmosphericStates (const Vector<MultiFab*>& states, Real /*time*/)
347{
351 if (finest_level < 0) { return; }
352
353 // Wind (m/s) — no unit conversion
354 if (vec_uwind[0] != nullptr) {
355 if (states.size() > AtmosState::Uwind && states[AtmosState::Uwind] != nullptr) {
356 vec_uwind[0]->ParallelCopy(*states[AtmosState::Uwind], 0, 0, 1);
357 vec_uwind[0]->FillBoundary(geom[0].periodicity());
359 }
360 }
361 if (vec_vwind[0] != nullptr) {
362 if (states.size() > AtmosState::Vwind && states[AtmosState::Vwind] != nullptr) {
363 vec_vwind[0]->ParallelCopy(*states[AtmosState::Vwind], 0, 0, 1);
364 vec_vwind[0]->FillBoundary(geom[0].periodicity());
366 }
367 }
368
369 // Atmospheric pressure: Pa → mb (REMORA bulk flux expects mb)
370 if (vec_Pair[0] != nullptr) {
371 if (states.size() > AtmosState::Pair && states[AtmosState::Pair] != nullptr) {
372 vec_Pair[0]->ParallelCopy(*states[AtmosState::Pair], 0, 0, 1);
373 vec_Pair[0]->mult(Real(0.01), 0, 1);
374 vec_Pair[0]->FillBoundary(geom[0].periodicity());
376 }
377 }
378
379 // Specific humidity (kg/kg) — no conversion
380 if (vec_qair[0] != nullptr) {
381 if (states.size() > AtmosState::Qair && states[AtmosState::Qair] != nullptr) {
382 vec_qair[0]->ParallelCopy(*states[AtmosState::Qair], 0, 0, 1);
383 vec_qair[0]->FillBoundary(geom[0].periodicity());
385 }
386 }
387
388 // Air temperature: K → °C (REMORA stores/uses Celsius internally)
389 if (vec_Tair[0] != nullptr) {
390 if (states.size() > AtmosState::Tair && states[AtmosState::Tair] != nullptr) {
391 vec_Tair[0]->ParallelCopy(*states[AtmosState::Tair], 0, 0, 1);
392 vec_Tair[0]->plus(Real(-273.15), 0, 1);
393 vec_Tair[0]->FillBoundary(geom[0].periodicity());
395 }
396 }
397
398 // Cloud fraction [0-1], rain, SW/LW radiation — no unit conversion
399 if (vec_cloud[0] != nullptr) {
400 if (states.size() > AtmosState::Cloud && states[AtmosState::Cloud] != nullptr) {
401 vec_cloud[0]->ParallelCopy(*states[AtmosState::Cloud], 0, 0, 1);
402 vec_cloud[0]->FillBoundary(geom[0].periodicity());
404 }
405 }
406 if (vec_rain[0] != nullptr) {
407 if (states.size() > AtmosState::Rain && states[AtmosState::Rain] != nullptr) {
408 vec_rain[0]->ParallelCopy(*states[AtmosState::Rain], 0, 0, 1);
409 vec_rain[0]->FillBoundary(geom[0].periodicity());
411 }
412 }
413 if (vec_srflx[0] != nullptr) {
414 if (states.size() > AtmosState::SWrad && states[AtmosState::SWrad] != nullptr) {
415 vec_srflx[0]->ParallelCopy(*states[AtmosState::SWrad], 0, 0, 1);
416 vec_srflx[0]->FillBoundary(geom[0].periodicity());
418 }
419 }
420 if (vec_longwave_down[0] != nullptr) {
421 if (states.size() > AtmosState::LWrad && states[AtmosState::LWrad] != nullptr) {
422 vec_longwave_down[0]->ParallelCopy(*states[AtmosState::LWrad], 0, 0, 1);
423 vec_longwave_down[0]->FillBoundary(geom[0].periodicity());
425 }
426 }
427
428}
429
430void
431REMORA::ApplyAtmosphericFluxes (const Vector<MultiFab*>& states, Real /*time*/)
432{
436 if (finest_level < 0) { return; }
437
438 if (states.size() <= AtmosFluxes::Evap ||
439 states[AtmosFluxes::TauX] == nullptr ||
440 states[AtmosFluxes::TauY] == nullptr ||
441 states[AtmosFluxes::SHflux] == nullptr ||
442 states[AtmosFluxes::LHflux] == nullptr ||
443 states[AtmosFluxes::SWrad] == nullptr ||
444 states[AtmosFluxes::LWrad] == nullptr ||
445 states[AtmosFluxes::Rain] == nullptr ||
446 states[AtmosFluxes::Evap] == nullptr ||
447 vec_sustr[0] == nullptr || vec_svstr[0] == nullptr ||
448 vec_stflux[0] == nullptr || vec_mskr[0] == nullptr ||
449 vec_msku[0] == nullptr || vec_mskv[0] == nullptr ||
450 vec_srflx[0] == nullptr || vec_lrflx[0] == nullptr ||
451 vec_lhflx[0] == nullptr || vec_shflx[0] == nullptr ||
452 vec_rain[0] == nullptr || vec_evap[0] == nullptr) {
453 return;
454 }
455
456 const Real Hscale2 = one / (solverChoice.rho0 * Cp);
457 const Real rho0 = solverChoice.rho0;
458
459 MultiFab tau_x_tmp(vec_sustr[0]->boxArray(), vec_sustr[0]->DistributionMap(), 1,
460 vec_sustr[0]->nGrowVect());
461 MultiFab tau_y_tmp(vec_svstr[0]->boxArray(), vec_svstr[0]->DistributionMap(), 1,
462 vec_svstr[0]->nGrowVect());
463 MultiFab shflux_tmp(vec_shflx[0]->boxArray(), vec_shflx[0]->DistributionMap(), 1,
464 vec_shflx[0]->nGrowVect());
465 MultiFab lhflux_tmp(vec_lhflx[0]->boxArray(), vec_lhflx[0]->DistributionMap(), 1,
466 vec_lhflx[0]->nGrowVect());
467 MultiFab lwflux_tmp(vec_lrflx[0]->boxArray(), vec_lrflx[0]->DistributionMap(), 1,
468 vec_lrflx[0]->nGrowVect());
469
470 tau_x_tmp.setVal(zero);
471 tau_x_tmp.ParallelCopy(*states[AtmosFluxes::TauX], 0, 0, 1);
472 tau_x_tmp.FillBoundary(geom[0].periodicity());
473
474 tau_y_tmp.setVal(zero);
475 tau_y_tmp.ParallelCopy(*states[AtmosFluxes::TauY], 0, 0, 1);
476 tau_y_tmp.FillBoundary(geom[0].periodicity());
477
478 shflux_tmp.setVal(zero);
479 shflux_tmp.ParallelCopy(*states[AtmosFluxes::SHflux], 0, 0, 1);
480 shflux_tmp.FillBoundary(geom[0].periodicity());
481
482 lhflux_tmp.setVal(zero);
483 lhflux_tmp.ParallelCopy(*states[AtmosFluxes::LHflux], 0, 0, 1);
484 lhflux_tmp.FillBoundary(geom[0].periodicity());
485
486 lwflux_tmp.setVal(zero);
487 lwflux_tmp.ParallelCopy(*states[AtmosFluxes::LWrad], 0, 0, 1);
488 lwflux_tmp.FillBoundary(geom[0].periodicity());
489
490 vec_srflx[0]->setVal(zero);
491 vec_srflx[0]->ParallelCopy(*states[AtmosFluxes::SWrad], 0, 0, 1);
492 vec_srflx[0]->FillBoundary(geom[0].periodicity());
493
494 vec_rain[0]->setVal(zero);
495 vec_rain[0]->ParallelCopy(*states[AtmosFluxes::Rain], 0, 0, 1);
496 vec_rain[0]->FillBoundary(geom[0].periodicity());
497
498 vec_evap[0]->setVal(zero);
499 vec_evap[0]->ParallelCopy(*states[AtmosFluxes::Evap], 0, 0, 1);
500 vec_evap[0]->FillBoundary(geom[0].periodicity());
501
502 vec_lrflx[0]->setVal(zero);
503 vec_lhflx[0]->setVal(zero);
504 vec_shflx[0]->setVal(zero);
505 vec_stflux[0]->setVal(zero);
506
507 for (MFIter mfi(*vec_sustr[0], TilingIfNotGPU()); mfi.isValid(); ++mfi) {
508 Array4<Real> const& sustr = vec_sustr[0]->array(mfi);
509 Array4<const Real> const& msku = vec_msku[0]->const_array(mfi);
510 Array4<const Real> const& tau_x = tau_x_tmp.const_array(mfi);
511 Box ubx = mfi.grownnodaltilebox(0, IntVect(NGROW,NGROW,0));
512 Box ubxD = ubx;
513 ubxD.makeSlab(2,0);
514 ParallelFor(ubxD, [=] AMREX_GPU_DEVICE (int i, int j, int ) {
515 sustr(i,j,0) = -tau_x(i,j,0) / rho0 * msku(i,j,0);
516 });
517 }
518
519 for (MFIter mfi(*vec_svstr[0], TilingIfNotGPU()); mfi.isValid(); ++mfi) {
520 Array4<Real> const& svstr = vec_svstr[0]->array(mfi);
521 Array4<const Real> const& mskv = vec_mskv[0]->const_array(mfi);
522 Array4<const Real> const& tau_y = tau_y_tmp.const_array(mfi);
523 Box vbx = mfi.grownnodaltilebox(1, IntVect(NGROW,NGROW,0));
524 Box vbxD = vbx;
525 vbxD.makeSlab(2,0);
526
527 ParallelFor(vbxD, [=] AMREX_GPU_DEVICE (int i, int j, int ) {
528 svstr(i,j,0) = -tau_y(i,j,0) / rho0 * mskv(i,j,0);
529 });
530 }
531
532 for (MFIter mfi(*vec_stflux[0], TilingIfNotGPU()); mfi.isValid(); ++mfi) {
533 Array4<Real> const& stflux = vec_stflux[0]->array(mfi);
534 Array4<Real> const& lrflx = vec_lrflx[0]->array(mfi);
535 Array4<Real> const& lhflx = vec_lhflx[0]->array(mfi);
536 Array4<Real> const& shflx = vec_shflx[0]->array(mfi);
537 Array4<const Real> const& mskr = vec_mskr[0]->const_array(mfi);
538 Array4<const Real> const& srflx = vec_srflx[0]->const_array(mfi);
539 Array4<const Real> const& rain = vec_rain[0]->const_array(mfi);
540 Array4<const Real> const& evap = vec_evap[0]->const_array(mfi);
541 Array4<const Real> const& shflux = shflux_tmp.const_array(mfi);
542 Array4<const Real> const& lhflux = lhflux_tmp.const_array(mfi);
543 Array4<const Real> const& lwflux = lwflux_tmp.const_array(mfi);
544
545 Box gbx2 = mfi.growntilebox(IntVect(NGROW,NGROW,0));
546 Box gbx2D = gbx2;
547 gbx2D.makeSlab(2,0);
548
549 ParallelFor(gbx2D, [=] AMREX_GPU_DEVICE (int i, int j, int ) {
550 lrflx(i,j,0) = lwflux(i,j,0) * Hscale2;
551 lhflx(i,j,0) = -lhflux(i,j,0) * Hscale2;
552 shflx(i,j,0) = -shflux(i,j,0) * Hscale2;
553 stflux(i,j,0,Temp_comp) =
554 (srflx(i,j,0) * Hscale2 + lrflx(i,j,0) + lhflx(i,j,0) + shflx(i,j,0))
555 * mskr(i,j,0);
556 stflux(i,j,0,Salt_comp) =
557 mskr(i,j,0) * (evap(i,j,0) - rain(i,j,0)) / rhow;
558 });
559 }
560
561 vec_sustr[0]->FillBoundary(geom[0].periodicity());
562 vec_svstr[0]->FillBoundary(geom[0].periodicity());
563 vec_srflx[0]->FillBoundary(geom[0].periodicity());
564 vec_lrflx[0]->FillBoundary(geom[0].periodicity());
565 vec_lhflx[0]->FillBoundary(geom[0].periodicity());
566 vec_shflx[0]->FillBoundary(geom[0].periodicity());
567 vec_stflux[0]->FillBoundary(geom[0].periodicity());
568 vec_rain[0]->FillBoundary(geom[0].periodicity());
569 vec_evap[0]->FillBoundary(geom[0].periodicity());
570 vec_stflux[0]->FillBoundary(geom[0].periodicity());
571
572 const Real sustr_min = vec_sustr[0]->min(0);
573 const Real sustr_max = vec_sustr[0]->max(0);
574 const Real svstr_min = vec_svstr[0]->min(0);
575 const Real svstr_max = vec_svstr[0]->max(0);
576 const Real stflux_temp_min = vec_stflux[0]->min(Temp_comp);
577 const Real stflux_temp_max = vec_stflux[0]->max(Temp_comp);
578 const Real stflux_salt_min = vec_stflux[0]->min(Salt_comp);
579 const Real stflux_salt_max = vec_stflux[0]->max(Salt_comp);
580 const Real srflx_min = vec_srflx[0]->min(0);
581 const Real srflx_max = vec_srflx[0]->max(0);
582 const Real lrflx_min = vec_lrflx[0]->min(0);
583 const Real lrflx_max = vec_lrflx[0]->max(0);
584 const Real lhflx_min = vec_lhflx[0]->min(0);
585 const Real lhflx_max = vec_lhflx[0]->max(0);
586 const Real shflx_min = vec_shflx[0]->min(0);
587 const Real shflx_max = vec_shflx[0]->max(0);
588
589 amrex::Print() << "REMORA ApplyAtmosphericFluxes validation:\n"
590 << " sustr: min=" << sustr_min << " max=" << sustr_max << "\n"
591 << " svstr: min=" << svstr_min << " max=" << svstr_max << "\n"
592 << " stflux(Temp): min=" << stflux_temp_min << " max=" << stflux_temp_max << "\n"
593 << " stflux(Salt): min=" << stflux_salt_min << " max=" << stflux_salt_max << "\n"
594 << " srflx: min=" << srflx_min << " max=" << srflx_max << "\n"
595 << " lrflx: min=" << lrflx_min << " max=" << lrflx_max << "\n"
596 << " lhflx: min=" << lhflx_min << " max=" << lhflx_max << "\n"
597 << " shflx: min=" << shflx_min << " max=" << shflx_max << "\n";
598}
constexpr amrex::Real one
constexpr amrex::Real zero
constexpr amrex::Real rhow
constexpr amrex::Real Cp
#define NGROW
#define Temp_comp
#define Salt_comp
void ConfigureDriverAtmosToOceanCoupling(bool use_coupling_driver, bool use_two_way_coupling, DriverAtmosForcingMode active_mode)
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_evap
evaporation rate [kg/m^2/s]
Definition REMORA.H:487
bool running_with_coupling_driver
True once REMORA has received forcing through the coupling driver.
Definition REMORA.H:496
void GetLandSeaMasks(const amrex::MultiFab *&mskr, const amrex::MultiFab *&msku, const amrex::MultiFab *&mskv) const
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_lrflx
longwave radiation
Definition REMORA.H:467
amrex::Vector< amrex::MultiFab * > cons_new
multilevel data container for current step's scalar data: temperature, salinity, passive tracer
Definition REMORA.H:368
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_vwind
Wind in the v direction, defined at rho-points.
Definition REMORA.H:456
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_mskr
land/sea mask at cell centers (2D)
Definition REMORA.H:539
amrex::Real stop_time
Time to stop.
Definition REMORA.H:1535
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_rain
precipitation rate [kg/m^2/s]
Definition REMORA.H:485
void GetAtmosToOceanPsiLonLat(const amrex::MultiFab *&lon_psi, const amrex::MultiFab *&lat_psi) const
void ApplyAtmosphericFluxes(const amrex::Vector< amrex::MultiFab * > &states, amrex::Real time)
Receives atmospheric flux lanes from the driver and assembles REMORA flux inputs.
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_sustr
Surface stress in the u direction.
Definition REMORA.H:449
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_yp
y_grid on psi-points (2D)
Definition REMORA.H:579
void GetAtmosToOceanVFaceLayout(amrex::BoxArray &ba, amrex::DistributionMapping &dm) const
void GetAtmosToOceanRhoLayout(amrex::BoxArray &ba, amrex::DistributionMapping &dm) const
void WriteAtIntermediateTime(int step, amrex::Real cur_time)
Write checkpoint and plotfiles at intermediate point of simulation, if needed.
Definition REMORA.cpp:331
amrex::Real EvolveOneStep(amrex::Real time, amrex::Real dt_request)
DriverAtmosForcingMode
Definition REMORA.H:93
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_msku
land/sea mask at x-faces (2D)
Definition REMORA.H:541
std::array< bool, AtmosState::NumTypes > driver_atmos_state_from_driver
provenance flags for driver-supplied atmospheric forcing lanes
Definition REMORA.H:494
void GetAtmosToOceanPsiCoordinates(const amrex::MultiFab *&x_psi, const amrex::MultiFab *&y_psi) const
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_uwind
Wind in the u direction, defined at rho-points.
Definition REMORA.H:454
DriverAtmosForcingMode driver_atmos_forcing_mode
Active atmosphere-to-ocean forcing contract on the most recent driver apply.
Definition REMORA.H:500
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_shflx
sensible heat flux
Definition REMORA.H:473
void post_timestep(int nstep, amrex::Real time, amrex::Real dt_lev)
Called after every level 0 timestep.
Definition REMORA.cpp:356
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_lhflx
latent heat flux
Definition REMORA.H:471
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_lonp
longitude on psi-points (2D, degrees east); only filled when the grid NetCDF file carries lon_psi
Definition REMORA.H:583
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_mskv
land/sea mask at y-faces (2D)
Definition REMORA.H:543
void ComputeDt()
a wrapper for estTimeStep()
amrex::Vector< int > istep
which step?
Definition REMORA.H:1469
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_svstr
Surface stress in the v direction.
Definition REMORA.H:451
amrex::Vector< amrex::Real > t_new
new time at each level
Definition REMORA.H:1473
static SolverChoice solverChoice
Container for algorithmic choices.
Definition REMORA.H:1603
void ApplyAtmosphericStates(const amrex::Vector< amrex::MultiFab * > &states, amrex::Real time)
Receives atmospheric states from the driver and applies unit conversions.
bool driver_uses_two_way_coupling
Driver-level direction flag copied in before InitData.
Definition REMORA.H:498
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_xp
x_grid on psi-points (2D)
Definition REMORA.H:577
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_longwave_down
Downward longwave radiation.
Definition REMORA.H:469
void GetAtmosToOceanUFaceLayout(amrex::BoxArray &ba, amrex::DistributionMapping &dm) const
void SetDriverAtmosToOceanForcingMode(DriverAtmosForcingMode mode)
void timeStep(int lev, amrex::Real time, int iteration)
advance a level by dt, includes a recursive call for finer levels
void timeStepML(amrex::Real time, int iteration)
advance all levels by dt, loops over finer levels
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_stflux
Surface tracer flux; input arrays.
Definition REMORA.H:478
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_cloud
cloud cover fraction [0-1], defined at rho-points
Definition REMORA.H:489
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_srflx
Shortwave radiation flux [W/m²], defined at rho-points.
Definition REMORA.H:465
amrex::Vector< amrex::Real > dt
time step at each level
Definition REMORA.H:1477
void PackSurfaceState(amrex::Vector< amrex::MultiFab * > &state, amrex::Real time, const amrex::MultiFab *weight_o2a_mf, const amrex::iMultiFab *index_o2a_mf, int max_stencil_size, const amrex::iMultiFab *dst_land_mask=nullptr)
Extracts SST from the 3D conservative state for the atmospheric driver.
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Pair
Air pressure [mb], defined at rho-points.
Definition REMORA.H:462
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_qair
Specific humidity [kg/kg], defined at rho-points.
Definition REMORA.H:460
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Tair
Air temperature [°C], defined at rho-points.
Definition REMORA.H:458
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_latp
latitude on psi-points (2D, degrees north); only filled when the grid NetCDF file carries lat_psi
Definition REMORA.H:586
@ LWrad
longwave flux lane
@ LHflux
latent heat flux lane
@ Rain
precipitation rate lane
@ Evap
evaporation rate lane
@ SHflux
sensible heat flux lane
@ TauX
surface zonal stress lane
@ TauY
surface meridional stress lane
@ SWrad
shortwave radiation lane
@ Pair
atmospheric pressure [Pa from driver, mb in REMORA]
@ Vwind
10-m meridional wind [m/s]
@ Qair
specific humidity [kg/kg]
@ SWrad
downward shortwave radiation [W/m^2]
@ LWrad
downward longwave radiation [W/m^2]
@ Uwind
10-m zonal wind [m/s]
@ Rain
precipitation rate [kg/m^2/s]
@ Cloud
cloud fraction [0-1]
@ Tair
air temperature [K from driver, degC in REMORA]