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 * @param plotMF cell-centered plot variables at this level
171 * @param lev level to write
172 * @param mf_omega omega at w points, or nullptr when it is not plotted
173 * @param mf_wvel w at w points, or nullptr when it is not plotted
174 */
175void REMORA::WriteNCPlotFile(int which_step, MultiFab const* plotMF, int lev,
176 MultiFab const* mf_omega, MultiFab const* mf_wvel) {
177 // A refined level is written to its own file, _d02 and up, the way ROMS names a nested
178 // grid. Its dimensions come from boxes_at_level, so a level covering nx by ny cells gets
179 // xi_rho = nx+2, xi_u = nx+1 and so on, exactly as ROMS shapes a child grid.
180 int which_subdomain = 0;
181 int which_step_in_chunk = -1;
182
183 // Create filename
184 std::string plt_string;
185 std::string plotfilename;
187 plotfilename = plot_file_name + "_his";
188 } else {
190 }
191 // If chunking, concatenate with which file we're in
196 }
197
198 // Set the full IO path for NetCDF output
199 std::string FullPath = plotfilename;
200 if (lev == 0) {
201 const std::string &extension = amrex::Concatenate("_d", lev + 1, 2);
202 FullPath += extension + ".nc";
203 } else {
204 const std::string &extension = amrex::Concatenate("_d", lev + 1 + which_subdomain, 2);
205 FullPath += extension + ".nc";
206 }
207
208 //
209 // Check if this file/directory already exists and if so,
210 // have the IOProcessor move the existing
211 // file/directory to filename.old
212 //
213 if ((!REMORA::write_history_file) || (history_count == 0) ||
214 (which_step_in_chunk == 0)) {
215 if (amrex::ParallelDescriptor::IOProcessor()) {
216 if (amrex::FileExists(FullPath)) {
217 std::string newoldname(FullPath + ".old." + amrex::UniqueString());
218 amrex::Print() << "WriteNCPlotFile: " << FullPath << " exists. Renaming to: " << newoldname << std::endl;
219 if (std::rename(FullPath.c_str(), newoldname.c_str())) {
220 amrex::Abort("WriteNCPlotfile:: std::rename failed");
221 }
222 }
223 }
224 ParallelDescriptor::Barrier();
225 }
226
227 bool is_history;
228
230 is_history = true;
231
232 if (history_file_in_use.size() <= lev) {
233 history_file_in_use.resize(lev+1);
234 history_record_shift.resize(lev+1, 0);
235 }
236 const long long record = chunk_history_file
239
240 // On the first write to a file, fix where its records start. A restarted
241 // run continues an existing file at the restart time, else starts at 0.
244 long long start = record;
245 if (!restart_chkfile.empty()) {
246 start = amrex::FileExists(FullPath)
248 }
250 }
251 bool write_header = !(amrex::FileExists(FullPath));
252
253 auto ncf =
255 ncutils::NCFile::create(FullPath, NC_CLOBBER|NC_64BIT_DATA, amrex::ParallelContext::CommunicatorSub(), MPI_INFO_NULL) :
256 ncutils::NCFile::open(FullPath, NC_WRITE, amrex::ParallelContext::CommunicatorSub(), MPI_INFO_NULL);
257
258 amrex::Print() << "Writing into level " << lev << " NetCDF history file " << FullPath << std::endl;
259
262
263 } else {
264
265 is_history = false;
266 bool write_header = true;
267
268 // Open new netcdf file to write data
269 auto ncf = ncutils::NCFile::create(FullPath, NC_CLOBBER|NC_64BIT_DATA, amrex::ParallelContext::CommunicatorSub(), MPI_INFO_NULL);
270 amrex::Print() << "Writing level " << lev << " NetCDF plot file " << FullPath << std::endl;
271
273 }
274}
275
276/**
277 * Find where a restarted run continues an existing history file: its first
278 * record at or after the restart time. A file for a different grid is renamed
279 * aside, and the run starts a new one at record 0.
280 *
281 * @param lev level the file holds
282 * @param path history file to continue
283 * @return record to write the restart step to
284 */
285long long REMORA::history_restart_record (int lev, const std::string& path)
286{
287 const Box subdomain = (lev == 0) ? geom[lev].Domain()
288 : boxes_at_level[lev][0];
289 const double t = model_time(t_new[lev]);
290 long long nrec = 0;
291 long long first = 0;
292 bool fits;
293 {
295 path, NC_NOWRITE, amrex::ParallelContext::CommunicatorSub(),
297 auto dim_is = [&] (const std::string& name, long long len) {
298 return ncf.has_dim(name) && ncf.dim(name).len() == len;
299 };
300 fits = dim_is("xi_rho", subdomain.length(0) + 2) &&
301 dim_is("eta_rho", subdomain.length(1) + 2) &&
302 dim_is("s_rho", subdomain.length(2)) &&
303 ncf.has_var("ocean_time");
304 if (fits) {
305 nrec = ncf.dim("ocean_time").len();
306 std::vector<double> times(nrec);
307 if (nrec > 0) {
308 ncf.var("ocean_time").get_all(times.data(), {0}, {nrec});
309 }
310 // Skip records from before the restart, allowing half a step
311 const double slack = 0.5 * static_cast<double>(dt[lev]);
312 while (first < nrec && times[first] < t - slack) { ++first; }
313 }
314 ncf.close();
315 }
316
317 if (!fits) {
318 if (amrex::ParallelDescriptor::IOProcessor()) {
319 std::string newoldname(path + ".old." + amrex::UniqueString());
320 amrex::Print() << "WriteNCPlotFile: " << path
321 << " does not match level " << lev
322 << ". Renaming to: " << newoldname << std::endl;
323 if (std::rename(path.c_str(), newoldname.c_str())) {
324 amrex::Abort("WriteNCPlotfile:: std::rename failed");
325 }
326 }
327 ParallelDescriptor::Barrier();
328 return 0;
329 }
330
331 amrex::Print() << "WriteNCPlotFile: continuing " << path
332 << " at record " << first;
333 if (nrec > first) {
334 amrex::Print() << " (" << nrec - first
335 << " later record(s) from the earlier run remain"
336 << " until written over)";
337 }
338 amrex::Print() << std::endl;
339 return first;
340}
341
342/**
343 * @param lev level of data to output
344 * @param which_subdomain index of subdomain if lev != 0
345 * @param plotMF cell-centered plot variables at this level
346 * @param mf_omega omega at w points, or nullptr when it is not plotted
347 * @param mf_wvel w at w points, or nullptr when it is not plotted
348 * @param write_header whether to write a header
349 * @param ncf netcdf file object
350 * @param is_history whether the file being written is a history file
351 * @param history_record record of the history file to write to
352 */
354 MultiFab const* mf_omega, MultiFab const* mf_wvel,
356 bool is_history, long long history_record)
357{
358 // Number of cells in this "domain" at this level
359 std::vector<int> n_cells;
360
361 // One level per file, so the geometry records below describe just this level.
362 int flev = lev + 1;
363
364 Box subdomain;
365 if (lev == 0) {
366 subdomain = geom[lev].Domain();
367 } else {
369 }
370
371 int nx = subdomain.length(0);
372 int ny = subdomain.length(1);
373 int nz = subdomain.length(2);
374
375 n_cells.push_back(nx);
376 n_cells.push_back(ny);
377 n_cells.push_back(nz);
378
379 const std::string nt_name = "ocean_time";
380 const std::string ndim_name = "num_geo_dimensions";
381
382 const std::string flev_name = "FINEST_LEVEL";
383
384 const std::string nx_name = "NX";
385 const std::string ny_name = "NY";
386 const std::string nz_name = "NZ";
387
388 const std::string nx_r_name = "xi_rho";
389 const std::string ny_r_name = "eta_rho";
390 const std::string nz_r_name = "s_rho";
391
392 const std::string nx_u_name = "xi_u";
393 const std::string ny_u_name = "eta_u";
394
395 const std::string nx_v_name = "xi_v";
396 const std::string ny_v_name = "eta_v";
397
398 const std::string nx_p_name = "xi_psi";
399 const std::string ny_p_name = "eta_psi";
400 const std::string nz_w_name = "s_w";
401
402 if (write_header) {
403 ncf.enter_def_mode();
404 ncf.put_attr("title", "REMORA data ");
405 // The time dimension is unlimited so the record count reflects what was
406 // actually written rather than an up-front estimate from max_step/plot_int.
407 // Note PnetCDF does not prefill record variables, so every element of a
408 // time-varying variable has to be written explicitly (see the loops below).
409 ncf.def_dim(nt_name, NC_UNLIMITED);
410 ncf.def_dim(ndim_name, AMREX_SPACEDIM);
411
412 ncf.def_dim(nx_r_name, nx + 2);
413 ncf.def_dim(ny_r_name, ny + 2);
414 ncf.def_dim(nz_r_name, nz);
415
416 ncf.def_dim(nx_u_name, nx + 1);
417 ncf.def_dim(ny_u_name, ny + 2);
418
419 ncf.def_dim(nx_v_name, nx + 2);
420 ncf.def_dim(ny_v_name, ny + 1);
421
422 ncf.def_dim(nx_p_name, nx + 1);
423 ncf.def_dim(ny_p_name, ny + 1);
424
425 ncf.def_dim(nz_w_name, nz + 1);
426
427 ncf.def_dim(flev_name, flev);
428
429 ncf.def_dim(nx_name, n_cells[0]);
430 ncf.def_dim(ny_name, n_cells[1]);
431 ncf.def_dim(nz_name, n_cells[2]);
432
433 ncf.def_var("probLo", ncutils::NCDType::Real, { ndim_name });
434 ncf.var("probLo").put_attr("long_name","Low side of problem domain in internal AMReX grid");
435 ncf.var("probLo").put_attr("units","meter");
436 ncf.def_var("probHi", ncutils::NCDType::Real, { ndim_name });
437 ncf.var("probHi").put_attr("long_name","High side of problem domain in internal AMReX grid");
438 ncf.var("probHi").put_attr("units","meter");
439
440 ncf.def_var("Geom.smallend", NC_INT, { flev_name, ndim_name });
441 ncf.var("Geom.smallend").put_attr("long_name","Low side of problem domain in index space");
442 ncf.def_var("Geom.bigend", NC_INT, { flev_name, ndim_name });
443 ncf.var("Geom.bigend").put_attr("long_name","High side of problem domain in index space");
444 ncf.def_var("CellSize", ncutils::NCDType::Real, { flev_name, ndim_name });
445 ncf.var("CellSize").put_attr("long_name","Cell size on internal AMReX grid");
446 ncf.var("CellSize").put_attr("units","meter");
447
448 ncf.def_var("theta_s",ncutils::NCDType::Real,{});
449 ncf.var("theta_s").put_attr("long_name","S-coordinate surface control parameter");
450 ncf.def_var("theta_b",ncutils::NCDType::Real,{});
451 ncf.var("theta_b").put_attr("long_name","S-coordinate bottom control parameter");
452 ncf.def_var("hc",ncutils::NCDType::Real,{});
453 ncf.var("hc").put_attr("long_name","S-coordinate parameter, critical depth");
454 ncf.var("hc").put_attr("units","meter");
455
456 ncf.def_var("grid",NC_INT, {});
457 ncf.var("grid").put_attr("cf_role","grid_topology");
458 ncf.var("grid").put_attr("topology_dimension",std::vector({2}));
459 ncf.var("grid").put_attr("node_dimensions", "xi_psi eta_psi");
460 ncf.var("grid").put_attr("face_dimensions", "xi_rho: xi_psi (padding: both) eta_rho: eta_psi (padding: both)");
461 ncf.var("grid").put_attr("edge1_dimensions", "xi_u: xi_psi eta_u: eta_psi (padding: both)");
462 ncf.var("grid").put_attr("edge2_dimensions", "xi_v: xi_psi (padding: both) eta_v: eta_psi");
463 ncf.var("grid").put_attr("node_coordinates", "x_psi y_psi");
464 ncf.var("grid").put_attr("face_coordinates", "x_rho y_rho");
465 ncf.var("grid").put_attr("edge1_coordinates", "x_u y_u");
466 ncf.var("grid").put_attr("edge2_coordinates", "x_v y_v");
467 ncf.var("grid").put_attr("vertical_dimensions", "s_rho: s_w (padding: none)");
468
469 ncf.def_var("s_rho",ncutils::NCDType::Real, {nz_r_name});
470 ncf.var("s_rho").put_attr("long_name","S-coordinate at RHO-points");
471 ncf.var("s_rho").put_attr("field","s_rho, scalar");
472
473 ncf.def_var("s_w",ncutils::NCDType::Real, {nz_w_name});
474 ncf.var("s_w").put_attr("long_name","S-coordinate at W-points");
475 ncf.var("s_w").put_attr("field","s_w, scalar");
476
478 ncf.var("pm").put_attr("long_name","curvilinear coordinate metric in XI");
479 ncf.var("pm").put_attr("units","meter-1");
480 ncf.var("pm").put_attr("grid","grid");
481 ncf.var("pm").put_attr("location","face");
482 ncf.var("pm").put_attr("coordinates","x_rho y_rho");
483 ncf.var("pm").put_attr("field","pm, scalar");
484
486 ncf.var("pn").put_attr("long_name","curvilinear coordinate metric in ETA");
487 ncf.var("pn").put_attr("units","meter-1");
488 ncf.var("pn").put_attr("grid","grid");
489 ncf.var("pn").put_attr("location","face");
490 ncf.var("pn").put_attr("coordinates","x_rho y_rho");
491 ncf.var("pn").put_attr("field","pn, scalar");
492
494 ncf.var("f").put_attr("long_name","Coriolis parameter at RHO-points");
495 ncf.var("f").put_attr("units","second-1");
496 ncf.var("f").put_attr("grid","grid");
497 ncf.var("f").put_attr("location","face");
498 ncf.var("f").put_attr("coordinates","x_rho y_rho");
499 ncf.var("f").put_attr("field","coriolis, scalar");
500
501 ncf.def_var("x_rho",ncutils::NCDType::Real, {ny_r_name, nx_r_name});
502 ncf.var("x_rho").put_attr("long_name","x-locations of RHO-points");
503 ncf.var("x_rho").put_attr("units","meter");
504 ncf.var("x_rho").put_attr("field","x_rho, scalar");
505
506 ncf.def_var("y_rho",ncutils::NCDType::Real, {ny_r_name, nx_r_name});
507 ncf.var("y_rho").put_attr("long_name","y-locations of RHO-points");
508 ncf.var("y_rho").put_attr("units","meter");
509 ncf.var("y_rho").put_attr("field","y_rho, scalar");
510
512 ncf.var("x_u").put_attr("long_name","x-locations of U-points");
513 ncf.var("x_u").put_attr("units","meter");
514 ncf.var("x_u").put_attr("field","x_u, scalar");
515
517 ncf.var("y_u").put_attr("long_name","y-locations of U-points");
518 ncf.var("y_u").put_attr("units","meter");
519 ncf.var("y_u").put_attr("field","y_u, scalar");
520
522 ncf.var("x_v").put_attr("long_name","x-locations of V-points");
523 ncf.var("x_v").put_attr("units","meter");
524 ncf.var("x_v").put_attr("field","x_v, scalar");
525
527 ncf.var("y_v").put_attr("long_name","y-locations of V-points");
528 ncf.var("y_v").put_attr("units","meter");
529 ncf.var("y_v").put_attr("field","y_v, scalar");
530
531 ncf.def_var("x_psi",ncutils::NCDType::Real, {ny_p_name, nx_p_name});
532 ncf.var("x_psi").put_attr("long_name","x-locations of PSI-points");
533 ncf.var("x_psi").put_attr("units","meter");
534 ncf.var("x_psi").put_attr("field","x_psi, scalar");
535
536 ncf.def_var("y_psi",ncutils::NCDType::Real, {ny_p_name, nx_p_name});
537 ncf.var("y_psi").put_attr("long_name","y-locations of PSI-points");
538 ncf.var("y_psi").put_attr("units","meter");
539 ncf.var("y_psi").put_attr("field","y_psi, scalar");
540
541 // Both time stamps run on the model clock, so both carry the reference
542 // date remora.time_ref names, as ROMS writes Rclock%string.
545
546 // Double regardless of Real, like dstart: seconds since the reference
547 // date outgrow a float's resolution within days.
548 ncf.def_var("ocean_time", NC_DOUBLE, { nt_name });
549 ncf.var("ocean_time").put_attr("long_name","time since initialization");
550 ncf.var("ocean_time").put_attr("units","seconds since " + ref_date_string);
551 ncf.var("ocean_time").put_attr("calendar",ref_calendar);
552 ncf.var("ocean_time").put_attr("field","time, scalar, series");
553
554 // ROMS DSTART: when the run starts, in days on the model clock. Double
555 // regardless of Real -- days since year 1 needs more than a float's
556 // seven significant digits, which would round this to the nearest hour.
557 ncf.def_var("dstart", NC_DOUBLE, {});
558 ncf.var("dstart").put_attr("long_name","time stamp assigned to model initialization");
559 ncf.var("dstart").put_attr("units","days since " + ref_date_string);
560 ncf.var("dstart").put_attr("calendar",ref_calendar);
561
562 ncf.def_var("Cs_r", ncutils::NCDType::Real, {nz_r_name});
563 ncf.var("Cs_r").put_attr("long_name", "S-coordinate stretching curves at RHO points");
564 ncf.var("Cs_r").put_attr("valid_min",std::vector({-one}));
565 ncf.var("Cs_r").put_attr("valid_max",std::vector({zero}));
566 ncf.var("Cs_r").put_attr("field","Cs_r, scalar");
567
568 ncf.def_var("Cs_w", ncutils::NCDType::Real, {nz_w_name});
569 ncf.var("Cs_w").put_attr("long_name", "S-coordinate stretching curves at W points");
570 ncf.var("Cs_w").put_attr("valid_min",std::vector({-one}));
571 ncf.var("Cs_w").put_attr("valid_max",std::vector({zero}));
572 ncf.var("Cs_w").put_attr("field","Cs_w, scalar");
573
574 ncf.def_var("h", ncutils::NCDType::Real, { ny_r_name, nx_r_name });
575 ncf.var("h").put_attr("long_name","bathymetry at RHO-points");
576 ncf.var("h").put_attr("units","meter");
577 ncf.var("h").put_attr("grid","grid");
578 ncf.var("h").put_attr("location","face");
579 ncf.var("h").put_attr("coordinates","x_rho y_rho");
580 ncf.var("h").put_attr("field","bath, scalar");
581
583 ncf.var("zeta").put_attr("long_name","free-surface");
584 ncf.var("zeta").put_attr("units","meter");
585 ncf.var("zeta").put_attr("time","ocean_time");
586 ncf.var("zeta").put_attr("grid","grid");
587 ncf.var("zeta").put_attr("location","face");
588 ncf.var("zeta").put_attr("coordinates","x_rho y_rho ocean_time");
589 ncf.var("zeta").put_attr("field","free-surface, scalar, series");
590
591 for (int n = 0; n < ncons; ++n) {
592 int comp = -1;
593 for (int i = 0; i < plot_var_names_3d.size(); i++) {
594 if (plot_var_names_3d[i] == cons_names[n]) comp = i;
595 }
596 if (comp >= 0) {
597 const std::string& nm = cons_names[n];
599 if (n == Temp_comp) {
600 ncf.var(nm).put_attr("long_name", "potential temperature");
601 ncf.var(nm).put_attr("units", "Celsius");
602 ncf.var(nm).put_attr("field", "temperature, scalar, series");
603 } else if (n == Salt_comp) {
604 ncf.var(nm).put_attr("long_name", "salinity");
605 ncf.var(nm).put_attr("field", "salinity, scalar, series");
606 } else {
607 ncf.var(nm).put_attr("long_name", nm);
608 ncf.var(nm).put_attr("field", nm + ", scalar, series");
609 }
610 ncf.var(nm).put_attr("time", "ocean_time");
611 ncf.var(nm).put_attr("grid", "grid");
612 ncf.var(nm).put_attr("location", "face");
613 ncf.var(nm).put_attr("coordinates", "x_rho y_rho s_rho ocean_time");
614 }
615 }
616
617 {
618 int comp = -1;
619 for (int i = 0; i < plot_var_names_3d.size(); i++) {
620 if (plot_var_names_3d[i] == "vorticity") comp = i;
621 }
622 if (comp >= 0) {
624 ncf.var("vorticity").put_attr("long_name","vorticity");
625 ncf.var("vorticity").put_attr("time","ocean_time");
626 ncf.var("vorticity").put_attr("grid","grid");
627 ncf.var("vorticity").put_attr("location","face");
628 ncf.var("vorticity").put_attr("coordinates","x_rho y_rho s_rho ocean_time");
629 ncf.var("vorticity").put_attr("field","vorticity, scalar, series");
630 }
631 } // end vorticity
632
633 if (plot_omega) {
635 ncf.var("omega").put_attr("long_name","S-coordinate vertical momentum component");
636 ncf.var("omega").put_attr("units","meter second-1");
637 ncf.var("omega").put_attr("time","ocean_time");
638 ncf.var("omega").put_attr("grid","grid");
639 ncf.var("omega").put_attr("location","face");
640 ncf.var("omega").put_attr("coordinates","x_rho y_rho s_w ocean_time");
641 ncf.var("omega").put_attr("field","omega, scalar, series");
642 }
643
644 if (plot_wvel) {
646 ncf.var("w").put_attr("long_name","vertical momentum component");
647 ncf.var("w").put_attr("units","meter second-1");
648 ncf.var("w").put_attr("time","ocean_time");
649 ncf.var("w").put_attr("grid","grid");
650 ncf.var("w").put_attr("location","face");
651 ncf.var("w").put_attr("coordinates","x_rho y_rho s_w ocean_time");
652 ncf.var("w").put_attr("field","w-velocity, scalar, series");
653 }
654
655 // Output 2D horizontal mixing coefficients if using scaled_to_grid option
657 ncf.def_var_fill("visc2", ncutils::NCDType::Real, { ny_r_name, nx_r_name }, &netcdf_fill_value);
658 ncf.var("visc2").put_attr("long_name","horizontal harmonic viscosity coefficient at RHO-points");
659 ncf.var("visc2").put_attr("units","meter2 second-1");
660 ncf.var("visc2").put_attr("grid","grid");
661 ncf.var("visc2").put_attr("location","face");
662 ncf.var("visc2").put_attr("coordinates","x_rho y_rho");
663 ncf.var("visc2").put_attr("field","visc2, scalar");
664
665 for (int n = 0; n < ncons; ++n) {
666 const std::string nm = std::string("diff2_") + cons_names[n];
668 ncf.var(nm).put_attr("long_name", std::string("horizontal harmonic diffusivity coefficient for ") + cons_names[n] + " at RHO-points");
669 ncf.var(nm).put_attr("units","meter2 second-1");
670 ncf.var(nm).put_attr("grid","grid");
671 ncf.var(nm).put_attr("location","face");
672 ncf.var(nm).put_attr("coordinates","x_rho y_rho");
673 ncf.var(nm).put_attr("field", nm + ", scalar");
674 }
675 }
676
678 ncf.var("u").put_attr("long_name","u-momentum component");
679 ncf.var("u").put_attr("units","meter second-1");
680 ncf.var("u").put_attr("time","ocean_time");
681 ncf.var("u").put_attr("grid","grid");
682 ncf.var("u").put_attr("location","edge1");
683 ncf.var("u").put_attr("coordinates","x_u y_u s_rho ocean_time");
684 ncf.var("u").put_attr("field","u-velocity, scalar, series");
685
687 ncf.var("v").put_attr("long_name","v-momentum component");
688 ncf.var("v").put_attr("units","meter second-1");
689 ncf.var("v").put_attr("time","ocean_time");
690 ncf.var("v").put_attr("grid","grid");
691 ncf.var("v").put_attr("location","edge2");
692 ncf.var("v").put_attr("coordinates","x_v y_v s_rho ocean_time");
693 ncf.var("v").put_attr("field","v-velocity, scalar, series");
694
696 ncf.var("ubar").put_attr("long_name","vertically integrated u-momentum component");
697 ncf.var("ubar").put_attr("units","meter second-1");
698 ncf.var("ubar").put_attr("time","ocean_time");
699 ncf.var("ubar").put_attr("grid","grid");
700 ncf.var("ubar").put_attr("location","edge1");
701 ncf.var("ubar").put_attr("coordinates","x_u y_u ocean_time");
702 ncf.var("ubar").put_attr("field","ubar-velocity, scalar, series");
703
705 ncf.var("vbar").put_attr("long_name","vertically integrated v-momentum component");
706 ncf.var("vbar").put_attr("units","meter second-1");
707 ncf.var("vbar").put_attr("time","ocean_time");
708 ncf.var("vbar").put_attr("grid","grid");
709 ncf.var("vbar").put_attr("location","edge2");
710 ncf.var("vbar").put_attr("coordinates","x_v y_v ocean_time");
711 ncf.var("vbar").put_attr("field","vbar-velocity, scalar, series");
712
713 ncf.def_var("sustr", ncutils::NCDType::Real, { nt_name, ny_u_name, nx_u_name });
714 ncf.var("sustr").put_attr("long_name","surface u-momentum stress");
715 ncf.var("sustr").put_attr("units","newton meter-2");
716 ncf.var("sustr").put_attr("time","ocean_time");
717 ncf.var("sustr").put_attr("grid","grid");
718 ncf.var("sustr").put_attr("location","edge1");
719 ncf.var("sustr").put_attr("coordinates","x_u y_u ocean_time");
720 ncf.var("sustr").put_attr("field","surface u-momentum stress, scalar, series");
721
722 ncf.def_var("svstr", ncutils::NCDType::Real, { nt_name, ny_v_name, nx_v_name });
723 ncf.var("svstr").put_attr("long_name","surface v-momentum stress");
724 ncf.var("svstr").put_attr("units","newton meter-2");
725 ncf.var("svstr").put_attr("time","ocean_time");
726 ncf.var("svstr").put_attr("grid","grid");
727 ncf.var("svstr").put_attr("location","edge2");
728 ncf.var("svstr").put_attr("coordinates","x_v y_v ocean_time");
729 ncf.var("svstr").put_attr("field","surface v-momentum stress, scalar, series");
730
731 ncf.def_var("mask_rho", ncutils::NCDType::Real, { nt_name, ny_r_name, nx_r_name });
732 ncf.var("mask_rho").put_attr("long_name","mask on RHO-points");
733 ncf.var("mask_rho").put_attr("time","ocean_time");
734 ncf.var("mask_rho").put_attr("flag_values",std::vector({Real(0.0),Real(1.0)}));
735 ncf.var("mask_rho").put_attr("flag_meanings","land water");
736
737 ncf.def_var("mask_u", ncutils::NCDType::Real, { nt_name, ny_u_name, nx_u_name });
738 ncf.var("mask_u").put_attr("long_name","mask on U-points");
739 ncf.var("mask_u").put_attr("time","ocean_time");
740 ncf.var("mask_u").put_attr("flag_values",std::vector({Real(0.0),Real(1.0)}));
741 ncf.var("mask_u").put_attr("flag_meanings","land water");
742
743 ncf.def_var("mask_v", ncutils::NCDType::Real, { nt_name, ny_v_name, nx_v_name });
744 ncf.var("mask_v").put_attr("long_name","mask on V-points");
745 ncf.var("mask_v").put_attr("time","ocean_time");
746 ncf.var("mask_v").put_attr("flag_values",std::vector({Real(0.0),Real(1.0)}));
747 ncf.var("mask_v").put_attr("flag_meanings","land water");
748
749 // Surface tracer fluxes, one per cell-centered tracer, matching what the AMReX
750 // plotfile writer already offers and requested through the same remora.plot_vars_2d
751 // key. vec_stflux is ncons wide and unconditionally allocated, so any tracer can be
752 // named. Components past temp and salt read zero unless something fills them --
753 // Fennel's air-sea gas exchange acts on the tracer directly rather than through this
754 // array -- so they show what the solver actually applied, which may be nothing.
755 for (int n = 0; n < ncons; ++n) {
756 const std::string nm = std::string("stflux_") + cons_names[n];
757 if (!plot_2d_var_requested(plot_var_names_2d, nm)) { continue; }
759 ncf.var(nm).put_attr("long_name", std::string("surface flux of ") + cons_names[n]);
760 // Kinematic, as ssflux is: the tracer's own units times meter second-1.
761 ncf.var(nm).put_attr("units", (n == Temp_comp) ? "Celsius meter second-1"
762 : "meter second-1");
763 ncf.var(nm).put_attr("time","ocean_time");
764 ncf.var(nm).put_attr("grid","grid");
765 ncf.var(nm).put_attr("location","face");
766 ncf.var(nm).put_attr("coordinates","x_rho y_rho ocean_time");
767 ncf.var(nm).put_attr("field", nm + ", scalar, series");
768 }
769
771 // Surface air temperature (Celsius)
773 ncf.var("Tair").put_attr("long_name","surface air temperature");
774 ncf.var("Tair").put_attr("units","Celsius");
775 ncf.var("Tair").put_attr("time","ocean_time");
776 ncf.var("Tair").put_attr("grid","grid");
777 ncf.var("Tair").put_attr("location","face");
778 ncf.var("Tair").put_attr("coordinates","x_rho y_rho ocean_time");
779 ncf.var("Tair").put_attr("field","Tair, scalar, series");
780
781 // Surface air pressure (Pascal)
783 ncf.var("Pair").put_attr("long_name","surface air pressure");
784 ncf.var("Pair").put_attr("units","Pascal");
785 ncf.var("Pair").put_attr("time","ocean_time");
786 ncf.var("Pair").put_attr("grid","grid");
787 ncf.var("Pair").put_attr("location","face");
788 ncf.var("Pair").put_attr("coordinates","x_rho y_rho ocean_time");
789 ncf.var("Pair").put_attr("field","Pair, scalar, series");
790
791 // Surface net heat flux (W/m2)
793 ncf.var("qnet").put_attr("long_name","surface net heat flux");
794 ncf.var("qnet").put_attr("units","watt meter-2");
795 ncf.var("qnet").put_attr("time","ocean_time");
796 ncf.var("qnet").put_attr("grid","grid");
797 ncf.var("qnet").put_attr("location","face");
798 ncf.var("qnet").put_attr("coordinates","x_rho y_rho ocean_time");
799 ncf.var("qnet").put_attr("field","surface heat flux, scalar, series");
800
801 // Surface net salt flux (kinematic)
802 ncf.def_var("ssflux", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
803 ncf.var("ssflux").put_attr("long_name","kinematic surface net salt flux, SALT*(E-P)/rhow");
804 ncf.var("ssflux").put_attr("units","meter second-1");
805 ncf.var("ssflux").put_attr("time","ocean_time");
806 ncf.var("ssflux").put_attr("grid","grid");
807 ncf.var("ssflux").put_attr("location","face");
808 ncf.var("ssflux").put_attr("coordinates","x_rho y_rho ocean_time");
809 ncf.var("ssflux").put_attr("field","surface net salt flux, scalar, series");
810
811 // Latent heat flux (W/m2)
812 ncf.def_var("latent", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
813 ncf.var("latent").put_attr("long_name","net latent heat flux");
814 ncf.var("latent").put_attr("units","watt meter-2");
815 ncf.var("latent").put_attr("time","ocean_time");
816 ncf.var("latent").put_attr("grid","grid");
817 ncf.var("latent").put_attr("location","face");
818 ncf.var("latent").put_attr("coordinates","x_rho y_rho ocean_time");
819 ncf.var("latent").put_attr("field","latent heat flux, scalar, series");
820
821 // Sensible heat flux (W/m2)
822 ncf.def_var("sensible", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
823 ncf.var("sensible").put_attr("long_name","net sensible heat flux");
824 ncf.var("sensible").put_attr("units","watt meter-2");
825 ncf.var("sensible").put_attr("time","ocean_time");
826 ncf.var("sensible").put_attr("grid","grid");
827 ncf.var("sensible").put_attr("location","face");
828 ncf.var("sensible").put_attr("coordinates","x_rho y_rho ocean_time");
829 ncf.var("sensible").put_attr("field","sensible heat flux, scalar, series");
830
831 // Longwave radiation (W/m2)
832 ncf.def_var("lwrad", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
833 ncf.var("lwrad").put_attr("long_name","net longwave radiation flux");
834 ncf.var("lwrad").put_attr("units","watt meter-2");
835 ncf.var("lwrad").put_attr("time","ocean_time");
836 ncf.var("lwrad").put_attr("grid","grid");
837 ncf.var("lwrad").put_attr("location","face");
838 ncf.var("lwrad").put_attr("coordinates","x_rho y_rho ocean_time");
839 ncf.var("lwrad").put_attr("field","longwave radiation, scalar, series");
840
841 // Shortwave radiation (W/m2)
842 ncf.def_var("swrad", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
843 ncf.var("swrad").put_attr("long_name","solar shortwave radiation flux");
844 ncf.var("swrad").put_attr("units","watt meter-2");
845 ncf.var("swrad").put_attr("time","ocean_time");
846 ncf.var("swrad").put_attr("grid","grid");
847 ncf.var("swrad").put_attr("location","face");
848 ncf.var("swrad").put_attr("coordinates","x_rho y_rho ocean_time");
849 ncf.var("swrad").put_attr("field","shortwave radiation, scalar, series");
850
851 // Evaporation rate (kg m-2 s-1)
852 ncf.def_var("evaporation", ncutils::NCDType::Real, {nt_name, ny_r_name, nx_r_name });
853 ncf.var("evaporation").put_attr("long_name","evaporation rate");
854 ncf.var("evaporation").put_attr("units","kilogram meter-2 second-1");
855 ncf.var("evaporation").put_attr("time","ocean_time");
856 ncf.var("evaporation").put_attr("grid","grid");
857 ncf.var("evaporation").put_attr("location","face");
858 ncf.var("evaporation").put_attr("coordinates","x_rho y_rho ocean_time");
859 ncf.var("evaporation").put_attr("field","evaporation, scalar, series");
860
861 // Rain rate (kg m-2 s-1)
863 ncf.var("rain").put_attr("long_name","rain fall rate");
864 ncf.var("rain").put_attr("units","kilogram meter-2 second-1");
865 ncf.var("rain").put_attr("time","ocean_time");
866 ncf.var("rain").put_attr("grid","grid");
867 ncf.var("rain").put_attr("location","face");
868 ncf.var("rain").put_attr("coordinates","x_rho y_rho ocean_time");
869 ncf.var("rain").put_attr("field","rain, scalar, series");
870 }
871 // Right now this is hard-wired to {temp, salt, tracer, u, v}
872 ncf.put_attr("space_dimension", std::vector<int> { AMREX_SPACEDIM });
873// ncf.put_attr("current_time", std::vector<double> { time });
874 ncf.put_attr("CurrentLevel", std::vector<int> { flev });
875 ncf.put_attr("DefaultGeometry", std::vector<int> { amrex::DefaultGeometry().Coord() });
876
877 ncf.exit_def_mode();
878
879 // We are doing single-level writes but it doesn't have to be level 0
880 //
881 // Write out the header information.
882 //
883
884 Real dx[AMREX_SPACEDIM];
885 for (int i = 0; i < AMREX_SPACEDIM; i++) {
886 dx[i] = geom[lev].CellSize()[i];
887 }
888 const auto *base = geom[lev].ProbLo();
889 RealBox rb(subdomain, dx, base);
890
891 amrex::Vector<Real> probLo;
892 amrex::Vector<Real> probHi;
893 for (int i = 0; i < AMREX_SPACEDIM; i++) {
894 probLo.push_back(rb.lo(i));
895 probHi.push_back(rb.hi(i));
896 }
897
898 //nc_probLo.par_access(NC_COLLECTIVE);
899 // small variable data written by just the master proc
901 if (amrex::ParallelDescriptor::IOProcessor()) // only master proc
902 {
903 auto nc_probLo = ncf.var("probLo");
904
905 nc_probLo.put(probLo.data(), { 0 }, { AMREX_SPACEDIM });
906
907 auto nc_probHi = ncf.var("probHi");
908 //nc_probHi.par_access(NC_COLLECTIVE);
909 nc_probHi.put(probHi.data(), { 0 }, { AMREX_SPACEDIM });
910
911 amrex::Vector<int> smallend;
912 amrex::Vector<int> bigend;
913 for (int i = lev; i < flev; i++) {
914 smallend.clear();
915 bigend.clear();
916 for (int j = 0; j < AMREX_SPACEDIM; j++) {
917 smallend.push_back(subdomain.smallEnd(j));
918 bigend.push_back(subdomain.bigEnd(j));
919 }
920 auto nc_Geom_smallend = ncf.var("Geom.smallend");
921 //nc_Geom_smallend.par_access(NC_COLLECTIVE);
922 nc_Geom_smallend.put(smallend.data(), { static_cast<long long int>(i - lev), 0 }, { 1,
923 AMREX_SPACEDIM });
924
925 auto nc_Geom_bigend = ncf.var("Geom.bigend");
926 //nc_Geom_bigend.par_access(NC_COLLECTIVE);
927 nc_Geom_bigend.put(bigend.data(), { static_cast<long long int>(i - lev), 0 }, { 1,
928 AMREX_SPACEDIM });
929 }
930
931 amrex::Vector<Real> CellSize;
932 for (int i = lev; i < flev; i++) {
933 CellSize.clear();
934 for (Real &j : dx) {
935 CellSize.push_back(amrex::Real(j));
936 }
937 auto nc_CellSize = ncf.var("CellSize");
938 //nc_CellSize.par_access(NC_COLLECTIVE);
939 nc_CellSize.put(CellSize.data(), { static_cast<long long int>(i - lev), 0 }, { 1,
941 }
942 Real hc = solverChoice.tcline;
943 Real theta_s = solverChoice.theta_s;
944 Real theta_b = solverChoice.theta_b;
945 ncf.var("hc").put(&hc);
946 ncf.var("theta_s").put(&theta_s);
947 ncf.var("theta_b").put(&theta_b);
948
949 // remora.start_time in seconds is ROMS DSTART in days.
950 double dstart = start_time / 86400.0;
951 ncf.var("dstart").put(&dstart);
952
953 }
955
956 } // end if write_header
957
958 // Past this point every write is collective. The loops below stage their
959 // hyperslabs into the collector rather than writing them, because the number
960 // of hyperslabs a rank contributes depends on how many boxes it owns; the
961 // single flush() at the end turns each variable into one ncmpi_put_varn_*_all.
962 VarnCollector collector;
963
964 //
965 // We compute the offsets based on location of the box within the domain
966 //
967 long long local_start_nt = is_history ? history_record : 0;
968 long long local_nt = 1; // We write data for only one time
969
970 // ocean_time is on the model clock, in double. Written here rather than staged,
971 // since the collector holds Real; collective, with only the IO rank contributing.
972 {
973 const double ocean_time = model_time(t_new[lev]);
974 const long long nt_count = amrex::ParallelDescriptor::IOProcessor() ? local_nt : 0;
975 ncf.var("ocean_time").put_all(&ocean_time, { local_start_nt }, { nt_count });
976 }
977
978 // Check whether there are any nans or infs in variables that we will write out
980 amrex::Abort("Found while writing output: zeta contains nan or inf");
981 }
982 // Check every cell-centered tracer that is actually being written: temperature,
983 // salinity, the passive scalars and the biology tracers. plotMF is indexed by
984 // position in plot_var_names_3d, not by cons component, so look the name up the
985 // same way the write loops below do.
986 for (int n = 0; n < ncons; ++n) {
987 int comp = -1;
988 for (int i = 0; i < plot_var_names_3d.size(); i++) {
989 if (plot_var_names_3d[i] == cons_names[n]) comp = i;
990 }
991 if (comp < 0) { continue; }
992 if (plotMF->contains_nan(comp,1) || plotMF->contains_inf(comp,1)) {
993 amrex::Abort("Found while writing output: " + cons_names[n] +
994 " contains nan or inf");
995 }
996 }
998 amrex::Abort("Found while writing output: velocity u contains nan or inf");
999 }
1000 if (vec_ubar[lev]->contains_nan(0,1) || vec_ubar[lev]->contains_inf(0,1)) {
1001 amrex::Abort("Found while writing output: velocity ubar contains nan or inf");
1002 }
1004 amrex::Abort("Found while writing output: velocity v contains nan or inf");
1005 }
1006 if (vec_vbar[lev]->contains_nan(0,1) || vec_vbar[lev]->contains_inf(0,1)) {
1007 amrex::Abort("Found while writing output: velocity vbar contains nan or inf");
1008 }
1009 if (mf_omega && (mf_omega->contains_nan() || mf_omega->contains_inf())) {
1010 amrex::Abort("Found while writing output: omega contains nan or inf");
1011 }
1012 if (mf_wvel && (mf_wvel->contains_nan() || mf_wvel->contains_inf())) {
1013 amrex::Abort("Found while writing output: velocity w contains nan or inf");
1014 }
1015
1016 for (MFIter mfi(*plotMF, false); mfi.isValid(); ++mfi) {
1017 auto bx = mfi.validbox();
1018 if (subdomain.contains(bx)) {
1019 //
1020 // We only include one grow cell at subdomain boundaries, not internal grid boundaries
1021 //
1022 Box tmp_bx(bx);
1023 if (tmp_bx.smallEnd()[0] == subdomain.smallEnd()[0])
1024 tmp_bx.growLo(0, 1);
1025 if (tmp_bx.smallEnd()[1] == subdomain.smallEnd()[1])
1026 tmp_bx.growLo(1, 1);
1027 if (tmp_bx.bigEnd()[0] == subdomain.bigEnd()[0])
1028 tmp_bx.growHi(0, 1);
1029 if (tmp_bx.bigEnd()[1] == subdomain.bigEnd()[1])
1030 tmp_bx.growHi(1, 1);
1031 // amrex::Print() << " BX " << bx << std::endl;
1032 // amrex::Print() << "TMP_BX " << tmp_bx << std::endl;
1033
1034 Box tmp_bx_2d(tmp_bx);
1035 tmp_bx_2d.makeSlab(2, 0);
1036
1037 Box tmp_bx_1d(tmp_bx);
1038 tmp_bx_1d.makeSlab(0, 0);
1039 tmp_bx_1d.makeSlab(1, 0);
1040
1041 //
1042 // These are the dimensions of the data we write for only this box
1043 //
1044 long long local_nx = tmp_bx.length()[0];
1045 long long local_ny = tmp_bx.length()[1];
1046 long long local_nz = tmp_bx.length()[2];
1047
1048 // We do the "+1" because the offset needs to start at 0
1049 // Offsets are relative to this level's subdomain: a refined level's boxes
1050 // start at its own index space, not at zero, while the file is sized to
1051 // that subdomain.
1052 long long local_start_x = static_cast<long long>(tmp_bx.smallEnd()[0] - subdomain.smallEnd()[0] + 1);
1053 long long local_start_y = static_cast<long long>(tmp_bx.smallEnd()[1] - subdomain.smallEnd()[1] + 1);
1054 long long local_start_z = static_cast<long long>(tmp_bx.smallEnd()[2] - subdomain.smallEnd()[2]);
1055
1056 if (write_header) {
1057 // Only write out s_rho and s_w at x=0,y=0 to avoid NaNs
1058 if (bx.contains(subdomain.smallEnd()))
1059 {
1060 {
1061 amrex::Vector<amrex::Real> tmp_srho(local_nz);
1062
1063#ifdef AMREX_USE_GPU
1064 Gpu::dtoh_memcpy(tmp_srho.data(), s_r.data(), sizeof(amrex::Real)*local_nz);
1065#else
1066 std::memcpy(tmp_srho.data(), s_r.data(), sizeof(amrex::Real)*local_nz);
1067#endif
1068 Gpu::streamSynchronize();
1069
1070 auto nc_plot_var = collector.var(ncf, "s_rho");
1071 //nc_plot_var.par_access(NC_INDEPENDENT);
1072 nc_plot_var.put(tmp_srho.data(), { local_start_z }, { local_nz });
1073 }
1074 {
1075 amrex::Vector<amrex::Real> tmp_sw(local_nz+1);
1076
1077#ifdef AMREX_USE_GPU
1078 Gpu::dtoh_memcpy(tmp_sw.data(), s_w.data(), sizeof(amrex::Real)*(local_nz+1));
1079#else
1080 std::memcpy(tmp_sw.data(), s_w.data(), sizeof(amrex::Real)*(local_nz+1));
1081#endif
1082 Gpu::streamSynchronize();
1083
1084 auto nc_plot_var = collector.var(ncf, "s_w");
1085 //nc_plot_var.par_access(NC_INDEPENDENT);
1086 nc_plot_var.put(tmp_sw.data(), { local_start_z }, { local_nz + 1});
1087 }
1088 {
1089 amrex::Vector<amrex::Real> tmp_Csrho(local_nz);
1090
1091#ifdef AMREX_USE_GPU
1092 Gpu::dtoh_memcpy(tmp_Csrho.data(), Cs_r.data(), sizeof(amrex::Real)*(local_nz));
1093#else
1094 std::memcpy(tmp_Csrho.data(), Cs_r.data(), sizeof(amrex::Real)*(local_nz));
1095#endif
1096 Gpu::streamSynchronize();
1097
1098 auto nc_plot_var = collector.var(ncf, "Cs_r");
1099 //nc_plot_var.par_access(NC_INDEPENDENT);
1100 nc_plot_var.put(tmp_Csrho.data(), { local_start_z }, { local_nz });
1101 }
1102 {
1103 amrex::Vector<amrex::Real> tmp_Csw(local_nz+1);
1104
1105#ifdef AMREX_USE_GPU
1106 Gpu::dtoh_memcpy(tmp_Csw.data(), Cs_w.data(), sizeof(amrex::Real)*(local_nz+1));
1107#else
1108 std::memcpy(tmp_Csw.data(), Cs_w.data(), sizeof(amrex::Real)*(local_nz+1));
1109#endif
1110
1111 Gpu::streamSynchronize();
1112
1113 auto nc_plot_var = collector.var(ncf, "Cs_w");
1114 //nc_plot_var.par_access(NC_INDEPENDENT);
1115 nc_plot_var.put(tmp_Csw.data(), { local_start_z }, { local_nz + 1});
1116 }
1117 }
1118
1119 {
1120 FArrayBox tmp_bathy;
1121 tmp_bathy.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1122
1123 tmp_bathy.template copy<RunOn::Device>((*vec_h[lev])[mfi.index()], 0, 0, 1);
1124 Gpu::streamSynchronize();
1125
1126 auto nc_plot_var = collector.var(ncf, "h");
1127 //nc_plot_var.par_access(NC_INDEPENDENT);
1128 nc_plot_var.put(tmp_bathy.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1129 }
1130
1131 {
1132 FArrayBox tmp_pm;
1133 tmp_pm.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1134
1135 tmp_pm.template copy<RunOn::Device>((*vec_pm[lev])[mfi.index()], 0, 0, 1);
1136 Gpu::streamSynchronize();
1137
1138 auto nc_plot_var = collector.var(ncf, "pm");
1139 //nc_plot_var.par_access(NC_INDEPENDENT);
1140
1141 nc_plot_var.put(tmp_pm.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1142 }
1143
1144 {
1145 FArrayBox tmp_pn;
1146 tmp_pn.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1147
1148 tmp_pn.template copy<RunOn::Device>((*vec_pn[lev])[mfi.index()], 0, 0, 1);
1149 Gpu::streamSynchronize();
1150
1151 auto nc_plot_var = collector.var(ncf, "pn");
1152 //nc_plot_var.par_access(NC_INDEPENDENT);
1153 nc_plot_var.put(tmp_pn.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1154 }
1155
1156 {
1157 FArrayBox tmp_f;
1158 tmp_f.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1159
1160 tmp_f.template copy<RunOn::Device>((*vec_fcor[lev])[mfi.index()], 0, 0, 1);
1161 Gpu::streamSynchronize();
1162
1163 auto nc_plot_var = collector.var(ncf, "f");
1164 //nc_plot_var.par_access(NC_INDEPENDENT);
1165 nc_plot_var.put(tmp_f.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1166 }
1167
1168 {
1169 FArrayBox tmp_xr;
1170 tmp_xr.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1171
1172 tmp_xr.template copy<RunOn::Device>((*vec_xr[lev])[mfi.index()], 0, 0, 1);
1173 Gpu::streamSynchronize();
1174
1175 auto nc_plot_var = collector.var(ncf, "x_rho");
1176 //nc_plot_var.par_access(NC_INDEPENDENT);
1177
1178 nc_plot_var.put(tmp_xr.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1179 }
1180
1181 {
1182 FArrayBox tmp_yr;
1183 tmp_yr.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1184
1185 tmp_yr.template copy<RunOn::Device>((*vec_yr[lev])[mfi.index()], 0, 0, 1);
1186 Gpu::streamSynchronize();
1187
1188 auto nc_plot_var = collector.var(ncf, "y_rho");
1189 //nc_plot_var.par_access(NC_INDEPENDENT);
1190 nc_plot_var.put(tmp_yr.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1191 }
1192 }
1193
1194 {
1195 FArrayBox tmp_zeta;
1196 tmp_zeta.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1197 tmp_zeta.template copy<RunOn::Device>((*vec_Zt_avg1[lev])[mfi.index()], 0, 0, 1);
1198 Gpu::streamSynchronize();
1199
1200 auto nc_plot_var = collector.var(ncf, "zeta");
1201 nc_plot_var.put(tmp_zeta.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny,
1202 local_nx });
1203 }
1204
1205 {
1206 FArrayBox tmp_mask_rho;
1207 tmp_mask_rho.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1208 tmp_mask_rho.template copy<RunOn::Device>((*vec_mskr[lev])[mfi.index()], 0, 0, 1);
1209 Gpu::streamSynchronize();
1210
1211 auto nc_plot_var = collector.var(ncf, "mask_rho");
1212 nc_plot_var.put(tmp_mask_rho.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny,
1213 local_nx });
1214 }
1215
1216 // stflux_*, one per tracer. Defined above under the same condition.
1217 for (int n = 0; n < ncons; ++n) {
1218 const std::string nm = std::string("stflux_") + cons_names[n];
1219 if (!plot_2d_var_requested(plot_var_names_2d, nm)) { continue; }
1220 FArrayBox tmp;
1221 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1222 tmp.template copy<RunOn::Device>((*vec_stflux[lev])[mfi.index()], n, 0, 1);
1223 Gpu::streamSynchronize();
1224
1225 auto nc_var = collector.var(ncf, nm);
1226 nc_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x },
1227 { local_nt, local_ny, local_nx });
1228 }
1229
1231 {
1232 const Real Hscale = solverChoice.rho0 * Cp;
1233 // The copy and the mult below are both async on the same stream, so the
1234 // sync has to follow the last of them and precede the host-side put().
1235 // Tair
1236 {
1237 FArrayBox tmp_Tair;
1238 tmp_Tair.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1239 tmp_Tair.template copy<RunOn::Device>((*vec_Tair[lev])[mfi.index()], 0, 0, 1);
1240 Gpu::streamSynchronize();
1241
1242 auto nc_plot_var = collector.var(ncf, "Tair");
1243 nc_plot_var.put(tmp_Tair.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1244 }
1245 // Pair
1246 {
1247 FArrayBox tmp_Pair;
1248 tmp_Pair.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1249 tmp_Pair.template copy<RunOn::Device>((*vec_Pair[lev])[mfi.index()], 0, 0, 1);
1250 Gpu::streamSynchronize();
1251
1252 auto nc_plot_var = collector.var(ncf, "Pair");
1253 nc_plot_var.put(tmp_Pair.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1254 }
1255 // qnet (stored °C m/s → write W/m²)
1256 {
1257 FArrayBox tmp;
1258 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1259
1260 // Copy stflux Temp component
1261 tmp.template copy<RunOn::Device>(
1262 (*vec_stflux[lev])[mfi.index()],
1263 Temp_comp, // source component
1264 0, // dest component
1265 1 // number of comps
1266 );
1267
1268 // Convert °C·m/s → W/m²
1269 tmp.mult<RunOn::Device>(Hscale);
1270
1271 Gpu::streamSynchronize();
1272
1273 auto nc_var = collector.var(ncf, "qnet");
1274 nc_var.put(tmp.dataPtr(),
1275 { local_start_nt, local_start_y, local_start_x },
1276 { local_nt, local_ny, local_nx });
1277 }
1278 // ssflux = surface net freshwater flux (kg/m²/s converted to m/s)
1279 {
1280 FArrayBox tmp;
1281 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1282
1283 // Copy stflux Salt component
1284 tmp.template copy<RunOn::Device>(
1285 (*vec_stflux[lev])[mfi.index()],
1286 Salt_comp, // source component
1287 0, // destination component
1288 1 // number of components
1289 );
1290
1291 Gpu::streamSynchronize();
1292
1293 auto nc_var = collector.var(ncf, "ssflux");
1294 nc_var.put(tmp.dataPtr(),
1295 { local_start_nt, local_start_y, local_start_x },
1296 { local_nt, local_ny, local_nx });
1297 }
1298 // latent (stored °C m/s → write W/m²)
1299 {
1300 FArrayBox tmp;
1301 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1302 tmp.template copy<RunOn::Device>((*vec_lhflx[lev])[mfi.index()], 0, 0, 1);
1303
1304 // Convert °C·m/s → W/m²
1305 tmp.mult<RunOn::Device>(Hscale);
1306
1307 Gpu::streamSynchronize();
1308
1309 auto nc_var = collector.var(ncf, "latent");
1310 nc_var.put(tmp.dataPtr(),
1311 { local_start_nt, local_start_y, local_start_x },
1312 { local_nt, local_ny, local_nx });
1313 }
1314 // sensible (stored °C m/s → write W/m²)
1315 {
1316 FArrayBox tmp;
1317 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1318 tmp.template copy<RunOn::Device>((*vec_shflx[lev])[mfi.index()], 0, 0, 1);
1319
1320 // Convert °C·m/s → W/m²
1321 tmp.mult<RunOn::Device>(Hscale);
1322
1323 Gpu::streamSynchronize();
1324
1325 auto nc_var = collector.var(ncf, "sensible");
1326 nc_var.put(tmp.dataPtr(),
1327 { local_start_nt, local_start_y, local_start_x },
1328 { local_nt, local_ny, local_nx });
1329 }
1330 // lwrad (stored °C m/s → write W/m²)
1331 {
1332 FArrayBox tmp;
1333 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1334 tmp.template copy<RunOn::Device>((*vec_lrflx[lev])[mfi.index()], 0, 0, 1);
1335
1336 // Convert °C·m/s → W/m²
1337 tmp.mult<RunOn::Device>(Hscale);
1338
1339 Gpu::streamSynchronize();
1340
1341 auto nc_var = collector.var(ncf, "lwrad");
1342 nc_var.put(tmp.dataPtr(),
1343 { local_start_nt, local_start_y, local_start_x },
1344 { local_nt, local_ny, local_nx });
1345 }
1346 // swrad, note this is stored explicitly as W/m², not degC m/s in REMORA.bulk_flux.cpp
1347 {
1348 FArrayBox tmp;
1349 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1350 tmp.template copy<RunOn::Device>((*vec_srflx[lev])[mfi.index()], 0, 0, 1);
1351 Gpu::streamSynchronize();
1352
1353 auto nc_var = collector.var(ncf, "swrad");
1354 nc_var.put(tmp.dataPtr(),
1355 { local_start_nt, local_start_y, local_start_x },
1356 { local_nt, local_ny, local_nx });
1357 }
1358 // evaporation
1359 {
1360 FArrayBox tmp;
1361 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1362 tmp.template copy<RunOn::Device>((*vec_evap[lev])[mfi.index()], 0, 0, 1);
1363 Gpu::streamSynchronize();
1364
1365 auto nc_var = collector.var(ncf, "evaporation");
1366 nc_var.put(tmp.dataPtr(),
1367 { local_start_nt, local_start_y, local_start_x },
1368 { local_nt, local_ny, local_nx });
1369 }
1370 // rain
1371 {
1372 FArrayBox tmp;
1373 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1374 tmp.template copy<RunOn::Device>((*vec_rain[lev])[mfi.index()], 0, 0, 1);
1375 Gpu::streamSynchronize();
1376
1377 auto nc_var = collector.var(ncf, "rain");
1378 nc_var.put(tmp.dataPtr(),
1379 { local_start_nt, local_start_y, local_start_x },
1380 { local_nt, local_ny, local_nx });
1381 }
1382 } // end output forcing
1383
1384 // **************************************************************************
1385 for (int n = 0; n < ncons; ++n) {
1386 int comp = -1;
1387 for (int i = 0; i < plot_var_names_3d.size(); i++) {
1388 if (plot_var_names_3d[i] == cons_names[n]) comp = i;
1389 }
1390 if (comp >= 0) {
1391 FArrayBox tmp;
1392 tmp.resize(tmp_bx, 1, amrex::The_Pinned_Arena());
1393 tmp.template copy<RunOn::Device>((*plotMF)[mfi.index()], comp, 0, 1);
1394 Gpu::streamSynchronize();
1395
1396 auto nc_plot_var = collector.var(ncf, cons_names[n]);
1397 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_z, local_start_y, local_start_x }, { local_nt,
1398 local_nz, local_ny, local_nx });
1399 }
1400 }
1401 // **************************************************************************
1402
1403 // **************************************************************************
1404 { // Vorticity
1405 int comp = -1;
1406 for (int i = 0; i < plot_var_names_3d.size(); i++) {
1407 if (plot_var_names_3d[i] == "vorticity") comp = i;
1408 }
1409 if (comp >= 0) {
1410 FArrayBox tmp;
1411 tmp.resize(tmp_bx, 1, amrex::The_Pinned_Arena());
1412 tmp.template copy<RunOn::Device>((*plotMF)[mfi.index()], comp, 0, 1);
1413 Gpu::streamSynchronize();
1414
1416 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_z, local_start_y, local_start_x }, { local_nt,
1417 local_nz, local_ny, local_nx });
1418 } // if vorticity exists in plotMF
1419 } // end vorticity
1420 // **************************************************************************
1421
1422 // **************************************************************************
1423 // omega and w, on the same columns as the tracers but at the nz+1 w points
1424 // **************************************************************************
1425 {
1426 Box tmp_bx_w(tmp_bx);
1427 tmp_bx_w.surroundingNodes(2);
1428 std::pair<const char*, MultiFab const*> w_vars[] = {{"omega", mf_omega}, {"w", mf_wvel}};
1429 for (const auto& wv : w_vars) {
1430 if (wv.second == nullptr) { continue; }
1431 FArrayBox tmp;
1432 tmp.resize(tmp_bx_w, 1, amrex::The_Pinned_Arena());
1433 tmp.template copy<RunOn::Device>((*wv.second)[mfi.index()], 0, 0, 1);
1434 Gpu::streamSynchronize();
1435
1436 auto nc_plot_var = collector.var(ncf, wv.first);
1437 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_z, local_start_y, local_start_x }, { local_nt,
1438 local_nz+1, local_ny, local_nx });
1439 }
1440 } // end omega and w
1441 // **************************************************************************
1442
1443 // **************************************************************************
1444 // Horizontal mixing coefficients (scaled_to_grid):
1445 // vertically homogeneous and time-invariant -> write static 2D fields once.
1446 // **************************************************************************
1448 { // visc2
1449 FArrayBox tmp;
1450 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1451 tmp.template copy<RunOn::Device>((*vec_visc2_r[lev])[mfi.index()], 0, 0, 1);
1452 Gpu::streamSynchronize();
1453
1454 auto nc_var = collector.var(ncf, "visc2");
1455 nc_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1456 }
1457
1458 // diff2_*
1459 for (int n = 0; n < ncons; ++n) {
1460 const std::string nm = std::string("diff2_") + cons_names[n];
1461 FArrayBox tmp;
1462 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1463 tmp.template copy<RunOn::Device>((*vec_diff2[lev])[mfi.index()], n, 0, 1);
1464 Gpu::streamSynchronize();
1465
1466 auto nc_var = collector.var(ncf, nm);
1467 nc_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1468 }
1469 }
1470
1471 } // subdomain
1472 } // mfi
1473
1474 // Flush at each loop boundary rather than once at the end, so the collector
1475 // only ever holds one loop's worth of staged data. All ranks reach these
1476 // points, which is what the collective call requires.
1477 collector.flush(ncf);
1478
1479 // Writing u (we loop over cons to get cell-centered box)
1480 for (MFIter mfi(*plotMF, false); mfi.isValid(); ++mfi) {
1481 Box bx = mfi.validbox();
1482
1483 if (subdomain.contains(bx)) {
1484 //
1485 // We only include one grow cell at subdomain boundaries, not internal grid boundaries
1486 //
1487 Box tmp_bx(bx);
1488 tmp_bx.surroundingNodes(0);
1489 if (tmp_bx.smallEnd()[1] == subdomain.smallEnd()[1])
1490 tmp_bx.growLo(1, 1);
1491 if (tmp_bx.bigEnd()[1] == subdomain.bigEnd()[1])
1492 tmp_bx.growHi(1, 1);
1493 Box tmp_bx_2d(tmp_bx);
1494 tmp_bx_2d.makeSlab(2, 0);
1495
1496 //
1497 // These are the dimensions of the data we write for only this box
1498 //
1499 long long local_nx = tmp_bx.length()[0];
1500 long long local_ny = tmp_bx.length()[1];
1501 long long local_nz = tmp_bx.length()[2];
1502
1503 // We do the "+1" because the offset needs to start at 0
1504 long long local_start_x = static_cast<long long>(tmp_bx.smallEnd()[0] - subdomain.smallEnd()[0]);
1505 long long local_start_y = static_cast<long long>(tmp_bx.smallEnd()[1] - subdomain.smallEnd()[1] + 1);
1506 long long local_start_z = static_cast<long long>(tmp_bx.smallEnd()[2] - subdomain.smallEnd()[2]);
1507
1508 if (write_header) {
1509 {
1510 FArrayBox tmp;
1511 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1512 tmp.template copy<RunOn::Device>((*vec_xu[lev])[mfi.index()], 0, 0, 1);
1513 Gpu::streamSynchronize();
1514
1515 auto nc_plot_var = collector.var(ncf, "x_u");
1516 //nc_plot_var.par_access(NC_INDEPENDENT);
1517 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1518 }
1519 {
1520 FArrayBox tmp;
1521 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1522 tmp.template copy<RunOn::Device>((*vec_yu[lev])[mfi.index()], 0, 0, 1);
1523 Gpu::streamSynchronize();
1524
1525 auto nc_plot_var = collector.var(ncf, "y_u");
1526 //nc_plot_var.par_access(NC_INDEPENDENT);
1527 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1528 }
1529 }
1530
1531 {
1532 FArrayBox tmp;
1533 tmp.resize(tmp_bx, 1, amrex::The_Pinned_Arena());
1534 tmp.template copy<RunOn::Device>((*xvel_new[lev])[mfi.index()], 0, 0, 1);
1535 Gpu::streamSynchronize();
1536
1537 auto nc_plot_var = collector.var(ncf, "u");
1538 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_z, local_start_y, local_start_x }, { local_nt,
1539 local_nz, local_ny, local_nx });
1540 }
1541
1542 {
1543 FArrayBox tmp;
1544 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1545 tmp.template copy<RunOn::Device>((*vec_ubar[lev])[mfi.index()], 0, 0, 1);
1546 Gpu::streamSynchronize();
1547
1548 auto nc_plot_var = collector.var(ncf, "ubar");
1549 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1550 }
1551 {
1552 FArrayBox tmp;
1553 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1554 tmp.template copy<RunOn::Device>((*vec_sustr[lev])[mfi.index()], 0, 0, 1);
1555 Gpu::streamSynchronize();
1556
1557 auto nc_plot_var = collector.var(ncf, "sustr");
1558 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1559 }
1560 {
1561 FArrayBox tmp;
1562 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1563 tmp.template copy<RunOn::Device>((*vec_msku[lev])[mfi.index()], 0, 0, 1);
1564 Gpu::streamSynchronize();
1565
1566 auto nc_plot_var = collector.var(ncf, "mask_u");
1567 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1568 }
1569 } // in subdomain
1570 } // mfi
1571
1572 collector.flush(ncf);
1573
1574 // Writing v (we loop over cons to get cell-centered box)
1575 for (MFIter mfi(*plotMF, false); mfi.isValid(); ++mfi) {
1576 Box bx = mfi.validbox();
1577
1578 if (subdomain.contains(bx)) {
1579 //
1580 // We only include one grow cell at subdomain boundaries, not internal grid boundaries
1581 //
1582 Box tmp_bx(bx);
1583 tmp_bx.surroundingNodes(1);
1584 if (tmp_bx.smallEnd()[0] == subdomain.smallEnd()[0])
1585 tmp_bx.growLo(0, 1);
1586 if (tmp_bx.bigEnd()[0] == subdomain.bigEnd()[0])
1587 tmp_bx.growHi(0, 1);
1588 // amrex::Print() << " BX " << bx << std::endl;
1589 // amrex::Print() << "TMP_BX " << tmp_bx << std::endl;
1590
1591 Box tmp_bx_2d(tmp_bx);
1592 tmp_bx_2d.makeSlab(2, 0);
1593
1594 //
1595 // These are the dimensions of the data we write for only this box
1596 //
1597 long long local_nx = tmp_bx.length()[0];
1598 long long local_ny = tmp_bx.length()[1];
1599 long long local_nz = tmp_bx.length()[2];
1600
1601 // We do the "+1" because the offset needs to start at 0
1602 long long local_start_x = static_cast<long long>(tmp_bx.smallEnd()[0] - subdomain.smallEnd()[0] + 1);
1603 long long local_start_y = static_cast<long long>(tmp_bx.smallEnd()[1] - subdomain.smallEnd()[1]);
1604 long long local_start_z = static_cast<long long>(tmp_bx.smallEnd()[2] - subdomain.smallEnd()[2]);
1605
1606 if (write_header) {
1607 {
1608 FArrayBox tmp;
1609 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1610 tmp.template copy<RunOn::Device>((*vec_xv[lev])[mfi.index()], 0, 0, 1);
1611 Gpu::streamSynchronize();
1612
1613 auto nc_plot_var = collector.var(ncf, "x_v");
1614 //nc_plot_var.par_access(NC_INDEPENDENT);
1615 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1616 }
1617 {
1618 FArrayBox tmp;
1619 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1620 tmp.template copy<RunOn::Device>((*vec_yv[lev])[mfi.index()], 0, 0, 1);
1621 Gpu::streamSynchronize();
1622
1623 auto nc_plot_var = collector.var(ncf, "y_v");
1624 //nc_plot_var.par_access(NC_INDEPENDENT);
1625 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1626 }
1627 }
1628
1629 {
1630 FArrayBox tmp;
1631 tmp.resize(tmp_bx, 1, amrex::The_Pinned_Arena());
1632 tmp.template copy<RunOn::Device>((*yvel_new[lev])[mfi.index()], 0, 0, 1);
1633 Gpu::streamSynchronize();
1634
1635 auto nc_plot_var = collector.var(ncf, "v");
1636 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_z, local_start_y, local_start_x }, { local_nt,
1637 local_nz, local_ny, local_nx });
1638 }
1639
1640 {
1641 FArrayBox tmp;
1642 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1643 tmp.template copy<RunOn::Device>((*vec_vbar[lev])[mfi.index()], 0, 0, 1);
1644 Gpu::streamSynchronize();
1645
1646 auto nc_plot_var = collector.var(ncf, "vbar");
1647 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1648 }
1649
1650 {
1651 FArrayBox tmp;
1652 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1653 tmp.template copy<RunOn::Device>((*vec_svstr[lev])[mfi.index()], 0, 0, 1);
1654 Gpu::streamSynchronize();
1655
1656 auto nc_plot_var = collector.var(ncf, "svstr");
1657 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1658 }
1659 {
1660 FArrayBox tmp;
1661 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1662 tmp.template copy<RunOn::Device>((*vec_mskv[lev])[mfi.index()], 0, 0, 1);
1663 Gpu::streamSynchronize();
1664
1665 auto nc_plot_var = collector.var(ncf, "mask_v");
1666 nc_plot_var.put(tmp.dataPtr(), { local_start_nt, local_start_y, local_start_x }, { local_nt, local_ny, local_nx });
1667 }
1668
1669 } // in subdomain
1670 } // mfi
1671
1672 collector.flush(ncf);
1673
1674 for (MFIter mfi(*plotMF, false); mfi.isValid(); ++mfi) {
1675 Box bx = mfi.validbox();
1676
1677 if (subdomain.contains(bx)) {
1678 //
1679 // We only include one grow cell at subdomain boundaries, not internal grid boundaries
1680 //
1681 Box tmp_bx(bx);
1682 tmp_bx.surroundingNodes(0);
1683 tmp_bx.surroundingNodes(1);
1684
1685 Box tmp_bx_2d(tmp_bx);
1686 tmp_bx_2d.makeSlab(2, 0);
1687
1688 //
1689 // These are the dimensions of the data we write for only this box
1690 //
1691 long long local_nx = tmp_bx.length()[0];
1692 long long local_ny = tmp_bx.length()[1];
1693
1694 // We do the "+1" because the offset needs to start at 0
1695 long long local_start_x = static_cast<long long>(tmp_bx.smallEnd()[0] - subdomain.smallEnd()[0]);
1696 long long local_start_y = static_cast<long long>(tmp_bx.smallEnd()[1] - subdomain.smallEnd()[1]);
1697
1698 if (write_header) {
1699 {
1700 FArrayBox tmp;
1701 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1702 tmp.template copy<RunOn::Device>((*vec_xp[lev])[mfi.index()], 0, 0, 1);
1703 Gpu::streamSynchronize();
1704
1705 auto nc_plot_var = collector.var(ncf, "x_psi");
1706 //nc_plot_var.par_access(NC_INDEPENDENT);
1707 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1708 }
1709 {
1710 FArrayBox tmp;
1711 tmp.resize(tmp_bx_2d, 1, amrex::The_Pinned_Arena());
1712 tmp.template copy<RunOn::Device>((*vec_yp[lev])[mfi.index()], 0, 0, 1);
1713 Gpu::streamSynchronize();
1714
1715 auto nc_plot_var = collector.var(ncf, "y_psi");
1716 //nc_plot_var.par_access(NC_INDEPENDENT);
1717 nc_plot_var.put(tmp.dataPtr(), { local_start_y, local_start_x }, { local_ny, local_nx });
1718 }
1719
1720 } // header
1721 } // in subdomain
1722 } // mfi
1723
1724 // One collective ncmpi_put_varn_*_all per variable. Collective writes keep
1725 // numrecs synced in the file header as they go, so no separate
1726 // ncmpi_end_indep_data() sync is needed.
1727 collector.flush(ncf);
1728
1729 ncf.close();
1730
1732}
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:1839
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:2009
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
void WriteNCPlotFile(int istep, amrex::MultiFab const *plotMF, int lev=0, amrex::MultiFab const *mf_omega=nullptr, amrex::MultiFab const *mf_wvel=nullptr)
Write plotfile using NetCDF (wrapper)
static bool write_history_file
Whether to output NetCDF files as a single history file with several time steps.
Definition REMORA.H:1619
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
bool plot_omega
Whether to write omega, the S-coordinate vertical velocity, to NetCDF output.
Definition REMORA.H:2005
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:1993
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:1989
void WriteNCPlotFile_which(int lev, int which_subdomain, amrex::MultiFab const *plotMF, amrex::MultiFab const *mf_omega, amrex::MultiFab const *mf_wvel, bool write_header, ncutils::NCFile &ncf, bool is_history, long long history_record=0)
Write a particular NetCDF plotfile.
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:1822
amrex::Vector< amrex::Vector< amrex::Box > > boxes_at_level
the boxes specified at each level by tagging criteria
Definition REMORA.H:1730
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_msku
land/sea mask at x-faces (2D)
Definition REMORA.H:586
bool plot_wvel
Whether to write z_velocity, the true vertical velocity, to NetCDF output as w.
Definition REMORA.H:2007
amrex::Vector< amrex::MultiFab * > yvel_new
multilevel data container for current step's y velocities (v in ROMS)
Definition REMORA.H:397
amrex::Vector< std::string > history_file_in_use
Per level, the history file this run last wrote to.
Definition REMORA.H:1995
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:1991
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
static int file_min_digits
Minimum number of digits in plotfile name or chunked history file.
Definition REMORA.H:2061
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:1744
static SolverChoice solverChoice
Container for algorithmic choices.
Definition REMORA.H:2017
static int total_nc_plot_file_step
Definition REMORA.H:1517
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
amrex::Real netcdf_fill_value
fill value for masked arrays in netcdf output
Definition REMORA.H:2025
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:2001
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_stflux
Surface tracer flux; input arrays.
Definition REMORA.H:508
long long history_restart_record(int lev, const std::string &path)
Record at which a restarted run continues a history file.
std::string plot_file_name
Plotfile prefix.
Definition REMORA.H:1976
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Zt_avg1
Average of the free surface, zeta (2D)
Definition REMORA.H:476
std::string restart_chkfile
If set, restart from this checkpoint file.
Definition REMORA.H:1825
amrex::Vector< std::string > plot_var_names_2d
Names of 2D variables to output to AMReX plotfile.
Definition REMORA.H:2003
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:2210
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_srflx
Shortwave radiation flux [W/m²], defined at rho-points.
Definition REMORA.H:495
amrex::Vector< amrex::Real > dt
time step at each level
Definition REMORA.H:1748
amrex::Vector< std::unique_ptr< amrex::MultiFab > > vec_Pair
Air pressure [mb], defined at rho-points.
Definition REMORA.H:492
amrex::Vector< long long > history_record_shift
Per level, history_count minus the record written in the file in use. Nonzero only after a restart.
Definition REMORA.H:1998
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.