Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
MBNcDispatch.hpp
Go to the documentation of this file.
1 //-------------------------------------------------------------------------
2 // Filename : MBNcDispatch.hpp
3 //
4 // Purpose : Runtime dispatch layer for NetCDF / Parallel-NetCDF I/O.
5 //
6 // Before this layer existed, src/io/{ReadNC,WriteNC,NCHelper*} chose
7 // between the standard NetCDF C API (nc_*) and Parallel-NetCDF
8 // (ncmpi_*) at *compile time* via the NCFUNC* macros in ReadNC.hpp /
9 // WriteNC.hpp. The compile-time choice is wrong: PNetCDF cannot read
10 // NetCDF-4/HDF5 files at all, so any build configured with
11 // MOAB_HAVE_PNETCDF (and not MOAB_HAVE_NETCDFPAR) silently fails to
12 // load SCRIP / ESMF / MPAS grid files that happen to be NetCDF-4.
13 //
14 // This header replaces the compile-time choice with runtime dispatch
15 // based on the *detected* file format. Both pnetcdf.h and netcdf.h
16 // are included whenever the corresponding feature flag is on, so the
17 // wrapper functions can route each call to the backend that actually
18 // handles the file.
19 //
20 // Selection logic (per ReadParallelMap pattern in
21 // src/Remapping/TempestOnlineMapIO.cpp):
22 // - Classic (CDF-1/2/5) : prefer PNetCDF; fall back to nc_*_par;
23 // last resort, buffered serial+scatter.
24 // - NetCDF-4 (HDF5) : nc_*_par; last resort, buffered serial.
25 // - Serial (mpi_size==1) : plain nc_* (handles either format).
26 // - Unknown / unreadable : error.
27 //
28 // File-id tagging
29 // Library file IDs returned by nc_open() and ncmpi_open() come from
30 // independent namespaces and CAN collide. Rather than maintain a
31 // registry keyed by the library id, this layer tags the backend
32 // identity into the high nibble of the fileId returned to callers:
33 //
34 // bit 31 bit 28 bit 0
35 // +-------+---------------------------+
36 // | tag | library file id |
37 // +-------+---------------------------+
38 //
39 // Every wrapper function untags the input id and dispatches on the
40 // tag. Callers store the *tagged* id wherever they currently store
41 // fileId; nothing else changes.
42 //
43 // Type bridging
44 // ncmpi_* takes MPI_Offset arrays for starts/counts and for some
45 // inquiry outputs (inq_dim length, inq_att length). nc_* takes
46 // size_t. Wrappers standardize on size_t at the API boundary and
47 // convert to MPI_Offset on the stack when routing to PNetCDF.
48 //
49 // Nonblocking calls
50 // PNetCDF provides ncmpi_iget_* + ncmpi_wait_all for request
51 // aggregation. Standard NetCDF has no equivalent. On non-PNetCDF
52 // backends, mbnc_iget_* degrades to an immediate blocking collective
53 // call and sets the request handle to a sentinel that mbnc_wait_all
54 // recognizes and ignores. Existing #ifdef MOAB_HAVE_PNETCDF blocks
55 // in callers therefore remain correct after the macro swap.
56 //
57 // Creator : Vijay Mahadevan, 2026-06-11
58 //-------------------------------------------------------------------------
59 
60 #ifndef MB_NC_DISPATCH_HPP
61 #define MB_NC_DISPATCH_HPP
62 
63 #include "moab/MOABConfig.h"
64 
65 #ifdef MOAB_HAVE_MPI
66 #include "moab_mpi.h"
67 #endif
68 
69 // Always include the standard NetCDF C API when MOAB has any NetCDF support.
70 // libnetcdf is a transitive dependency of libpnetcdf, so its headers are
71 // reachable whenever PNetCDF is configured. The wrapper layer needs both
72 // pnetcdf.h (for ncmpi_*) AND netcdf.h (for nc_*) compiled in simultaneously.
73 #include "netcdf.h"
74 
75 #ifdef MOAB_HAVE_NETCDFPAR
76 #include "netcdf_par.h"
77 #endif
78 
79 #ifdef MOAB_HAVE_PNETCDF
80 #include "pnetcdf.h"
81 #endif
82 
83 #include <cstddef> // size_t, ptrdiff_t
84 
85 namespace moab
86 {
87 
88 // ============================================================================
89 // Backend tags
90 // ============================================================================
91 
92 /// Identifies which underlying NetCDF library will service a given file.
93 /// Encoded into the high nibble of every tagged file id this layer hands
94 /// back, so dispatch is branch-free and stateless.
96 {
97  NCB_NONE = 0, ///< Invalid / closed handle
98  NCB_NETCDF_SERIAL = 1, ///< Plain nc_*, single rank or rank-0 reads
99  NCB_PNETCDF = 2, ///< ncmpi_*, classic CDF-1/2/5 parallel
100  NCB_NETCDF_PAR = 3, ///< nc_*_par, parallel HDF5 / parallel CDF (when libnetcdf has PNetCDF backend)
101  NCB_BUFFERED = 4 ///< nc_* on rank 0, MPI scatter/broadcast on every read
102 };
103 
104 /// File format detected from the on-disk magic bytes. Independent of
105 /// which library will end up servicing the file (a classic file can be
106 /// opened by either PNetCDF or libnetcdf-parallel, for instance).
108 {
110  NCFMT_CLASSIC = 1, ///< NetCDF-3: CDF-1, CDF-2, CDF-5
111  NCFMT_NETCDF4 = 2 ///< NetCDF-4 / HDF5
112 };
113 
114 // ============================================================================
115 // Tagged file id encoding
116 // ============================================================================
117 
118 /// Bits reserved for the backend tag in the top of a tagged file id.
119 /// 4 bits leaves 28 bits (268M) for the underlying library file id,
120 /// which is far more than any NetCDF library will hand out in practice.
121 constexpr int MBNC_TAG_BITS = 4;
122 constexpr int MBNC_TAG_SHIFT = 32 - MBNC_TAG_BITS;
123 constexpr int MBNC_ID_MASK = ( 1 << MBNC_TAG_SHIFT ) - 1;
124 
125 inline int mbnc_make_tagged( int libId, NcBackend backend )
126 {
127  return ( static_cast< int >( backend ) << MBNC_TAG_SHIFT ) | ( libId & MBNC_ID_MASK );
128 }
129 
130 inline int mbnc_lib_id( int taggedId )
131 {
132  return taggedId & MBNC_ID_MASK;
133 }
134 
135 inline NcBackend mbnc_backend_of( int taggedId )
136 {
137  // Use unsigned shift to avoid sign-extension on negative-looking ids.
138  return static_cast< NcBackend >( static_cast< unsigned >( taggedId ) >> MBNC_TAG_SHIFT );
139 }
140 
141 // ============================================================================
142 // Format probe + backend chooser (pure functions — no I/O state)
143 // ============================================================================
144 
145 /// Inspect the file header to classify NetCDF format. Reads only the
146 /// first 8 bytes — relies on the well-known "CDF\xNN" and HDF5 magic
147 /// signatures. Returns NCFMT_UNKNOWN if the file cannot be opened or
148 /// the signature does not match. Safe to call from any single rank.
149 int mbnc_detect_format( const char* path );
150 
151 /// Pick the best available backend for reading a file of the given
152 /// format with the given MPI size. Returns NCB_NONE if no compatible
153 /// backend is configured (caller should error out with a precise
154 /// message naming the missing capability).
155 ///
156 /// Decision matrix at runtime, given compile-time MOAB_HAVE_PNETCDF
157 /// and MOAB_HAVE_NETCDFPAR flags (visible to this function via #ifdef):
158 ///
159 /// format=classic, mpi_size=1 -> NCB_NETCDF_SERIAL
160 /// format=netcdf4, mpi_size=1 -> NCB_NETCDF_SERIAL
161 /// format=classic, parallel, have PNetCDF -> NCB_PNETCDF
162 /// format=classic, parallel, have NETCDFPAR -> NCB_NETCDF_PAR
163 /// format=classic, parallel, neither -> NCB_BUFFERED
164 /// format=netcdf4, parallel, have NETCDFPAR -> NCB_NETCDF_PAR
165 /// format=netcdf4, parallel, only PNetCDF -> NCB_BUFFERED
166 /// format=unknown -> NCB_NONE
167 NcBackend mbnc_choose_backend_for_read( int format, int mpi_size );
168 
169 /// Pick a backend for writing. The format is provided by the caller
170 /// (typically derived from a user option / file extension), not probed.
171 NcBackend mbnc_choose_backend_for_write( int requested_format, int mpi_size );
172 
173 // ============================================================================
174 // Buffered-fallback context registry
175 //
176 // Only NCB_BUFFERED files need extra metadata (the MPI communicator and
177 // rank/size) so the wrappers can scatter / broadcast on each call. The
178 // registry is keyed by the *tagged* file id and consulted only by the
179 // buffered code path.
180 // ============================================================================
181 
182 #ifdef MOAB_HAVE_MPI
183 void mbnc_register_buffered( int taggedFileId, MPI_Comm comm );
184 void mbnc_unregister_buffered( int taggedFileId );
185 MPI_Comm mbnc_buffered_comm( int taggedFileId );
186 int mbnc_buffered_rank( int taggedFileId );
187 int mbnc_buffered_size( int taggedFileId );
188 #endif
189 
190 // ============================================================================
191 // File open / close / create
192 //
193 // These return TAGGED file ids; pass them unmodified to every other
194 // mbnc_* function. The caller must close via mbnc_close() (never
195 // nc_close / ncmpi_close directly).
196 // ============================================================================
197 
198 #ifdef MOAB_HAVE_MPI
199 /// Parallel open with explicit backend (chosen via mbnc_choose_backend_for_read).
200 /// Returns NC_NOERR on success, NetCDF error code otherwise; *taggedFileId
201 /// is set only on success.
202 int mbnc_open_par( NcBackend backend, MPI_Comm comm, MPI_Info info, const char* path, int omode, int* taggedFileId );
203 
204 /// Parallel create with explicit backend (chosen via mbnc_choose_backend_for_write).
205 int mbnc_create_par( NcBackend backend, MPI_Comm comm, MPI_Info info, const char* path, int cmode,
206  int* taggedFileId );
207 #endif
208 
209 /// Serial open. Tags the returned id as NCB_NETCDF_SERIAL.
210 int mbnc_open( const char* path, int omode, int* taggedFileId );
211 
212 /// Serial create.
213 int mbnc_create( const char* path, int cmode, int* taggedFileId );
214 
215 /// Close. Untags + dispatches.
216 int mbnc_close( int taggedFileId );
217 
218 // ============================================================================
219 // Define mode (writes only)
220 // ============================================================================
221 
222 int mbnc_redef( int taggedFileId );
223 int mbnc_enddef( int taggedFileId );
224 int mbnc_def_dim( int taggedFileId, const char* name, size_t len, int* dimid );
225 int mbnc_def_var( int taggedFileId, const char* name, nc_type xtype, int ndims, const int* dimids, int* varid );
226 
227 // ============================================================================
228 // PNetCDF independent / collective mode toggles
229 //
230 // These are PNetCDF-specific knobs that switch the library between
231 // collective and independent I/O modes. No-ops on non-PNetCDF backends.
232 // ============================================================================
233 
234 int mbnc_begin_indep_data( int taggedFileId );
235 int mbnc_end_indep_data( int taggedFileId );
236 
237 // ============================================================================
238 // Inquiry — file / dimension / variable / attribute metadata
239 // ============================================================================
240 
241 int mbnc_inq_natts( int taggedFileId, int* nattsp );
242 int mbnc_inq_ndims( int taggedFileId, int* ndimsp );
243 int mbnc_inq_nvars( int taggedFileId, int* nvarsp );
244 
245 int mbnc_inq_dimid( int taggedFileId, const char* name, int* dimidp );
246 int mbnc_inq_dim( int taggedFileId, int dimid, char* name, size_t* lenp );
247 int mbnc_inq_dimlen( int taggedFileId, int dimid, size_t* lenp );
248 
249 int mbnc_inq_varid( int taggedFileId, const char* name, int* varidp );
250 int mbnc_inq_varname( int taggedFileId, int varid, char* name );
251 int mbnc_inq_vartype( int taggedFileId, int varid, nc_type* xtypep );
252 int mbnc_inq_varndims( int taggedFileId, int varid, int* ndimsp );
253 int mbnc_inq_vardimid( int taggedFileId, int varid, int* dimids );
254 int mbnc_inq_varnatts( int taggedFileId, int varid, int* nattsp );
255 
256 int mbnc_inq_attname( int taggedFileId, int varid, int attnum, char* name );
257 int mbnc_inq_att( int taggedFileId, int varid, const char* name, nc_type* xtypep, size_t* lenp );
258 
259 // ============================================================================
260 // Attribute get / put
261 // ============================================================================
262 
263 int mbnc_get_att_text( int taggedFileId, int varid, const char* name, char* value );
264 int mbnc_get_att_int( int taggedFileId, int varid, const char* name, int* value );
265 int mbnc_get_att_short( int taggedFileId, int varid, const char* name, short* value );
266 int mbnc_get_att_long( int taggedFileId, int varid, const char* name, long* value );
267 int mbnc_get_att_float( int taggedFileId, int varid, const char* name, float* value );
268 int mbnc_get_att_double( int taggedFileId, int varid, const char* name, double* value );
269 
270 int mbnc_put_att_text( int taggedFileId, int varid, const char* name, size_t len, const char* value );
271 int mbnc_put_att_int( int taggedFileId, int varid, const char* name, nc_type xtype, size_t len, const int* value );
272 int mbnc_put_att_short( int taggedFileId, int varid, const char* name, nc_type xtype, size_t len, const short* value );
273 int mbnc_put_att_float( int taggedFileId, int varid, const char* name, nc_type xtype, size_t len, const float* value );
274 int mbnc_put_att_double( int taggedFileId, int varid, const char* name, nc_type xtype, size_t len,
275  const double* value );
276 
277 // ============================================================================
278 // Variable get_vara / put_vara — collective by default on parallel backends
279 //
280 // All array arguments are size_t. PNetCDF wrappers convert to MPI_Offset
281 // on the stack (NetCDF variables rarely have more than ~6 dimensions, so
282 // the conversion is a few-cycle loop).
283 // ============================================================================
284 
285 int mbnc_get_vara_double( int taggedFileId, int varid, const size_t* start, const size_t* count, double* data );
286 int mbnc_get_vara_int( int taggedFileId, int varid, const size_t* start, const size_t* count, int* data );
287 int mbnc_get_vara_long( int taggedFileId, int varid, const size_t* start, const size_t* count, long* data );
288 int mbnc_get_vara_text( int taggedFileId, int varid, const size_t* start, const size_t* count, char* data );
289 
290 int mbnc_get_vars_double( int taggedFileId, int varid, const size_t* start, const size_t* count,
291  const ptrdiff_t* stride, double* data );
292 
293 int mbnc_put_vara_double( int taggedFileId, int varid, const size_t* start, const size_t* count, const double* data );
294 int mbnc_put_vara_int( int taggedFileId, int varid, const size_t* start, const size_t* count, const int* data );
295 int mbnc_put_vara_text( int taggedFileId, int varid, const size_t* start, const size_t* count, const char* data );
296 
297 // Independent-mode get/put variants — used inside begin_indep_data /
298 // end_indep_data brackets on PNetCDF. On non-PNetCDF backends the
299 // collective/independent distinction is per-variable (set externally
300 // via nc_var_par_access on NETCDF_PAR; meaningless for SERIAL), so
301 // these route to the same nc_get_vara_* / nc_put_vara_* call as the
302 // collective wrappers and the caller is responsible for any per-var
303 // access-mode toggling on the libnetcdf side.
304 int mbnc_get_vara_double_indep( int taggedFileId, int varid, const size_t* start, const size_t* count, double* data );
305 int mbnc_get_vara_int_indep( int taggedFileId, int varid, const size_t* start, const size_t* count, int* data );
306 int mbnc_get_vara_long_indep( int taggedFileId, int varid, const size_t* start, const size_t* count, long* data );
307 int mbnc_get_vara_text_indep( int taggedFileId, int varid, const size_t* start, const size_t* count, char* data );
308 int mbnc_get_vars_double_indep( int taggedFileId, int varid, const size_t* start, const size_t* count,
309  const ptrdiff_t* stride, double* data );
310 
311 int mbnc_put_vara_double_indep( int taggedFileId, int varid, const size_t* start, const size_t* count,
312  const double* data );
313 int mbnc_put_vara_int_indep( int taggedFileId, int varid, const size_t* start, const size_t* count, const int* data );
314 int mbnc_put_vara_text_indep( int taggedFileId, int varid, const size_t* start, const size_t* count,
315  const char* data );
316 
317 // ============================================================================
318 // Nonblocking variable get + wait (PNetCDF request aggregation)
319 //
320 // On non-PNetCDF backends:
321 // - mbnc_iget_* performs an immediate blocking collective call and
322 // sets *req = MBNC_REQ_NULL.
323 // - mbnc_wait_all is a no-op that returns NC_NOERR and writes
324 // NC_NOERR into every entry of statuses[] whose corresponding
325 // request equals MBNC_REQ_NULL.
326 //
327 // Net effect: existing #ifdef MOAB_HAVE_PNETCDF blocks in callers
328 // continue to compile and execute correctly even when the runtime
329 // backend isn't PNetCDF.
330 // ============================================================================
331 
332 constexpr int MBNC_REQ_NULL = -1; ///< sentinel for "no real request pending"
333 
334 int mbnc_iget_vara_double( int taggedFileId, int varid, const size_t* start, const size_t* count, double* data,
335  int* req );
336 int mbnc_iget_vara_int( int taggedFileId, int varid, const size_t* start, const size_t* count, int* data, int* req );
337 
338 // Nonblocking puts (symmetric to iget_*). On non-PNetCDF backends: blocking
339 // collective put; *req = MBNC_REQ_NULL. mbnc_wait_all handles both iget and
340 // iput requests on PNetCDF (the underlying ncmpi_wait_all is direction-agnostic).
341 int mbnc_iput_vara_double( int taggedFileId, int varid, const size_t* start, const size_t* count, const double* data,
342  int* req );
343 int mbnc_iput_vara_int( int taggedFileId, int varid, const size_t* start, const size_t* count, const int* data,
344  int* req );
345 
346 int mbnc_wait_all( int taggedFileId, int nreq, int* requests, int* statuses );
347 
348 } // namespace moab
349 
350 #endif // MB_NC_DISPATCH_HPP