REMORA
Regional Modeling of Oceans Refined Adaptively
Loading...
Searching...
No Matches
REMORA_NCPlotFile.cpp
Go to the documentation of this file.
1#include <iomanip>
2#include <iostream>
3#include <map>
4#include <string>
5#include <vector>
6#include <ctime>
7
8#ifdef _OPENMP
9#include <omp.h>
10#endif
11
12#include <AMReX_Utility.H>
13#include <AMReX_buildInfo.H>
14#include <AMReX_ParmParse.H>
15
16#include "REMORA.H"
17#include "REMORA_DateClock.H"
18#include "REMORA_NCInterface.H"
19#include "REMORA_NCPlotFile.H"
20#include "REMORA_IndexDefines.H"
21
22using namespace amrex;
23
24namespace {
25/**
26 * \brief Was this 2D field named in remora.plot_vars_2d?
27 *
28 * Mirrors the name test the AMReX plotfile writer does when it walks plot_var_names_2d,
29 * so both writers answer to the same input key.
30 */
31bool
32plot_2d_var_requested (const amrex::Vector<std::string>& names, const std::string& nm)
33{
34 for (int i = 0; i < names.size(); ++i) {
35 if (names[i] == nm) { return true; }
36 }
37 return false;
38}
39
40/**
41 * \brief Accumulates each rank's hyperslabs and writes them with collective I/O.
42 *
43 * PnetCDF's collective APIs have to be called by every rank that opened the file,
44 * the same number of times and in the same order. REMORA's write loops are MFIter
45 * loops, so the number of hyperslabs is per-rank -- a plain put_all() inside those
46 * loops would mismatch and hang. ncmpi_put_varn_*_all() takes all of a rank's
47 * subarrays for one variable in a single call and tolerates counts that differ
48 * between ranks, including zero, which is exactly the shape we need.
49 *
50 * So the loops hand their hyperslabs here instead of writing, and flush() issues
51 * one collective call per variable at the end. Data is copied into m_entries on
52 * add(), so callers may reuse or destroy their staging buffer immediately -- unlike
53 * the nonblocking iput() path, where PnetCDF aliases buffers larger than 4 KB and
54 * only reads them at wait_all().
55 */
56class VarnCollector
57{
58public:
59 //! Stage one hyperslab of `varid`. `start`/`count` are in NetCDF dimension
60 //! order and `dptr` holds product(count) values.
61 void add (int varid,
62 const std::vector<MPI_Offset>& start,
63 const std::vector<MPI_Offset>& count,
64 const amrex::Real* dptr)
65 {
66 AMREX_ASSERT(start.size() == count.size());
67 MPI_Offset nelems = 1;
68 for (auto c : count) { nelems *= c; }
69
70 Entry& e = m_entries[varid];
71 e.starts.push_back(start);
72 e.counts.push_back(count);
73 e.data.insert(e.data.end(), dptr, dptr + nelems);
74 }
75
76 /**
77 * \brief Write everything staged so far. Collective: all ranks must call it.
78 *
79 * Ranks own different boxes, so they stage different variables -- a rank with
80 * no boxes stages nothing at all. The set of variables to write therefore has
81 * to be agreed on before any collective call, which is what the reduction over
82 * the touched-variable mask below does. Iterating that agreed set in ascending
83 * varid order gives every rank the same call sequence.
84 */
85 void flush (const ncutils::NCFile& ncf)
86 {
87 const int nvars = ncf.num_variables();
88 if (nvars <= 0) { return; }
89
90 std::vector<int> touched(nvars, 0);
91 for (const auto& kv : m_entries) {
92 if (kv.first >= 0 && kv.first < nvars) { touched[kv.first] = 1; }
93 }
94#ifdef AMREX_USE_MPI
96 amrex::ParallelContext::CommunicatorSub());
97#endif
98
99 for (int varid = 0; varid < nvars; ++varid) {
100 if (!touched[varid]) { continue; }
101
102 auto it = m_entries.find(varid);
103 if (it == m_entries.end()) {
104 // This rank has nothing for this variable, but the call is
105 // collective, so it still has to take part with zero subarrays.
106 ncutils::NCVar { ncf.ncid, varid }.put_varn_all(
107 0, nullptr, nullptr, static_cast<const amrex::Real*>(nullptr));
108 continue;
109 }
110
111 Entry& e = it->second;
112 const int num = static_cast<int>(e.starts.size());
113
114 // ncmpi_put_varn_* wants arrays of pointers into the start/count rows.
115 std::vector<MPI_Offset*> start_ptrs(num);
116 std::vector<MPI_Offset*> count_ptrs(num);
117 for (int i = 0; i < num; ++i) {
118 start_ptrs[i] = e.starts[i].data();
119 count_ptrs[i] = e.counts[i].data();
120 }
121
122 ncutils::NCVar { ncf.ncid, varid }.put_varn_all(
123 num, start_ptrs.data(), count_ptrs.data(), e.data.data());
124 }
125
126 m_entries.clear();
127 }
128
129 /**
130 * \brief Handle with the same put() shape as ncutils::NCVar, staging instead
131 * of writing, so the write loops read the same as they always did.
132 */
133 class StagedVar
134 {
135 public:
136 StagedVar (VarnCollector& coll, int varid) : m_coll(coll), m_varid(varid) {}
137
138 void put (const amrex::Real* dptr,
139 const std::vector<MPI_Offset>& start,
140 const std::vector<MPI_Offset>& count) const
141 {
142 m_coll.add(m_varid, start, count, dptr);
143 }
144
145 private:
146 VarnCollector& m_coll;
147 int m_varid;
148 };
149
150 //! Handle for staging writes to the named variable.
151 StagedVar var (const ncutils::NCFile& ncf, const std::string& name)
152 {
153 return StagedVar(*this, ncf.var(name).varid);
154 }
155
156private:
157 struct Entry {
158 std::vector<std::vector<MPI_Offset>> starts;
159 std::vector<std::vector<MPI_Offset>> counts;
160 std::vector<amrex::Real> data;
161 };
162
163 //! Keyed by varid and held in a std::map so iteration is in varid order.
164 std::map<int, Entry> m_entries;
165};
166} // namespace
167
168/**
169 * @param which_step current step for output
170 */
171void REMORA::WriteNCPlotFile(int which_step, MultiFab const* plotMF, int lev) {
172 // A refined level is written to its own file, _d02 and up, the way ROMS names a nested
173 // grid. Its dimensions come from boxes_at_level, so a level covering nx by ny cells gets
174 // xi_rho = nx+2, xi_u = nx+1 and so on, exactly as ROMS shapes a child grid.
175 int which_subdomain = 0;
176 int which_step_in_chunk = -1;
177
178 // Create filename
179 std::string plt_string;
180 std::string plotfilename;
182 plotfilename = plot_file_name + "_his";
183 } else {
185 }
186 // If chunking, concatenate with which file we're in
191 }
192
193 // Set the full IO path for NetCDF output
194 std::string FullPath = plotfilename;
195 if (lev == 0) {
196 const std::string &extension = amrex::Concatenate("_d", lev + 1, 2);
197 FullPath += extension + ".nc";
198 } else {
199 const std::string &extension = amrex::Concatenate("_d", lev + 1 + which_subdomain, 2);
200 FullPath += extension + ".nc";
201 }
202
203 //
204 // Check if this file/directory already exists and if so,
205 // have the IOProcessor move the existing
206 // file/directory to filename.old
207 //
208 if ((!REMORA::write_history_file) || (which_step == 0) || (which_step_in_chunk == 0)) {
209 if (amrex::ParallelDescriptor::IOProcessor()) {
210 if (amrex::FileExists(FullPath)) {
211 std::string newoldname(FullPath + ".old." + amrex::UniqueString());
212 amrex::Print() << "WriteNCPlotFile: " << FullPath << " exists. Renaming to: " << newoldname << std::endl;
213 if (std::rename(FullPath.c_str(), newoldname.c_str())) {
214 amrex::Abort("WriteNCPlotfile:: std::rename failed");
215 }
216 }
217 }
218 ParallelDescriptor::Barrier();
219 }
220
221 bool is_history;
222
224 is_history = true;
225 bool write_header = !(amrex::FileExists(FullPath));
226
227 auto ncf =
229 ncutils::NCFile::create(FullPath, NC_CLOBBER|NC_64BIT_DATA, amrex::ParallelContext::CommunicatorSub(), MPI_INFO_NULL) :
230 ncutils::NCFile::open(FullPath, NC_WRITE, amrex::ParallelContext::CommunicatorSub(), MPI_INFO_NULL);
231
232 amrex::Print() << "Writing into level " << lev << " NetCDF history file " << FullPath << std::endl;
233
235
236 } else {
237
238 is_history = false;
239 bool write_header = true;
240
241 // Open new netcdf file to write data
242 auto ncf = ncutils::NCFile::create(FullPath, NC_CLOBBER|NC_64BIT_DATA, amrex::ParallelContext::CommunicatorSub(), MPI_INFO_NULL);
243 amrex::Print() << "Writing level " << lev << " NetCDF plot file " << FullPath << std::endl;
244
246 }
247}
248
249/**
250 * @param lev level of data to output
251 * @param which_subdomain index of subdomain if lev != 0
252 * @param write_header whether to write a header
253 * @param ncf netcdf file object
254 * @param is_history whether the file being written is a history file
255 */
258{
259 // Number of cells in this "domain" at this level
260 std::vector<int> n_cells;
261
262 // One level per file, so the geometry records below describe just this level.
263 int flev = lev + 1;
264
265 Box subdomain;
266 if (lev == 0) {
267 subdomain = geom[lev].Domain();
268 } else {
270 }
271
272 int nx = subdomain.length(0);
273 int ny = subdomain.length(1);
274 int nz = subdomain.length(2);
275
276 n_cells.push_back(nx);
277 n_cells.push_back(ny);
278 n_cells.push_back(nz);
279
280 const std::string nt_name = "ocean_time";
281 const std::string ndim_name = "num_geo_dimensions";
282
283 const std::string flev_name = "FINEST_LEVEL";
284
285 const std::string nx_name = "NX";
286 const std::string ny_name = "NY";
287 const std::string nz_name = "NZ";
288
289 const std::string nx_r_name = "xi_rho";
290 const std::string ny_r_name = "eta_rho";
291 const std::string nz_r_name = "s_rho";
292
293 const std::string nx_u_name = "xi_u";
294 const std::string ny_u_name = "eta_u";
295
296 const std::string nx_v_name = "xi_v";
297 const std::string ny_v_name = "eta_v";
298
299 const std::string nx_p_name = "xi_psi";
300 const std::string ny_p_name = "eta_psi";
301 const std::string nz_w_name = "s_w";
302
303 if (write_header) {
304 ncf.enter_def_mode();
305 ncf.put_attr("title", "REMORA data ");
306 // The time dimension is unlimited so the record count reflects what was
307 // actually written rather than an up-front estimate from max_step/plot_int.
308 // Note PnetCDF does not prefill record variables, so every element of a
309 // time-varying variable has to be written explicitly (see the loops below).
310 ncf.def_dim(nt_name, NC_UNLIMITED);
311 ncf.def_dim(ndim_name, AMREX_SPACEDIM);
312
313 ncf.def_dim(nx_r_name, nx + 2);
314 ncf.def_dim(ny_r_name, ny + 2);
315 ncf.def_dim(nz_r_name, nz);
316
317 ncf.def_dim(nx_u_name, nx + 1);
318 ncf.def_dim(ny_u_name, ny + 2);
319
320 ncf.def_dim(nx_v_name, nx + 2);
321 ncf.def_dim(ny_v_name, ny + 1);
322
323 ncf.def_dim(nx_p_name, nx + 1);
324 ncf.def_dim(ny_p_name, ny + 1);
325
326 ncf.def_dim(nz_w_name, nz + 1);
327
328 ncf.def_dim(flev_name, flev);
329
330 ncf.def_dim(nx_name, n_cells[0]);
331 ncf.def_dim(ny_name, n_cells[1]);
332 ncf.def_dim(nz_name, n_cells[2]);
333
334 ncf.def_var("probLo", ncutils::NCDType::Real, { ndim_name });
335 ncf.var("probLo").put_attr("long_name","Low side of problem domain in internal AMReX grid");
336 ncf.var("probLo").put_attr("units","meter");
337 ncf.def_var("probHi", ncutils::NCDType::Real, { ndim_name });
338 ncf.var("probHi").put_attr("long_name","High side of problem domain in internal AMReX grid");
339 ncf.var("probHi").put_attr("units","meter");
340
341 ncf.def_var("Geom.smallend", NC_INT, { flev_name, ndim_name });
342 ncf.var("Geom.smallend").put_attr("long_name","Low side of problem domain in index space");
343 ncf.def_var("Geom.bigend", NC_INT, { flev_name, ndim_name });
344 ncf.var("Geom.bigend").put_attr("long_name","High side of problem domain in index space");
345 ncf.def_var("CellSize", ncutils::NCDType::Real, { flev_name, ndim_name });
346 ncf.var("CellSize").put_attr("long_name","Cell size on internal AMReX grid");
347 ncf.var("CellSize").put_attr("units","meter");
348
349 ncf.def_var("theta_s",ncutils::NCDType::Real,{});
350 ncf.var("theta_s").put_attr("long_name","S-coordinate surface control parameter");
351 ncf.def_var("theta_b",ncutils::NCDType::Real,{});
352 ncf.var("theta_b").put_attr("long_name","S-coordinate bottom control parameter");
353 ncf.def_var("hc",ncutils::NCDType::Real,{});
354 ncf.var("hc").put_attr("long_name","S-coordinate parameter, critical depth");
355 ncf.var("hc").put_attr("units","meter");
356
357 ncf.def_var("grid",NC_INT, {});
358 ncf.var("grid").put_attr("cf_role","grid_topology");
359 ncf.var("grid").put_attr("topology_dimension",std::vector({2}));
360 ncf.var("grid").put_attr("node_dimensions", "xi_psi eta_psi");
361 ncf.var("grid").put_attr("face_dimensions", "xi_rho: xi_psi (padding: both) eta_rho: eta_psi (padding: both)");
362 ncf.var("grid").put_attr("edge1_dimensions", "xi_u: xi_psi eta_u: eta_psi (padding: both)");
363 ncf.var("grid").put_attr("edge2_dimensions", "xi_v: xi_psi (padding: both) eta_v: eta_psi");
364 ncf.var("grid").put_attr("node_coordinates", "x_psi y_psi");
365 ncf.var("grid").put_attr("face_coordinates", "x_rho y_rho");
366 ncf.var("grid").put_attr("edge1_coordinates", "x_u y_u");
367 ncf.var("grid").put_attr("edge2_coordinates", "x_v y_v");
368 ncf.var("grid").put_attr("vertical_dimensions", "s_rho: s_w (padding: none)");
369
370 ncf.def_var("s_rho",ncutils::NCDType::Real, {nz_r_name});
371 ncf.var("s_rho").put_attr("long_name","S-coordinate at RHO-points");
372 ncf.var("s_rho").put_attr("field","s_rho, scalar");
373
374 ncf.def_var("s_w",ncutils::NCDType::Real, {nz_w_name});
375 ncf.var("s_w").put_attr("long_name","S-coordinate at W-points");
376 ncf.var("s_w").put_attr("field","s_w, scalar");
377
379 ncf.var("pm").put_attr("long_name","curvilinear coordinate metric in XI");
380 ncf.var("pm").put_attr("units","meter-1");
381 ncf.var("pm").put_attr("grid","grid");
382 ncf.var("pm").put_attr("location","face");
383 ncf.var("pm").put_attr("coordinates","x_rho y_rho");
384 ncf.var("pm").put_attr("field","pm, scalar");
385
387 ncf.var("pn").put_attr("long_name","curvilinear coordinate metric in ETA");
388 ncf.var("pn").put_attr("units","meter-1");
389 ncf.var("pn").put_attr("grid","grid");
390 ncf.var("pn").put_attr("location","face");
391 ncf.var("pn").put_attr("coordinates","x_rho y_rho");
392 ncf.var("pn").put_attr("field","pn, scalar");
393
395 ncf.var("f").put_attr("long_name","Coriolis parameter at RHO-points");
396 ncf.var("f").put_attr("units","second-1");
397 ncf.var("f").put_attr("grid","grid");
398 ncf.var("f").put_attr("location","face");
399 ncf.var("f").put_attr("coordinates","x_rho y_rho");
400 ncf.var("f").put_attr("field","coriolis, scalar");
401
402 ncf.def_var("x_rho",ncutils::NCDType::Real, {ny_r_name, nx_r_name});
403 ncf.var("x_rho").put_attr("long_name","x-locations of RHO-points");
404 ncf.var("x_rho").put_attr("units","meter");
405 ncf.var("x_rho").put_attr("field","x_rho, scalar");
406
407 ncf.def_var("y_rho",ncutils::NCDType::Real, {ny_r_name, nx_r_name});
408 ncf.var("y_rho").put_attr("long_name","y-locations of RHO-points");
409 ncf.var("y_rho").put_attr("units","meter");
410 ncf.var("y_rho").put_attr("field","y_rho, scalar");
411
413 ncf.var("x_u").put_attr("long_name","x-locations of U-points");
414 ncf.var("x_u").put_attr("units","meter");
415 ncf.var("x_u").put_attr("field","x_u, scalar");
416
418 ncf.var("y_u").put_attr("long_name","y-locations of U-points");
419 ncf.var("y_u").put_attr("units","meter");
420 ncf.var("y_u").put_attr("field","y_u, scalar");
421
423 ncf.var("x_v").put_attr("long_name","x-locations of V-points");
424 ncf.var("x_v").put_attr("units","meter");
425 ncf.var("x_v").put_attr("field","x_v, scalar");
426
428 ncf.var("y_v").put_attr("long_name","y-locations of V-points");
429 ncf.var("y_v").put_attr("units","meter");
430 ncf.var("y_v").put_attr("field","y_v, scalar");
431
432 ncf.def_var("x_psi",ncutils::NCDType::Real, {ny_p_name, nx_p_name});
433 ncf.var("x_psi").put_attr("long_name","x-locations of PSI-points");
434 ncf.var("x_psi").put_attr("units","meter");
435 ncf.var("x_psi").put_attr("field","x_psi, scalar");
436
437 ncf.def_var("y_psi",ncutils::NCDType::Real, {ny_p_name, nx_p_name});
438 ncf.var("y_psi").put_attr("long_name","y-locations of PSI-points");
439 ncf.var("y_psi").put_attr("units","meter");
440 ncf.var("y_psi").put_attr("field","y_psi, scalar");
441
442 // Both time stamps run on the model clock, so both carry the reference
443 // date remora.time_ref names, as ROMS writes Rclock%string.
446
447 // Double regardless of Real, like dstart: seconds since the reference
448 // date outgrow a float's resolution within days.
449 ncf.def_var("ocean_time", NC_DOUBLE, { nt_name });
450 ncf.var("ocean_time").put_attr("long_name","time since initialization");
451 ncf.var("ocean_time").put_attr("units","seconds since " + ref_date_string);
452 ncf.var("ocean_time").put_attr("calendar",ref_calendar);
453 ncf.var("ocean_time").put_attr("field","time, scalar, series");
454
455 // ROMS DSTART: when the run starts, in days on the model clock. Double
456 // regardless of Real -- days since year 1 needs more than a float's
457 // seven significant digits, which would round this to the nearest hour.
458 ncf.def_var("dstart", NC_DOUBLE, {});
459 ncf.var("dstart").put_attr("long_name","time stamp assigned to model initialization");
460 ncf.var("dstart").put_attr("units","days since " + ref_date_string);
461 ncf.var("dstart").put_attr("calendar",ref_calendar);
462
463 ncf.def_var("Cs_r", ncutils::NCDType::Real, {nz_r_name});
464 ncf.var("Cs_r").put_attr("long_name", "S-coordinate stretching curves at RHO points");
465 ncf.var("Cs_r").put_attr("valid_min",std::vector({-one}));
466 ncf.var("Cs_r").put_attr("valid_max",std::vector({zero}));
467 ncf.var("Cs_r").put_attr("field","Cs_r, scalar");
468
469 ncf.def_var("Cs_w", ncutils::NCDType::Real, {nz_w_name});
470 ncf.var("Cs_w").put_attr("long_name", "S-coordinate stretching curves at W points");
471 ncf.var("Cs_w").put_attr("valid_min",std::vector({-one}));
472 ncf.var("Cs_w").put_attr("valid_max",std::vector({zero}));
473 ncf.var("Cs_w").put_attr("field","Cs_w, scalar");
474
475 ncf.def_var("h", ncutils::NCDType::Real, { ny_r_name, nx_r_name });
476 ncf.var("h").put_attr("long_name","bathymetry at RHO-points");
477 ncf.var("h").put_attr("units","meter");
478 ncf.var("h").put_attr("grid","grid");
479 ncf.var("h").put_attr("location","face");
480 ncf.var("h").put_attr("coordinates","x_rho y_rho");
481 ncf.var("h").put_attr("field","bath, scalar");
482
484 ncf.var("zeta").put_attr("long_name","free-surface");
485 ncf.var("zeta").put_attr("units","meter");
486 ncf.var("zeta").put_attr("time","ocean_time");
487 ncf.var("zeta").put_attr("grid","grid");
488 ncf.var("zeta").put_attr("location","face");
489 ncf.var("zeta").put_attr("coordinates","x_rho y_rho ocean_time");
490 ncf.var("zeta").put_attr("field","free-surface, scalar, series");
491
492 for (int n = 0; n < ncons; ++n) {
493 int comp = -1;
494 for (int i = 0; i < plot_var_names_3d.size(); i++) {
495 if (plot_var_names_3d[i] == cons_names[n]) comp = i;
496 }
497 if (comp >= 0) {
498 const std::string& nm = cons_names[n];
500 if (n == Temp_comp) {
501 ncf.var(nm).put_attr("long_name", "potential temperature");
502 ncf.var(nm).put_attr("units", "Celsius");
503 ncf.var(nm).put_attr("field", "temperature, scalar, series");
504 } else if (n == Salt_comp) {
505 ncf.var(nm).put_attr("long_name", "salinity");
506 ncf.var(nm).put_attr("field", "salinity, scalar, series");
507 } else {
508 ncf.var(nm).put_attr("long_name", nm);
509 ncf.var(nm).put_attr("field", nm + ", scalar, series");
510 }
511 ncf.var(nm).put_attr("time", "ocean_time");
512 ncf.var(nm).put_attr("grid", "grid");
513 ncf.var(nm).put_attr("location", "face");
514 ncf.var(nm).put_attr("coordinates", "x_rho y_rho s_rho ocean_time");
515 }
516 }
517
518 {
519 int comp = -1;
520 for (int i = 0; i < plot_var_names_3d.size(); i++) {
521 if (plot_var_names_3d[i] == "vorticity") comp = i;
522 }
523 if (comp >= 0) {
525 ncf.var("vorticity").put_attr("long_name","vorticity");
526 ncf.var("vorticity").put_attr("time","ocean_time");
527 ncf.var("vorticity").put_attr("grid","grid");
528 ncf.var("vorticity").put_attr("location","face");
529 ncf.var("vorticity").put_attr("coordinates","x_rho y_rho s_rho ocean_time");
530 ncf.var("vorticity").put_attr("field","vorticity, scalar, series");
531 }
532 } // end vorticity
533
534 // Output 2D horizontal mixing coefficients if using scaled_to_grid option
536 ncf.def_var_fill("visc2", ncutils::NCDType::Real, { ny_r_name, nx_r_name }, &netcdf_fill_value);
537 ncf.var("visc2").put_attr("long_name","horizontal harmonic viscosity coefficient at RHO-points");
538 ncf.var("visc2").put_attr("units","meter2 second-1");
539 ncf.var("visc2").put_attr("grid","grid");
540 ncf.var("visc2").put_attr("location","face");
541 ncf.var("visc2").put_attr("coordinates","x_rho y_rho");
542 ncf.var("visc2").put_attr("field","visc2, scalar");
543
544 for (int n = 0; n < ncons; ++n) {
545 const std::string nm = std::string("diff2_") + cons_names[n];
547 ncf.var(nm).put_attr("long_name", std::string("horizontal harmonic diffusivity coefficient for ") + cons_names[n] + " at RHO-points");
548 ncf.var(nm).put_attr("units","meter2 second-1");
549 ncf.var(nm).put_attr("grid","grid");
550 ncf.var(nm).put_attr("location","face");
551 ncf.var(nm).put_attr("coordinates","x_rho y_rho");
552 ncf.var(nm).put_attr("field", nm + ", scalar");
553 }
554 }
555
557 ncf.var("u").put_attr("long_name","u-momentum component");
558 ncf.var("u").put_attr("units","meter second-1");
559 ncf.var("u").put_attr("time","ocean_time");
560 ncf.var("u").put_attr("grid","grid");
561 ncf.var("u").put_attr("location","edge1");
562 ncf.var("u").put_attr("coordinates","x_u y_u s_rho ocean_time");
563 ncf.var("u").put_attr("field","u-velocity, scalar, series");
564
566 ncf.var("v").put_attr("long_name","v-momentum component");
567 ncf.var("v").put_attr("units","meter second-1");
568 ncf.var("v").put_attr("time","ocean_time");
569 ncf.var("v").put_attr("grid","grid");
570 ncf.var("v").put_attr("location","edge2");
571 ncf.var("v").put_attr("coordinates","x_v y_v s_rho ocean_time");
572 ncf.var("v").put_attr("field","v-velocity, scalar, series");
573
575 ncf.var("ubar").put_attr("long_name","vertically integrated u-momentum component");
576 ncf.var("ubar").put_attr("units","meter second-1");
577 ncf.var("ubar").put_attr("time","ocean_time");
578 ncf.var("ubar").put_attr("grid","grid");
579 ncf.var("ubar").put_attr("location","edge1");
580 ncf.var("ubar").put_attr("coordinates","x_u y_u ocean_time");
581 ncf.var("ubar").put_attr("field","ubar-velocity, scalar, series");
582
584 ncf.var("vbar").put_attr("long_name","vertically integrated v-momentum component");
585 ncf.var("vbar").put_attr("units","meter second-1");
586 ncf.var("vbar").put_attr("time","ocean_time");
587 ncf.var("vbar").put_attr("grid","grid");
588 ncf.var("vbar").put_attr("location","edge2");
589 ncf.var("vbar").put_attr("coordinates","x_v y_v ocean_time");
590 ncf.var("vbar").put_attr("field","vbar-velocity, scalar, series");
591
592 ncf.def_var("sustr", ncutils::NCDType::Real, { nt_name, ny_u_name, nx_u_name });
593 ncf.var("sustr").put_attr("long_name","surface u-momentum stress");
594 ncf.var("sustr").put_attr("units","newton meter-2");
595 ncf.var("sustr").put_attr("time","ocean_time");
596 ncf.var("sustr").put_attr("grid","grid");
597 ncf.var("sustr").put_attr("location","edge1");
598 ncf.var("sustr").put_attr("coordinates","x_u y_u ocean_time");
599 ncf.var("sustr").put_attr("field","surface u-momentum stress, scalar, series");
600
601 ncf.def_var("svstr", ncutils::NCDType::Real, { nt_name, ny_v_name, nx_v_name });
602 ncf.var("svstr").put_attr("long_name","surface v-momentum stress");
603 ncf.var("svstr").put_attr("units","newton meter-2");
604 ncf.var("svstr").put_attr("time","ocean_time");
605 ncf.var("svstr").put_attr("grid","grid");
606 ncf.var("svstr").put_attr("location","edge2");
607 ncf.var("svstr").put_attr("coordinates","x_v y_v ocean_time");
608 ncf.var("svstr").put_attr("field","surface v-momentum stress, scalar, series");
609
610 ncf.def_var("mask_rho", ncutils::NCDType::Real, { nt_name, ny_r_name, nx_r_name });
611 ncf.var("mask_rho").put_attr("long_name","mask on RHO-points");
612 ncf.var("mask_rho").put_attr("time","ocean_time");
613 ncf.var("mask_rho").put_attr("flag_values",std::vector({Real(0.0),Real(1.0)}));
614 ncf.var("mask_rho").put_attr("flag_meanings","land water");
615
616 ncf.def_var("mask_u", ncutils::NCDType::Real, { nt_name, ny_u_name, nx_u_name });
617 ncf.var("mask_u").put_attr("long_name","mask on U-points");
618 ncf.var("mask_u").put_attr("time","ocean_time");
619 ncf.var("mask_u").put_attr("flag_values",std::vector({Real(0.0),Real(1.0)}));
620 ncf.var("mask_u").put_attr("flag_meanings","land water");
621
622 ncf.def_var("mask_v", ncutils::NCDType::Real, { nt_name, ny_v_name, nx_v_name });
623 ncf.var("mask_v").put_attr("long_name","mask on V-points");
624 ncf.var("mask_v").put_attr("time","ocean_time");
625 ncf.var("mask_v").put_attr("flag_values",std::vector({Real(0.0),Real(1.0)}));
626 ncf.var("mask_v").put_attr("flag_meanings","land water");
627
628 // Surface tracer fluxes, one per cell-centered tracer, matching what the AMReX
629 // plotfile writer already offers and requested through the same remora.plot_vars_2d
630 // key. vec_stflux is ncons wide and unconditionally allocated, so any tracer can be
631 // named. Components past temp and salt read zero unless something fills them --
632 // Fennel's air-sea gas exchange acts on the tracer directly rather than through this
633 // array -- so they show what the solver actually applied, which may be nothing.
634 for (int n = 0; n < ncons; ++n) {
635 const std::string nm = std::string("stflux_") + cons_names[n];
636 if (!plot_2d_var_requested(plot_var_names_2d, nm)) { continue; }
638 ncf.var(nm).put_attr("long_name", std::string("surface flux of ") + cons_names[n]);
639 // Kinematic, as ssflux is: the tracer's own units times meter second-1.
640 ncf.var(nm).put_attr("units", (n == Temp_comp) ? "Celsius meter second-1"
641 : "meter second-1");
642 ncf.var(nm).put_attr("time","ocean_time");
643 ncf.var(nm).put_attr("grid","grid");
644 ncf.var(nm).put_attr("location","face");
645 ncf.var(nm).put_attr("coordinates","x_rho y_rho ocean_time");
646 ncf.var(nm).put_attr("field", nm + ", scalar, series");
647 }
648
650 // Surface air temperature (Celsius)
652 ncf.var("Tair").put_attr("long_name","surface air temperature");
653 ncf.var("Tair").put_attr("units","Celsius");
654 ncf.var("Tair").put_attr("time","ocean_time");
655 ncf.var("Tair").put_attr("grid","grid");
656 ncf.var("Tair").put_attr("location","face");
657 ncf.var("Tair").put_attr("coordinates","x_rho y_rho ocean_time");
658 ncf.var("Tair").put_attr("field","Tair, scalar, series");
659
660 // Surface air pressure (Pascal)
662 ncf.var("Pair").put_attr("long_name","surface air pressure");
663 ncf.var("Pair").put_attr("units","Pascal");
664 ncf.var("Pair").put_attr("time","ocean_time");
665 ncf.var("Pair").put_attr("grid","grid");
666 ncf.var("Pair").put_attr("location","face");
667 ncf.var("Pair").put_attr("coordinates","x_rho y_rho ocean_time");
668 ncf.var("Pair").put_attr("field","Pair, scalar, series");
669
670 // Surface net heat flux (W/m2)
672 ncf.var("qnet").put_attr("long_name","surface net heat flux");
673 ncf.var("qnet").put_attr("units","watt meter-2");
674 ncf.var("qnet").put_attr("time","ocean_time");
675 ncf.var("qnet").put_attr("grid","grid");
676 ncf.var("qnet").put_attr("location","face");
677 ncf.var("qnet").put_attr("coordinates","x_rho y_rho ocean_time");
678 ncf.var("qnet").put_attr("field","surface heat flux, scalar, series");
679
680 // Surface net salt flux (kinematic)
681 ncf.def_var("ssflux", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
682 ncf.var("ssflux").put_attr("long_name","kinematic surface net salt flux, SALT*(E-P)/rhow");
683 ncf.var("ssflux").put_attr("units","meter second-1");
684 ncf.var("ssflux").put_attr("time","ocean_time");
685 ncf.var("ssflux").put_attr("grid","grid");
686 ncf.var("ssflux").put_attr("location","face");
687 ncf.var("ssflux").put_attr("coordinates","x_rho y_rho ocean_time");
688 ncf.var("ssflux").put_attr("field","surface net salt flux, scalar, series");
689
690 // Latent heat flux (W/m2)
691 ncf.def_var("latent", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
692 ncf.var("latent").put_attr("long_name","net latent heat flux");
693 ncf.var("latent").put_attr("units","watt meter-2");
694 ncf.var("latent").put_attr("time","ocean_time");
695 ncf.var("latent").put_attr("grid","grid");
696 ncf.var("latent").put_attr("location","face");
697 ncf.var("latent").put_attr("coordinates","x_rho y_rho ocean_time");
698 ncf.var("latent").put_attr("field","latent heat flux, scalar, series");
699
700 // Sensible heat flux (W/m2)
701 ncf.def_var("sensible", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
702 ncf.var("sensible").put_attr("long_name","net sensible heat flux");
703 ncf.var("sensible").put_attr("units","watt meter-2");
704 ncf.var("sensible").put_attr("time","ocean_time");
705 ncf.var("sensible").put_attr("grid","grid");
706 ncf.var("sensible").put_attr("location","face");
707 ncf.var("sensible").put_attr("coordinates","x_rho y_rho ocean_time");
708 ncf.var("sensible").put_attr("field","sensible heat flux, scalar, series");
709
710 // Longwave radiation (W/m2)
711 ncf.def_var("lwrad", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
712 ncf.var("lwrad").put_attr("long_name","net longwave radiation flux");
713 ncf.var("lwrad").put_attr("units","watt meter-2");
714 ncf.var("lwrad").put_attr("time","ocean_time");
715 ncf.var("lwrad").put_attr("grid","grid");
716 ncf.var("lwrad").put_attr("location","face");
717 ncf.var("lwrad").put_attr("coordinates","x_rho y_rho ocean_time");
718 ncf.var("lwrad").put_attr("field","longwave radiation, scalar, series");
719
720 // Shortwave radiation (W/m2)
721 ncf.def_var("swrad", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
722 ncf.var("swrad").put_attr("long_name","solar shortwave radiation flux");
723 ncf.var("swrad").put_attr("units","watt meter-2");
724 ncf.var("swrad").put_attr("time","ocean_time");
725 ncf.var("swrad").put_attr("grid","grid");
726 ncf.var("swrad").put_attr("location","face");
727 ncf.var("swrad").put_attr("coordinates","x_rho y_rho ocean_time");
728 ncf.var("swrad").put_attr("field","shortwave radiation, scalar, series");
729
730 // Evaporation rate (kg m-2 s-1)
731 ncf.def_var("evaporation", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
732 ncf.var("evaporation").put_attr("long_name","evaporation rate");
733 ncf.var("evaporation").put_attr("units","kilogram meter-2 second-1");
734 ncf.var("evaporation").put_attr("time","ocean_time");
735 ncf.var("evaporation").put_attr("grid","grid");
736 ncf.var("evaporation").put_attr("location","face");
737 ncf.var("evaporation").put_attr("coordinates","x_rho y_rho ocean_time");
738 ncf.var("evaporation").put_attr("field","evaporation, scalar, series");
739
740 // Rain rate (kg m-2 s-1)
742 ncf.var("rain").put_attr("long_name","rain fall rate");
743 ncf.var("rain").put_attr("units","kilogram meter-2 second-1");
744 ncf.var("rain").put_attr("time","ocean_time");
745 ncf.var("rain").put_attr("grid","grid");
746 ncf.var("rain").put_attr("location","face");
747 ncf.var("rain").put_attr("coordinates","x_rho y_rho ocean_time");
748 ncf.var("rain").put_attr("field","rain, scalar, series");
749 }
750 // Right now this is hard-wired to {temp, salt, tracer, u, v}
751 ncf.put_attr("space_dimension", std::vector<int> { AMREX_SPACEDIM });
752// ncf.put_attr("current_time", std::vector<double> { time });
753 ncf.put_attr("CurrentLevel", std::vector<int> { flev });
754 ncf.put_attr("DefaultGeometry", std::vector<int> { amrex::DefaultGeometry().Coord() });
755
756 ncf.exit_def_mode();
757
758 // We are doing single-level writes but it doesn't have to be level 0
759 //
760 // Write out the header information.
761 //
762
763 Real dx[AMREX_SPACEDIM];
764 for (int i = 0; i < AMREX_SPACEDIM; i++) {
765 dx[i] = geom[lev].CellSize()[i];
766 }
767 const auto *base = geom[lev].ProbLo();
769
770 amrex::Vector<Real> probLo;
771 amrex::Vector<Real> probHi;
772 for (int i = 0; i < AMREX_SPACEDIM; i++) {
773 probLo.push_back(rb.lo(i));
774 probHi.push_back(rb.hi(i));
775 }
776
777 //nc_probLo.par_access(NC_COLLECTIVE);
778 // small variable data written by just the master proc
780 if (amrex::ParallelDescriptor::IOProcessor()) // only master proc
781 {
782 auto nc_probLo = ncf.var("probLo");
783
784 nc_probLo.put(probLo.data(), { 0 }, { AMREX_SPACEDIM });
785
786 auto nc_probHi = ncf.var("probHi");
787 //nc_probHi.par_access(NC_COLLECTIVE);
788 nc_probHi.put(probHi.data(), { 0 }, { AMREX_SPACEDIM });
789
790 amrex::Vector<int> smallend;
791 amrex::Vector<int> bigend;
792 for (int i = lev; i < flev; i++) {
793 smallend.clear();
794 bigend.clear();
795 for (int j = 0; j < AMREX_SPACEDIM; j++) {
796 smallend.push_back(subdomain.smallEnd(j));
797 bigend.push_back(subdomain.bigEnd(j));
798 }
799 auto nc_Geom_smallend = ncf.var("Geom.smallend");
800 //nc_Geom_smallend.par_access(NC_COLLECTIVE);
801 nc_Geom_smallend.put(smallend.data(), { static_cast<long long int>(i - lev), 0 }, { 1,
802 AMREX_SPACEDIM });
803
804 auto nc_Geom_bigend = ncf.var("Geom.bigend");
805 //nc_Geom_bigend.par_access(NC_COLLECTIVE);
806 nc_Geom_bigend.put(bigend.data(), { static_cast<long long int>(i - lev), 0 }, { 1,
807 AMREX_SPACEDIM });
808 }
809
810 amrex::Vector<Real> CellSize;
811 for (int i = lev; i < flev; i++) {
812 CellSize.clear();
813 for (Real &j : dx) {
814 CellSize.push_back(amrex::Real(j));
815 }
816 auto nc_CellSize = ncf.var("CellSize");
817 //nc_CellSize.par_access(NC_COLLECTIVE);
818 nc_CellSize.put(CellSize.data(), { static_cast<long long int>(i - lev), 0 }, { 1,
820 }
821 Real hc = solverChoice.tcline;
822 Real theta_s = solverChoice.theta_s;
823 Real theta_b = solverChoice.theta_b;
824 ncf.var("hc").put(&hc);
825 ncf.var("theta_s").put(&theta_s);
826 ncf.var("theta_b").put(&theta_b);
827
828 // remora.start_time in seconds is ROMS DSTART in days.
829 double dstart = start_time / 86400.0;
830 ncf.var("dstart").put(&dstart);
831
832 }
834
835 } // end if write_header
836
837 // Past this point every write is collective. The loops below stage their
838 // hyperslabs into the collector rather than writing them, because the number
839 // of hyperslabs a rank contributes depends on how many boxes it owns; the
840 // single flush() at the end turns each variable into one ncmpi_put_varn_*_all.
841 VarnCollector collector;
842
843 //
844 // We compute the offsets based on location of the box within the domain
845 //
847 long long local_start_nt = (is_history ? static_cast<long long>(adjusted_history_count) : static_cast<long long>(0));
848 long long local_nt = 1; // We write data for only one time
849
850 // ocean_time is on the model clock, in double. Written here rather than staged,
851 // since the collector holds Real; collective, with only the IO rank contributing.
852 {
853 const double ocean_time = model_time(t_new[lev]);
854 const long long nt_count = amrex::ParallelDescriptor::IOProcessor() ? local_nt : 0;
855 ncf.var("ocean_time").put_all(&ocean_time, { local_start_nt }, { nt_count });
856 }
857
858 // Check whether there are any nans or infs in variables that we will write out
860 amrex::Abort("Found while writing output: zeta contains nan or inf");
861 }
862 // Check every cell-centered tracer that is actually being written: temperature,
863 // salinity, the passive scalars and the biology tracers. plotMF is indexed by
864 // position in plot_var_names_3d, not by cons component, so look the name up the
865 // same way the write loops below do.
866 for (int n = 0; n < ncons; ++n) {
867 int comp = -1;
868 for (int i = 0; i < plot_var_names_3d.size(); i++) {
869 if (plot_var_names_3d[i] == cons_names[n]) comp = i;
870 }
871 if (comp < 0) { continue; }
872 if (plotMF->contains_nan(comp,1) || plotMF->contains_inf(comp,1)) {
873 amrex::Abort("Found while writing output: " + cons_names[n] +
874 " contains nan or inf");
875 }
876 }
878 amrex::Abort("Found while writing output: velocity u contains nan or inf");
879 }
880 if (vec_ubar[lev]->contains_nan(0,1) || vec_ubar[lev]->contains_inf(0,1)) {
881 amrex::Abort("Found while writing output: velocity ubar contains nan or inf");
882 }
884 amrex::Abort("Found while writing output: velocity v contains nan or inf");
885 }
886 if (vec_vbar[lev]->contains_nan(0,1) || vec_vbar[lev]->contains_inf(0,1)) {
887 amrex::Abort("Found while writing output: velocity vbar contains nan or inf");
888 }
889
890 for (MFIter mfi(*plotMF, false); mfi.isValid(); ++mfi) {
891 auto bx = mfi.validbox();
892 if (subdomain.contains(bx)) {
893 //
894 // We only include one grow cell at subdomain boundaries, not internal grid boundaries
895 //
896 Box tmp_bx(bx);
897 if (tmp_bx.smallEnd()[0] == subdomain.smallEnd()[0])
898 tmp_bx.growLo(0, 1);
899 if (tmp_bx.smallEnd()[1] == subdomain.smallEnd()[1])
900 tmp_bx.growLo(1, 1);
901 if (tmp_bx.bigEnd()[0] == subdomain.bigEnd()[0])
902 tmp_bx.growHi(0, 1);
903 if (tmp_bx.bigEnd()[1] == subdomain.bigEnd()[1])
904 tmp_bx.growHi(1, 1);
905 // amrex::Print() << " BX " << bx << std::endl;
906 // amrex::Print() << "TMP_BX " << tmp_bx << std::endl;
907
908 Box tmp_bx_2d(tmp_bx);
909 tmp_bx_2d.makeSlab(2, 0);
910
911 Box tmp_bx_1d(tmp_bx);
912 tmp_bx_1d.makeSlab(0, 0);
913 tmp_bx_1d.makeSlab(1, 0);
914
915 //
916 // These are the dimensions of the data we write for only this box
917 //
918 long long local_nx = tmp_bx.length()[0];
919 long long local_ny = tmp_bx.length()[1];
920 long long local_nz = tmp_bx.length()[2];
921
922 // We do the "+1" because the offset needs to start at 0
923 // Offsets are relative to this level's subdomain: a refined level's boxes
924 // start at its own index space, not at zero, while the file is sized to
925 // that subdomain.
926 long long local_start_x = static_cast<long long>(tmp_bx.smallEnd()[0] - subdomain.smallEnd()[0] + 1);
927 long long local_start_y = static_cast<long long>(tmp_bx.smallEnd()[1] - subdomain.smallEnd()[1] + 1);
928 long long local_start_z = static_cast<long long>(tmp_bx.smallEnd()[2] - subdomain.smallEnd()[2]);
929
930 if (write_header) {
931 // Only write out s_rho and s_w at x=0,y=0 to avoid NaNs
932 if (bx.contains(subdomain.smallEnd()))
933 {
934 {
935 amrex::Vector<amrex::Real> tmp_srho(local_nz);
936
937#ifdef AMREX_USE_GPU
938 Gpu::dtoh_memcpy(tmp_srho.data(), s_r.data(), sizeof(amrex::Real)*local_nz);
939#else
940 std::memcpy(tmp_srho.data(), s_r.data(), sizeof(amrex::Real)*local_nz);
941#endif
942 Gpu::streamSynchronize();
943
944 auto nc_plot_var = collector.var(ncf, "s_rho");
945 //nc_plot_var.par_access(NC_INDEPENDENT);
946 nc_plot_var.put(tmp_srho.data(), { local_start_z }, { local_nz });
947 }
948 {
949 amrex::Vector<amrex::Real> tmp_sw(local_nz+1);
950
951#ifdef AMREX_USE_GPU
952 Gpu::dtoh_memcpy(tmp_sw.data(), s_w.data(), sizeof(amrex::Real)*(local_nz+1));
953#else
954 std::memcpy(tmp_sw.data(), s_w.data(), sizeof(amrex::Real)*(local_nz+1));
955#endif
956 Gpu::streamSynchronize();
957
958 auto nc_plot_var = collector.var(ncf, "s_w");
959 //nc_plot_var.par_access(NC_INDEPENDENT);
960 nc_plot_var.put(tmp_sw.data(), { local_start_z }, { local_nz + 1});
961 }
962 {
963 amrex::Vector<amrex::Real> tmp_Csrho(local_nz);
964
965#ifdef AMREX_USE_GPU
966 Gpu::dtoh_memcpy(tmp_Csrho.data(), Cs_r.data(), sizeof(amrex::Real)*(local_nz));
967#else
968 std::memcpy(tmp_Csrho.data(), Cs_r.data(), sizeof(amrex::Real)*(local_nz));
969#endif
970 Gpu::streamSynchronize();
971
972 auto nc_plot_var = collector.var(ncf, "Cs_r");
973 //nc_plot_var.par_access(NC_INDEPENDENT);
974 nc_plot_var.put(tmp_Csrho.data(), { local_start_z }, { local_nz });
975 }
976 {
977 amrex::Vector<amrex::Real> tmp_Csw(local_nz+1);
978
979#ifdef AMREX_USE_GPU
980 Gpu::dtoh_memcpy(tmp_Csw.data(), Cs_w.data(), sizeof(amrex::Real)*(local_nz+1));
981#else
982 std::memcpy(tmp_Csw.data(), Cs_w.data(), sizeof(amrex::Real)*(local_nz+1));
983#endif
984
985 Gpu::streamSynchronize();
986
987 auto nc_plot_var = collector.var(ncf, "Cs_w");
988 //nc_plot_var.par_access(NC_INDEPENDENT);
989 nc_plot_var.put(tmp_Csw.data(), { local_start_z }, { local_nz + 1});
990 }
991 }
992
993 {
994 FArrayBox tmp_bathy;
995 tmp_bathy.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
996
997 tmp_bathy.template copy<RunOn::Device>((*vec_h[lev])[mfi.index()], 0, 0, 1);
998 Gpu::streamSynchronize();
999
1000 auto nc_plot_var = collector.var(ncf, "h");
1001 //nc_plot_var.par_access(NC_INDEPENDENT);
1002 nc_plot_var.put(tmp_bathy.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1003 }
1004
1005 {
1006 FArrayBox tmp_pm;
1007 tmp_pm.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1008
1009 tmp_pm.template copy<RunOn::Device>((*vec_pm[lev])[mfi.index()], 0, 0, 1);
1010 Gpu::streamSynchronize();
1011
1012 auto nc_plot_var = collector.var(ncf, "pm");
1013 //nc_plot_var.par_access(NC_INDEPENDENT);
1014
1015 nc_plot_var.put(tmp_pm.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1016 }
1017
1018 {
1019 FArrayBox tmp_pn;
1020 tmp_pn.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1021
1022 tmp_pn.template copy<RunOn::Device>((*vec_pn[lev])[mfi.index()], 0, 0, 1);
1023 Gpu::streamSynchronize();
1024
1025 auto nc_plot_var = collector.var(ncf, "pn");
1026 //nc_plot_var.par_access(NC_INDEPENDENT);
1027 nc_plot_var.put(tmp_pn.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1028 }
1029
1030 {
1031 FArrayBox tmp_f;
1032 tmp_f.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1033
1034 tmp_f.template copy<RunOn::Device>((*vec_fcor[lev])[mfi.index()], 0, 0, 1);
1035 Gpu::streamSynchronize();
1036
1037 auto nc_plot_var = collector.var(ncf, "f");
1038 //nc_plot_var.par_access(NC_INDEPENDENT);
1039 nc_plot_var.put(tmp_f.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1040 }
1041
1042 {
1043 FArrayBox tmp_xr;
1044 tmp_xr.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1045
1046 tmp_xr.template copy<RunOn::Device>((*vec_xr[lev])[mfi.index()], 0, 0, 1);
1047 Gpu::streamSynchronize();
1048
1049 auto nc_plot_var = collector.var(ncf, "x_rho");
1050 //nc_plot_var.par_access(NC_INDEPENDENT);
1051
1052 nc_plot_var.put(tmp_xr.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1053 }
1054
1055 {
1056 FArrayBox tmp_yr;
1057 tmp_yr.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1058
1059 tmp_yr.template copy<RunOn::Device>((*vec_yr[lev])[mfi.index()], 0, 0, 1);
1060 Gpu::streamSynchronize();
1061
1062 auto nc_plot_var = collector.var(ncf, "y_rho");
1063 //nc_plot_var.par_access(NC_INDEPENDENT);
1064 nc_plot_var.put(tmp_yr.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1065 }
1066 }
1067
1068 {
1069 FArrayBox tmp_zeta;
1070 tmp_zeta.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1071 tmp_zeta.template copy<RunOn::Device>((*vec_Zt_avg1[lev])[mfi.index()], 0, 0, 1);
1072 Gpu::streamSynchronize();
1073
1074 auto nc_plot_var = collector.var(ncf, "zeta");
1075 nc_plot_var.put(tmp_zeta.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny,
1076 local_nx });
1077 }
1078
1079 {
1080 FArrayBox tmp_mask_rho;
1081 tmp_mask_rho.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1082 tmp_mask_rho.template copy<RunOn::Device>((*vec_mskr[lev])[mfi.index()], 0, 0, 1);
1083 Gpu::streamSynchronize();
1084
1085 auto nc_plot_var = collector.var(ncf, "mask_rho");
1086 nc_plot_var.put(tmp_mask_rho.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny,
1087 local_nx });
1088 }
1089
1090 // stflux_*, one per tracer. Defined above under the same condition.
1091 for (int n = 0; n < ncons; ++n) {
1092 const std::string nm = std::string("stflux_") + cons_names[n];
1093 if (!plot_2d_var_requested(plot_var_names_2d, nm)) { continue; }
1094 FArrayBox tmp;
1095 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1096 tmp.template copy<RunOn::Device>((*vec_stflux[lev])[mfi.index()], n, 0, 1);
1097 Gpu::streamSynchronize();
1098
1099 auto nc_var = collector.var(ncf, nm);
1100 nc_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x },
1101 { local_nt, local_ny, local_nx });
1102 }
1103
1105 {
1106 const Real Hscale = solverChoice.rho0 * Cp;
1107 // The copy and the mult below are both async on the same stream, so the
1108 // sync has to follow the last of them and precede the host-side put().
1109 // Tair
1110 {
1111 FArrayBox tmp_Tair;
1112 tmp_Tair.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1113 tmp_Tair.template copy<RunOn::Device>((*vec_Tair[lev])[mfi.index()], 0, 0, 1);
1114 Gpu::streamSynchronize();
1115
1116 auto nc_plot_var = collector.var(ncf, "Tair");
1117 nc_plot_var.put(tmp_Tair.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1118 }
1119 // Pair
1120 {
1121 FArrayBox tmp_Pair;
1122 tmp_Pair.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1123 tmp_Pair.template copy<RunOn::Device>((*vec_Pair[lev])[mfi.index()], 0, 0, 1);
1124 Gpu::streamSynchronize();
1125
1126 auto nc_plot_var = collector.var(ncf, "Pair");
1127 nc_plot_var.put(tmp_Pair.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1128 }
1129 // qnet (stored °C m/s → write W/m²)
1130 {
1131 FArrayBox tmp;
1132 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1133
1134 // Copy stflux Temp component
1135 tmp.template copy<RunOn::Device>(
1136 (*vec_stflux[lev])[mfi.index()],
1137 Temp_comp, // source component
1138 0, // dest component
1139 1 // number of comps
1140 );
1141
1142 // Convert °C·m/s → W/m²
1143 tmp.mult<RunOn::Device>(Hscale);
1144
1145 Gpu::streamSynchronize();
1146
1147 auto nc_var = collector.var(ncf, "qnet");
1148 nc_var.put(tmp.dataPtr(),
1149 { local_start_nt, local_start_y, local_start_x },
1150 { local_nt, local_ny, local_nx });
1151 }
1152 // ssflux = surface net freshwater flux (kg/m²/s converted to m/s)
1153 {
1154 FArrayBox tmp;
1155 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1156
1157 // Copy stflux Salt component
1158 tmp.template copy<RunOn::Device>(
1159 (*vec_stflux[lev])[mfi.index()],
1160 Salt_comp, // source component
1161 0, // destination component
1162 1 // number of components
1163 );
1164
1165 Gpu::streamSynchronize();
1166
1167 auto nc_var = collector.var(ncf, "ssflux");
1168 nc_var.put(tmp.dataPtr(),
1169 { local_start_nt, local_start_y, local_start_x },
1170 { local_nt, local_ny, local_nx });
1171 }
1172 // latent (stored °C m/s → write W/m²)
1173 {
1174 FArrayBox tmp;
1175 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1176 tmp.template copy<RunOn::Device>((*vec_lhflx[lev])[mfi.index()], 0, 0, 1);
1177
1178 // Convert °C·m/s → W/m²
1179 tmp.mult<RunOn::Device>(Hscale);
1180
1181 Gpu::streamSynchronize();
1182
1183 auto nc_var = collector.var(ncf, "latent");
1184 nc_var.put(tmp.dataPtr(),
1185 { local_start_nt, local_start_y, local_start_x },
1186 { local_nt, local_ny, local_nx });
1187 }
1188 // sensible (stored °C m/s → write W/m²)
1189 {
1190 FArrayBox tmp;
1191 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1192 tmp.template copy<RunOn::Device>((*vec_shflx[lev])[mfi.index()], 0, 0, 1);
1193
1194 // Convert °C·m/s → W/m²
1195 tmp.mult<RunOn::Device>(Hscale);
1196
1197 Gpu::streamSynchronize();
1198
1199 auto nc_var = collector.var(ncf, "sensible");
1200 nc_var.put(tmp.dataPtr(),
1201 { local_start_nt, local_start_y, local_start_x },
1202 { local_nt, local_ny, local_nx });
1203 }
1204 // lwrad (stored °C m/s → write W/m²)
1205 {
1206 FArrayBox tmp;
1207 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1208 tmp.template copy<RunOn::Device>((*vec_lrflx[lev])[mfi.index()], 0, 0, 1);
1209
1210 // Convert °C·m/s → W/m²
1211 tmp.mult<RunOn::Device>(Hscale);
1212
1213 Gpu::streamSynchronize();
1214
1215 auto nc_var = collector.var(ncf, "lwrad");
1216 nc_var.put(tmp.dataPtr(),
1217 { local_start_nt, local_start_y, local_start_x },
1218 { local_nt, local_ny, local_nx });
1219 }
1220 // swrad, note this is stored explicitly as W/m², not degC m/s in REMORA.bulk_flux.cpp
1221 {
1222 FArrayBox tmp;
1223 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1224 tmp.template copy<RunOn::Device>((*vec_srflx[lev])[mfi.index()], 0, 0, 1);
1225 Gpu::streamSynchronize();
1226
1227 auto nc_var = collector.var(ncf, "swrad");
1228 nc_var.put(tmp.dataPtr(),
1229 { local_start_nt, local_start_y, local_start_x },
1230 { local_nt, local_ny, local_nx });
1231 }
1232 // evaporation
1233 {
1234 FArrayBox tmp;
1235 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1236 tmp.template copy<RunOn::Device>((*vec_evap[lev])[mfi.index()], 0, 0, 1);
1237 Gpu::streamSynchronize();
1238
1239 auto nc_var = collector.var(ncf, "evaporation");
1240 nc_var.put(tmp.dataPtr(),
1241 { local_start_nt, local_start_y, local_start_x },
1242 { local_nt, local_ny, local_nx });
1243 }
1244 // rain
1245 {
1246 FArrayBox tmp;
1247 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1248 tmp.template copy<RunOn::Device>((*vec_rain[lev])[mfi.index()], 0, 0, 1);
1249 Gpu::streamSynchronize();
1250
1251 auto nc_var = collector.var(ncf, "rain");
1252 nc_var.put(tmp.dataPtr(),
1253 { local_start_nt, local_start_y, local_start_x },
1254 { local_nt, local_ny, local_nx });
1255 }
1256 } // end output forcing
1257
1258 // **************************************************************************
1259 for (int n = 0; n < ncons; ++n) {
1260 int comp = -1;
1261 for (int i = 0; i < plot_var_names_3d.size(); i++) {
1262 if (plot_var_names_3d[i] == cons_names[n]) comp = i;
1263 }
1264 if (comp >= 0) {
1265 FArrayBox tmp;
1266 tmp.resize(tmp_bx, 1, amrex::The_Pinned_Arena());
1267 tmp.template copy<RunOn::Device>((*plotMF)[mfi.index()], comp, 0, 1);
1268 Gpu::streamSynchronize();
1269
1270 auto nc_plot_var = collector.var(ncf, cons_names[n]);
1271 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_z, local_start_y, local_start_x }, { local_nt,
1272 local_nz, local_ny, local_nx });
1273 }
1274 }
1275 // **************************************************************************
1276
1277 // **************************************************************************
1278 { // Vorticity
1279 int comp = -1;
1280 for (int i = 0; i < plot_var_names_3d.size(); i++) {
1281 if (plot_var_names_3d[i] == "vorticity") comp = i;
1282 }
1283 if (comp >= 0) {
1284 FArrayBox tmp;
1285 tmp.resize(tmp_bx, 1, amrex::The_Pinned_Arena());
1286 tmp.template copy<RunOn::Device>((*plotMF)[mfi.index()], comp, 0, 1);
1287 Gpu::streamSynchronize();
1288
1290 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_z, local_start_y, local_start_x }, { local_nt,
1291 local_nz, local_ny, local_nx });
1292 } // if vorticity exists in plotMF
1293 } // end vorticity
1294 // **************************************************************************
1295
1296 // **************************************************************************
1297 // Horizontal mixing coefficients (scaled_to_grid):
1298 // vertically homogeneous and time-invariant -> write static 2D fields once.
1299 // **************************************************************************
1301 { // visc2
1302 FArrayBox tmp;
1303 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1304 tmp.template copy<RunOn::Device>((*vec_visc2_r[lev])[mfi.index()], 0, 0, 1);
1305 Gpu::streamSynchronize();
1306
1307 auto nc_var = collector.var(ncf, "visc2");
1308 nc_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1309 }
1310
1311 // diff2_*
1312 for (int n = 0; n < ncons; ++n) {
1313 const std::string nm = std::string("diff2_") + cons_names[n];
1314 FArrayBox tmp;
1315 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1316 tmp.template copy<RunOn::Device>((*vec_diff2[lev])[mfi.index()], n, 0, 1);
1317 Gpu::streamSynchronize();
1318
1319 auto nc_var = collector.var(ncf, nm);
1320 nc_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1321 }
1322 }
1323
1324 } // subdomain
1325 } // mfi
1326
1327 // Flush at each loop boundary rather than once at the end, so the collector
1328 // only ever holds one loop's worth of staged data. All ranks reach these
1329 // points, which is what the collective call requires.
1330 collector.flush(ncf);
1331
1332 // Writing u (we loop over cons to get cell-centered box)
1333 for (MFIter mfi(*plotMF, false); mfi.isValid(); ++mfi) {
1334 Box bx = mfi.validbox();
1335
1336 if (subdomain.contains(bx)) {
1337 //
1338 // We only include one grow cell at subdomain boundaries, not internal grid boundaries
1339 //
1340 Box tmp_bx(bx);
1341 tmp_bx.surroundingNodes(0);
1342 if (tmp_bx.smallEnd()[1] == subdomain.smallEnd()[1])
1343 tmp_bx.growLo(1, 1);
1344 if (tmp_bx.bigEnd()[1] == subdomain.bigEnd()[1])
1345 tmp_bx.growHi(1, 1);
1346 Box tmp_bx_2d(tmp_bx);
1347 tmp_bx_2d.makeSlab(2, 0);
1348
1349 //
1350 // These are the dimensions of the data we write for only this box
1351 //
1352 long long local_nx = tmp_bx.length()[0];
1353 long long local_ny = tmp_bx.length()[1];
1354 long long local_nz = tmp_bx.length()[2];
1355
1356 // We do the "+1" because the offset needs to start at 0
1357 long long local_start_x = static_cast<long long>(tmp_bx.smallEnd()[0] - subdomain.smallEnd()[0]);
1358 long long local_start_y = static_cast<long long>(tmp_bx.smallEnd()[1] - subdomain.smallEnd()[1] + 1);
1359 long long local_start_z = static_cast<long long>(tmp_bx.smallEnd()[2] - subdomain.smallEnd()[2]);
1360
1361 if (write_header) {
1362 {
1363 FArrayBox tmp;
1364 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1365 tmp.template copy<RunOn::Device>((*vec_xu[lev])[mfi.index()], 0, 0, 1);
1366 Gpu::streamSynchronize();
1367
1368 auto nc_plot_var = collector.var(ncf, "x_u");
1369 //nc_plot_var.par_access(NC_INDEPENDENT);
1370 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1371 }
1372 {
1373 FArrayBox tmp;
1374 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1375 tmp.template copy<RunOn::Device>((*vec_yu[lev])[mfi.index()], 0, 0, 1);
1376 Gpu::streamSynchronize();
1377
1378 auto nc_plot_var = collector.var(ncf, "y_u");
1379 //nc_plot_var.par_access(NC_INDEPENDENT);
1380 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1381 }
1382 }
1383
1384 {
1385 FArrayBox tmp;
1386 tmp.resize(tmp_bx, 1, amrex::The_Pinned_Arena());
1387 tmp.template copy<RunOn::Device>((*xvel_new[lev])[mfi.index()], 0, 0, 1);
1388 Gpu::streamSynchronize();
1389
1390 auto nc_plot_var = collector.var(ncf, "u");
1391 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_z, local_start_y, local_start_x }, { local_nt,
1392 local_nz, local_ny, local_nx });
1393 }
1394
1395 {
1396 FArrayBox tmp;
1397 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1398 tmp.template copy<RunOn::Device>((*vec_ubar[lev])[mfi.index()], 0, 0, 1);
1399 Gpu::streamSynchronize();
1400
1401 auto nc_plot_var = collector.var(ncf, "ubar");
1402 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1403 }
1404 {
1405 FArrayBox tmp;
1406 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1407 tmp.template copy<RunOn::Device>((*vec_sustr[lev])[mfi.index()], 0, 0, 1);
1408 Gpu::streamSynchronize();
1409
1410 auto nc_plot_var = collector.var(ncf, "sustr");
1411 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1412 }
1413 {
1414 FArrayBox tmp;
1415 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1416 tmp.template copy<RunOn::Device>((*vec_msku[lev])[mfi.index()], 0, 0, 1);
1417 Gpu::streamSynchronize();
1418
1419 auto nc_plot_var = collector.var(ncf, "mask_u");
1420 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1421 }
1422 } // in subdomain
1423 } // mfi
1424
1425 collector.flush(ncf);
1426
1427 // Writing v (we loop over cons to get cell-centered box)
1428 for (MFIter mfi(*plotMF, false); mfi.isValid(); ++mfi) {
1429 Box bx = mfi.validbox();
1430
1431 if (subdomain.contains(bx)) {
1432 //
1433 // We only include one grow cell at subdomain boundaries, not internal grid boundaries
1434 //
1435 Box tmp_bx(bx);
1436 tmp_bx.surroundingNodes(1);
1437 if (tmp_bx.smallEnd()[0] == subdomain.smallEnd()[0])
1438 tmp_bx.growLo(0, 1);
1439 if (tmp_bx.bigEnd()[0] == subdomain.bigEnd()[0])
1440 tmp_bx.growHi(0, 1);
1441 // amrex::Print() << " BX " << bx << std::endl;
1442 // amrex::Print() << "TMP_BX " << tmp_bx << std::endl;
1443
1444 Box tmp_bx_2d(tmp_bx);
1445 tmp_bx_2d.makeSlab(2, 0);
1446
1447 //
1448 // These are the dimensions of the data we write for only this box
1449 //
1450 long long local_nx = tmp_bx.length()[0];
1451 long long local_ny = tmp_bx.length()[1];
1452 long long local_nz = tmp_bx.length()[2];
1453
1454 // We do the "+1" because the offset needs to start at 0
1455 long long local_start_x = static_cast<long long>(tmp_bx.smallEnd()[0] - subdomain.smallEnd()[0] + 1);
1456 long long local_start_y = static_cast<long long>(tmp_bx.smallEnd()[1] - subdomain.smallEnd()[1]);
1457 long long local_start_z = static_cast<long long>(tmp_bx.smallEnd()[2] - subdomain.smallEnd()[2]);
1458
1459 if (write_header) {
1460 {
1461 FArrayBox tmp;
1462 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1463 tmp.template copy<RunOn::Device>((*vec_xv[lev])[mfi.index()], 0, 0, 1);
1464 Gpu::streamSynchronize();
1465
1466 auto nc_plot_var = collector.var(ncf, "x_v");
1467 //nc_plot_var.par_access(NC_INDEPENDENT);
1468 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1469 }
1470 {
1471 FArrayBox tmp;
1472 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1473 tmp.template copy<RunOn::Device>((*vec_yv[lev])[mfi.index()], 0, 0, 1);
1474 Gpu::streamSynchronize();
1475
1476 auto nc_plot_var = collector.var(ncf, "y_v");
1477 //nc_plot_var.par_access(NC_INDEPENDENT);
1478 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1479 }
1480 }
1481
1482 {
1483 FArrayBox tmp;
1484 tmp.resize(tmp_bx, 1, amrex::The_Pinned_Arena());
1485 tmp.template copy<RunOn::Device>((*yvel_new[lev])[mfi.index()], 0, 0, 1);
1486 Gpu::streamSynchronize();
1487
1488 auto nc_plot_var = collector.var(ncf, "v");
1489 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_z, local_start_y, local_start_x }, { local_nt,
1490 local_nz, local_ny, local_nx });
1491 }
1492
1493 {
1494 FArrayBox tmp;
1495 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1496 tmp.template copy<RunOn::Device>((*vec_vbar[lev])[mfi.index()], 0, 0, 1);
1497 Gpu::streamSynchronize();
1498
1499 auto nc_plot_var = collector.var(ncf, "vbar");
1500 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1501 }
1502
1503 {
1504 FArrayBox tmp;
1505 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1506 tmp.template copy<RunOn::Device>((*vec_svstr[lev])[mfi.index()], 0, 0, 1);
1507 Gpu::streamSynchronize();
1508
1509 auto nc_plot_var = collector.var(ncf, "svstr");
1510 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1511 }
1512 {
1513 FArrayBox tmp;
1514 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1515 tmp.template copy<RunOn::Device>((*vec_mskv[lev])[mfi.index()], 0, 0, 1);
1516 Gpu::streamSynchronize();
1517
1518 auto nc_plot_var = collector.var(ncf, "mask_v");
1519 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1520 }
1521
1522 } // in subdomain
1523 } // mfi
1524
1525 collector.flush(ncf);
1526
1527 for (MFIter mfi(*plotMF, false); mfi.isValid(); ++mfi) {
1528 Box bx = mfi.validbox();
1529
1530 if (subdomain.contains(bx)) {
1531 //
1532 // We only include one grow cell at subdomain boundaries, not internal grid boundaries
1533 //
1534 Box tmp_bx(bx);
1535 tmp_bx.surroundingNodes(0);
1536 tmp_bx.surroundingNodes(1);
1537
1538 Box tmp_bx_2d(tmp_bx);
1539 tmp_bx_2d.makeSlab(2, 0);
1540
1541 //
1542 // These are the dimensions of the data we write for only this box
1543 //
1544 long long local_nx = tmp_bx.length()[0];
1545 long long local_ny = tmp_bx.length()[1];
1546
1547 // We do the "+1" because the offset needs to start at 0
1548 long long local_start_x = static_cast<long long>(tmp_bx.smallEnd()[0] - subdomain.smallEnd()[0]);
1549 long long local_start_y = static_cast<long long>(tmp_bx.smallEnd()[1] - subdomain.smallEnd()[1]);
1550
1551 if (write_header) {
1552 {
1553 FArrayBox tmp;
1554 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1555 tmp.template copy<RunOn::Device>((*vec_xp[lev])[mfi.index()], 0, 0, 1);
1556 Gpu::streamSynchronize();
1557
1558 auto nc_plot_var = collector.var(ncf, "x_psi");
1559 //nc_plot_var.par_access(NC_INDEPENDENT);
1560 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1561 }
1562 {
1563 FArrayBox tmp;
1564 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1565 tmp.template copy<RunOn::Device>((*vec_yp[lev])[mfi.index()], 0, 0, 1);
1566 Gpu::streamSynchronize();
1567
1568 auto nc_plot_var = collector.var(ncf, "y_psi");
1569 //nc_plot_var.par_access(NC_INDEPENDENT);
1570 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1571 }
1572
1573 } // header
1574 } // in subdomain
1575 } // mfi
1576
1577 // One collective ncmpi_put_varn_*_all per variable. Collective writes keep
1578 // numrecs synced in the file header as they go, so the separate
1579 // ncmpi_end_indep_data() sync the independent path needed is no longer
1580 // required here.
1581 collector.flush(ncf);
1582
1583 ncf.close();
1584
1586}
constexpr amrex::Real one
constexpr amrex::Real zero
constexpr amrex::Real Cp
AMREX_FORCE_INLINE std::string remora_ref_date_string(double time_ref)
Reference date as YYYY-MM-DD hh:mm:ss. ROMS ref_clock's Rclockstring.
AMREX_FORCE_INLINE std::string remora_ref_calendar(double time_ref)
CF calendar name for time_ref. ROMS ref_clock's Rclockcalendar.
#define Temp_comp
#define Salt_comp
mf_h setVal(geomdata.ProbHi(2))
int ncons
Number of conserved scalars in the state (temperature + salt + passive scalars + biology tracers)
Definition REMORA.H:1797
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_evap
evaporation rate [kg/m^2/s]
Definition REMORA.H:517
amrex::Vector< std::string > cons_names
Names of scalars for plotfile output.
Definition REMORA.H:1941
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_fcor
coriolis factor (2D)
Definition REMORA.H:612
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_lrflx
longwave radiation
Definition REMORA.H:497
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_yv
y_grid on v-points (2D)
Definition REMORA.H:627
static bool write_history_file
Whether to output NetCDF files as a single history file with several time steps.
Definition REMORA.H:1575
amrex::Gpu::DeviceVector< amrex::Real > s_w
Scaled vertical coordinate (range [0,1]) that transforms to z, defined at w-points (cell faces)
Definition REMORA.H:460
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_mskr
land/sea mask at cell centers (2D)
Definition REMORA.H:584
int history_count
Counter for which time index we are writing to in the netcdf history file.
Definition REMORA.H:1934
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_rain
precipitation rate [kg/m^2/s]
Definition REMORA.H:515
bool chunk_history_file
Whether to split the netcdf history file into fixed-length chunks.
Definition REMORA.H:1930
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_sustr
Surface stress in the u direction.
Definition REMORA.H:479
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_yp
y_grid on psi-points (2D)
Definition REMORA.H:632
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_xr
x_grid on rho points (2D)
Definition REMORA.H:615
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_xv
x_grid on v-points (2D)
Definition REMORA.H:625
double start_time
Time of the start of the simulation, in seconds on the model clock.
Definition REMORA.H:1780
amrex::Vector< amrex::Vector< amrex::Box > > boxes_at_level
the boxes specified at each level by tagging criteria
Definition REMORA.H:1686
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_msku
land/sea mask at x-faces (2D)
Definition REMORA.H:586
amrex::Vector< amrex::MultiFab * > yvel_new
multilevel data container for current step's y velocities (v in ROMS)
Definition REMORA.H:397
amrex::Gpu::DeviceVector< amrex::Real > s_r
Scaled vertical coordinate (range [0,1]) that transforms to z, defined at rho points (cell centers)
Definition REMORA.H:458
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_shflx
sensible heat flux
Definition REMORA.H:503
int steps_per_history_file
Time steps per netcdf history file. Must be > 0 if chunk_history_file.
Definition REMORA.H:1932
amrex::Vector< amrex::MultiFab * > xvel_new
multilevel data container for current step's x velocities (u in ROMS)
Definition REMORA.H:395
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_lhflx
latent heat flux
Definition REMORA.H:501
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_mskv
land/sea mask at y-faces (2D)
Definition REMORA.H:588
void WriteNCPlotFile(int istep, amrex::MultiFab const *plotMF, int lev=0)
Write plotfile using NetCDF (wrapper)
static int file_min_digits
Minimum number of digits in plotfile name or chunked history file.
Definition REMORA.H:1993
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_visc2_r
Harmonic viscosity defined on the rho points (centers)
Definition REMORA.H:448
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_svstr
Surface stress in the v direction.
Definition REMORA.H:481
amrex::Gpu::DeviceVector< amrex::Real > Cs_r
Stretching coefficients at rho points.
Definition REMORA.H:468
amrex::Vector< amrex::Real > t_new
new time at each level, in seconds since start_time
Definition REMORA.H:1700
static SolverChoice solverChoice
Container for algorithmic choices.
Definition REMORA.H:1949
static int total_nc_plot_file_step
Definition REMORA.H:1480
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_xp
x_grid on psi-points (2D)
Definition REMORA.H:630
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_vbar
barotropic y velocity (2D)
Definition REMORA.H:576
amrex::Gpu::DeviceVector< amrex::Real > Cs_w
Stretching coefficients at w points.
Definition REMORA.H:470
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_yu
y_grid on u-points (2D)
Definition REMORA.H:622
void WriteNCPlotFile_which(int lev, int which_subdomain, amrex::MultiFab const *plotMF, bool write_header, ncutils::NCFile &ncf, bool is_history)
Write a particular NetCDF plotfile.
amrex::Real netcdf_fill_value
fill value for masked arrays in netcdf output
Definition REMORA.H:1957
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_xu
x_grid on u-points (2D)
Definition REMORA.H:620
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_pn
horizontal scaling factor: 1 / dy (2D)
Definition REMORA.H:605
amrex::Vector< std::string > plot_var_names_3d
Names of 3D variables to output to AMReX plotfile.
Definition REMORA.H:1937
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_stflux
Surface tracer flux; input arrays.
Definition REMORA.H:508
std::string plot_file_name
Plotfile prefix.
Definition REMORA.H:1917
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Zt_avg1
Average of the free surface, zeta (2D)
Definition REMORA.H:476
amrex::Vector< std::string > plot_var_names_2d
Names of 2D variables to output to AMReX plotfile.
Definition REMORA.H:1939
double model_time(amrex::Real elapsed) const noexcept
Time on the model clock, in seconds, of an elapsed time such as t_new.
Definition REMORA.H:2170
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_srflx
Shortwave radiation flux [W/m²], defined at rho-points.
Definition REMORA.H:495
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Pair
Air pressure [mb], defined at rho-points.
Definition REMORA.H:492
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Tair
Air temperature [°C], defined at rho-points.
Definition REMORA.H:488
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_yr
y_grid on rho points (2D)
Definition REMORA.H:617
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_diff2
Harmonic diffusivity for temperature / salinity.
Definition REMORA.H:450
static NCFile create(const std::string &name, const int cmode=NC_CLOBBER|NC_64BIT_DATA, MPI_Comm comm=MPI_COMM_WORLD, MPI_Info info=MPI_INFO_NULL)
Create a file. Defaults to CDF-5; classic CDF-1 has a 2GB limit.
static NCFile open(const std::string &name, const int cmode=NC_NOWRITE, MPI_Comm comm=MPI_COMM_WORLD, MPI_Info info=MPI_INFO_NULL)
Open an existing file.
@ data
annual climatology of Laurent et al. (2017)
HorizMixingType horiz_mixing_type
amrex::Real theta_b
amrex::Real theta_s
amrex::Real tcline
static constexpr nc_type Real
Representation of a NetCDF variable.
const int ncid
File/Group identifier.