Skip to content

Commit 65e8b22

Browse files
committed
Added support for reading ADIOS2 BP files.
1 parent 7992f64 commit 65e8b22

2 files changed

Lines changed: 291 additions & 1 deletion

File tree

src/boutdata/collect.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1182,7 +1182,7 @@ def findFiles(path, prefix):
11821182
prefix = prefix[:-1]
11831183

11841184
# Look for parallel dump files
1185-
suffixes = [".nc", ".ncdf", ".cdl", ".h5", ".hdf5", ".hdf"]
1185+
suffixes = [".nc", ".ncdf", ".cdl", ".h5", ".hdf5", ".hdf", ".bp", ".bp5"]
11861186
file_list_parallel = None
11871187
suffix_parallel = ""
11881188
for test_suffix in suffixes:

src/boututils/datafile.py

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
- ``h5py`` (for HDF5 files)
99
- ``netCDF4`` (preferred NetCDF library)
10+
- ``adios2`` (for ADIOS2 BP files)
1011
1112
NOTE
1213
----
@@ -45,6 +46,14 @@
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+
4857
class 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

Comments
 (0)