Repository navigation
Model.init_lib raises AttributeError when directory is a pathlib.Path #4185
Description
Bug Description
Model.init_lib() documents its directory argument as PathLike or None (openmc/model/model.py:485; the annotation at :456 is directory: PathLike | None, with PathLike = str | os.PathLike from openmc/checkvalue.py:9). With a str it works. With a pathlib.Path it writes the XML files to the directory and then fails in openmc.lib.init() with AttributeError: 'PosixPath' object has no attribute 'encode'.
Line numbers are at a5bc348; the cited files are the same on the current develop (8b12115).
init_lib()passesdirectoryto_process_CLI_arguments()aspath_input=directory(openmc/model/model.py:497-500). It then exports the XML files todirectoryon rank 0 (:514-517) and callsopenmc.lib.init(args=args, ...)(:524)._process_CLI_arguments()documentspath_input : str or PathLikeand returns "Iterable of str" (openmc/executor.py:46,:54). It convertsrestart_filewithstr()(:77-78), but appendspath_inputas given:args += [path_input](:89-90).openmc.lib.init()documentsargs : list of strand callscreate_string_buffer(arg.encode())on every argument (openmc/lib/core.py:322,:339-340). APathhas noencode(), so it raisesAttributeError.
openmc.run() and openmc.calculate_volumes() use the same _process_CLI_arguments(), but they pass the list to subprocess.Popen (openmc/executor.py:97), which accepts path-like arguments. Only the in-memory initialization fails.
restart_file has a related problem: _process_CLI_arguments() converts it with str() (openmc/executor.py:77-78), and str() of an os.PathLike that is not a pathlib path, such as an os.DirEntry or a class that defines only __fspath__(), is its repr, so openmc.run(), Model.run() and Model.init_lib() pass, for example, -r "<DirEntry 'restart.h5'>". Converting path_input with str() in the same way would bring that problem to directory as well.
Expected: init_lib(directory=pathlib.Path(...)) initializes OpenMC from the XML files in that directory, as it does with a str.
The directory argument was added in #3374 and annotated PathLike in #3399. Reading the code shows the same behaviour in v0.15.3 and v0.16.0; nothing was run on those versions.
A pull request with a proposed fix will follow.
Steps to Reproduce
The script below needs no nuclear data: it writes its own one-group multigroup library. It initializes the same model twice, first with a str directory and then with a pathlib.Path directory. Run it in an empty directory:
"""Model.init_lib accepts a str directory but not a pathlib.Path one.
Run in an empty directory. Needs no nuclear data (one-group multigroup
library written by the script).
"""
import os
import pathlib
import openmc
import openmc.lib
groups = openmc.mgxs.EnergyGroups([0.0, 20.0e6])
xsdata = openmc.XSdata('medium', groups)
xsdata.order = 0
xsdata.set_total([1.0])
xsdata.set_absorption([0.25])
xsdata.set_scatter_matrix([[[0.75]]])
xsdata.set_inverse_velocity([1.0e-5])
library = openmc.MGXSLibrary(groups)
library.add_xsdata(xsdata)
library.export_to_hdf5('one_group.h5')
medium = openmc.Material()
medium.set_density('macro', 1.0)
medium.add_macroscopic('medium')
model = openmc.Model()
model.materials = openmc.Materials([medium])
model.materials.cross_sections = os.path.abspath('one_group.h5')
box = openmc.model.RectangularParallelepiped(
-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, boundary_type='reflective')
model.geometry = openmc.Geometry([openmc.Cell(fill=medium, region=-box)])
model.settings.energy_mode = 'multi-group'
model.settings.run_mode = 'fixed source'
model.settings.batches = 2
model.settings.particles = 100
model.settings.source = openmc.IndependentSource(
space=openmc.stats.Point(),
energy=openmc.stats.Discrete([1.0e6], [1.0]))
# A str directory works
model.init_lib(output=False, directory='sub_str')
print('directory=str: openmc.lib.is_initialized =', openmc.lib.is_initialized)
model.finalize_lib()
print('files in sub_str:', sorted(os.listdir('sub_str')))
# A pathlib.Path directory fails after the XML files have been written
try:
model.init_lib(output=False, directory=pathlib.Path('sub_path'))
finally:
print('files in sub_path:', sorted(os.listdir('sub_path')))Output on develop (a5bc348), local paths shortened to ...:
directory=str: openmc.lib.is_initialized = True
files in sub_str: ['geometry.xml', 'materials.xml', 'settings.xml']
files in sub_path: ['geometry.xml', 'materials.xml', 'settings.xml']
Traceback (most recent call last):
File ".../repro_init_lib_path.py", line 49, in <module>
model.init_lib(output=False, directory=pathlib.Path('sub_path'))
File ".../openmc/model/model.py", line 524, in init_lib
openmc.lib.init(args=args, intracomm=intracomm, output=output)
File ".../openmc/lib/core.py", line 340, in init
argv[i] = create_string_buffer(arg.encode())
^^^^^^^^^^
AttributeError: 'PosixPath' object has no attribute 'encode'
The XML files are already in sub_path when the error is raised (the finally block lists them), so the export works and the failure is in openmc.lib.init().
Environment
- OpenMC develop at a5bc348 (
openmc --version: 0.16.1-dev57), built from source with CMake: RelWithDebInfo,-DOPENMC_ENABLE_STRICT_FP=on, OpenMP on, MPI off, HDF5 1.10.10 (serial). - Ubuntu 24.04 (x86_64), GCC 13.3.0, Python 3.12.3 with numpy 2.5.3 and h5py 3.16.0;
OMP_NUM_THREADS=2. - Nuclear data: none; the script writes its own multigroup library.
Activity