Repository: yt-project/yt

Issue:
reloading after `save_as_dataset` fails 
### Bug report

Originally reported on slack: if you `save_as_dataset()` then reload in a new python session, it will error for `athena_pp` datasets (and maybe others?). 

**Code for reproduction**

```python
import yt
fi = "AM06/AM06.out1.00300.athdf"
ds = yt.load(fi)
prj = yt.ProjectionPlot(ds, 'z', ('athena_pp', 'rho'), center='c')
prj.data_source.save_as_dataset()
```
will save `./AM06.out1.00300_quad_proj.h5`. 

If you restart your python kernel and try

```python
import yt
ds = yt.load('AM06.out1.00300_quad_proj.h5')
```

Then you get an error


**Actual outcome**

```
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Input In [1], in <cell line: 2>()
      1 import yt
----> 2 ds_2 = yt.load('AM06.out1.00300_quad_proj.h5')

File ~/src/yt_general/yt/yt/_maintenance/deprecation.py:73, in future_positional_only.<locals>.outer.<locals>.inner(*args, **kwargs)
     65     value = kwargs[name]
     66     issue_deprecation_warning(
     67         f"Using the {name!r} argument as keyword (on position {no}) "
     68         "is deprecated. "
   (...)
     71         **depr_kwargs,
     72     )
---> 73 return func(*args, **kwargs)

File ~/src/yt_general/yt/yt/loaders.py:123, in load(fn, hint, *args, **kwargs)
    120 candidates = find_lowest_subclasses(candidates, hint=hint)
    122 if len(candidates) == 1:
<!--A description of the expected outcome from the code snippet-->
<!--If this used to work in an earlier version of yt, please note the
version it used to work on-->

**Version Information**
<!--Please specify your platform and versions of the relevant libraries you are
using:-->
  * Operating System:
  * Python Version:
  * yt version:
  * Other Libraries (if applicable):

<!--Please tell us how you installed yt and python e.g., from source,
pip, conda. If you installed from conda, please specify which channel you used
if not the default-->

--> 123     return candidates[0](fn, *args, **kwargs)
    125 if len(candidates) > 1:
    126     raise YTAmbiguousDataType(fn, candidates)

File ~/src/yt_general/yt/yt/frontends/ytdata/data_structures.py:366, in YTSpatialPlotDataset.__init__(self, *args, **kwargs)
    365 def __init__(self, *args, **kwargs):
--> 366     super().__init__(*args, dataset_type="ytspatialplot_hdf5", **kwargs)

File ~/src/yt_general/yt/yt/frontends/ytdata/data_structures.py:257, in YTDataContainerDataset.__init__(self, filename, dataset_type, index_order, index_filename, units_override, unit_system)
    255 self.index_order = validate_index_order(index_order)
    256 self.index_filename = index_filename
--> 257 super().__init__(
    258     filename,
    259     dataset_type,
    260     units_override=units_override,
    261     unit_system=unit_system,
    262 )

File ~/src/yt_general/yt/yt/data_objects/static_output.py:302, in Dataset.__init__(self, filename, dataset_type, units_override, unit_system, default_species_fields, axis_order)
    298     used_unit_system = unit_system
    300 self._create_unit_registry(used_unit_system)
--> 302 self._parse_parameter_file()
    303 self.set_units()
    304 self.setup_cosmology()

File ~/src/yt_general/yt/yt/frontends/ytdata/data_structures.py:369, in YTSpatialPlotDataset._parse_parameter_file(self)
    368 def _parse_parameter_file(self):
--> 369     super()._parse_parameter_file()
    370     if self.parameters["container_type"] == "proj":
    371         if (
    372             isinstance(self.parameters["weight_field"], str)
    373             and self.parameters["weight_field"] == "None"
    374         ):

File ~/src/yt_general/yt/yt/frontends/ytdata/data_structures.py:265, in YTDataContainerDataset._parse_parameter_file(self)
    264 def _parse_parameter_file(self):
--> 265     super()._parse_parameter_file()
    266     self.particle_types_raw = tuple(self.num_particles.keys())
    267     self.particle_types = self.particle_types_raw

File ~/src/yt_general/yt/yt/frontends/ytdata/data_structures.py:99, in SavedDataset._parse_parameter_file(self)
     97     unit_system = "cgs"
     98 # reset unit system since we may have a new unit registry
---> 99 self._assign_unit_system(unit_system)
    101 # assign units to parameters that have associated unit string
    102 del_pars = []

File ~/src/yt_general/yt/yt/data_objects/static_output.py:1297, in Dataset._assign_unit_system(self, unit_system)
   1293     mag_dims = None
   1294 if unit_system != "code":
   1295     # if the unit system is known, we can check if it
   1296     # has a "current_mks" unit
-> 1297     us = unit_system_registry[str(unit_system).lower()]
   1298     mks_system = us.base_units[current_mks] is not None
   1299 elif mag_dims and current_mks in mag_dims:
   1300     # if we're using the not-yet defined code unit system,
   1301     # then we check if the magnetic field unit has a SI
   1302     # current dimension in it

KeyError: '2493b7e1126463ce3bae781a48ef579b'
```

**Expected outcome**

reload the dataset






Discussion:

Resolving patch:
diff --git a/yt/frontends/ytdata/utilities.py b/yt/frontends/ytdata/utilities.py
--- a/yt/frontends/ytdata/utilities.py
+++ b/yt/frontends/ytdata/utilities.py
@@ -103,7 +103,12 @@ def save_as_dataset(ds, filename, data, field_types=None, extra_attrs=None):
         _yt_array_hdf5_attr(fh, "unit_registry_json", ds.unit_registry.to_json())
 
     if hasattr(ds, "unit_system"):
-        _yt_array_hdf5_attr(fh, "unit_system_name", ds.unit_system.name.split("_")[0])
+        # Note: ds._unit_system_name is written here rather than
+        # ds.unit_system.name because for a 'code' unit system, ds.unit_system.name
+        # is a hash, not a unit system. And on re-load, we want to designate
+        # a unit system not a hash value.
+        # See https://github.com/yt-project/yt/issues/4315 for more background.
+        _yt_array_hdf5_attr(fh, "unit_system_name", ds._unit_system_name)
 
     for attr in base_attrs:
         if isinstance(ds, dict):


Test patch:
diff --git a/tests/tests.yaml b/tests/tests.yaml
--- a/tests/tests.yaml
+++ b/tests/tests.yaml
@@ -230,6 +230,7 @@ other_tests:
      - "--ignore-file=test_firefly\\.py"
      - "--ignore-file=test_callable_grids\\.py"
      - "--ignore-file=test_external_frontends\\.py"
+     - "--ignore-file=test_data_reload\\.py"
      - "--ignore-file=test_stream_stretched\\.py"
      - "--ignore-files=test_sanitize_center\\.py"
      - "--exclude-test=yt.frontends.gdf.tests.test_outputs.TestGDF"
diff --git a/yt/frontends/ytdata/tests/test_data_reload.py b/yt/frontends/ytdata/tests/test_data_reload.py
new file mode 100644
--- /dev/null
+++ b/yt/frontends/ytdata/tests/test_data_reload.py
@@ -0,0 +1,26 @@
+import numpy as np
+
+from yt.loaders import load_uniform_grid
+from yt.testing import requires_module_pytest
+from yt.utilities.on_demand_imports import _h5py as h5py
+
+
+@requires_module_pytest("h5py")
+def test_save_as_data_unit_system(tmp_path):
+    # This test checks that the file saved with calling save_as_dataset
+    # for a ds with a "code" unit system contains the proper "unit_system_name".
+    # It checks the hdf5 file directly rather than using yt.load(), because
+    # https://github.com/yt-project/yt/issues/4315 only manifested restarting
+    # the python kernel (because the unit registry is state dependent).
+
+    fi = tmp_path / "output_data.h5"
+    shp = (4, 4, 4)
+    data = {"density": np.random.random(shp)}
+    ds = load_uniform_grid(data, shp, unit_system="code")
+    assert "code" in ds._unit_system_name
+
+    sp = ds.sphere(ds.domain_center, ds.domain_width[0] / 2.0)
+    sp.save_as_dataset(fi)
+
+    with h5py.File(fi, mode="r") as f:
+        assert f.attrs["unit_system_name"] == "code"