Repository: idaholab/MontePy

Issue:
Multi-Universe fill can't be created from scratch
**Describe the bug**

Multi-universe fills can't be realistically updated, and is really just read-only

**To Reproduce**

``` python
import montepy
cell = montepy.Cell(number=1)
lattice_cell.fill.multiple_universes = True
inst_universe = montepy.Universe(1)
guide_universe = montepy.Universe(2)
lattice_cell.fill.universes = np.array([[inst_universe, guide_universe]])
lattice_cell.format_for_mcnp_input((6,2,0))
```

**Error Message (if any)**

This leads to:


<details>

``` python
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[50], line 1
----> 1 lattice_cell.format_for_mcnp_input((6,2,0))

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:50](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=49), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     48 self._handling_exception = True
     49 try:
---> 50     add_line_number_to_exception(e, self)
     51 finally:
     52     del self._handling_exception

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py:225](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py#line=224), in add_line_number_to_exception(error, broken_robot)
    223 # avoid calling this n times recursively
    224 if hasattr(error, "montepy_handled"):
--> 225     raise error
    226 error.montepy_handled = True
    227 args = error.args

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:42](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=41), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     39 @functools.wraps(func)
     40 def wrapped(*args, **kwargs):
     41     try:
---> 42         return func(*args, **kwargs)
     43     except Exception as e:
     44         if len(args) > 0 and isinstance(args[0], MCNP_Object):

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/cell.py:742](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/cell.py#line=741), in Cell.format_for_mcnp_input(self, mcnp_version)
    728 """Creates a string representation of this MCNP_Object that can be
    729 written to file.
    730 
   (...)    739     a list of strings for the lines that this input will occupy.
    740 """
    741 self.validate()
--> 742 self._update_values()
    743 self._tree.check_for_graveyard_comments()
    744 modifier_keywords = {
    745     cls._class_prefix(): cls for cls in self._INPUTS_TO_PROPERTY.keys()
    746 }

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:50](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=49), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     48 self._handling_exception = True
     49 try:
---> 50     add_line_number_to_exception(e, self)
     51 finally:
     52     del self._handling_exception

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py:225](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py#line=224), in add_line_number_to_exception(error, broken_robot)
    223 # avoid calling this n times recursively
    224 if hasattr(error, "montepy_handled"):
--> 225     raise error
    226 error.montepy_handled = True
    227 args = error.args

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:42](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=41), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     39 @functools.wraps(func)
     40 def wrapped(*args, **kwargs):
     41     try:
---> 42         return func(*args, **kwargs)
     43     except Exception as e:
     44         if len(args) > 0 and isinstance(args[0], MCNP_Object):

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/cell.py:639](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/cell.py#line=638), in Cell._update_values(self)
    637 self._tree.nodes["geometry"] = self.geometry.node
    638 for input_class, (attr, _) in self._INPUTS_TO_PROPERTY.items():
--> 639     getattr(self, attr)._update_values()

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:50](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=49), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     48 self._handling_exception = True
     49 try:
---> 50     add_line_number_to_exception(e, self)
     51 finally:
     52     del self._handling_exception

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py:225](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py#line=224), in add_line_number_to_exception(error, broken_robot)
    223 # avoid calling this n times recursively
    224 if hasattr(error, "montepy_handled"):
--> 225     raise error
    226 error.montepy_handled = True
    227 args = error.args

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:42](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=41), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     39 @functools.wraps(func)
     40 def wrapped(*args, **kwargs):
     41     try:
---> 42         return func(*args, **kwargs)
     43     except Exception as e:
     44         if len(args) > 0 and isinstance(args[0], MCNP_Object):

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/cell_modifier.py:224](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/cell_modifier.py#line=223), in CellModifierInput._update_values(self)
    222 def _update_values(self):
    223     if self.in_cell_block:
--> 224         self._update_cell_values()
    225     else:
    226         new_vals = self._collect_new_values()

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:50](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=49), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     48 self._handling_exception = True
     49 try:
---> 50     add_line_number_to_exception(e, self)
     51 finally:
     52     del self._handling_exception

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py:225](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py#line=224), in add_line_number_to_exception(error, broken_robot)
    223 # avoid calling this n times recursively
    224 if hasattr(error, "montepy_handled"):
--> 225     raise error
    226 error.montepy_handled = True
    227 args = error.args

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:42](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=41), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     39 @functools.wraps(func)
     40 def wrapped(*args, **kwargs):
     41     try:
---> 42         return func(*args, **kwargs)
     43     except Exception as e:
     44         if len(args) > 0 and isinstance(args[0], MCNP_Object):

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/fill.py:509](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/fill.py#line=508), in Fill._update_cell_values(self)
    507 else:
    508     self._tree["classifier"].modifier = None
--> 509 new_vals = self._update_cell_universes(new_vals)
    510 if self.transform is None:
    511     try:

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:50](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=49), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     48 self._handling_exception = True
     49 try:
---> 50     add_line_number_to_exception(e, self)
     51 finally:
     52     del self._handling_exception

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py:225](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py#line=224), in add_line_number_to_exception(error, broken_robot)
    223 # avoid calling this n times recursively
    224 if hasattr(error, "montepy_handled"):
--> 225     raise error
    226 error.montepy_handled = True
    227 args = error.args

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:42](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=41), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     39 @functools.wraps(func)
     40 def wrapped(*args, **kwargs):
     41     try:
---> 42         return func(*args, **kwargs)
     43     except Exception as e:
     44         if len(args) > 0 and isinstance(args[0], MCNP_Object):

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/fill.py:552](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/fill.py#line=551), in Fill._update_cell_universes(self, new_vals)
    550 if self.multiple_universes:
    551     payload = []
--> 552     for i in self._axis_range(0):
    553         for j in self._axis_range(1):
    554             for k in self._axis_range(2):

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:50](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=49), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     48 self._handling_exception = True
     49 try:
---> 50     add_line_number_to_exception(e, self)
     51 finally:
     52     del self._handling_exception

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py:225](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py#line=224), in add_line_number_to_exception(error, broken_robot)
    223 # avoid calling this n times recursively
    224 if hasattr(error, "montepy_handled"):
--> 225     raise error
    226 error.montepy_handled = True
    227 args = error.args

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:42](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=41), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     39 @functools.wraps(func)
     40 def wrapped(*args, **kwargs):
     41     try:
---> 42         return func(*args, **kwargs)
     43     except Exception as e:
     44         if len(args) > 0 and isinstance(args[0], MCNP_Object):

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/fill.py:461](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/fill.py#line=460), in Fill._axis_range(self, axis)
    448 def _axis_range(self, axis):
    449     """Returns an iterator for iterating over the given axis.
    450 
    451     Parameters
   (...)    459         range
    460     """
--> 461     return range(self._axis_size(axis))

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:50](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=49), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     48 self._handling_exception = True
     49 try:
---> 50     add_line_number_to_exception(e, self)
     51 finally:
     52     del self._handling_exception

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py:225](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py#line=224), in add_line_number_to_exception(error, broken_robot)
    223 # avoid calling this n times recursively
    224 if hasattr(error, "montepy_handled"):
--> 225     raise error
    226 error.montepy_handled = True
    227 args = error.args

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:42](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=41), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     39 @functools.wraps(func)
     40 def wrapped(*args, **kwargs):
     41     try:
---> 42         return func(*args, **kwargs)
     43     except Exception as e:
     44         if len(args) > 0 and isinstance(args[0], MCNP_Object):

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/fill.py:476](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/fill.py#line=475), in Fill._axis_size(self, axis)
    463 def _axis_size(self, axis):
    464     """Get the length of the given axis.
    465 
    466     Parameters
   (...)    474         the length of the given axis of the universe matrix.
    475     """
--> 476     return int(self.max_index[axis] - self.min_index[axis]) + 1

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:50](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=49), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     48 self._handling_exception = True
     49 try:
---> 50     add_line_number_to_exception(e, self)
     51 finally:
     52     del self._handling_exception

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py:249](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/errors.py#line=248), in add_line_number_to_exception(error, broken_robot)
    247 args = (message,) + args[1:]
    248 error.args = args
--> 249 raise error.with_traceback(trace)

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py:42](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/mcnp_object.py#line=41), in _ExceptionContextAdder._wrap_attr_call.<locals>.wrapped(*args, **kwargs)
     39 @functools.wraps(func)
     40 def wrapped(*args, **kwargs):
     41     try:
---> 42         return func(*args, **kwargs)
     43     except Exception as e:
     44         if len(args) > 0 and isinstance(args[0], MCNP_Object):

File [~/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/fill.py:296](http://127.0.0.1:8888/home/mgale/mambaforge/envs/montepy_mc/lib/python3.12/site-packages/montepy/data_inputs/fill.py#line=295), in Fill.max_index(self)
    285 @property
    286 def max_index(self):
    287     """The maximum indices of the matrix in each dimension.
    288 
    289     For the order of the indices see: ``DIMENSIONS``.
   (...)    294         the maximum indices of the matrix for complex fills
    295     """
--> 296     return self._max_index

AttributeError: 'Fill' object has no attribute '_max_index'

Error came from Fill: Universe: None, transform: None from an unknown file.
```

</details>

**Version**

* version 1.0.0a4

Discussion:

Patch:
diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst
index 10a31c93..ff57bacf 100644
--- a/doc/source/changelog.rst
+++ b/doc/source/changelog.rst
@@ -38,6 +38,7 @@ MontePy Changelog
 * Fixed bug that wouldn't allow cloning most surfaces (:issue:`704`).
 * Fixed bug that crashed when some cells were not assigned to any universes (:issue:`705`).
 * Fixed bug where setting ``surf.is_reflecting`` to ``False`` did not always get exported properly (:issue:`709`).
+* Fixed bug where setting multiple universes for a cell fill not being properly exported (:issue:`714`).
  
 **Breaking Changes**
 
diff --git a/montepy/data_inputs/fill.py b/montepy/data_inputs/fill.py
index 294f7bcc..21180e11 100644
--- a/montepy/data_inputs/fill.py
+++ b/montepy/data_inputs/fill.py
@@ -1,5 +1,8 @@
-# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved.
+# Copyright 2024 - 2025, Battelle Energy Alliance, LLC All Rights Reserved.
 import itertools as it
+from numbers import Integral, Real
+import numpy as np
+
 from montepy.data_inputs.cell_modifier import CellModifierInput, InitInput
 from montepy.data_inputs.transform import Transform
 from montepy.errors import *
@@ -9,7 +12,14 @@ from montepy.input_parser import syntax_node
 from montepy.mcnp_object import MCNP_Object
 from montepy.universe import Universe
 from montepy.utilities import *
-import numpy as np
+
+
+def _verify_3d_index(self, indices):
+    for index in indices:
+        if not isinstance(index, Integral):
+            raise TypeError(f"Index values for fill must be an int. {index} given.")
+    if len(indices) != 3:
+        raise ValueError(f"3 values must be given for fill. {indices} given")
 
 
 class Fill(CellModifierInput):
@@ -45,6 +55,8 @@ class Fill(CellModifierInput):
         self._hidden_transform = None
         self._old_transform_number = None
         self._multi_universe = False
+        self._min_index = None
+        self._max_index = None
         super().__init__(input, in_cell_block, key, value)
         if self.in_cell_block:
             if key:
@@ -71,7 +83,7 @@ class Fill(CellModifierInput):
         list_node.append(self._generate_default_node(float, None))
         classifier = syntax_node.ClassifierNode()
         classifier.prefix = self._generate_default_node(
-            str, self._class_prefix().upper(), None
+            str, self._class_prefix().upper(), None, never_pad=True
         )
         self._tree = syntax_node.SyntaxNode(
             "fill",
@@ -259,17 +271,35 @@ class Fill(CellModifierInput):
     def universes(self, value):
         if not isinstance(value, (np.ndarray, type(None))):
             raise TypeError(f"Universes must be set to an array. {value} given.")
-        if not self.multiple_universes:
+        if value.ndim != 3:
             raise ValueError(
-                "Multiple universes can only be set when multiple_universes is True."
+                f"3D array must be given for fill.universes. Array of shape: {value.shape} given."
+            )
+
+        def is_universes(array):
+            type_checker = lambda x: isinstance(x, (Universe, type(None)))
+            return map(type_checker, array.flat)
+
+        if value.dtype != np.object_ or not all(is_universes(value)):
+            raise TypeError(
+                f"All values in array must be a Universe (or None). {value} given."
             )
+        self.multiple_universes = True
+        if self.min_index is None:
+            self.min_index = np.array([0] * 3)
+        self.max_index = self.min_index + np.array(value.shape) - 1
         self._universes = value
 
     @universes.deleter
     def universes(self):
         self._universes = None
 
-    @property
+    @make_prop_pointer(
+        "_min_index",
+        (list, np.ndarray),
+        validator=_verify_3d_index,
+        deletable=True,
+    )
     def min_index(self):
         """The minimum indices of the matrix in each dimension.
 
@@ -280,9 +310,14 @@ class Fill(CellModifierInput):
         :class:`numpy.ndarry`
             the minimum indices of the matrix for complex fills
         """
-        return self._min_index
+        pass
 
-    @property
+    @make_prop_pointer(
+        "_max_index",
+        (list, np.ndarray),
+        validator=_verify_3d_index,
+        deletable=True,
+    )
     def max_index(self):
         """The maximum indices of the matrix in each dimension.
 
@@ -293,7 +328,7 @@ class Fill(CellModifierInput):
         :class:`numpy.ndarry`
             the maximum indices of the matrix for complex fills
         """
-        return self._max_index
+        pass
 
     @property
     def multiple_universes(self):
@@ -500,13 +535,15 @@ class Fill(CellModifierInput):
         )
 
     def _update_cell_values(self):
-        # Todo update matrix fills
         new_vals = list(self._tree["data"])
         if self.transform and self.transform.is_in_degrees:
             self._tree["classifier"].modifier = "*"
         else:
             self._tree["classifier"].modifier = None
         new_vals = self._update_cell_universes(new_vals)
+        self._update_cell_transform_values(new_vals)
+
+    def _update_cell_transform_values(self, new_vals):
         if self.transform is None:
             try:
                 values = [val.value for val in self._tree["data"]]
@@ -579,7 +616,29 @@ class Fill(CellModifierInput):
         for universe, value in zip(payload, value_nodes):
             value.value = universe
             buffer.append(value)
-        buffer = new_vals[:start_matrix] + buffer
+        buffer = self._update_multi_index_limits(new_vals[:start_matrix]) + buffer
         if start_transform:
             buffer += new_vals[start_transform:]
         return buffer
+
+    def _update_multi_index_limits(self, new_vals):
+        if not self.multiple_universes and ":" not in new_vals:
+            return new_vals
+        if ":" not in new_vals:
+            for min_idx, max_idx in zip(self.min_index, self.max_index):
+                new_vals.extend(
+                    [
+                        self._generate_default_node(int, str(min_idx), padding=None),
+                        syntax_node.PaddingNode(":"),
+                        self._generate_default_node(int, str(max_idx), padding=" "),
+                    ]
+                )
+            return new_vals
+        vals_iter = iter(new_vals)
+        for min_idx, max_idx in zip(self.min_index, self.max_index):
+            min_val = next(vals_iter)
+            min_val.value = min_idx
+            next(vals_iter)
+            max_val = next(vals_iter)
+            max_val.value = max_idx
+        return new_vals


Test patch:
diff --git a/tests/test_universe.py b/tests/test_universe.py
index a922a7a0..955055e7 100644
--- a/tests/test_universe.py
+++ b/tests/test_universe.py
@@ -1,4 +1,6 @@
-# Copyright 2024, Battelle Energy Alliance, LLC All Rights Reserved.
+# Copyright 2024 - 2025, Battelle Energy Alliance, LLC All Rights Reserved.
+from hypothesis import given, strategies as st
+import pytest
 from unittest import TestCase
 
 import copy
@@ -141,7 +143,7 @@ class TestLattice(TestCase):
         list_node = syntax_node.ListNode("numbers")
         list_node.append(syntax_node.ValueNode("1", float))
         classifier = syntax_node.ClassifierNode()
-        classifier.prefix = "lat"
+        classifier.prefix = syntax_node.ValueNode("lat", str)
         tree = syntax_node.SyntaxNode(
             "lattice",
             {
@@ -221,10 +223,12 @@ class TestFill(TestCase):
     def setUp(self):
         list_node = syntax_node.ListNode("num")
         list_node.append(syntax_node.ValueNode("5", float))
+        classifier = syntax_node.ClassifierNode()
+        classifier.prefix = syntax_node.ValueNode("fill", str)
         tree = syntax_node.SyntaxNode(
             "fill",
             {
-                "classifier": "",
+                "classifier": classifier,
                 "seperator": syntax_node.ValueNode("=", str),
                 "data": list_node,
             },
@@ -370,8 +374,10 @@ class TestFill(TestCase):
         with self.assertRaises(TypeError):
             fill.universes = "hi"
         fill.multiple_universes = False
-        with self.assertRaises(ValueError):
-            fill.universes = fill_array
+        with pytest.raises(ValueError):
+            fill.universes = np.array([1, 2])
+        with pytest.raises(TypeError):
+            fill.universes = np.array([[[1]]])
 
     def test_fill_str(self):
         input = Input(["1 0 -1 fill=0:1 0:1 0:1 1 2 3 4 5 6 7 8"], BlockType.CELL)
@@ -388,3 +394,75 @@ class TestFill(TestCase):
         fill2 = Fill(card)
         with self.assertRaises(MalformedInputError):
             fill1.merge(fill2)
+
+    @given(
+        indices=st.lists(st.integers(), min_size=3, max_size=3),
+        width=st.lists(st.integers(1), min_size=3, max_size=3),
+    )
+    def test_fill_index_setter(self, indices, width):
+        fill = self.simple_fill.clone()
+        fill.multiple_universes = True
+        fill.min_index = indices
+        end = np.array(indices) + np.array(width)
+        fill.max_index = end
+        assert fill.min_index == indices
+        assert (fill.max_index == end).all()
+
+    def test_fill_index_bad_setter(self):
+        fill = self.simple_fill
+        with pytest.raises(TypeError):
+            fill.min_index = "hi"
+        with pytest.raises(TypeError):
+            fill.max_index = "hi"
+        with pytest.raises(TypeError):
+            fill.min_index = ["hi"]
+        with pytest.raises(TypeError):
+            fill.max_index = ["hi"]
+        with pytest.raises(ValueError):
+            fill.min_index = [1]
+        with pytest.raises(ValueError):
+            fill.max_index = [1]
+
+    @given(
+        universes=st.lists(st.integers(0, 1_000_000), min_size=1, max_size=10),
+        y_len=st.integers(1, 10),
+        z_len=st.integers(1, 10),
+    )
+    @pytest.mark.filterwarnings("ignore")
+    def test_fill_multi_unis(self, universes, y_len, z_len):
+        fill = self.simple_fill.clone()
+        universes = np.array([[[Universe(u) for u in universes]] * y_len] * z_len)
+        fill.multiple_universes = True
+        fill.universes = universes
+        assert (fill.universes == universes).all()
+        assert (fill.min_index == np.array([0, 0, 0])).all()
+        assert (fill.max_index == np.array(universes.shape) - np.array([1, 1, 1])).all()
+        self.verify_export(fill)
+
+    def verify_export(self, fill):
+        output = fill.format_for_mcnp_input((6, 3, 0))
+        print(output)
+        cell = montepy.Cell("1 0 -2 " + "\n".join(output))
+        new_fill = cell.fill
+        for attr in [
+            "multiple_universes",
+            "old_universe_numbers",
+            "old_universe_number",
+        ]:
+            old_val = getattr(fill, attr)
+            if "old" in attr:
+                if attr.endswith("s"):
+                    old_val = getattr(fill, "universes")
+                    if old_val is not None:
+                        numberer = np.vectorize(lambda u: u.number)
+                        old_val = numberer(old_val)
+                else:
+                    old_val = getattr(fill, "universe")
+                    if old_val is not None:
+                        old_val = old_val.number
+            new_val = getattr(new_fill, attr)
+            print(attr, old_val, new_val)
+            if isinstance(old_val, np.ndarray):
+                assert (old_val == new_val).all()
+            else:
+                assert old_val == new_val