-
Notifications
You must be signed in to change notification settings - Fork 6
Added the ability to read xdmf dumps from Idefix
#336
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
d5ea13d
0449cfb
6599921
991580c
6a01779
6f4c6fd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -31,6 +31,7 @@ | |||||||||||||||||||||||||||
| from .fields import ( | ||||||||||||||||||||||||||||
| IdefixDmpFields, | ||||||||||||||||||||||||||||
| IdefixVtkFields, | ||||||||||||||||||||||||||||
| IdefixXdmfFields, | ||||||||||||||||||||||||||||
| PlutoFields, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -263,7 +264,7 @@ def _cell_centers(self) -> tuple[XCoords, YCoords, ZCoords]: | |||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class PlutoXdmfHierarchy(GoodBoyHierarchy): | ||||||||||||||||||||||||||||
| class XdmfHierarchy(GoodBoyHierarchy): | ||||||||||||||||||||||||||||
| @cached_property | ||||||||||||||||||||||||||||
| def _cell_widths(self) -> tuple[XSpans, YSpans, ZSpans]: | ||||||||||||||||||||||||||||
| cell_edges = h5_io.read_grid_coordinates( | ||||||||||||||||||||||||||||
|
|
@@ -842,6 +843,222 @@ def _is_valid(cls, filename: str, *args, **kwargs) -> bool: # NOQA: ARG003 | |||||||||||||||||||||||||||
| return "Idefix" in header | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class XdmfMixin(Dataset): | ||||||||||||||||||||||||||||
| _index_class = XdmfHierarchy | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class IdefixXdmfDataset(XdmfMixin, IdefixDataset): | ||||||||||||||||||||||||||||
| _dataset_type = "idefix-xdmf" | ||||||||||||||||||||||||||||
| _field_info_class = IdefixXdmfFields | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||
| @classmethod | ||||||||||||||||||||||||||||
| def _is_valid(cls, filename: str, *args, **kwargs) -> bool: # NOQA: ARG003 | ||||||||||||||||||||||||||||
| if not ( | ||||||||||||||||||||||||||||
| filename.endswith((".dbl.h5", ".flt.h5")) | ||||||||||||||||||||||||||||
| and os.path.isfile(filename.removesuffix(".h5") + ".xmf") | ||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||
| fileh = h5py.File(filename, mode="r") | ||||||||||||||||||||||||||||
| except (ImportError, OSError): | ||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| base_groups = list(fileh.keys()) | ||||||||||||||||||||||||||||
| key_entry = "" | ||||||||||||||||||||||||||||
| for key in base_groups: | ||||||||||||||||||||||||||||
| if "Timestep_" in key: | ||||||||||||||||||||||||||||
| key_entry = key | ||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||
| if key_entry == "": | ||||||||||||||||||||||||||||
| fileh.close() | ||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||
| attributes = list(fileh[f"/{key_entry}"].attrs.keys()) | ||||||||||||||||||||||||||||
| if "version" not in attributes: | ||||||||||||||||||||||||||||
| fileh.close() | ||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can probably make this slightly more efficient by reading keys eagerly instead of greedily
Suggested change
|
||||||||||||||||||||||||||||
| version = fileh[f"/{key_entry}"].attrs["version"][0].decode() | ||||||||||||||||||||||||||||
| fileh.close() | ||||||||||||||||||||||||||||
| return "Idefix" in version and "XDMF" in version | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||
| def _parse_parameter_file(self): | ||||||||||||||||||||||||||||
| super()._parse_parameter_file() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # parse the grid | ||||||||||||||||||||||||||||
| coords = h5_io.read_grid_coordinates( | ||||||||||||||||||||||||||||
| self.filename, geometry=self.attributes["geometry"] | ||||||||||||||||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the only difference (seems to me) with |
||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| _default_field_list = [f[0] for f in self._field_info_class.known_other_fields] | ||||||||||||||||||||||||||||
| with h5py.File(self.filename, mode="r") as h5f: | ||||||||||||||||||||||||||||
| root = list(h5f.keys())[0] | ||||||||||||||||||||||||||||
| self._detected_field_list = list(h5f[f"{root}/vars/"]) | ||||||||||||||||||||||||||||
| self._field_name_map = {} | ||||||||||||||||||||||||||||
| # some versions of Pluto define field names in lower case | ||||||||||||||||||||||||||||
| # so we normalize standard output field names to upper case | ||||||||||||||||||||||||||||
| # to avoid duplicating data in PlutoFields.known_other_fields | ||||||||||||||||||||||||||||
| for varname in self._detected_field_list: | ||||||||||||||||||||||||||||
| if varname.upper() in _default_field_list: | ||||||||||||||||||||||||||||
| # The key in hdf5 is case sensitive, so we have to preserve | ||||||||||||||||||||||||||||
| # the info of original field names for reading data | ||||||||||||||||||||||||||||
| self._field_name_map[varname.upper()] = varname | ||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| self._field_name_map[varname] = varname | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self._detected_field_list = self._field_name_map.keys() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.domain_dimensions = np.array(coords.array_shape) | ||||||||||||||||||||||||||||
| self.dimensionality = np.count_nonzero(self.domain_dimensions - 1) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| dle = np.array([arr.min() for arr in coords.arrays], dtype="float64") | ||||||||||||||||||||||||||||
| dre = np.array([arr.max() for arr in coords.arrays], dtype="float64") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # temporary hack to prevent 0-width dimensions for 2D data | ||||||||||||||||||||||||||||
| dre = np.where(dre == dle, dle + 1, dre) | ||||||||||||||||||||||||||||
| self.domain_left_edge = dle | ||||||||||||||||||||||||||||
| self.domain_right_edge = dre | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self._periodicity = (True, True, True) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||
| def _read_data_header(self) -> str: | ||||||||||||||||||||||||||||
| with h5py.File(self.filename, mode="r") as h5f: | ||||||||||||||||||||||||||||
| base_groups = list(h5f.keys()) | ||||||||||||||||||||||||||||
| key_entry = "" | ||||||||||||||||||||||||||||
| for key in base_groups: | ||||||||||||||||||||||||||||
| if "Timestep_" in key: | ||||||||||||||||||||||||||||
| key_entry = key | ||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||
|
Comment on lines
+941
to
+946
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
(though this should be equivalent to what you have, I am not sure about the |
||||||||||||||||||||||||||||
| self.current_time = float(h5f[f"/{key_entry}"].attrs["time"]) | ||||||||||||||||||||||||||||
| version_line = h5f[f"/{key_entry}"].attrs["version"][0].decode() | ||||||||||||||||||||||||||||
| attributes = list(h5f[f"/{key_entry}"].attrs.keys()) | ||||||||||||||||||||||||||||
| self.attributes = {} | ||||||||||||||||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general, we shouldn't need to add arbitrary attributes to |
||||||||||||||||||||||||||||
| for attribute in attributes: | ||||||||||||||||||||||||||||
| if attribute == "version": | ||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||
| if attribute == "dump_datatype" or attribute == "geometry": | ||||||||||||||||||||||||||||
| self.attributes[attribute] = ( | ||||||||||||||||||||||||||||
| h5f[f"/{key_entry}"].attrs[attribute][0].decode() | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| self.attributes[attribute] = h5f[f"/{key_entry}"].attrs[attribute] | ||||||||||||||||||||||||||||
| match = re.search(r"\d+\.\d+\.?\d*[-\w+]*", version_line) | ||||||||||||||||||||||||||||
| if match is None: | ||||||||||||||||||||||||||||
| warnings.warn( | ||||||||||||||||||||||||||||
| "Could not determine code version from file HDF5 file attribute", | ||||||||||||||||||||||||||||
| stacklevel=2, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| return "unknown" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return match.group() | ||||||||||||||||||||||||||||
|
Comment on lines
+960
to
+968
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to avoid warnings here because it's very hard to know what stacklevel value is relevant (and it may depend on the situation)
Suggested change
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||
| def _setup_geometry(self) -> None: | ||||||||||||||||||||||||||||
| self.geometry = Geometry(self.parameters["definitions"]["geometry"]) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||
| def _set_code_unit_attributes(self): | ||||||||||||||||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this function, as well as |
||||||||||||||||||||||||||||
| """Conversion between physical units and code units.""" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Base units are length, velocity and density, but here we consider | ||||||||||||||||||||||||||||
| # length, mass and time as base units. Since it can make us easy to calculate | ||||||||||||||||||||||||||||
| # all units when self.units_override is not None. | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Default values of Pluto's base units which are stored in self.parameters | ||||||||||||||||||||||||||||
| # if they can be read from definitions.h | ||||||||||||||||||||||||||||
| # Otherwise, they are set to the following default values adopted | ||||||||||||||||||||||||||||
| # velocity_unit = km/s | ||||||||||||||||||||||||||||
| # density_unit = mp/cm**3 | ||||||||||||||||||||||||||||
| # length_unit = AU | ||||||||||||||||||||||||||||
| defs = self.parameters["definitions"] | ||||||||||||||||||||||||||||
| idefix_units = {} | ||||||||||||||||||||||||||||
| idefix_units["length_unit"] = self.quan(defs.get("length_unit", 1.0), "cm") | ||||||||||||||||||||||||||||
| idefix_units["velocity_unit"] = self.quan( | ||||||||||||||||||||||||||||
| defs.get( | ||||||||||||||||||||||||||||
| "velocity_unit", | ||||||||||||||||||||||||||||
| defs.get("length_unit", 1.0) / defs.get("time_unit", 1.0), | ||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||
| "cm/s", | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| idefix_units["density_unit"] = self.quan( | ||||||||||||||||||||||||||||
| defs.get( | ||||||||||||||||||||||||||||
| "density_unit", | ||||||||||||||||||||||||||||
| defs.get("mass_unit", 1.0) / defs.get("length_unit", 1.0) ** 3, | ||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||
| "g/cm**3", | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| uo_size = len(self.units_override) | ||||||||||||||||||||||||||||
| if uo_size > 0 and uo_size < 3: | ||||||||||||||||||||||||||||
| ytLogger.info( | ||||||||||||||||||||||||||||
| "Less than 3 units were specified in units_override (got %s). " | ||||||||||||||||||||||||||||
| "Need to rely on PLUTO's internal units to derive other units", | ||||||||||||||||||||||||||||
| uo_size, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| uo_cache = self.units_override.copy() | ||||||||||||||||||||||||||||
| while len(uo_cache) < 3: | ||||||||||||||||||||||||||||
| # If less than 3 units were passed into units_override, | ||||||||||||||||||||||||||||
| # the rest will be chosen from Pluto's units | ||||||||||||||||||||||||||||
| unit, value = idefix_units.popitem() | ||||||||||||||||||||||||||||
| # If any Pluto's base unit is specified in units_override, it'll be preserved | ||||||||||||||||||||||||||||
| if unit in uo_cache: | ||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||
| uo_cache[unit] = value | ||||||||||||||||||||||||||||
| # Make sure the combination of units are able to derive base units | ||||||||||||||||||||||||||||
| # No need of validation and logging when no unit to be overrided | ||||||||||||||||||||||||||||
| if uo_size > 0: | ||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||
| self.__class__._validate_units_override_keys(uo_cache) | ||||||||||||||||||||||||||||
| except ValueError: | ||||||||||||||||||||||||||||
| # It means the combination is invalid | ||||||||||||||||||||||||||||
| del uo_cache[unit] | ||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| ytLogger.info("Relying on %s: %s.", unit, uo_cache[unit]) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| bu = _PlutoBaseUnits(uo_cache) | ||||||||||||||||||||||||||||
| for unit, value in bu._data.items(): | ||||||||||||||||||||||||||||
| setattr(self, unit, value) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| self.velocity_unit = self.length_unit / self.time_unit | ||||||||||||||||||||||||||||
| self.density_unit = self.mass_unit / self.length_unit**3 | ||||||||||||||||||||||||||||
| self.magnetic_unit = ( | ||||||||||||||||||||||||||||
| np.sqrt(4.0 * np.pi * self.density_unit) * self.velocity_unit | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| self.magnetic_unit.convert_to_units("gauss") | ||||||||||||||||||||||||||||
| self.temperature_unit = self.quan(1.0, "K") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| invalid_unit_combinations = [ | ||||||||||||||||||||||||||||
| {"magnetic_unit", "velocity_unit", "density_unit"}, | ||||||||||||||||||||||||||||
| {"velocity_unit", "time_unit", "length_unit"}, | ||||||||||||||||||||||||||||
| {"density_unit", "length_unit", "mass_unit"}, | ||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| default_units = { | ||||||||||||||||||||||||||||
| "length_unit": "cm", | ||||||||||||||||||||||||||||
| "time_unit": "s", | ||||||||||||||||||||||||||||
| "mass_unit": "g", | ||||||||||||||||||||||||||||
| "velocity_unit": "cm/s", | ||||||||||||||||||||||||||||
| "magnetic_unit": "gauss", | ||||||||||||||||||||||||||||
| "temperature_unit": "K", | ||||||||||||||||||||||||||||
| # this is the one difference with Dataset.default_units: | ||||||||||||||||||||||||||||
| # we accept density_unit as a valid override | ||||||||||||||||||||||||||||
| "density_unit": "g/cm**3", | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||
| def _parse_definitions_header(self) -> None: | ||||||||||||||||||||||||||||
| self.parameters["definitions"] = {} | ||||||||||||||||||||||||||||
| self.parameters["definitions"]["geometry"] = self.attributes["geometry"] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| for attribute in self.attributes: | ||||||||||||||||||||||||||||
| if "unit" not in attribute: | ||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||
| self.parameters["definitions"][attribute] = self.attributes[attribute] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class PlutoVtkDataset(VtkMixin, StaticPlutoDataset): | ||||||||||||||||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this file is becoming a mess, I get it, but reorganizing the code makes |
||||||||||||||||||||||||||||
| _dataset_type = "pluto-vtk" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -860,9 +1077,9 @@ def _get_log_file(self) -> str: | |||||||||||||||||||||||||||
| return os.path.join(self.directory, "vtk.out") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class PlutoXdmfDataset(StaticPlutoDataset): | ||||||||||||||||||||||||||||
| class PlutoXdmfDataset(XdmfMixin, StaticPlutoDataset): | ||||||||||||||||||||||||||||
| _dataset_type = "pluto-xdmf" | ||||||||||||||||||||||||||||
| _index_class = PlutoXdmfHierarchy | ||||||||||||||||||||||||||||
| _index_class = XdmfHierarchy | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| @override | ||||||||||||||||||||||||||||
| def _get_log_file(self) -> str: | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it possible that
key == ""? if not, we should simplify the logic here as(in case you to read more about this uncommon pattern: https://docs.python.org/3/tutorial/controlflow.html#else-clauses-on-loops)