Mesh Oriented datABase  (version 5.6.0)
An array-based unstructured mesh library
ReadNC.cpp
Go to the documentation of this file.
1 #include "ReadNC.hpp"
2 #include "NCHelper.hpp"
3 
4 #include "moab/ReadUtilIface.hpp"
5 #include "MBTagConventions.hpp"
6 #include "moab/FileOptions.hpp"
7 
8 namespace moab
9 {
10 
12 {
13  return new ReadNC( iface );
14 }
15 
17  : mbImpl( impl ), fileId( -1 ), mGlobalIdTag( 0 ), mpFileIdTag( NULL ), dbgOut( stderr ), isParallel( false ),
18  partMethod( ScdParData::ALLJORKORI ), scdi( NULL ),
19 #ifdef MOAB_HAVE_MPI
20  myPcomm( NULL ),
21 #endif
22  noMesh( false ), noVars( false ), spectralMesh( false ), noMixedElements( false ), noEdges( false ),
23  culling( true ), repartition( false ), gatherSetRank( -1 ), tStepBase( -1 ), trivialPartitionShift( 0 ),
24  myHelper( NULL )
25 {
26  assert( impl != NULL );
28 }
29 
31 {
33  if( myHelper != NULL ) delete myHelper;
34 }
35 
36 ErrorCode ReadNC::load_file( const char* file_name,
37  const EntityHandle* file_set,
38  const FileOptions& opts,
39  const ReaderIface::SubsetList* /*subset_list*/,
40  const Tag* file_id_tag )
41 {
42  // See if opts has variable(s) specified
43  std::vector< std::string > var_names;
44  std::vector< int > tstep_nums;
45  std::vector< double > tstep_vals;
46 
47  // Get and cache predefined tag handles
49  // Store the pointer to the tag; if not null, set when global id tag
50  // is set too, with the same data, duplicated
51  mpFileIdTag = file_id_tag;
52 
53  MB_CHK_SET_ERR( parse_options( opts, var_names, tstep_nums, tstep_vals ), "Trouble parsing option string" );
54 
55  // Open the file
56  dbgOut.tprintf( 1, "Opening file %s\n", file_name );
57  fileName = std::string( file_name );
58  int success;
59 
60  // Probe on-disk format and select the backend that can actually read it.
61  // This replaces the old compile-time choice between nc_open and ncmpi_open
62  // (which silently failed on NetCDF-4/HDF5 files under PNetCDF-only builds).
63  // For parallel reads the probe runs on rank 0 and the answer is
64  // broadcast; the file header read is cheap (8 bytes) so the cost is
65  // negligible vs. doing the probe collectively on every rank.
66  int fileFormat = NCFMT_UNKNOWN;
67 #ifdef MOAB_HAVE_MPI
68  if( isParallel )
69  {
70  int rank = myPcomm->proc_config().proc_rank();
71  if( rank == 0 ) fileFormat = mbnc_detect_format( file_name );
72  MPI_Bcast( &fileFormat, 1, MPI_INT, 0, myPcomm->proc_config().proc_comm() );
73  }
74  else
75 #endif
76  {
77  fileFormat = mbnc_detect_format( file_name );
78  }
79 
80 #ifdef MOAB_HAVE_MPI
81  const int mpi_size = isParallel ? myPcomm->proc_config().proc_size() : 1;
82 #else
83  const int mpi_size = 1;
84 #endif
85 
86  const NcBackend backend = mbnc_choose_backend_for_read( fileFormat, mpi_size );
87  if( backend == NCB_NONE )
88  {
89  const char* fmtName = ( fileFormat == NCFMT_CLASSIC ) ? "classic CDF-1/2/5"
90  : ( fileFormat == NCFMT_NETCDF4 ) ? "NetCDF-4 / HDF5"
91  : "unrecognized / non-NetCDF";
92  MB_SET_ERR( MB_FAILURE,
93  "Cannot find a compatible parallel reader for file '"
94  << file_name << "' (detected format: " << fmtName
95  << "). PNetCDF cannot read NetCDF-4 files; libnetcdf parallel "
96  "must be configured for that case. Classic-format files require "
97  "either PNetCDF or libnetcdf built with --enable-pnetcdf." );
98  }
99 
100 #ifdef MOAB_HAVE_MPI
101  if( backend == NCB_NETCDF_SERIAL || mpi_size == 1 )
102  {
103  success = mbnc_open( file_name, 0, &fileId );
104  }
105  else
106  {
107  success = mbnc_open_par( backend, myPcomm->proc_config().proc_comm(), MPI_INFO_NULL, file_name, 0, &fileId );
108  }
109 #else
110  success = mbnc_open( file_name, 0, &fileId );
111 #endif
112  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble opening file " << file_name );
113 
114  // Read the header (num dimensions, dimensions, num variables, global attribs)
115  MB_CHK_SET_ERR( read_header(), "Trouble reading file header" );
116 
117  // Make sure there's a file set to put things in
118  EntityHandle tmp_set;
119  if( noMesh && !file_set )
120  {
121  MB_SET_ERR( MB_FAILURE, "NOMESH option requires non-NULL file set on input" );
122  }
123  else if( !file_set || ( file_set && *file_set == 0 ) )
124  {
125  MB_CHK_SET_ERR( mbImpl->create_meshset( MESHSET_SET, tmp_set ), "Trouble creating file set" );
126  }
127  else
128  tmp_set = *file_set;
129 
130  // Get the scd interface
131  scdi = nullptr;
132  MB_CHK_SET_ERR( mbImpl->query_interface( scdi ), "failed to get SCD interface from query" );
133  if( nullptr == scdi ) return MB_FAILURE;
134 
135  if( nullptr != myHelper ) delete myHelper;
136 
137  // Get appropriate NC helper instance based on information read from the header
138  myHelper = NCHelper::get_nc_helper( this, fileId, opts, tmp_set );
139  if( nullptr == myHelper )
140  {
141  MB_SET_ERR( MB_FAILURE, "Failed to get NCHelper class instance" );
142  }
143 
144  // Initialize mesh values
145  MB_CHK_SET_ERR( myHelper->init_mesh_vals(), "Trouble initializing mesh values" );
146 
147  // Check existing mesh from last read
148  if( noMesh && !noVars )
149  {
150  MB_CHK_SET_ERR( myHelper->check_existing_mesh(), "Trouble checking mesh from last read" );
151  }
152 
153  // Create some conventional tags, e.g. __NUM_DIMS
154  // For multiple reads to a specified file set, we assume a single file, or a series of
155  // files with separated timesteps. Keep a flag on the file set to prevent conventional
156  // tags from being created again on a second read
157  Tag convTagsCreated = 0;
158  int def_val = 0;
159  MB_CHK_SET_ERR( mbImpl->tag_get_handle( "__CONV_TAGS_CREATED", 1, MB_TYPE_INTEGER, convTagsCreated,
160  MB_TAG_SPARSE | MB_TAG_CREAT, &def_val ),
161  "Trouble getting _CONV_TAGS_CREATED tag" );
162  int create_conv_tags_flag = 0;
163  MB_CHK_SET_ERR( mbImpl->tag_get_data( convTagsCreated, &tmp_set, 1, &create_conv_tags_flag ),
164  "failed to get conventional tags" );
165  // The first read to the file set
166  if( 0 == create_conv_tags_flag )
167  {
168  // Read dimensions (coordinate variables) by default to create tags like __<var_name>_DIMS
169  // This is done only once (assume that all files read to the file set have the same
170  // dimensions)
171  MB_CHK_SET_ERR( myHelper->read_variables( dimNames, tstep_nums ), "Trouble reading dimensions" );
172 
173  MB_CHK_SET_ERR( myHelper->create_conventional_tags( tstep_nums ), "Trouble creating NC conventional tags" );
174 
175  create_conv_tags_flag = 1;
176  MB_CHK_SET_ERR( mbImpl->tag_set_data( convTagsCreated, &tmp_set, 1, &create_conv_tags_flag ),
177  "Trouble setting data to _CONV_TAGS_CREATED tag" );
178  }
179  else // Another read to the file set
180  {
181  if( tStepBase > -1 )
182  {
183  // If timesteps spread across files, merge time values read
184  // from current file to existing time tag
185  MB_CHK_SET_ERR( myHelper->update_time_tag_vals(), "Trouble updating time tag values" );
186  }
187  }
188 
189  // Create mesh vertex/edge/face sequences
190  Range faces;
191  if( !noMesh )
192  {
193  MB_CHK_SET_ERR( myHelper->create_mesh( faces ), "Trouble creating mesh" );
194  }
195 
196  // Read specified variables onto grid
197  if( !noVars )
198  {
199  if( var_names.empty() )
200  {
201  // If VARIABLE option is missing, read all variables
202  MB_CHK_SET_ERR( myHelper->read_variables( var_names, tstep_nums ), "Trouble reading all variables" );
203  }
204  else
205  {
206  // Exclude dimensions that are read to the file set by default
207  std::vector< std::string > non_dim_var_names;
208  for( unsigned int i = 0; i < var_names.size(); i++ )
209  {
210  if( std::find( dimNames.begin(), dimNames.end(), var_names[i] ) == dimNames.end() )
211  non_dim_var_names.push_back( var_names[i] );
212  }
213 
214  if( !non_dim_var_names.empty() )
215  {
216  MB_CHK_SET_ERR( myHelper->read_variables( non_dim_var_names, tstep_nums ),
217  "Trouble reading specified variables" );
218  }
219  }
220  }
221 
222 #ifdef MOAB_HAVE_MPI
223  // Create partition set, and populate with elements
224  if( isParallel )
225  {
226  // Write partition tag name on partition set
227  Tag part_tag = myPcomm->partition_tag();
228  int dum_rank = myPcomm->proc_config().proc_rank();
229  // the tmp_set is the file_set
230  MB_CHK_SET_ERR( mbImpl->tag_set_data( part_tag, &tmp_set, 1, &dum_rank ),
231  "Trouble writing partition tag name on partition set" );
232  }
233 #endif
234 
236  scdi = NULL;
237 
238  // Close the file
239  success = NCFUNC( close )( fileId );
240  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble closing file" );
241 
242  return MB_SUCCESS;
243 }
244 
246  std::vector< std::string >& var_names,
247  std::vector< int >& tstep_nums,
248  std::vector< double >& tstep_vals )
249 {
250  int tmpval;
251  if( MB_SUCCESS == opts.get_int_option( "DEBUG_IO", 1, tmpval ) )
252  {
253  dbgOut.set_verbosity( tmpval );
254  dbgOut.set_prefix( "NC " );
255  }
256 
257  ErrorCode rval = opts.get_strs_option( "VARIABLE", var_names );
258  if( MB_TYPE_OUT_OF_RANGE == rval )
259  noVars = true;
260  else
261  noVars = false;
262 
263  opts.get_ints_option( "TIMESTEP", tstep_nums );
264  opts.get_reals_option( "TIMEVAL", tstep_vals );
265 
266  rval = opts.get_null_option( "NOMESH" );
267  if( MB_SUCCESS == rval ) noMesh = true;
268 
269  rval = opts.get_null_option( "SPECTRAL_MESH" );
270  if( MB_SUCCESS == rval ) spectralMesh = true;
271 
272  rval = opts.get_null_option( "NO_MIXED_ELEMENTS" );
273  if( MB_SUCCESS == rval ) noMixedElements = true;
274 
275  rval = opts.get_null_option( "NO_EDGES" );
276  if( MB_SUCCESS == rval ) noEdges = true;
277 
278  rval = opts.get_null_option( "NO_CULLING" ); // used now only for domain nc convention
279  if( MB_SUCCESS == rval ) culling = false;
280 
281  rval = opts.get_null_option( "REPARTITION" ); // used now only for domain nc, to repartition with zoltan
282  if( MB_SUCCESS == rval ) repartition = true;
283 
284  if( 2 <= dbgOut.get_verbosity() )
285  {
286  if( !var_names.empty() )
287  {
288  std::cerr << "Variables requested: ";
289  for( unsigned int i = 0; i < var_names.size(); i++ )
290  std::cerr << var_names[i];
291  std::cerr << std::endl;
292  }
293 
294  if( !tstep_nums.empty() )
295  {
296  std::cerr << "Timesteps requested: ";
297  for( unsigned int i = 0; i < tstep_nums.size(); i++ )
298  std::cerr << tstep_nums[i];
299  std::cerr << std::endl;
300  }
301 
302  if( !tstep_vals.empty() )
303  {
304  std::cerr << "Time vals requested: ";
305  for( unsigned int i = 0; i < tstep_vals.size(); i++ )
306  std::cerr << tstep_vals[i];
307  std::cerr << std::endl;
308  }
309  }
310 
311  rval = opts.get_int_option( "GATHER_SET", 0, gatherSetRank );
312  if( MB_TYPE_OUT_OF_RANGE == rval )
313  {
314  MB_SET_ERR( rval, "Invalid value for GATHER_SET option" );
315  }
316 
317  rval = opts.get_int_option( "TIMESTEPBASE", 0, tStepBase );
318  if( MB_TYPE_OUT_OF_RANGE == rval )
319  {
320  MB_SET_ERR( rval, "Invalid value for TIMESTEPBASE option" );
321  }
322 
323  rval = opts.get_int_option( "TRIVIAL_PARTITION_SHIFT", 1, trivialPartitionShift );
324  if( MB_TYPE_OUT_OF_RANGE == rval )
325  {
326  MB_SET_ERR( rval, "Invalid value for TRIVIAL_PARTITION_SHIFT option" );
327  }
328 
329 #ifdef MOAB_HAVE_MPI
330  isParallel = ( opts.match_option( "PARALLEL", "READ_PART" ) != MB_ENTITY_NOT_FOUND );
331 
332  if( !isParallel )
333  // Return success here, since rval still has _NOT_FOUND from not finding option
334  // in this case, myPcomm will be NULL, so it can never be used; always check for isParallel
335  // before any use for myPcomm
336  return MB_SUCCESS;
337 
338  int pcomm_no = 0;
339  rval = opts.get_int_option( "PARALLEL_COMM", pcomm_no );
340  if( MB_TYPE_OUT_OF_RANGE == rval )
341  {
342  MB_SET_ERR( rval, "Invalid value for PARALLEL_COMM option" );
343  }
344  myPcomm = ParallelComm::get_pcomm( mbImpl, pcomm_no );
345  if( 0 == myPcomm )
346  {
347  myPcomm = new ParallelComm( mbImpl, MPI_COMM_WORLD );
348  }
349  const int rank = myPcomm->proc_config().proc_rank();
350  dbgOut.set_rank( rank );
351 
352  int dum;
353  rval = opts.match_option( "PARTITION_METHOD", ScdParData::PartitionMethodNames, dum );
354  if( MB_FAILURE == rval )
355  {
356  MB_SET_ERR( rval, "Unknown partition method specified" );
357  }
358  else if( MB_ENTITY_NOT_FOUND == rval )
360  else
361  partMethod = dum;
362 #endif
363 
364  return MB_SUCCESS;
365 }
366 
368 {
369  dbgOut.tprint( 1, "Reading header...\n" );
370 
371  // Get the global attributes
372  int numgatts;
373  int success;
374  success = NCFUNC( inq_natts )( fileId, &numgatts );
375  if( success ) MB_SET_ERR( MB_FAILURE, "Couldn't get number of global attributes" );
376 
377  // Read attributes into globalAtts
378  ErrorCode result = get_attributes( NC_GLOBAL, numgatts, globalAtts );
379  MB_CHK_SET_ERR( result, "Trouble getting global attributes" );
380  dbgOut.tprintf( 1, "Read %u attributes\n", (unsigned int)globalAtts.size() );
381 
382  // Read in dimensions into dimNames and dimLens
383  result = get_dimensions( fileId, dimNames, dimLens );
384  MB_CHK_SET_ERR( result, "Trouble getting dimensions" );
385  dbgOut.tprintf( 1, "Read %u dimensions\n", (unsigned int)dimNames.size() );
386 
387  // Read in variables into varInfo
388  result = get_variables();
389  MB_CHK_SET_ERR( result, "Trouble getting variables" );
390  dbgOut.tprintf( 1, "Read %u variables\n", (unsigned int)varInfo.size() );
391 
392  return MB_SUCCESS;
393 }
394 
395 ErrorCode ReadNC::get_attributes( int var_id, int num_atts, std::map< std::string, AttData >& atts, const char* prefix )
396 {
397  char dum_name[120];
398 
399  for( int i = 0; i < num_atts; i++ )
400  {
401  // Get the name
402  int success = NCFUNC( inq_attname )( fileId, var_id, i, dum_name );
403  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble getting attribute name" );
404 
405  AttData& data = atts[std::string( dum_name )];
406  data.attName = std::string( dum_name );
407  success = NCFUNC( inq_att )( fileId, var_id, dum_name, &data.attDataType, &data.attLen );
408  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble getting info for attribute " << data.attName );
409  data.attVarId = var_id;
410 
411  dbgOut.tprintf( 2, "%sAttribute %s: length=%u, varId=%d, type=%d\n", ( prefix ? prefix : "" ),
412  data.attName.c_str(), (unsigned int)data.attLen, data.attVarId, data.attDataType );
413  }
414 
415  return MB_SUCCESS;
416 }
417 
418 ErrorCode ReadNC::get_dimensions( int file_id, std::vector< std::string >& dim_names, std::vector< int >& dim_lens )
419 {
420  // Get the number of dimensions
421  int num_dims;
422  int success = NCFUNC( inq_ndims )( file_id, &num_dims );
423  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble getting number of dimensions" );
424 
425  if( num_dims > NC_MAX_DIMS )
426  {
427  MB_SET_ERR( MB_FAILURE,
428  "ReadNC: File contains " << num_dims << " dims but NetCDF library supports only " << NC_MAX_DIMS );
429  }
430 
431  char dim_name[NC_MAX_NAME + 1];
432  NCDF_SIZE dim_len;
433  dim_names.resize( num_dims );
434  dim_lens.resize( num_dims );
435 
436  for( int i = 0; i < num_dims; i++ )
437  {
438  success = NCFUNC( inq_dim )( file_id, i, dim_name, &dim_len );
439  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble getting dimension info" );
440 
441  dim_names[i] = std::string( dim_name );
442  dim_lens[i] = dim_len;
443 
444  dbgOut.tprintf( 2, "Dimension %s, length=%u\n", dim_name, (unsigned int)dim_len );
445  }
446 
447  return MB_SUCCESS;
448 }
449 
451 {
452  // First cache the number of time steps
453  std::vector< std::string >::iterator vit = std::find( dimNames.begin(), dimNames.end(), "time" );
454  if( vit == dimNames.end() ) vit = std::find( dimNames.begin(), dimNames.end(), "t" );
455 
456  int ntimes = 0;
457  if( vit != dimNames.end() ) ntimes = dimLens[vit - dimNames.begin()];
458  if( !ntimes ) ntimes = 1;
459 
460  // Get the number of variables
461  int num_vars;
462  int success = NCFUNC( inq_nvars )( fileId, &num_vars );
463  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble getting number of variables" );
464 
465  if( num_vars > NC_MAX_VARS )
466  {
467  MB_SET_ERR( MB_FAILURE,
468  "ReadNC: File contains " << num_vars << " vars but NetCDF library supports only " << NC_MAX_VARS );
469  }
470 
471  char var_name[NC_MAX_NAME + 1];
472  int var_ndims;
473 
474  for( int i = 0; i < num_vars; i++ )
475  {
476  // Get the name first, so we can allocate a map iterate for this var
477  success = NCFUNC( inq_varname )( fileId, i, var_name );
478  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble getting variable name" );
479  VarData& data = varInfo[std::string( var_name )];
480  data.varName = std::string( var_name );
481  data.varId = i;
482  data.varTags.resize( ntimes, 0 );
483 
484  // Get the data type
485  success = NCFUNC( inq_vartype )( fileId, i, &data.varDataType );
486  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble getting data type for variable " << data.varName );
487 
488  // Get the number of dimensions, then the dimensions
489  success = NCFUNC( inq_varndims )( fileId, i, &var_ndims );
490  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble getting number of dims for variable " << data.varName );
491  data.varDims.resize( var_ndims );
492 
493  success = NCFUNC( inq_vardimid )( fileId, i, &data.varDims[0] );
494  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble getting dimensions for variable " << data.varName );
495 
496  // Finally, get the number of attributes, then the attributes
497  success = NCFUNC( inq_varnatts )( fileId, i, &data.numAtts );
498  if( success ) MB_SET_ERR( MB_FAILURE, "Trouble getting number of dims for variable " << data.varName );
499 
500  // Print debug info here so attribute info comes afterwards
501  dbgOut.tprintf( 2, "Variable %s: Id=%d, numAtts=%d, datatype=%d, num_dims=%u\n", data.varName.c_str(),
502  data.varId, data.numAtts, data.varDataType, (unsigned int)data.varDims.size() );
503 
504  MB_CHK_SET_ERR( get_attributes( i, data.numAtts, data.varAtts, " " ),
505  "Trouble getting attributes for variable " << data.varName );
506  }
507 
508  return MB_SUCCESS;
509 }
510 
512  const char*,
513  const FileOptions&,
514  std::vector< int >&,
515  const SubsetList* )
516 {
517  return MB_FAILURE;
518 }
519 
520 } // namespace moab