77
88- ``h5py`` (for HDF5 files)
99- ``netCDF4`` (preferred NetCDF library)
10+ - ``adios2`` (for ADIOS2 BP files)
1011
1112NOTE
1213----
4546 has_h5py = False
4647
4748
49+ try :
50+ import adios2
51+
52+ has_adios2 = True
53+ except ImportError :
54+ has_adios2 = False
55+
56+
4857class DataFile (object ):
4958 """File I/O class
5059
@@ -94,6 +103,10 @@ def __init__(
94103 self .impl = DataFile_HDF5 (
95104 filename = filename , write = write , create = create , format = format
96105 )
106+ elif filename .split ("." )[- 1 ] in ("bp" , "bp5" ):
107+ self .impl = DataFile_ADIOS2 (
108+ filename = filename , write = write , create = create , format = format
109+ )
97110 else :
98111 self .impl = DataFile_netCDF (
99112 filename = filename ,
@@ -106,6 +119,10 @@ def __init__(
106119 self .impl = DataFile_HDF5 (
107120 filename = filename , write = write , create = create , format = format
108121 )
122+ elif format .lower ().startswith () == "adios" :
123+ self .impl = DataFile_ADIOS2 (
124+ filename = filename , write = write , create = create , format = format
125+ )
109126 else :
110127 self .impl = DataFile_netCDF (
111128 filename = filename , write = write , create = create , format = format , ** kwargs
@@ -984,3 +1001,276 @@ def attributes(self, varname):
9841001 self ._attributes_cache [varname ] = attributes
9851002
9861003 return attributes
1004+
1005+
1006+ class DataFile_ADIOS2 (DataFile ):
1007+ handle = None
1008+
1009+ def open (self , filename , write = False , create = False , format = None ):
1010+ if (not write ) and (not create ):
1011+ self .handle = adios2 .FileReader (filename )
1012+ else :
1013+ message = "DataFile: Writing to ADIOS2 not supported"
1014+ raise ImportError (message )
1015+ # elif create:
1016+ # self.handle = adios2.Stream(filename,"w")
1017+ # else:
1018+ # self.handle = adios2.Stream(filename, "a")
1019+ # Record if writing
1020+ self .writeable = write or create
1021+
1022+ def close (self ):
1023+ if self .handle is not None :
1024+ self .handle .close ()
1025+ self .handle = None
1026+
1027+ def __init__ (self , filename = None , write = False , create = False , format = None ):
1028+ if not has_adios2 :
1029+ message = "DataFile: No supported ADIOS2 python-modules available"
1030+ raise ImportError (message )
1031+ if filename is not None :
1032+ self .open (filename , write = write , create = create , format = format )
1033+ self ._attributes_cache = {}
1034+
1035+ def __del__ (self ):
1036+ self .close ()
1037+
1038+ def __enter__ (self ):
1039+ return self
1040+
1041+ def __exit__ (self , type , value , traceback ):
1042+ self .close ()
1043+
1044+ def read (self , name , ranges = None , asBoutArray = True ):
1045+ if self .handle is None :
1046+ return None
1047+
1048+ var = self .handle .inquire_variable (name )
1049+ n = name
1050+ if var is None :
1051+ # Not found. Try to find using case-insensitive search
1052+ nlow = name .lower ()
1053+ for n in self .handle .available_variables ():
1054+ if n .lower () == nlow :
1055+ print (f"WARNING: Reading '{ n } ' instead of '{ name } '" )
1056+ var = self .handle .inquire_variable (n )
1057+ if var is None :
1058+ return None
1059+
1060+ attributes = self .attributes (n ) if asBoutArray else {}
1061+
1062+ time_dependent = var .steps () > 1
1063+ if time_dependent :
1064+ tdim = 1
1065+ else :
1066+ tdim = 0
1067+
1068+ ndims = len (var .shape ()) + tdim
1069+
1070+ # print(f"\n Read {name} shape = {var.shape()} ndims = {ndims} tdim = {tdim} Ranges = {ranges}\n")
1071+
1072+ if ndims == 0 : # single value, no time
1073+ data = self .handle .read (var )
1074+ if asBoutArray :
1075+ data = BoutArray (data , attributes = attributes )
1076+ return data
1077+ else :
1078+ if ranges :
1079+ # adios python does not support slicing,
1080+ # need to transform slices to start/count arrays
1081+ if len (ranges ) == 2 * ndims :
1082+ start = ranges [::2 ]
1083+ count = ranges [1 ::2 ] - start + 1
1084+ elif len (ranges ) != ndims :
1085+ raise ValueError (
1086+ "Incorrect number of elements in ranges argument "
1087+ f"(got { ranges } , expected { ndims } or { 2 * ndims } )"
1088+ )
1089+ else :
1090+ count = [int (r .stop - r .start ) for r in ranges ]
1091+ start = [int (r .start ) for r in ranges ]
1092+ steps = [int (r .step ) for r in ranges ]
1093+ if any (s != 1 for s in steps ):
1094+ raise ValueError (
1095+ "ADIOS2 does not support 'step != 1' in ranges argument "
1096+ f"(got { ranges } when reading variable { name } )"
1097+ )
1098+
1099+ if time_dependent :
1100+ var .set_step_selection ([start [0 ], count [0 ]])
1101+ var .set_selection ([start [1 :], count [1 :]])
1102+ else :
1103+ var .set_selection ([start , count ])
1104+
1105+ data = self .handle .read (var )
1106+ if time_dependent :
1107+ data = data .reshape (count )
1108+ # print(f"Read data of size {data.shape}")
1109+ if asBoutArray :
1110+ data = BoutArray (data , attributes = attributes )
1111+ return data
1112+ else :
1113+ # boutdata assumes reading all steps when no ranges are given
1114+ var .set_step_selection ([0 , var .steps ()])
1115+ data = self .handle .read (var )
1116+ if asBoutArray :
1117+ data = BoutArray (data , attributes = attributes )
1118+ return data
1119+
1120+ def __getitem__ (self , name ):
1121+ var = self .read (name )
1122+ if var is None :
1123+ raise KeyError (f"No variable found: { name } " )
1124+ return var
1125+
1126+ def __setitem__ (self , key , value ):
1127+ self .write (key , value )
1128+
1129+ def list (self ):
1130+ if self .handle is None :
1131+ return []
1132+ vars = self .handle .available_variables ()
1133+ names = vars .keys ()
1134+ return names
1135+
1136+ def keys (self ):
1137+ return self .list ()
1138+
1139+ def dimensions (self , varname ):
1140+ bout_type = self .bout_type (varname )
1141+ dims = BoutArray .dims_from_type (bout_type )
1142+ if dims is None :
1143+ raise ValueError (
1144+ "Variable bout_type not recognized (got {})" .format (bout_type )
1145+ )
1146+ return dims
1147+
1148+ def _bout_type_from_array (self , data ):
1149+ """Get the bout_type from the array 'data'
1150+
1151+ If 'data' is a BoutArray, it knows its bout_type, otherwise we
1152+ have to guess.
1153+
1154+ Parameters
1155+ ----------
1156+ data : :py:obj:`~boututils.boutarray.BoutArray` or ndarray
1157+ An array with between 0 and 4 dimensions
1158+
1159+ Returns
1160+ -------
1161+ str
1162+ Either the actual bout_type or our best guess
1163+
1164+ See Also
1165+ --------
1166+ - `DataFile.bout_type`
1167+
1168+ TODO
1169+ ----
1170+ - Make standalone function
1171+
1172+ """
1173+ try :
1174+ # If data is a BoutArray, it should have a type attribute that we can use
1175+ return data .attributes ["bout_type" ]
1176+ except AttributeError :
1177+ # Otherwise data is a numpy.ndarray and we have to guess the bout_type
1178+ pass
1179+
1180+ try :
1181+ ndim = len (data .shape )
1182+ except AttributeError :
1183+ ndim = 0
1184+ if ndim == 4 :
1185+ return "Field3D_t"
1186+ elif ndim == 3 :
1187+ # not ideal, 3d field might be time-evolving 2d field,
1188+ # 'Field2D_t', but can't think of a good way to distinguish
1189+ alwayswarn (
1190+ "Warning: assuming bout_type of 3d array is Field3D. If it "
1191+ "should be a time-evolving Field2D, this may cause errors in "
1192+ "dimension sizes."
1193+ )
1194+ return "Field3D"
1195+ elif ndim == 2 :
1196+ return "Field2D"
1197+ elif ndim == 1 :
1198+ return "scalar_t"
1199+ elif ndim == 0 :
1200+ return "scalar"
1201+ else :
1202+ raise ValueError (f"Unrecognized variable bout_type, ndims={ ndim } " )
1203+
1204+ def ndims (self , varname ):
1205+ if self .handle is None :
1206+ return None
1207+
1208+ var = self .handle .inquire_variable (varname )
1209+ if var is None :
1210+ raise ValueError ("Variable not found" )
1211+
1212+ return len (var .shape )
1213+
1214+ def sync (self ):
1215+ return None
1216+
1217+ def size (self , varname ):
1218+ if self .handle is None :
1219+ return None
1220+ var = self .handle .inquire_variable (varname )
1221+ if var is None :
1222+ return None
1223+ return var .shape ()
1224+
1225+ def _bout_type_from_dimensions (self , dims , steps ):
1226+ t = ()
1227+ if steps > 1 :
1228+ t = t + ('t' ,)
1229+ for d in dims :
1230+ t = t + (d ,)
1231+ bt = BoutArray .type_from_dims (t )
1232+ return bt
1233+
1234+ def write (self , name , data , info = False ):
1235+ raise Exception ("ADIOS2 File not writeable." )
1236+
1237+ def read_file_attribute (self , name ):
1238+ attr = self .handle .inquire_attribute (name )
1239+ if attr is None :
1240+ raise AttributeError (f"DataFile (ADIOS2) has no file attribute { name } " )
1241+ return self .handle .read_attribute (name )
1242+
1243+ def write_file_attribute (self , name , value ):
1244+ return None
1245+ # self.handle.attrs[name] = value
1246+
1247+ def list_file_attributes (self ):
1248+ attrs = self .handle .available_attributes ()
1249+ return attrs .keys ()
1250+
1251+ def attributes (self , varname ):
1252+ # adios has attribute names and values in memory after open
1253+ # so we don't really need caching here but want to avoid
1254+ # creating the dictionary every time
1255+ try :
1256+ return self ._attributes_cache [varname ]
1257+ except KeyError :
1258+ # Need to add attributes for this variable to the cache
1259+ attributes = {}
1260+ varattrs = self .handle .available_attributes (varname )
1261+ for attrname in varattrs :
1262+ attribute = self .handle .read_attribute (attrname , varname )
1263+ if type (attribute ) in [bytes , np .bytes_ ]:
1264+ attribute = str (attribute , encoding = "utf-8" )
1265+ attributes [attrname ] = attribute
1266+
1267+ if "bout_type" not in attributes :
1268+ if "__xarray_dimensions__" in attributes :
1269+ var = self .handle .inquire_variable (varname )
1270+ attributes ["bout_type" ] = self ._bout_type_from_dimensions (
1271+ attributes ["__xarray_dimensions__" ], var .steps ())
1272+
1273+ # Save the attributes for this variable to the cache
1274+ self ._attributes_cache [varname ] = attributes
1275+
1276+ return attributes
0 commit comments