Repository: openmc-dev/openmc

Issue:
Restart depletion fails with MPI
When performing a restart run with multiple MPI processes, it appears that one operator tries building reaction rate data/material data before all the cross sections have been loaded.

## Steps to reproduce
1. Build a simple problem, and deplete for some period of time with some power
1. Create a new operator for restart mode by reading in from the previous `depletion_results.h5` file
1. Deplete again with some power and some time step but with multiple MPI processes.

I have two files: `start.py` and `restart.py` for the first depletion sequence and for the restart. Running `mpirun -n N restart.py` always fails, whether or not `start.py` used multiple processes.

## Error
`mpirun -n 2 restart.py 2>&1 > out.log`
```
 Reading c_H_in_H2O from /home/drew/nndc_hdf5/c_H_in_H2O.h5
 Maximum neutron transport energy: 20000000.000000 eV for U235
 Reading tallies XML file...
 Writing summary.h5 file...
Time to matexp:  0.1556260585784912
Traceback (most recent call last):
  File "restart.py", line 20, in <module>
    openmc.deplete.integrator.predictor(op, [time_steps[0]], power)
  File "/home/drew/openmc/openmc/deplete/integrator/predictor.py", line 100, in predictor
    op_results = [operator(x[0], power[-1])]
  File "/home/drew/openmc/openmc/deplete/operator.py", line 175, in __call__
    self.number.set_density(vec)
  File "/home/drew/openmc/openmc/deplete/atom_number.py", line 214, in set_density
    self.set_mat_slice(i, density_slice)
  File "/home/drew/openmc/openmc/deplete/atom_number.py", line 199, in set_mat_slice
    self[mat, :self.n_nuc_burn] = val
  File "/home/drew/openmc/openmc/deplete/atom_number.py", line 104, in __setitem__
    self.number[mat, nuc] = val
IndexError: index 0 is out of bounds for axis 0 with size 0
 Reading I135 from /home/drew/nndc_hdf5/I135.h5
 Reading Xe135 from /home/drew/nndc_hdf5/Xe135.h5
```
[Full error - pastebin](https://pastebin.com/RwaDfNc5)

The process also hung at the last line
Restart depletion fails with MPI
When performing a restart run with multiple MPI processes, it appears that one operator tries building reaction rate data/material data before all the cross sections have been loaded.

## Steps to reproduce
1. Build a simple problem, and deplete for some period of time with some power
1. Create a new operator for restart mode by reading in from the previous `depletion_results.h5` file
1. Deplete again with some power and some time step but with multiple MPI processes.

I have two files: `start.py` and `restart.py` for the first depletion sequence and for the restart. Running `mpirun -n N restart.py` always fails, whether or not `start.py` used multiple processes.

## Error
`mpirun -n 2 restart.py 2>&1 > out.log`
```
 Reading c_H_in_H2O from /home/drew/nndc_hdf5/c_H_in_H2O.h5
 Maximum neutron transport energy: 20000000.000000 eV for U235
 Reading tallies XML file...
 Writing summary.h5 file...
Time to matexp:  0.1556260585784912
Traceback (most recent call last):
  File "restart.py", line 20, in <module>
    openmc.deplete.integrator.predictor(op, [time_steps[0]], power)
  File "/home/drew/openmc/openmc/deplete/integrator/predictor.py", line 100, in predictor
    op_results = [operator(x[0], power[-1])]
  File "/home/drew/openmc/openmc/deplete/operator.py", line 175, in __call__
    self.number.set_density(vec)
  File "/home/drew/openmc/openmc/deplete/atom_number.py", line 214, in set_density
    self.set_mat_slice(i, density_slice)
  File "/home/drew/openmc/openmc/deplete/atom_number.py", line 199, in set_mat_slice
    self[mat, :self.n_nuc_burn] = val
  File "/home/drew/openmc/openmc/deplete/atom_number.py", line 104, in __setitem__
    self.number[mat, nuc] = val
IndexError: index 0 is out of bounds for axis 0 with size 0
 Reading I135 from /home/drew/nndc_hdf5/I135.h5
 Reading Xe135 from /home/drew/nndc_hdf5/Xe135.h5
```
[Full error - pastebin](https://pastebin.com/RwaDfNc5)

The process also hung at the last line


Discussion:
The cause of the error is the distribution (or lack there of) of the previous results to multiple operators on different MPI processes. 

Each operator/process  [knows it's particular materials to deplete](https://github.com/openmc-dev/openmc/blob/a7d4dc0d5c4dd3546ad9e9c3b392f7c6ab7b3d9e/openmc/deplete/operator.py#L139), and creates corresponding `AtomNumber` objects to hold densities through time.

However, when the previous `ResultsList` is created with `ResultsList("depletion_results.h5")`, all MPI processes essentially share this object with knowledge of all materials, with no distribution. This is passed [directly on to the operator](https://github.com/openmc-dev/openmc/blob/develop/openmc/deplete/operator.py#L123-L128). At the start of the depletion routine, the initial concentrations are taken [directly from the operator](https://github.com/openmc-dev/openmc/blob/a7d4dc0d5c4dd3546ad9e9c3b392f7c6ab7b3d9e/openmc/deplete/integrator/cecm.py#L82). This puts all the processes in charge of depleting all materials, and then passing concentrations through `operator.__call__` to the `AtomNumber` objects, which are configured to only handle process-specific materials.

## Possible fixes

1) `ResultsList.__init__` handles the distribution, with a unique instance on each process, and these are passed directly to the `Operator`, or
2) When previous results are passed into `Operator`s, they distribute and create process-specific `prev_results` attributes.

My preference would be for path 2, because the `ResultsList` is not aware of what materials an `operator` may need at initialization. 
Yes, I agree option 2 sounds better.
The cause of the error is the distribution (or lack there of) of the previous results to multiple operators on different MPI processes. 

Each operator/process  [knows it's particular materials to deplete](https://github.com/openmc-dev/openmc/blob/a7d4dc0d5c4dd3546ad9e9c3b392f7c6ab7b3d9e/openmc/deplete/operator.py#L139), and creates corresponding `AtomNumber` objects to hold densities through time.

However, when the previous `ResultsList` is created with `ResultsList("depletion_results.h5")`, all MPI processes essentially share this object with knowledge of all materials, with no distribution. This is passed [directly on to the operator](https://github.com/openmc-dev/openmc/blob/develop/openmc/deplete/operator.py#L123-L128). At the start of the depletion routine, the initial concentrations are taken [directly from the operator](https://github.com/openmc-dev/openmc/blob/a7d4dc0d5c4dd3546ad9e9c3b392f7c6ab7b3d9e/openmc/deplete/integrator/cecm.py#L82). This puts all the processes in charge of depleting all materials, and then passing concentrations through `operator.__call__` to the `AtomNumber` objects, which are configured to only handle process-specific materials.

## Possible fixes

1) `ResultsList.__init__` handles the distribution, with a unique instance on each process, and these are passed directly to the `Operator`, or
2) When previous results are passed into `Operator`s, they distribute and create process-specific `prev_results` attributes.

My preference would be for path 2, because the `ResultsList` is not aware of what materials an `operator` may need at initialization. 
Yes, I agree option 2 sounds better.

Resolving patch:
diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py
--- a/openmc/deplete/operator.py
+++ b/openmc/deplete/operator.py
@@ -24,6 +24,7 @@
 from .abc import TransportOperator, OperatorResult
 from .atom_number import AtomNumber
 from .reaction_rates import ReactionRates
+from .results_list import ResultsList
 from .helpers import DirectReactionRateHelper, ChainFissionHelper
 
 
@@ -73,7 +74,8 @@ class Operator(TransportOperator):
         specified, the depletion calculation will start from the latest state
         in the previous results.
     diff_burnable_mats : bool, optional
-        Whether to differentiate burnable materials with multiple instances
+        Whether to differentiate burnable materials with multiple instances.
+        Default: False.
     fission_q : dict, optional
         Dictionary of nuclides and their fission Q values [eV]. If not given,
         values will be pulled from the ``chain_file``.
@@ -123,14 +125,11 @@ def __init__(self, geometry, settings, chain_file=None, prev_results=None,
                  dilute_initial=1.0e3):
         super().__init__(chain_file, fission_q, dilute_initial, prev_results)
         self.round_number = False
+        self.prev_res = None
         self.settings = settings
         self.geometry = geometry
         self.diff_burnable_mats = diff_burnable_mats
 
-        if self.prev_res is not None:
-            # Reload volumes into geometry
-            self.prev_results[-1].transfer_volumes(geometry)
-
         # Differentiate burnable materials with multiple instances
         if self.diff_burnable_mats:
             self._differentiate_burnable_mats()
@@ -144,6 +143,21 @@ def __init__(self, geometry, settings, chain_file=None, prev_results=None,
         self._mat_index_map = {
             lm: self.burnable_mats.index(lm) for lm in self.local_mats}
 
+        if self.prev_res is not None:
+            # Reload volumes into geometry
+            prev_results[-1].transfer_volumes(geometry)
+
+            # Store previous results in operator
+            # Distribute reaction rates according to those tracked
+            # on this process
+            if comm.size == 1:
+                self.prev_res = prev_results
+            else:
+                self.prev_res = ResultsList()
+                mat_indexes = _distribute(range(len(self.burnable_mats)))
+                for res_obj in prev_results:
+                    new_res = res_obj.distribute(self.local_mats, mat_indexes)
+                    self.prev_res.append(new_res)
 
         # Determine which nuclides have incident neutron data
         self.nuclides_with_data = self._get_nuclides_with_data()
diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py
--- a/openmc/deplete/results.py
+++ b/openmc/deplete/results.py
@@ -153,6 +153,37 @@ def allocate(self, volume, nuc_list, burn_list, full_burn_list, stages):
         # Create storage array
         self.data = np.zeros((stages, self.n_mat, self.n_nuc))
 
+    def distribute(self, local_materials, ranges):
+        """Create a new object containing data for distributed materials
+
+        Parameters
+        ----------
+        local_materials : iterable of str
+            Materials for this process
+        ranges : iterable of int
+            Slice-like object indicating indicies of ``local_materials``
+            in the material dimension of :attr:`data` and each element
+            in :attr:`rates`
+
+        Returns
+        -------
+        Results
+            New results object
+        """
+        new = Results()
+        new.volume = {lm: self.volume[lm] for lm in local_materials}
+        new.mat_to_ind = dict(zip(
+            local_materials, range(len(local_materials))))
+        # Direct transfer
+        direct_attrs = ("time", "k", "power", "nuc_to_ind",
+                        "mat_to_hdf5_ind", "proc_time")
+        for attr in direct_attrs:
+            setattr(new, attr, getattr(self, attr))
+        # Get applicable slice of data
+        new.data = self.data[:, ranges]
+        new.rates = [r[ranges] for r in self.rates]
+        return new
+
     def export_to_hdf5(self, filename, step):
         """Export results to an HDF5 file
 
diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py
--- a/openmc/deplete/results_list.py
+++ b/openmc/deplete/results_list.py
@@ -8,22 +8,34 @@
 class ResultsList(list):
     """A list of openmc.deplete.Results objects
 
-    Parameters
-    ----------
-    filename : str
-        The filename to read from.
-
+    It is recommended to use :meth:`from_hdf5` over
+    direct creation.
     """
-    def __init__(self, filename):
-        super().__init__()
+
+    @classmethod
+    def from_hdf5(cls, filename):
+        """Load in depletion results from a previous file
+
+        Parameters
+        ----------
+        filename : str
+            Path to depletion result file
+
+        Returns
+        -------
+        new : ResultsList
+            New instance of depletion results
+        """
         with h5py.File(str(filename), "r") as fh:
             check_filetype_version(fh, 'depletion results', _VERSION_RESULTS[0])
+            new = cls()
 
             # Get number of results stored
             n = fh["number"][...].shape[0]
 
             for i in range(n):
-                self.append(Results.from_hdf5(fh, i))
+                new.append(Results.from_hdf5(fh, i))
+        return new
 
     def get_atoms(self, mat, nuc):
         """Get number of nuclides over time from a single material


Test patch:
diff --git a/tests/regression_tests/deplete/test.py b/tests/regression_tests/deplete/test.py
--- a/tests/regression_tests/deplete/test.py
+++ b/tests/regression_tests/deplete/test.py
@@ -66,8 +66,8 @@ def test_full(run_in_tmpdir):
         return
 
     # Load the reference/test results
-    res_test = openmc.deplete.ResultsList(path_test)
-    res_ref = openmc.deplete.ResultsList(path_reference)
+    res_test = openmc.deplete.ResultsList.from_hdf5(path_test)
+    res_ref = openmc.deplete.ResultsList.from_hdf5(path_reference)
 
     # Assert same mats
     for mat in res_ref[0].mat_to_ind:
diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py
--- a/tests/unit_tests/test_deplete_cecm.py
+++ b/tests/unit_tests/test_deplete_cecm.py
@@ -4,8 +4,7 @@
 """
 
 from pytest import approx
-import openmc.deplete
-from openmc.deplete import CECMIntegrator
+from openmc.deplete import CECMIntegrator, ResultsList
 
 from tests import dummy_operator
 
@@ -23,7 +22,7 @@ def test_cecm(run_in_tmpdir):
     integrator.integrate()
 
     # Load the files
-    res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    res = ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
diff --git a/tests/unit_tests/test_deplete_celi.py b/tests/unit_tests/test_deplete_celi.py
--- a/tests/unit_tests/test_deplete_celi.py
+++ b/tests/unit_tests/test_deplete_celi.py
@@ -21,7 +21,7 @@ def test_celi(run_in_tmpdir):
     CELIIntegrator(op, dt, power).integrate()
 
     # Load the files
-    res = ResultsList(op.output_dir / "depletion_results.h5")
+    res = ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
diff --git a/tests/unit_tests/test_deplete_cf4.py b/tests/unit_tests/test_deplete_cf4.py
--- a/tests/unit_tests/test_deplete_cf4.py
+++ b/tests/unit_tests/test_deplete_cf4.py
@@ -21,7 +21,7 @@ def test_cf4(run_in_tmpdir):
     CF4Integrator(op, dt, power).integrate()
 
     # Load the files
-    res = ResultsList(op.output_dir / "depletion_results.h5")
+    res = ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
diff --git a/tests/unit_tests/test_deplete_epc_rk4.py b/tests/unit_tests/test_deplete_epc_rk4.py
--- a/tests/unit_tests/test_deplete_epc_rk4.py
+++ b/tests/unit_tests/test_deplete_epc_rk4.py
@@ -21,7 +21,7 @@ def test_epc_rk4(run_in_tmpdir):
     EPCRK4Integrator(op, dt, power).integrate()
 
     # Load the files
-    res = ResultsList(op.output_dir / "depletion_results.h5")
+    res = ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py
--- a/tests/unit_tests/test_deplete_integrator.py
+++ b/tests/unit_tests/test_deplete_integrator.py
@@ -86,7 +86,7 @@ def test_results_save(run_in_tmpdir):
     Results.save(op, x2, op_result2, t2, 0, 1)
 
     # Load the files
-    res = ResultsList("depletion_results.h5")
+    res = ResultsList.from_hdf5("depletion_results.h5")
 
     for i in range(stages):
         for mat_i, mat in enumerate(burn_list):
diff --git a/tests/unit_tests/test_deplete_leqi.py b/tests/unit_tests/test_deplete_leqi.py
--- a/tests/unit_tests/test_deplete_leqi.py
+++ b/tests/unit_tests/test_deplete_leqi.py
@@ -21,7 +21,7 @@ def test_leqi(run_in_tmpdir):
     LEQIIntegrator(op, dt, power).integrate()
 
     # Load the files
-    res = ResultsList(op.output_dir / "depletion_results.h5")
+    res = ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py
--- a/tests/unit_tests/test_deplete_predictor.py
+++ b/tests/unit_tests/test_deplete_predictor.py
@@ -22,7 +22,7 @@ def test_predictor(run_in_tmpdir):
 
 
     # Load the files
-    res = ResultsList(op.output_dir / "depletion_results.h5")
+    res = ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py
--- a/tests/unit_tests/test_deplete_restart.py
+++ b/tests/unit_tests/test_deplete_restart.py
@@ -27,7 +27,7 @@ def test_restart_predictor(run_in_tmpdir):
     PredictorIntegrator(op, dt, power).integrate()
 
     # Load the files
-    prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    prev_res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     # Re-create depletion operator and load previous results
     op = dummy_operator.DummyOperator(prev_res)
@@ -37,7 +37,7 @@ def test_restart_predictor(run_in_tmpdir):
     PredictorIntegrator(op, dt, power).integrate()
 
     # Load the files
-    res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
@@ -67,7 +67,7 @@ def test_restart_cecm(run_in_tmpdir):
     cecm.integrate()
 
     # Load the files
-    prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    prev_res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     # Re-create depletion operator and load previous results
     op = dummy_operator.DummyOperator(prev_res)
@@ -78,7 +78,7 @@ def test_restart_cecm(run_in_tmpdir):
     cecm_restart.integrate()
 
     # Load the files
-    res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
@@ -107,7 +107,7 @@ def test_restart_predictor_cecm(run_in_tmpdir):
     PredictorIntegrator(op, dt, power).integrate()
 
     # Load the files
-    prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    prev_res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     # Re-create depletion operator and load previous results
     op = dummy_operator.DummyOperator(prev_res)
@@ -133,7 +133,7 @@ def test_restart_cecm_predictor(run_in_tmpdir):
     cecm.integrate()
 
     # Load the files
-    prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    prev_res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     # Re-create depletion operator and load previous results
     op = dummy_operator.DummyOperator(prev_res)
@@ -156,7 +156,7 @@ def test_restart_cf4(run_in_tmpdir):
     CF4Integrator(op, dt, power).integrate()
 
     # Load the files
-    prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    prev_res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     # Re-create depletion operator and load previous results
     op = dummy_operator.DummyOperator(prev_res)
@@ -166,7 +166,7 @@ def test_restart_cf4(run_in_tmpdir):
     CF4Integrator(op, dt, power).integrate()
 
     # Load the files
-    res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
@@ -195,7 +195,7 @@ def test_restart_epc_rk4(run_in_tmpdir):
     EPCRK4Integrator(op, dt, power).integrate()
 
     # Load the files
-    prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    prev_res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     # Re-create depletion operator and load previous results
     op = dummy_operator.DummyOperator(prev_res)
@@ -205,7 +205,7 @@ def test_restart_epc_rk4(run_in_tmpdir):
     EPCRK4Integrator(op, dt, power).integrate()
 
     # Load the files
-    res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
@@ -234,7 +234,7 @@ def test_restart_celi(run_in_tmpdir):
     CELIIntegrator(op, dt, power).integrate()
 
     # Load the files
-    prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    prev_res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     # Re-create depletion operator and load previous results
     op = dummy_operator.DummyOperator(prev_res)
@@ -244,7 +244,7 @@ def test_restart_celi(run_in_tmpdir):
     CELIIntegrator(op, dt, power).integrate()
 
     # Load the files
-    res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
@@ -273,7 +273,7 @@ def test_restart_leqi(run_in_tmpdir):
     LEQIIntegrator(op, dt, power).integrate()
 
     # Load the files
-    prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    prev_res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     # Re-create depletion operator and load previous results
     op = dummy_operator.DummyOperator(prev_res)
@@ -283,7 +283,7 @@ def test_restart_leqi(run_in_tmpdir):
     LEQIIntegrator(op, dt, power).integrate()
 
     # Load the files
-    res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
@@ -311,7 +311,7 @@ def test_restart_si_celi(run_in_tmpdir):
     SICELIIntegrator(op, dt, power).integrate()
 
     # Load the files
-    prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    prev_res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     # Re-create depletion operator and load previous results
     op = dummy_operator.DummyOperator(prev_res)
@@ -321,7 +321,7 @@ def test_restart_si_celi(run_in_tmpdir):
     SICELIIntegrator(op, dt, power).integrate()
 
     # Load the files
-    res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
@@ -351,7 +351,7 @@ def test_restart_si_leqi(run_in_tmpdir):
     SILEQIIntegrator(op, dt, power, nstages).integrate()
 
     # Load the files
-    prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    prev_res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     # Re-create depletion operator and load previous results
     op = dummy_operator.DummyOperator(prev_res)
@@ -361,7 +361,7 @@ def test_restart_si_leqi(run_in_tmpdir):
     SILEQIIntegrator(op, dt, power, nstages).integrate()
 
     # Load the files
-    res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5")
+    res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
diff --git a/tests/unit_tests/test_deplete_resultslist.py b/tests/unit_tests/test_deplete_resultslist.py
--- a/tests/unit_tests/test_deplete_resultslist.py
+++ b/tests/unit_tests/test_deplete_resultslist.py
@@ -12,7 +12,7 @@ def res():
     """Load the reference results"""
     filename = (Path(__file__).parents[1] / 'regression_tests' / 'deplete'
                 / 'test_reference.h5')
-    return openmc.deplete.ResultsList(filename)
+    return openmc.deplete.ResultsList.from_hdf5(filename)
 
 
 def test_get_atoms(res):
diff --git a/tests/unit_tests/test_deplete_si_celi.py b/tests/unit_tests/test_deplete_si_celi.py
--- a/tests/unit_tests/test_deplete_si_celi.py
+++ b/tests/unit_tests/test_deplete_si_celi.py
@@ -21,7 +21,7 @@ def test_si_celi(run_in_tmpdir):
     SICELIIntegrator(op, dt, power).integrate()
 
     # Load the files
-    res = ResultsList(op.output_dir / "depletion_results.h5")
+    res = ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
diff --git a/tests/unit_tests/test_deplete_si_leqi.py b/tests/unit_tests/test_deplete_si_leqi.py
--- a/tests/unit_tests/test_deplete_si_leqi.py
+++ b/tests/unit_tests/test_deplete_si_leqi.py
@@ -21,7 +21,7 @@ def test_si_leqi(run_in_tmpdir):
     SILEQIIntegrator(op, dt, power, 10).integrate()
 
     # Load the files
-    res = ResultsList(op.output_dir / "depletion_results.h5")
+    res = ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     _, y1 = res.get_atoms("1", "1")
     _, y2 = res.get_atoms("1", "2")
diff --git a/tests/unit_tests/test_transfer_volumes.py b/tests/unit_tests/test_transfer_volumes.py
--- a/tests/unit_tests/test_transfer_volumes.py
+++ b/tests/unit_tests/test_transfer_volumes.py
@@ -22,7 +22,7 @@ def test_transfer_volumes(run_in_tmpdir):
     PredictorIntegrator(op, dt, power).integrate()
 
     # Load the files
-    res = ResultsList(op.output_dir / "depletion_results.h5")
+    res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5")
 
     # Create a dictionary of volumes to transfer
     res[0].volume['1'] = 1.5