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,
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
75 using ReduceTuple = typename decltype(reduce_data)::Type;
76
78 [=] AMREX_GPU_DEVICE (int i, int j, int k) -> ReduceTuple
79 {
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
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
122// fallback_val fills destination cells that no source cell overlaps.
123//
124// Zero is the wrong default for a physical field: the receiving model cannot tell
125// "no data here" from "the ocean says zero", and for an intensive quantity zero is
126// not merely inaccurate, it drives the receiver's flux formulas outside their valid
127// domain. This mirrors the rationale on the atmosphere->ocean twin at
128// ERF/Source/Coupling/ERF_to_REMORA.cpp:122.
129//
130// The ocean->atmosphere SST lane deliberately leaves this at zero and relies on the
131// per-cell coverage flag instead: where REMORA does not cover an ERF cell, ERF keeps
132// its own wrflowinp SST rather than consuming a value invented here. That is a
133// withholding contract, not a value, so no climatological constant belongs at that
134// call site. The parameter exists because the mask blend below needs it and because
135// other lanes have a meaningful value to pass.
136void
137ApplyConservativeRemap (const amrex::MultiFab& src,
138 amrex::MultiFab& dst,
139 const amrex::MultiFab& weight_mf,
140 const amrex::iMultiFab& index_mf,
142 const amrex::MultiFab* dst_mask = nullptr,
143 const amrex::iMultiFab* dst_land_mask = nullptr,
144 amrex::Real fallback_val = amrex::Real(0.0))
145{
146 using namespace amrex;
147
148 // 1. Data Routing: Route REMORA SST data onto the ERF atmospheric layout,
149 // staging exactly the source region the stencils reference.
151 dst.DistributionMap(), src.nComp(), 0);
152 src_on_dst.setVal(0.0);
153 src_on_dst.ParallelCopy(src);
154
155 dst.setVal(0.0);
156
157 // 2. Stencil Application: Execute the local sparse dot product
158 for (MFIter mfi(dst, TilingIfNotGPU()); mfi.isValid(); ++mfi) {
159 Box bx = mfi.tilebox();
160
161 auto const& w_arr = weight_mf.const_array(mfi);
162 auto const& idx_arr = index_mf.const_array(mfi);
163 auto const& src_arr = src_on_dst.const_array(mfi);
164 auto dst_arr = dst.array(mfi);
165 const bool has_mask = (dst_mask != nullptr);
166 auto const& mask_arr = has_mask ? dst_mask->const_array(mfi) : Array4<const Real>{};
167 // ERF land mask: 0 = water, anything non-zero is land. Destination cells
168 // over land are zeroed out (wet/dry masking only, no vector rotation).
169 const bool has_land_mask = (dst_land_mask != nullptr);
170 auto const& land_arr = has_land_mask ? dst_land_mask->const_array(mfi) : Array4<const int>{};
171
172 ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k) {
173 // No stencil entry at all means no source cell overlapped this
174 // destination cell. Hand back the fallback rather than an accumulated
175 // zero, and do not apply the masks to it: a masked-out cell still gets
176 // evaluated by the receiving model's flux formulas before the mask is
177 // applied, so it too must hold an admissible value. Mirrors
178 // ERF/Source/Coupling/ERF_to_REMORA.cpp:174.
179 if (idx_arr(i, j, k, 0) < 0) {
180 dst_arr(i, j, k) = fallback_val;
181 return;
182 }
183
184 Real sum = 0.0;
185 for (int m = 0; m < max_stencil_size; ++m) {
186 Real w = w_arr(i, j, k, m);
187 if (w > 0.0) {
188 int src_i = idx_arr(i, j, k, m * 3);
189 int src_j = idx_arr(i, j, k, m * 3 + 1);
190 int src_k = idx_arr(i, j, k, m * 3 + 2);
191
192 sum += w * src_arr(src_i, src_j, src_k);
193 }
194 }
195 // Blend toward the fallback rather than multiplying by the mask.
196 // Multiplying drives partially masked cells toward exactly zero, which
197 // is right for a flux lane (fallback_val = 0, so this reduces to
198 // sum *= mask and is bit-identical) but destructive for an intensive
199 // state lane such as SST, where it scales a temperature toward 0 K.
200 // Mirrors ERF/Source/Coupling/ERF_to_REMORA.cpp:202.
201 if (has_mask) {
202 const Real mask = mask_arr(i, j, k);
203 sum = mask * sum + (Real(1.0) - mask) * fallback_val;
204 }
205 // Test against zero, not against 1: ERF stamps lmask = 2 for cells
206 // under ImmersedForcing buildings (ERF/Source/ERF_MakeNewArrays.cpp:890),
207 // so an == 1 test reads every building as water and hands it a remapped
208 // SST. The driver's atmosphere->ocean side already uses the tolerant
209 // form (ERFRemoraMultiBlockContainer.cpp:1767), so == 1 also made the
210 // two directions disagree about the same cell within one timestep.
211 if (has_land_mask && land_arr(i, j, k) != 0) { sum = Real(0.0); }
212 dst_arr(i, j, k) = sum;
213 });
214 }
215}
216}
217
218amrex::Real
219REMORA::EvolveOneStep (amrex::Real /*time*/, amrex::Real /*dt_request*/)
220{
221 Real cur_time = t_new[0];
222 const int step = istep[0];
223
224 if (cur_time >= stop_time) {
225 return zero;
226 }
227
228 ComputeDt();
229
230 int lev = 0;
231 int iteration = 1;
232 if (max_level == 0) {
234 } else {
236 }
237
238 cur_time += dt[0];
239
241
243
244 return dt[0];
245}
246
247void
256
257void
259{
260 // "constant" is any non-computed type: it makes setup_step pass
261 // vec_longwave_down to bulk_fluxes instead of leaving lw_ptr null. The two
262 // flags below are what ReadParameters would have derived for that type.
266}
267
268void
270 std::array<bool, AtmosState::NumTypes>& deck_configured) const
271{
272 // AtmosState and BulkFlux agree on lanes 0-8 today, but they are separate
273 // enums of separate lengths (BulkFlux carries EminusP, which has no driver
274 // lane). Map explicitly, so inserting into either breaks the build here
275 // rather than silently transposing the answer.
276 static constexpr std::array<int, AtmosState::NumTypes> lane_to_bulk_flux {{
277 BulkFlux::Uwind, // AtmosState::Uwind
278 BulkFlux::Vwind, // AtmosState::Vwind
279 BulkFlux::Pair, // AtmosState::Pair
280 BulkFlux::Qair, // AtmosState::Qair
281 BulkFlux::Tair, // AtmosState::Tair
282 BulkFlux::Cloud, // AtmosState::Cloud
283 BulkFlux::Rain, // AtmosState::Rain
284 BulkFlux::SWrad, // AtmosState::SWrad
285 BulkFlux::LWrad // AtmosState::LWrad
286 }};
287
288 deck_configured.fill(false);
289 for (int lane = 0; lane < AtmosState::NumTypes; ++lane) {
290 const int idx = lane_to_bulk_flux[lane];
293 }
294}
295
296void
301
302void
304 amrex::DistributionMapping& dm) const
305{
307 !vec_srflx.empty() && vec_srflx[0] != nullptr,
308 "REMORA::GetAtmosToOceanRhoLayout requires post-InitData rho-point forcing storage.");
309 ba = vec_srflx[0]->boxArray();
310 dm = vec_srflx[0]->DistributionMap();
311}
312
313void
315 amrex::DistributionMapping& dm) const
316{
318 !vec_sustr.empty() && vec_sustr[0] != nullptr,
319 "REMORA::GetAtmosToOceanUFaceLayout requires post-InitData u-face forcing storage.");
320 ba = vec_sustr[0]->boxArray();
321 dm = vec_sustr[0]->DistributionMap();
322}
323
324void
326 amrex::DistributionMapping& dm) const
327{
329 !vec_svstr.empty() && vec_svstr[0] != nullptr,
330 "REMORA::GetAtmosToOceanVFaceLayout requires post-InitData v-face forcing storage.");
331 ba = vec_svstr[0]->boxArray();
332 dm = vec_svstr[0]->DistributionMap();
333}
334
335void
337 const amrex::MultiFab*& y_psi) const
338{
339 if (vec_xp.empty() || vec_yp.empty() ||
340 vec_xp[0] == nullptr || vec_yp[0] == nullptr) {
341 x_psi = nullptr;
342 y_psi = nullptr;
343 return;
344 }
345 x_psi = vec_xp[0].get();
346 y_psi = vec_yp[0].get();
347}
348
349void
351 const amrex::MultiFab*& lat_psi) const
352{
353 if (vec_lonp.empty() || vec_latp.empty() ||
354 vec_lonp[0] == nullptr || vec_latp[0] == nullptr) {
355 lon_psi = nullptr;
356 lat_psi = nullptr;
357 return;
358 }
359 lon_psi = vec_lonp[0].get();
360 lat_psi = vec_latp[0].get();
361}
362
363void
364REMORA::GetLandSeaMasks (const amrex::MultiFab*& mskr,
365 const amrex::MultiFab*& msku,
366 const amrex::MultiFab*& mskv) const
367{
368 mskr = (!vec_mskr.empty() && vec_mskr[0]) ? vec_mskr[0].get() : nullptr;
369 msku = (!vec_msku.empty() && vec_msku[0]) ? vec_msku[0].get() : nullptr;
370 mskv = (!vec_mskv.empty() && vec_mskv[0]) ? vec_mskv[0].get() : nullptr;
371}
372
373/*
374 * \brief Extracts SST from the 3D conservative state for the atmospheric driver.
375 *
376 * Reads Temp_comp at the top water-column cell (k_sfc), converts from
377 * Celsius to Kelvin, and conservatively remaps the result into state[SSTIndex].
378 */
379void
381 Real /*time*/,
382 const amrex::MultiFab* weight_o2a_mf,
383 const amrex::iMultiFab* index_o2a_mf,
385 const amrex::iMultiFab* dst_land_mask)
386{
387 if (state.empty() || state[SSTIndex] == nullptr) { return; }
388 const int lev = 0;
389
390 // REMORA stores temperature in Celsius. Surface is at k=N (top of water column).
391 const int k_sfc = cons_new[lev]->boxArray().minimalBox().bigEnd(2);
392
393 // Build a temp MultiFab on REMORA's ba2d (k=0) derived from cons_new's BoxArray.
394 BoxList bl2d = cons_new[lev]->boxArray().boxList();
395 for (auto& b : bl2d) { b.setRange(2, 0); }
396 BoxArray ba2d(std::move(bl2d));
397 MultiFab tmp(ba2d, cons_new[lev]->DistributionMap(), 1, 0);
398
399 for (MFIter mfi(*cons_new[lev]); mfi.isValid(); ++mfi) {
400 auto const& c = cons_new[lev]->const_array(mfi);
401 auto t = tmp.array(mfi);
402 Box bx = makeSlab(mfi.validbox(), 2, k_sfc);
403 ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int) {
404 // Write to k=0 in tmp (ba2d range); convert Celsius → Kelvin.
405 t(i, j, 0) = c(i, j, k_sfc, Temp_comp) + Real(273.15);
406 });
407 }
408
409 MultiFab& dst = *state[SSTIndex];
410
411 if (weight_o2a_mf != nullptr && index_o2a_mf != nullptr) {
412 // Execute sparse conservative remap from REMORA SST to ERF layout
414 nullptr, dst_land_mask);
415 } else {
416 // Fallback for un-stenciled or synthetic runs
417 dst.setVal(zero);
418 dst.ParallelCopy(tmp, 0, 0, 1);
419 }
420}
421
422/*
423 * \brief Receives atmospheric states from the driver and applies unit conversions.
424 */
425void
427{
431 if (finest_level < 0) { return; }
432
433 // Wind (m/s) — no unit conversion
434 if (vec_uwind[0] != nullptr) {
435 if (states.size() > AtmosState::Uwind && states[AtmosState::Uwind] != nullptr) {
436 vec_uwind[0]->ParallelCopy(*states[AtmosState::Uwind], 0, 0, 1);
437 vec_uwind[0]->FillBoundary(geom[0].periodicity());
439 }
440 }
441 if (vec_vwind[0] != nullptr) {
442 if (states.size() > AtmosState::Vwind && states[AtmosState::Vwind] != nullptr) {
443 vec_vwind[0]->ParallelCopy(*states[AtmosState::Vwind], 0, 0, 1);
444 vec_vwind[0]->FillBoundary(geom[0].periodicity());
446 }
447 }
448
449 // Atmospheric pressure: Pa → mb (REMORA bulk flux expects mb)
450 if (vec_Pair[0] != nullptr) {
451 if (states.size() > AtmosState::Pair && states[AtmosState::Pair] != nullptr) {
452 vec_Pair[0]->ParallelCopy(*states[AtmosState::Pair], 0, 0, 1);
453 vec_Pair[0]->mult(Real(0.01), 0, 1);
454 vec_Pair[0]->FillBoundary(geom[0].periodicity());
456 }
457 }
458
459 // Specific humidity (kg/kg) — no conversion
460 if (vec_qair[0] != nullptr) {
461 if (states.size() > AtmosState::Qair && states[AtmosState::Qair] != nullptr) {
462 vec_qair[0]->ParallelCopy(*states[AtmosState::Qair], 0, 0, 1);
463 vec_qair[0]->FillBoundary(geom[0].periodicity());
465 }
466 }
467
468 // Air temperature: K → °C (REMORA stores/uses Celsius internally)
469 if (vec_Tair[0] != nullptr) {
470 if (states.size() > AtmosState::Tair && states[AtmosState::Tair] != nullptr) {
471 vec_Tair[0]->ParallelCopy(*states[AtmosState::Tair], 0, 0, 1);
472 vec_Tair[0]->plus(Real(-273.15), 0, 1);
473 vec_Tair[0]->FillBoundary(geom[0].periodicity());
475 }
476 }
477
478 // Cloud fraction [0-1], rain, SW/LW radiation — no unit conversion
479 if (vec_cloud[0] != nullptr) {
480 if (states.size() > AtmosState::Cloud && states[AtmosState::Cloud] != nullptr) {
481 vec_cloud[0]->ParallelCopy(*states[AtmosState::Cloud], 0, 0, 1);
482 vec_cloud[0]->FillBoundary(geom[0].periodicity());
484 }
485 }
486 if (vec_rain[0] != nullptr) {
487 if (states.size() > AtmosState::Rain && states[AtmosState::Rain] != nullptr) {
488 vec_rain[0]->ParallelCopy(*states[AtmosState::Rain], 0, 0, 1);
489 vec_rain[0]->FillBoundary(geom[0].periodicity());
491 }
492 }
493 if (vec_srflx[0] != nullptr) {
494 if (states.size() > AtmosState::SWrad && states[AtmosState::SWrad] != nullptr) {
495 vec_srflx[0]->ParallelCopy(*states[AtmosState::SWrad], 0, 0, 1);
496 vec_srflx[0]->FillBoundary(geom[0].periodicity());
498 }
499 }
500 if (vec_longwave_down[0] != nullptr) {
501 if (states.size() > AtmosState::LWrad && states[AtmosState::LWrad] != nullptr) {
502 vec_longwave_down[0]->ParallelCopy(*states[AtmosState::LWrad], 0, 0, 1);
503 vec_longwave_down[0]->FillBoundary(geom[0].periodicity());
505 }
506 }
507
508}
509
510void
512{
516 if (finest_level < 0) { return; }
517
518 if (states.size() <= AtmosFluxes::Evap ||
519 states[AtmosFluxes::TauX] == nullptr ||
520 states[AtmosFluxes::TauY] == nullptr ||
521 states[AtmosFluxes::SHflux] == nullptr ||
522 states[AtmosFluxes::LHflux] == nullptr ||
523 states[AtmosFluxes::SWrad] == nullptr ||
524 states[AtmosFluxes::LWrad] == nullptr ||
525 states[AtmosFluxes::Rain] == nullptr ||
526 states[AtmosFluxes::Evap] == nullptr ||
527 vec_sustr[0] == nullptr || vec_svstr[0] == nullptr ||
528 vec_stflux[0] == nullptr || vec_mskr[0] == nullptr ||
529 vec_msku[0] == nullptr || vec_mskv[0] == nullptr ||
530 vec_srflx[0] == nullptr || vec_lrflx[0] == nullptr ||
531 vec_lhflx[0] == nullptr || vec_shflx[0] == nullptr ||
532 vec_rain[0] == nullptr || vec_evap[0] == nullptr) {
533 return;
534 }
535
536 const Real Hscale2 = one / (solverChoice.rho0 * Cp);
537 const Real rho0 = solverChoice.rho0;
538
539 MultiFab tau_x_tmp(vec_sustr[0]->boxArray(), vec_sustr[0]->DistributionMap(), 1,
540 vec_sustr[0]->nGrowVect());
541 MultiFab tau_y_tmp(vec_svstr[0]->boxArray(), vec_svstr[0]->DistributionMap(), 1,
542 vec_svstr[0]->nGrowVect());
543 MultiFab shflux_tmp(vec_shflx[0]->boxArray(), vec_shflx[0]->DistributionMap(), 1,
544 vec_shflx[0]->nGrowVect());
545 MultiFab lhflux_tmp(vec_lhflx[0]->boxArray(), vec_lhflx[0]->DistributionMap(), 1,
546 vec_lhflx[0]->nGrowVect());
547 MultiFab lwflux_tmp(vec_lrflx[0]->boxArray(), vec_lrflx[0]->DistributionMap(), 1,
548 vec_lrflx[0]->nGrowVect());
549
550 tau_x_tmp.setVal(zero);
551 tau_x_tmp.ParallelCopy(*states[AtmosFluxes::TauX], 0, 0, 1);
552 tau_x_tmp.FillBoundary(geom[0].periodicity());
553
554 tau_y_tmp.setVal(zero);
555 tau_y_tmp.ParallelCopy(*states[AtmosFluxes::TauY], 0, 0, 1);
556 tau_y_tmp.FillBoundary(geom[0].periodicity());
557
558 shflux_tmp.setVal(zero);
559 shflux_tmp.ParallelCopy(*states[AtmosFluxes::SHflux], 0, 0, 1);
560 shflux_tmp.FillBoundary(geom[0].periodicity());
561
562 lhflux_tmp.setVal(zero);
563 lhflux_tmp.ParallelCopy(*states[AtmosFluxes::LHflux], 0, 0, 1);
564 lhflux_tmp.FillBoundary(geom[0].periodicity());
565
566 lwflux_tmp.setVal(zero);
567 lwflux_tmp.ParallelCopy(*states[AtmosFluxes::LWrad], 0, 0, 1);
568 lwflux_tmp.FillBoundary(geom[0].periodicity());
569
570 vec_srflx[0]->setVal(zero);
571 vec_srflx[0]->ParallelCopy(*states[AtmosFluxes::SWrad], 0, 0, 1);
572 vec_srflx[0]->FillBoundary(geom[0].periodicity());
573
574 vec_rain[0]->setVal(zero);
575 vec_rain[0]->ParallelCopy(*states[AtmosFluxes::Rain], 0, 0, 1);
576 vec_rain[0]->FillBoundary(geom[0].periodicity());
577
578 vec_evap[0]->setVal(zero);
579 vec_evap[0]->ParallelCopy(*states[AtmosFluxes::Evap], 0, 0, 1);
580 vec_evap[0]->FillBoundary(geom[0].periodicity());
581
582 vec_lrflx[0]->setVal(zero);
583 vec_lhflx[0]->setVal(zero);
584 vec_shflx[0]->setVal(zero);
585 vec_stflux[0]->setVal(zero);
586
587 for (MFIter mfi(*vec_sustr[0], TilingIfNotGPU()); mfi.isValid(); ++mfi) {
588 Array4<Real> const& sustr = vec_sustr[0]->array(mfi);
589 Array4<const Real> const& msku = vec_msku[0]->const_array(mfi);
590 Array4<const Real> const& tau_x = tau_x_tmp.const_array(mfi);
591 Box ubx = mfi.grownnodaltilebox(0, IntVect(NGROW,NGROW,0));
592 Box ubxD = ubx;
593 ubxD.makeSlab(2,0);
594 ParallelFor(ubxD, [=] AMREX_GPU_DEVICE (int i, int j, int ) {
595 sustr(i,j,0) = -tau_x(i,j,0) / rho0 * msku(i,j,0);
596 });
597 }
598
599 for (MFIter mfi(*vec_svstr[0], TilingIfNotGPU()); mfi.isValid(); ++mfi) {
600 Array4<Real> const& svstr = vec_svstr[0]->array(mfi);
601 Array4<const Real> const& mskv = vec_mskv[0]->const_array(mfi);
602 Array4<const Real> const& tau_y = tau_y_tmp.const_array(mfi);
603 Box vbx = mfi.grownnodaltilebox(1, IntVect(NGROW,NGROW,0));
604 Box vbxD = vbx;
605 vbxD.makeSlab(2,0);
606
607 ParallelFor(vbxD, [=] AMREX_GPU_DEVICE (int i, int j, int ) {
608 svstr(i,j,0) = -tau_y(i,j,0) / rho0 * mskv(i,j,0);
609 });
610 }
611
612 for (MFIter mfi(*vec_stflux[0], TilingIfNotGPU()); mfi.isValid(); ++mfi) {
613 Array4<Real> const& stflux = vec_stflux[0]->array(mfi);
614 Array4<Real> const& lrflx = vec_lrflx[0]->array(mfi);
615 Array4<Real> const& lhflx = vec_lhflx[0]->array(mfi);
616 Array4<Real> const& shflx = vec_shflx[0]->array(mfi);
617 Array4<const Real> const& mskr = vec_mskr[0]->const_array(mfi);
618 Array4<const Real> const& srflx = vec_srflx[0]->const_array(mfi);
619 Array4<const Real> const& rain = vec_rain[0]->const_array(mfi);
620 Array4<const Real> const& evap = vec_evap[0]->const_array(mfi);
621 Array4<const Real> const& shflux = shflux_tmp.const_array(mfi);
622 Array4<const Real> const& lhflux = lhflux_tmp.const_array(mfi);
623 Array4<const Real> const& lwflux = lwflux_tmp.const_array(mfi);
624
625 Box gbx2 = mfi.growntilebox(IntVect(NGROW,NGROW,0));
626 Box gbx2D = gbx2;
627 gbx2D.makeSlab(2,0);
628
629 ParallelFor(gbx2D, [=] AMREX_GPU_DEVICE (int i, int j, int ) {
630 lrflx(i,j,0) = lwflux(i,j,0) * Hscale2;
631 lhflx(i,j,0) = -lhflux(i,j,0) * Hscale2;
632 shflx(i,j,0) = -shflux(i,j,0) * Hscale2;
633 stflux(i,j,0,Temp_comp) =
634 (srflx(i,j,0) * Hscale2 + lrflx(i,j,0) + lhflx(i,j,0) + shflx(i,j,0))
635 * mskr(i,j,0);
636 stflux(i,j,0,Salt_comp) =
637 mskr(i,j,0) * (evap(i,j,0) - rain(i,j,0)) / rhow;
638 });
639 }
640
641 vec_sustr[0]->FillBoundary(geom[0].periodicity());
642 vec_svstr[0]->FillBoundary(geom[0].periodicity());
643 vec_srflx[0]->FillBoundary(geom[0].periodicity());
644 vec_lrflx[0]->FillBoundary(geom[0].periodicity());
645 vec_lhflx[0]->FillBoundary(geom[0].periodicity());
646 vec_shflx[0]->FillBoundary(geom[0].periodicity());
647 vec_stflux[0]->FillBoundary(geom[0].periodicity());
648 vec_rain[0]->FillBoundary(geom[0].periodicity());
649 vec_evap[0]->FillBoundary(geom[0].periodicity());
650 vec_stflux[0]->FillBoundary(geom[0].periodicity());
651
652 const Real sustr_min = vec_sustr[0]->min(0);
653 const Real sustr_max = vec_sustr[0]->max(0);
654 const Real svstr_min = vec_svstr[0]->min(0);
655 const Real svstr_max = vec_svstr[0]->max(0);
656 const Real stflux_temp_min = vec_stflux[0]->min(Temp_comp);
657 const Real stflux_temp_max = vec_stflux[0]->max(Temp_comp);
658 const Real stflux_salt_min = vec_stflux[0]->min(Salt_comp);
659 const Real stflux_salt_max = vec_stflux[0]->max(Salt_comp);
660 const Real srflx_min = vec_srflx[0]->min(0);
661 const Real srflx_max = vec_srflx[0]->max(0);
662 const Real lrflx_min = vec_lrflx[0]->min(0);
663 const Real lrflx_max = vec_lrflx[0]->max(0);
664 const Real lhflx_min = vec_lhflx[0]->min(0);
665 const Real lhflx_max = vec_lhflx[0]->max(0);
666 const Real shflx_min = vec_shflx[0]->min(0);
667 const Real shflx_max = vec_shflx[0]->max(0);
668
669 amrex::Print() << "REMORA ApplyAtmosphericFluxes validation:\n"
670 << " sustr: min=" << sustr_min << " max=" << sustr_max << "\n"
671 << " svstr: min=" << svstr_min << " max=" << svstr_max << "\n"
672 << " stflux(Temp): min=" << stflux_temp_min << " max=" << stflux_temp_max << "\n"
673 << " stflux(Salt): min=" << stflux_salt_min << " max=" << stflux_salt_max << "\n"
674 << " srflx: min=" << srflx_min << " max=" << srflx_max << "\n"
675 << " lrflx: min=" << lrflx_min << " max=" << lrflx_max << "\n"
676 << " lhflx: min=" << lhflx_min << " max=" << lhflx_max << "\n"
677 << " shflx: min=" << shflx_min << " max=" << shflx_max << "\n";
678}
constexpr amrex::Real one
constexpr amrex::Real zero
constexpr amrex::Real rhow
constexpr amrex::Real Cp
#define NGROW
#define Temp_comp
#define Salt_comp
mf_h setVal(geomdata.ProbHi(2))
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:505
bool running_with_coupling_driver
True once REMORA has received forcing through the coupling driver.
Definition REMORA.H:514
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:485
amrex::Vector< amrex::MultiFab * > cons_new
multilevel data container for current step's scalar data: temperature, salinity, passive tracer
Definition REMORA.H:386
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_vwind
Wind in the v direction, defined at rho-points.
Definition REMORA.H:474
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_mskr
land/sea mask at cell centers (2D)
Definition REMORA.H:557
amrex::Real stop_time
Whether max_step was set in the inputs; the default above is not a distinguishable sentinel.
Definition REMORA.H:1624
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_rain
precipitation rate [kg/m^2/s]
Definition REMORA.H:503
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:467
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_yp
y_grid on psi-points (2D)
Definition REMORA.H:597
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:348
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:559
std::array< bool, AtmosState::NumTypes > driver_atmos_state_from_driver
provenance flags for driver-supplied atmospheric forcing lanes
Definition REMORA.H:512
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:472
DriverAtmosForcingMode driver_atmos_forcing_mode
Active atmosphere-to-ocean forcing contract on the most recent driver apply.
Definition REMORA.H:518
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_shflx
sensible heat flux
Definition REMORA.H:491
void post_timestep(int nstep, amrex::Real time, amrex::Real dt_lev)
Called after every level 0 timestep.
Definition REMORA.cpp:373
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_lhflx
latent heat flux
Definition REMORA.H:489
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:601
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_mskv
land/sea mask at y-faces (2D)
Definition REMORA.H:561
void ComputeDt()
a wrapper for estTimeStep()
amrex::Vector< int > istep
which step?
Definition REMORA.H:1552
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_svstr
Surface stress in the v direction.
Definition REMORA.H:469
void GetDeckConfiguredAtmosStateLanes(std::array< bool, AtmosState::NumTypes > &deck_configured) const
amrex::Vector< amrex::Real > t_new
new time at each level
Definition REMORA.H:1556
static SolverChoice solverChoice
Container for algorithmic choices.
Definition REMORA.H:1717
void ApplyAtmosphericStates(const amrex::Vector< amrex::MultiFab * > &states, amrex::Real time)
Receives atmospheric states from the driver and applies unit conversions.
void SetLongwaveFromDriver()
bool driver_uses_two_way_coupling
Driver-level direction flag copied in before InitData.
Definition REMORA.H:516
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_xp
x_grid on psi-points (2D)
Definition REMORA.H:595
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_longwave_down
Downward longwave radiation.
Definition REMORA.H:487
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:496
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_cloud
cloud cover fraction [0-1], defined at rho-points
Definition REMORA.H:507
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_srflx
Shortwave radiation flux [W/m²], defined at rho-points.
Definition REMORA.H:483
amrex::Vector< amrex::Real > dt
time step at each level
Definition REMORA.H:1560
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:480
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_qair
Specific humidity [kg/kg], defined at rho-points.
Definition REMORA.H:478
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Tair
Air temperature [°C], defined at rho-points.
Definition REMORA.H:476
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:604
@ 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]
@ Vwind
10-m meridional wind [m/s]
@ Pair
atmospheric pressure [mb]
@ Uwind
10-m zonal wind [m/s]
@ LWrad
longwave radiation [W/m^2]
@ Tair
air temperature [degC]
@ Qair
specific humidity or relative humidity [kg/kg or fraction]
@ Cloud
cloud fraction [0-1]
@ SWrad
downward shortwave radiation [W/m^2]
@ Rain
precipitation rate [kg/m^2/s]
std::array< bool, BulkFlux::NumTypes > bulk_flux_type_specified
std::array< bool, BulkFlux::NumTypes > bulk_flux_value_specified
std::array< BulkForcingType, BulkFlux::NumTypes > bulk_flux_type