Repository: openmc-dev/openmc

Issue:
Transport-corrected MGXS generation with merging broken
<!--
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

This exception occurs when generating transport-corrected MGXS with merged tallies:

```

File ~/.local/lib/python3.11/site-packages/openmc-0.14.1.dev0-py3.11-linux-x86_64.egg/openmc/checkvalue.py:42, in check_type(name, value, expected_type, expected_iter_type, none_ok)
     39     else:
     40         msg = (f'Unable to set "{name}" to "{value}" which is not of type "'
     41                f'{expected_type.__name__}"')
---> 42     raise TypeError(msg)
     44 if expected_iter_type:
     45     if isinstance(value, np.ndarray):

TypeError: Unable to set "expansion order" to "['P0' 'P1']" which is not of type "Integral"
```


## Steps to Reproduce
<!--Steps to reproduce the behavior (input file, or modifications to an existing input file, etc.)-->

Get a copy of the `mg-mode-part-ii` notebook from the examples. Comment out the scattering order to generate TCP0 cross sections. Set the MGXS types to:

```
# Specify multi-group cross section types to compute
mgxs_lib.mgxs_types = ['total', 'transport', 'absorption', 'nu-fission', 'fission',
                       'nu-scatter matrix', 'multiplicity matrix', 'chi', 'consistent scatter matrix']
```

The above exception will come up.

# Possible solutions

The problem is that the merge method assumes that the "bins" are what should be passed to the constructor of the newly merged tally, but for ExpansionFilters, it should actually be the highest order of the two.
So, there are two possible ways to solve this.
Add custom logic to check if we're dealing with an expansion filter in Filter.merge and use filter.order as the first argument to the constructor, otherwise filter.bins.
Make the ExpansionFilter take bins as an argument instead, e.g. ['P0', 'P1'] instead of 1. Now, that's a lot less clean, but at least it's not hacky.



Discussion:

Resolving patch:
diff --git a/openmc/filter_expansion.py b/openmc/filter_expansion.py
--- a/openmc/filter_expansion.py
+++ b/openmc/filter_expansion.py
@@ -53,6 +53,34 @@ def from_xml_element(cls, elem, **kwargs):
         order = int(elem.find('order').text)
         return cls(order, filter_id=filter_id)
 
+    def merge(self, other):
+        """Merge this filter with another.
+
+        This overrides the behavior of the parent Filter class, since its
+        merging technique is to take the union of the set of bins of each
+        filter. That technique does not apply to expansion filters, since the
+        argument should be the maximum filter order rather than the list of all
+        bins.
+
+        Parameters
+        ----------
+        other : openmc.Filter
+            Filter to merge with
+
+        Returns
+        -------
+        merged_filter : openmc.Filter
+            Filter resulting from the merge
+
+        """
+
+        if not self.can_merge(other):
+            msg = f'Unable to merge "{type(self)}" with "{type(other)}"'
+            raise ValueError(msg)
+
+        # Create a new filter with these bins and a new auto-generated ID
+        return type(self)(max(self.order, other.order))
+
 
 class LegendreFilter(ExpansionFilter):
     r"""Score Legendre expansion moments up to specified order.


Test patch: