Repository: openmc-dev/openmc

Issue:
Final step in depletion being written to depletion_results.h5 regardless of filename given to integrator
<!--
If you are a user of OpenMC and are running into trouble with the code or are
seeking general user support, we highly recommend posting on the OpenMC
discourse forum first. GitHub issues should be used specifically for bug reports
and feature requests.

https://openmc.discourse.group/

-->

## Bug Description
The last step always tries to write to an h5 file called depletion_results.h5, even if a user gave something like my_decay.h5 to Integrator.integrate. This is because the kwarg for the file name is written at all steps but the last in the integrator base class.

It throws an exception as a result that looks like:

```
bigrig% python decay_mwe.py 
/usr/lib/python3/dist-packages/scipy/__init__.py:146: UserWarning: A NumPy version >=1.17.3 and <1.25.0 is required for this version of SciPy (detected version 1.26.2
  warnings.warn(f"A NumPy version >={np_minversion} and <{np_maxversion}"
[openmc.deplete] t=0.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=50.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=100.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=150.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=200.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=250.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=300.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=350.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=400.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=450.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=500.0 (final operator evaluation)
Traceback (most recent call last):
  File "/home/gavin/Scratch/decay_mwe.py", line 28, in <module>
    integ.integrate(path='decaystep.h5')
  File "/home/gavin/Code/openmc/openmc/deplete/abc.py", line 850, in integrate
    StepResult.save(self.operator, [n], res_list, [t, t],
  File "/home/gavin/Code/openmc/openmc/deplete/stepresult.py", line 570, in save
    results.export_to_hdf5(path, step_ind)
  File "/home/gavin/Code/openmc/openmc/deplete/stepresult.py", line 271, in export_to_hdf5
    res._to_hdf5(handle, step, parallel=False)
  File "/home/gavin/Code/openmc/openmc/deplete/stepresult.py", line 426, in _to_hdf5
    number_dset[index, i, low:high+1] = self.data[i]
  File "h5py/_objects.pyx", line 54, in h5py._objects.with_phil.wrapper
  File "h5py/_objects.pyx", line 55, in h5py._objects.with_phil.wrapper
  File "/home/gavin/.local/lib/python3.10/site-packages/h5py/_hl/dataset.py", line 997, in __setitem__
    mspace = h5s.create_simple(selection.expand_shape(mshape))
  File "/home/gavin/.local/lib/python3.10/site-packages/h5py/_hl/selections.py", line 264, in expand_shape
    raise TypeError("Can't broadcast %s -> %s" % (source_shape, self.array_shape))  # array shape
TypeError: Can't broadcast (1, 3820) -> (1, 3819)

```

## Steps to Reproduce
<!--Steps to reproduce the behavior (input file, or modifications to an existing input file, etc.)-->
See the MWE in #2966 and set the path as follows:

```
integ.integrate(path='decaystep.h5')

results = openmc.deplete.Results('decaystep.h5')

```

## Environment
<!--Operating system, OpenMC version, how OpenMC was installed, nuclear data being used, etc.-->
Does not matter.
Final step in depletion being written to depletion_results.h5 regardless of filename given to integrator
<!--
If you are a user of OpenMC and are running into trouble with the code or are
seeking general user support, we highly recommend posting on the OpenMC
discourse forum first. GitHub issues should be used specifically for bug reports
and feature requests.

https://openmc.discourse.group/

-->

## Bug Description
The last step always tries to write to an h5 file called depletion_results.h5, even if a user gave something like my_decay.h5 to Integrator.integrate. This is because the kwarg for the file name is written at all steps but the last in the integrator base class.

It throws an exception as a result that looks like:

```
bigrig% python decay_mwe.py 
/usr/lib/python3/dist-packages/scipy/__init__.py:146: UserWarning: A NumPy version >=1.17.3 and <1.25.0 is required for this version of SciPy (detected version 1.26.2
  warnings.warn(f"A NumPy version >={np_minversion} and <{np_maxversion}"
[openmc.deplete] t=0.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=50.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=100.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=150.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=200.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=250.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=300.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=350.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=400.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=450.0 s, dt=50.0 s, source=0.0
[openmc.deplete] t=500.0 (final operator evaluation)
Traceback (most recent call last):
  File "/home/gavin/Scratch/decay_mwe.py", line 28, in <module>
    integ.integrate(path='decaystep.h5')
  File "/home/gavin/Code/openmc/openmc/deplete/abc.py", line 850, in integrate
    StepResult.save(self.operator, [n], res_list, [t, t],
  File "/home/gavin/Code/openmc/openmc/deplete/stepresult.py", line 570, in save
    results.export_to_hdf5(path, step_ind)
  File "/home/gavin/Code/openmc/openmc/deplete/stepresult.py", line 271, in export_to_hdf5
    res._to_hdf5(handle, step, parallel=False)
  File "/home/gavin/Code/openmc/openmc/deplete/stepresult.py", line 426, in _to_hdf5
    number_dset[index, i, low:high+1] = self.data[i]
  File "h5py/_objects.pyx", line 54, in h5py._objects.with_phil.wrapper
  File "h5py/_objects.pyx", line 55, in h5py._objects.with_phil.wrapper
  File "/home/gavin/.local/lib/python3.10/site-packages/h5py/_hl/dataset.py", line 997, in __setitem__
    mspace = h5s.create_simple(selection.expand_shape(mshape))
  File "/home/gavin/.local/lib/python3.10/site-packages/h5py/_hl/selections.py", line 264, in expand_shape
    raise TypeError("Can't broadcast %s -> %s" % (source_shape, self.array_shape))  # array shape
TypeError: Can't broadcast (1, 3820) -> (1, 3819)

```

## Steps to Reproduce
<!--Steps to reproduce the behavior (input file, or modifications to an existing input file, etc.)-->
See the MWE in #2966 and set the path as follows:

```
integ.integrate(path='decaystep.h5')

results = openmc.deplete.Results('decaystep.h5')

```

## Environment
<!--Operating system, OpenMC version, how OpenMC was installed, nuclear data being used, etc.-->
Does not matter.


Discussion:

Resolving patch:
diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py
--- a/openmc/deplete/abc.py
+++ b/openmc/deplete/abc.py
@@ -848,7 +848,7 @@ def integrate(
                 print(f"[openmc.deplete] t={t} (final operator evaluation)")
             res_list = [self.operator(n, source_rate if final_step else 0.0)]
             StepResult.save(self.operator, [n], res_list, [t, t],
-                         source_rate, self._i_res + len(self), proc_time)
+                         source_rate, self._i_res + len(self), proc_time, path)
             self.operator.write_bos_data(len(self) + self._i_res)
 
         self.operator.finalize()
diff --git a/openmc/deplete/independent_operator.py b/openmc/deplete/independent_operator.py
--- a/openmc/deplete/independent_operator.py
+++ b/openmc/deplete/independent_operator.py
@@ -241,7 +241,6 @@ def from_nuclides(cls, volume, nuclides,
                    reduce_chain_level=reduce_chain_level,
                    fission_yield_opts=fission_yield_opts)
 
-
     @staticmethod
     def _consolidate_nuclides_to_material(nuclides, nuc_units, volume):
         """Puts nuclide list into an openmc.Materials object.
@@ -270,7 +269,6 @@ def _load_previous_results(self):
         self.prev_res[-1].transfer_volumes(model)
         self.materials = model.materials
 
-
         # Store previous results in operator
         # Distribute reaction rates according to those tracked
         # on this process
@@ -282,7 +280,6 @@ def _load_previous_results(self):
                 new_res = res_obj.distribute(self.local_mats, mat_indexes)
                 self.prev_res.append(new_res)
 
-
     def _get_nuclides_with_data(self, cross_sections: List[MicroXS]) -> Set[str]:
         """Finds nuclides with cross section data"""
         return set(cross_sections[0].nuclides)
@@ -412,6 +409,12 @@ def __call__(self, vec, source_rate):
 
         self._update_materials_and_nuclides(vec)
 
+        # If the source rate is zero, return zero reaction rates
+        if source_rate == 0.0:
+            rates = self.reaction_rates.copy()
+            rates.fill(0.0)
+            return OperatorResult(ufloat(0.0, 0.0), rates)
+
         rates = self._calculate_reaction_rates(source_rate)
         keff = self._keff
 


Test patch:
diff --git a/tests/unit_tests/test_deplete_decay_products.py b/tests/unit_tests/test_deplete_decay.py
similarity index 55%
rename from tests/unit_tests/test_deplete_decay_products.py
rename to tests/unit_tests/test_deplete_decay.py
--- a/tests/unit_tests/test_deplete_decay_products.py
+++ b/tests/unit_tests/test_deplete_decay.py
@@ -1,3 +1,5 @@
+from pathlib import Path
+
 import openmc.deplete
 import numpy as np
 import pytest
@@ -45,3 +47,37 @@ def test_deplete_decay_products(run_in_tmpdir):
     # H1 and He4
     assert h1[1] == pytest.approx(1e24)
     assert he4[1] == pytest.approx(1e24)
+
+
+def test_deplete_decay_step_fissionable(run_in_tmpdir):
+    """Ensures that power is not computed in zero power cases with
+    fissionable material present. This tests decay calculations without
+    power, although this specific example does not exhibit any decay.
+
+    Proves github issue #2963 is fixed
+    """
+
+    # Set up a pure decay operator
+    micro_xs = openmc.deplete.MicroXS(np.empty((0, 0)), [], [])
+    mat = openmc.Material()
+    mat.name = 'I do not decay.'
+    mat.add_nuclide('U238', 1.0, 'ao')
+    mat.volume = 10.0
+    mat.set_density('g/cc', 1.0)
+    original_atoms = mat.get_nuclide_atoms()['U238']
+
+    mats = openmc.Materials([mat])
+    op = openmc.deplete.IndependentOperator(
+        mats, [1.0], [micro_xs], Path(__file__).parents[1] / "chain_simple.xml")
+
+    # Create time integrator and integrate
+    integrator = openmc.deplete.PredictorIntegrator(
+        op, [1.0], power=[0.0], timestep_units='s'
+    )
+    integrator.integrate()
+
+    # Get concentration of U238. It should be unchanged since this chain has no U238 decay.
+    results = openmc.deplete.Results('depletion_results.h5')
+    _, u238 = results.get_atoms("1", "U238")
+
+    assert u238[1] == pytest.approx(original_atoms)