Object state is not enforced for Lattice
Bug Description
While trying to use a lattice in a geometry that isn't fully specified (not having set lower_left) the errors produced during export are very non-intuitive and not user friendly.
Prior to exporting an object possibly some state enforcement should be done. In MontePy we have a validate function that is meant to do basically sanity checking (e.g., checking there is a density set for a non-void cell, etc.), and raise an IllegalStateException that explains what properties are causing the issue.
Steps to Reproduce
universe_1 = openmc.Universe(...)
universe_2 = openmc.Universe(...)
latt = openmc.RectLattice()
latt.pitch = (4,4)
lattice_fill = [[universe_1, universe_2]]
latt.universes = lattice_fill
parent_cell = openmc.Cell(fill=latt)
parent_cell.plot()
plt.show()
This leads to the following error, which is caused by not setting latt.lower_left:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[14], line 13
10 #latt.lower_left=None
12 parent_cell = openmc.Cell(fill=latt)
---> 13 parent_cell.plot()
14 plt.show()
File /usr/local/lib/python3.9/dist-packages/openmc/cell.py:624, in Cell.plot(self, *args, **kwargs)
622 u = openmc.Universe(cells=[self], universe_id=openmc.Universe.next_id + 1)
623 openmc.Universe.used_ids.remove(u.id)
--> 624 return u.plot(*args, **kwargs)
File /usr/local/lib/python3.9/dist-packages/openmc/universe.py:446, in Universe.plot(self, origin, width, pixels, basis, color_by, colors, seed, openmc_exec, axes, legend, axis_units, legend_kwargs, outline, **kwargs)
443 model.plots.append(plot)
445 # Run OpenMC in geometry plotting mode
--> 446 model.plot_geometry(False, cwd=tmpdir, openmc_exec=openmc_exec)
448 # Read image from file
449 img_path = Path(tmpdir) / f'plot_{plot.id}.png'
File /usr/local/lib/python3.9/dist-packages/openmc/model/model.py:827, in Model.plot_geometry(self, output, cwd, openmc_exec)
825 openmc.lib.plot_geometry(output)
826 else:
--> 827 self.export_to_xml()
828 openmc.plot_geometry(output=output, openmc_exec=openmc_exec)
File /usr/local/lib/python3.9/dist-packages/openmc/model/model.py:454, in Model.export_to_xml(self, directory, remove_surfs)
451 d.mkdir(parents=True)
453 self.settings.export_to_xml(d)
--> 454 self.geometry.export_to_xml(d, remove_surfs=remove_surfs)
456 # If a materials collection was specified, export it. Otherwise, look
457 # for all materials in the geometry and use that to automatically build
458 # a collection.
459 if self.materials:
File /usr/local/lib/python3.9/dist-packages/openmc/geometry.py:163, in Geometry.export_to_xml(self, path, remove_surfs)
149 def export_to_xml(self, path='geometry.xml', remove_surfs=False):
150 """Export geometry to an XML file.
151
152 Parameters
(...)
161
162 """
--> 163 root_element = self.to_xml_element(remove_surfs)
165 # Check if path is a directory
166 p = Path(path)
File /usr/local/lib/python3.9/dist-packages/openmc/geometry.py:137, in Geometry.to_xml_element(self, remove_surfs)
135 # Create XML representation
136 element = ET.Element("geometry")
--> 137 self.root_universe.create_xml_subelement(element, memo=set())
139 # Sort the elements in the file
140 element[:] = sorted(element, key=lambda x: (
141 x.tag, int(x.get('id'))))
File /usr/local/lib/python3.9/dist-packages/openmc/universe.py:695, in Universe.create_xml_subelement(self, xml_element, memo)
692 memo.add(cell)
694 # Create XML subelement for this Cell
--> 695 cell_element = cell.create_xml_subelement(xml_element, memo)
697 # Append the Universe ID to the subelement and add to Element
698 cell_element.set("universe", str(self._id))
File /usr/local/lib/python3.9/dist-packages/openmc/cell.py:662, in Cell.create_xml_subelement(self, xml_element, memo)
660 elif self.fill_type in ('universe', 'lattice'):
661 element.set("fill", str(self.fill.id))
--> 662 self.fill.create_xml_subelement(xml_element, memo)
664 if self.region is not None:
665 # Set the region attribute with the region specification
666 region = str(self.region)
File /usr/local/lib/python3.9/dist-packages/openmc/lattice.py:880, in RectLattice.create_xml_subelement(self, xml_element, memo)
878 # Export Lattice lower left
879 lower_left = ET.SubElement(lattice_subelement, "lower_left")
--> 880 lower_left.text = ' '.join(map(str, self._lower_left))
882 # Export the Lattice nested Universe IDs
883 universe_ids = '\n'
TypeError: 'NoneType' object is not iterable
Environment
@pshriwise's CAE environment at UW-Madison.
Yikes, I agree! This hearkens back to your comment a while ago about slots. For instance if you put mylat.lower_letf this same error would pop up, and that can be a real pain to figure out what's going wrong in a big input script.
To be clear though __slots__ wouldn't enforce a valid state.
I almost opened this issue again. -_-