REMORA
Regional Modeling of Oceans Refined Adaptively
Loading...
Searching...
No Matches
REMORA_NCFile.H
Go to the documentation of this file.
1#ifndef _REMORA_NCFILE_H_
2#define _REMORA_NCFILE_H_
3
4#include <sstream>
5#include <string>
6#include <ctime>
7#include <atomic>
8
9#include "AMReX_FArrayBox.H"
10#include "AMReX_IArrayBox.H"
11// this includes pnetcdf.h, and mpi.h
12#include "REMORA_NCInterface.H"
13
14using PlaneVector = amrex::Vector<amrex::FArrayBox>;
15
20 SN_WE,
23 BT_Riv,
24};
25
26/**
27 * \brief NDArray is the datatype designed to hold any data, including scalars, multidimensional
28 * arrays, that read from the NetCDF file.
29 *
30 * The data read from NetCDF file are stored in a continuous memory, and the data layout is described
31 * by using a vector (shape). AMRex Box can be constructed using the data shape information, and MultiFab
32 * data array can be setup using the data that stored in the NDArray.
33 */
34template <typename DataType>
35struct NDArray
36{
37 using DType = typename std::remove_const<DataType>::type;
38
39 /** \brief constructor */
40 explicit NDArray (const std::string vname, const std::vector<MPI_Offset>& vshape)
41 : name{vname}, shape{vshape}, ref_counted{1}, owned{true} {
42 data = new DType [this->ndim()];
43 }
44
45 /** \brief default constructor */
46 NDArray () : name{"null"}, ref_counted{1}, owned{false}, data{nullptr} {}
47
48 /** \brief copy constructor */
49 NDArray (const NDArray& array) {
50 name = array.name;
51 shape = array.shape;
52 data = array.data;
53 owned = false;
54 ref_counted.fetch_add(1, std::memory_order_relaxed);
55 }
56
57 /** \brief copy assignment */
58 NDArray& operator=(const NDArray& array) {
59 name = array.name;
60 shape = array.shape;
61 data = array.data;
62 owned = false;
63 ref_counted.fetch_add(1, std::memory_order_relaxed);
64 return *this;
65 }
66
67 /** \brief destructor */
69 ref_counted.fetch_sub(1, std::memory_order_acq_rel);
70 if (ref_counted == 1 && owned) delete[] data;
71 }
72
73 /** \brief get the data pointer */
74 decltype(auto) get_data () {
75 ref_counted.fetch_add(1, std::memory_order_relaxed);
76 return data;
77 }
78
79 /** \brief get the variable name */
80 std::string get_vname () {
81 return name;
82 }
83
84 /** \brief get the variable data shape */
85 std::vector<MPI_Offset> get_vshape () {
86 return shape;
87 }
88
89 /** \brief return the total number of data */
90 size_t ndim () {
91 size_t num = 1;
92 int isize = static_cast<int>(shape.size());
93 for (auto i=0; i<isize; ++i) num *= shape[i];
94 return num;
95 }
96
97 /** \brief set the data shape information */
98 void set_vshape (std::vector<MPI_Offset> vshape) {
99 shape = vshape;
100 }
101
102 private:
103 std::string name;
104 std::vector<MPI_Offset> shape;
105 mutable std::atomic<size_t> ref_counted;
106 bool owned;
108};
109
110/** \brief Read in data from netcdf file and save to data arrays
111 *
112 * @param[in ] fname file name to read from
113 * @param[in ] names field names to read
114 * @param[inout] arrays vector of data arrays to fill
115 * @param[in ] one_time whether to read in a single time step of data
116 * @param[in ] fill_time if reading a single time, what time step to read from the file?
117 */
118template<typename DType>
119void ReadNetCDFFile (const std::string& fname, amrex::Vector<std::string> names,
120 amrex::Vector<NDArray<DType> >& arrays, bool one_time=false, int fill_time=0)
121{
122 AMREX_ASSERT(arrays.size() == names.size());
123
124 auto ncf = ncutils::NCFile::open(fname, NC_NOCLOBBER );
125 ncmpi_begin_indep_data(ncf.ncid);
126 if (amrex::ParallelDescriptor::IOProcessor())
127 {
128
129 /*
130 // get the dimension information
131 int Time = static_cast<int>(ncf.dim("Time").len());
132 int DateStrLen = static_cast<int>(ncf.dim("DateStrLen").len());
133 int west_east = static_cast<int>(ncf.dim("west_east").len());
134 int south_north = static_cast<int>(ncf.dim("south_north").len());
135 int bottom_top = static_cast<int>(ncf.dim("bottom_top").len());
136 int bottom_top_stag = static_cast<int>(ncf.dim("bottom_top_stag").len());
137 int west_east_stag = static_cast<int>(ncf.dim("west_east_stag").len());
138 int south_north_stag = static_cast<int>(ncf.dim("south_north_stag").len());
139 */
140
141// amrex::Print() << "Reading the dimensions from the netcdf file " << "\n";
142
143 for (auto n=0; n<arrays.size(); ++n) {
144 // read the data from NetCDF file
145 std::string vname_to_write = names[n];
146 std::string vname_to_read = names[n];
147
148 if (!ncf.has_var(vname_to_read)) {
149 amrex::Print() << "Variable name " << vname_to_read << " not found!" << std::endl;
150 }
151
152 // amrex::AllPrint() << "About to read " << vname_to_read
153 // << " while filling the array for " << vname_to_write << std::endl;
154
155 std::vector<MPI_Offset> shape = ncf.var(vname_to_read).shape();
156 arrays[n] = NDArray<DType>(vname_to_read,shape);
157 DType* dataPtr = arrays[n].get_data();
158
159 std::vector<MPI_Offset> start(shape.size(), 0);
160 // If only reading one time step, adjust start and shape accordingly
161 if (one_time) {
162 start[0] = fill_time;
163 shape[0] = 1;
164 }
165
166 // auto numPts = arrays[n].ndim();
167 // amrex::Print() << "NetCDF Variable name = " << vname_to_read << std::endl;
168 // amrex::Print() << "numPts read from NetCDF file/var = " << numPts << std::endl;
169 // amrex::Print() << "Dims of the variable = (";
170 // for (auto &dim:shape)
171 // amrex::Print() << dim << ", " ;
172 // amrex::Print() << ")" << std::endl;
173
174 ncf.var(vname_to_read).get(dataPtr, start, shape);
175 }
176 }
177 ncf.close();
178}
179
180/**
181 * \brief Helper function for reading a single variable attribute
182 */
183std::string ReadNetCDFVarAttrStr (const std::string& fname,
184 const std::string& var_name,
185 const std::string& attr_name);
186
187/**
188 * \brief Helper function for reading data from NetCDF file into a
189 * provided FAB.
190 *
191 * @param iv Index for which variable we are going to fill
192 * @param nc_arrays Arrays of data from NetCDF file
193 * @param var_name Variable name
194 * @param NC_dim_type Dimension type for the variable as stored in the NetCDF file
195 * @param temp FAB where we store the variable data from the NetCDF Arrays
196 */
197template<class FAB,typename DType>
198void
200 amrex::Vector<NDArray<amrex::Real>>& nc_arrays,
201 const std::string& var_name,
202 NC_Data_Dims_Type& NC_dim_type,
203 FAB& temp)
204{
205 int nsx, nsy, nsz;
206 if (NC_dim_type == NC_Data_Dims_Type::SN_WE) {
207 nsz = 1;
208 nsy = nc_arrays[iv].get_vshape()[0];
209 nsx = nc_arrays[iv].get_vshape()[1];
210 // amrex::Print() << "TYPE SN WE " << nsy << " " << nsx << std::endl;
211 } else if (NC_dim_type == NC_Data_Dims_Type::BT_SN_WE) {
212 nsz = nc_arrays[iv].get_vshape()[0];
213 nsy = nc_arrays[iv].get_vshape()[1];
214 nsx = nc_arrays[iv].get_vshape()[2];
215 // amrex::Print() << "TYPE BT SN WE " << nz << " " << nsy << " " << nsx << std::endl;
216 } else if (NC_dim_type == NC_Data_Dims_Type::Time_SN_WE) {
217 nsz = 1;
218 nsy = nc_arrays[iv].get_vshape()[1];
219 nsx = nc_arrays[iv].get_vshape()[2];
220 // amrex::Print() << "TYPE TIME SN WE " << nsy << " " << nsx << std::endl;
221 } else if (NC_dim_type == NC_Data_Dims_Type::Time_BT_SN_WE) {
222 nsz = nc_arrays[iv].get_vshape()[1];
223 nsy = nc_arrays[iv].get_vshape()[2];
224 nsx = nc_arrays[iv].get_vshape()[3];
225 // amrex::Print() << "TYPE TIME BT SN WE " << nsx << " " << nsy << " " << nsz << std::endl;
226 } else if (NC_dim_type == NC_Data_Dims_Type::Time_BT_Riv) {
227 nsz = nc_arrays[iv].get_vshape()[1]; // number of z-coords
228 nsy = 1; // dummy
229 nsx = nc_arrays[iv].get_vshape()[2]; // number of rivers
230 } else if (NC_dim_type == NC_Data_Dims_Type::Time_Riv) {
231 nsz = 1; // number of z-coords
232 nsy = 1; // dummy
233 nsx = nc_arrays[iv].get_vshape()[1]; // number of rivers
234 } else if (NC_dim_type == NC_Data_Dims_Type::BT_Riv) {
235 nsz = nc_arrays[iv].get_vshape()[0]; // number of z-coords
236 nsy = 1; // dummy
237 nsx = nc_arrays[iv].get_vshape()[1]; // number of rivers
238 } else {
239 amrex::Abort("Dont know this NC_Data_Dims_Type");
240 }
241
242 amrex::Box my_box;
243
244 if (var_name == "u" || var_name == "ubar" || var_name == "mask_u" || var_name == "x_u" ||
245 var_name == "y_u" || var_name == "sustr")
246 {
247 my_box.setSmall(amrex::IntVect(0,-1,0));
248 my_box.setBig(amrex::IntVect(nsx-1,nsy-2,nsz-1));
249 my_box.setType(amrex::IndexType(amrex::IntVect(1,0,0)));
250 }
251 else if (var_name == "v" || var_name == "vbar" || var_name == "mask_v" || var_name == "x_v" ||
252 var_name == "y_v" || var_name == "svstr")
253 {
254 my_box.setSmall(amrex::IntVect(-1,0,0));
255 my_box.setBig(amrex::IntVect(nsx-2,nsy-1,nsz-1));
256 my_box.setType(amrex::IndexType(amrex::IntVect(0,1,0)));
257 }
258 else if (var_name == "mask_psi" || var_name == "x_psi" || var_name == "y_psi")
259 {
260 my_box.setSmall(amrex::IntVect(0,0,0));
261 my_box.setBig(amrex::IntVect(nsx-1,nsy-1,nsz-1));
262 my_box.setType(amrex::IndexType(amrex::IntVect(1,1,0)));
263 // River data has no ghost cells
264 } else if (NC_dim_type == NC_Data_Dims_Type::Time_BT_Riv ||
265 NC_dim_type == NC_Data_Dims_Type::Time_Riv ||
266 NC_dim_type == NC_Data_Dims_Type::BT_Riv) {
267 my_box.setSmall(amrex::IntVect(0,0,0));
268 my_box.setBig(amrex::IntVect(nsx-1,nsy-1,nsz-1));
269 }
270 else // everything cell-centered -- these have one ghost cell
271 {
272 my_box.setSmall(amrex::IntVect(-1,-1,0));
273 my_box.setBig(amrex::IntVect(nsx-2,nsy-2,nsz-1));
274 }
275
276 // amrex::Print() <<" NAME and BOX for this VAR " << var_name << " " << my_box << std::endl;
277
278#ifdef AMREX_USE_GPU
279 // Make sure temp lives on CPU since nc_arrays lives on CPU only
280 temp.resize(my_box,1,amrex::The_Pinned_Arena());
281#else
282 temp.resize(my_box,1);
283#endif
284 amrex::Array4<DType> fab_arr = temp.array();
285
286 int ioff = temp.box().smallEnd()[0];
287 int joff = temp.box().smallEnd()[1];
288
289 auto num_pts_in_box = my_box.numPts();
290 auto num_pts_in_src = nsx*nsy*nsz;
291 // amrex::Print() <<" NUM PTS IN BOX " << num_pts_in_box << std::endl;
292 // amrex::Print() <<" NUM PTS IN SRC " << num_pts_in_src << std::endl;
293 AMREX_ALWAYS_ASSERT(num_pts_in_box >= num_pts_in_src);
294
295 for (int n(0); n < num_pts_in_src; ++n) {
296 int nplane = nsx*nsy;
297 int k = n / nplane;
298 int j = (n - k*nplane) / nsx + joff;
299 int i = n - k*nplane - (j-joff) * nsx + ioff;
300 fab_arr(i,j,k,0) = static_cast<DType>(*(nc_arrays[iv].get_data()+n));
301 }
302}
303
304/**
305 * \brief Function to read NetCDF variables and fill the corresponding Array4's
306 *
307 * @param fname Name of the NetCDF file to be read
308 * @param nc_var_names Variable names in the NetCDF file
309 * @param NC_dim_types NetCDF data dimension types
310 * @param fab_vars Fab data we are to fill
311 * @param one_time Whether to read in a single time step from file
312 * @param fill_time What time step to read from file, if only reading one step
313 */
314template<class FAB,typename DType>
315void
316BuildFABsFromNetCDFFile (const amrex::Box& domain,
317 const std::string &fname,
318 amrex::Vector<std::string> nc_var_names,
319 amrex::Vector<enum NC_Data_Dims_Type> NC_dim_types,
320 amrex::Vector<FAB*> fab_vars,
321 bool one_time=false, int fill_time=0)
322{
323 int ioproc = amrex::ParallelDescriptor::IOProcessorNumber(); // I/O rank
324
325 amrex::Vector<NDArray<amrex::Real>> nc_arrays(nc_var_names.size());
326
327
328 ReadNetCDFFile(fname, nc_var_names, nc_arrays, one_time, fill_time); // this is filled only on proc 0
329
330 for (int iv = 0; iv < nc_var_names.size(); iv++)
331 {
332 FAB tmp;
333 if (amrex::ParallelDescriptor::IOProcessor()) {
334 fill_fab_from_arrays<FAB,DType>(iv, nc_arrays, nc_var_names[iv], NC_dim_types[iv], tmp);
335 }
336
337 int ncomp = tmp.nComp();
338 amrex::Box box = tmp.box();
339
340 amrex::ParallelDescriptor::Bcast(&box, 1, ioproc);
341 amrex::ParallelDescriptor::Bcast(&ncomp, 1, ioproc);
342
343 if (!amrex::ParallelDescriptor::IOProcessor()) {
344#ifdef AMREX_USE_GPU
345 tmp.resize(box,ncomp,amrex::The_Pinned_Arena());
346#else
347 tmp.resize(box,ncomp);
348#endif
349 }
350
351 amrex::ParallelDescriptor::Bcast(tmp.dataPtr(), tmp.size(), ioproc);
352
353 // Shift box by the domain lower corner
354 amrex::Box fab_bx = tmp.box();
355 amrex::Dim3 dom_lb = lbound(domain);
356 fab_bx += amrex::IntVect(dom_lb.x,dom_lb.y,dom_lb.z);
357 // fab_vars points to data on device
358 fab_vars[iv]->resize(fab_bx,1);
359#ifdef AMREX_USE_GPU
360 amrex::Gpu::copy(amrex::Gpu::hostToDevice,
361 tmp.dataPtr(), tmp.dataPtr() + tmp.size(),
362 fab_vars[iv]->dataPtr());
363#else
364 // Provided by BaseFab inheritance through FArrayBox
365 fab_vars[iv]->copy(tmp,tmp.box(),0,fab_bx,0,1);
366#endif
367 }
368}
369
370#endif
NC_Data_Dims_Type
void ReadNetCDFFile(const std::string &fname, amrex::Vector< std::string > names, amrex::Vector< NDArray< DType > > &arrays, bool one_time=false, int fill_time=0)
Read in data from netcdf file and save to data arrays.
void BuildFABsFromNetCDFFile(const amrex::Box &domain, const std::string &fname, amrex::Vector< std::string > nc_var_names, amrex::Vector< enum NC_Data_Dims_Type > NC_dim_types, amrex::Vector< FAB * > fab_vars, bool one_time=false, int fill_time=0)
Function to read NetCDF variables and fill the corresponding Array4's.
void fill_fab_from_arrays(int iv, amrex::Vector< NDArray< amrex::Real > > &nc_arrays, const std::string &var_name, NC_Data_Dims_Type &NC_dim_type, FAB &temp)
Helper function for reading data from NetCDF file into a provided FAB.
std::string ReadNetCDFVarAttrStr(const std::string &fname, const std::string &var_name, const std::string &attr_name)
Helper function for reading a single variable attribute.
amrex::Vector< amrex::FArrayBox > PlaneVector
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.
NDArray is the datatype designed to hold any data, including scalars, multidimensional arrays,...
std::string name
size_t ndim()
return the total number of data
NDArray()
default constructor
std::string get_vname()
get the variable name
NDArray(const NDArray &array)
copy constructor
std::vector< MPI_Offset > shape
NDArray(const std::string vname, const std::vector< MPI_Offset > &vshape)
constructor
std::atomic< size_t > ref_counted
typename std::remove_const< DataType >::type DType
DType * data
decltype(auto) get_data()
get the data pointer
NDArray & operator=(const NDArray &array)
copy assignment
void set_vshape(std::vector< MPI_Offset > vshape)
set the data shape information
std::vector< MPI_Offset > get_vshape()
get the variable data shape
~NDArray()
destructor