Openmc.search_for_keff failing with newer OpenMC + python install

Over the summer with python 3.11.11 and this commit hash af35184871abbc4d38d5d18dcae1718aca721800, I was able to do criticality searches with the following code (named critical_search.py)

import openmc
import numpy as np
import matplotlib.pyplot as plt
from argparse import ArgumentParser

r_inner = 10.0
r_outer = 20.0
height = 100.0

def build_model(rotation):
    model = openmc.Model()
    # materials
    fuel = openmc.Material(name="fuel")
    fuel.add_element("U", 1.0, enrichment=19.95)
    fuel.set_density("g/cm3", 17.3)

    water = openmc.Material(name="water")
    water.add_elements_from_formula("H2O")
    water.set_density("g/cm3", 1.0)

    model.materials = openmc.Materials([fuel, water])

    # geometry
    z_min = openmc.ZPlane(z0=0.0, boundary_type="vacuum")
    z_max = openmc.ZPlane(z0=specs.height, boundary_type="vacuum")
    x_divider = openmc.XPlane(x0=0.0)  # at rotation=zero

    cyl_inner = openmc.ZCylinder(r=specs.r_inner)
    cyl_outer = openmc.ZCylinder(r=specs.r_outer, boundary_type="vacuum")

    inner_fuel_cell = openmc.Cell(
        region=+z_min & -z_max & -x_divider & -cyl_inner, fill=fuel
    )
    inner_mod_cell = openmc.Cell(
        region=+z_min & -z_max & +x_divider & -cyl_inner, fill=water
    )
    outer_fuel_cell = openmc.Cell(
        region=+z_min & -z_max & -x_divider & +cyl_inner & -cyl_outer, fill=fuel
    )
    outer_mod_cell = openmc.Cell(
        region=+z_min & -z_max & +x_divider & +cyl_inner & -cyl_outer, fill=water
    )

    inner_cyl_univ = openmc.Universe(cells=[inner_fuel_cell, inner_mod_cell])
    inner_cyl_cell = openmc.Cell(region=-cyl_inner & +z_min & -z_max, fill=inner_cyl_univ)
    inner_cyl_cell.rotation = (0.0, 0.0, rotation)  # rotate about z axis
    model.geometry = openmc.Geometry([inner_cyl_cell, outer_fuel_cell, outer_mod_cell])

    # build settings
    settings = openmc.Settings()
    settings.batches = 300
    settings.inactive = 100
    settings.particles = 500000

    # Create an initial uniform spatial source distribution over the whole geometry, the first generation will fix it to be only fissionable sites
    radii = openmc.stats.Uniform(0.0, specs.r_outer)
    angles = openmc.stats.Uniform(0, 2 * np.pi)
    heights = openmc.stats.Uniform(0.0, specs.height)
    cylindrical_source = openmc.stats.CylindricalIndependent(r=radii, phi=angles, z=heights)
    settings.source = openmc.IndependentSource(space=cylindrical_source)
    settings.output = {"summary": False}
    settings.temperature = {
        "default": 294.0,
        "method": "nearest",
        "range": (294.0, 1600.0),
    }
    model.settings = settings
    return model


def main(a, b, tol, run_args):
    # do search
    procs = run_args["mpi_args"][-1]
    print(run_args)
    bracket = [a, b]
    crit_angle, guesses, keffs = openmc.search_for_keff(
        build_model,
        bracketed_method="brentq",
        bracket=bracket,
        tol=tol,
        print_iterations=True,
        run_args=run_args
    )
    print(
        f"The critical angle achieved with a tolerance of {tol} was {crit_angle} using {procs} mpi procs"
    )
    ks = [keffs[i].nominal_value for i in range(len(keffs))]
    sig_ks = [keffs[i].std_dev for i in range(len(keffs))]

    with open("guesses.txt", "w") as guess_file:
        for iteration, (guess, k, sig) in enumerate(zip(guesses, ks, sig_ks)):
            guess_file.write(
                f"Iteration {iteration} had {guess} degrees resulting in {k}+/-{sig}\n"
            )
        guess_file.write(
            f"The search resulted in a criticial guess of {crit_angle} using {procs} mpi procs and tolerance={tol}"
        )


def build_parser():
    ap = ArgumentParser()
    ap.add_argument(
        "-np",
        dest="mpi_procs",
        type=int,
        default="1",
        help="The number of MPI processes to run",
    )
    ap.add_argument(
        "--tol",
        dest="tol",
        type=float,
        default=0.001,
        help="The tolerance to use in the critical search",
    )
    ap.add_argument(
        "-a",
        dest="a",
        type=float,
        default=0.0,
        help="The bracket lower bound, inclusive, (rotation in degrees) for the search",
    )
    ap.add_argument(
        "-b",
        dest="b",
        type=float,
        default=90.0,
        help="The bracket upper bound, inclusive, (rotation in degrees) for the search",
    )
    ap.add_argument(
        "--print",
        dest="print",
        action="store_true",
        help="when passed, print the k-eigenvalue solves",
    )
    return ap.parse_args()

if __name__ == "__main__":
    args = build_parser()
    run_args = {"mpi_args": ["mpiexec", "-n", f"{args.mpi_procs}"], "threads": "1"}
    if not args.print:
        run_args["output"] = False
    main(args.a, args.b, args.tol, run_args)

I’ve revisited this code with a newer OpenMC (using this commit 5847b0de23eaf152cb5078801ee13cad2d2cc015) and python 3.13.1. When I try to run a search via python critical_search.py --print -np 24, I’m getting a strange error

Traceback (most recent call last):
  File "/home/lgross/test_crit_search_for_PR/make_openmc_model.py", line 173, in <module>
    main(args.a, args.b, args.tol, run_args)
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lgross/test_crit_search_for_PR/make_openmc_model.py", line 73, in main
    crit_angle, guesses, keffs = openmc.search_for_keff(
                                 ~~~~~~~~~~~~~~~~~~~~~~^
        build_model,
        ^^^^^^^^^^^^
    ...<4 lines>...
        run_args=run_args
        ^^^^^^^^^^^^^^^^^
    )
    ^
  File "/home/lgross/openmc/openmc/search.py", line 202, in search_for_keff
    zero_value = root_finder(**args)
  File "/home/lgross/.pyenv/versions/3.13.1/lib/python3.13/site-packages/scipy/optimize/_zeros_py.py", line 798, in brentq
    r = _zeros._brentq(f, a, b, xtol, rtol, maxiter, args, full_output, disp)
  File "/home/lgross/.pyenv/versions/3.13.1/lib/python3.13/site-packages/scipy/optimize/_zeros_py.py", line 94, in f_raise
    fx = f(x, *args)
  File "/home/lgross/openmc/openmc/search.py", line 54, in _search_keff
    sp_filepath = model.run(**run_args)
  File "/home/lgross/openmc/openmc/model/model.py", line 882, in run
    openmc.run(particles, threads, geometry_debug, restart_file,
    ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
               tracks, output, Path('.'), openmc_exec, mpi_args,
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
               event_based, path_input)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/lgross/openmc/openmc/executor.py", line 314, in run
    _run(args, output, cwd)
    ~~~~^^^^^^^^^^^^^^^^^^^
  File "/home/lgross/openmc/openmc/executor.py", line 125, in _run
    raise RuntimeError(error_msg)
RuntimeError: OpenMC aborted unexpectedly.

I’ve tried a few debugging tactics and have narrowed it down a bit. The first thing I tried was supplying an empty dictionary {} to run_args in the search method, which allows the criticality search to start with 1 MPI proc and 1 OpenMP thread. I can also supply run_args = {"mpi_args":[]} without the error.

As soon as I specify anything in the value of the "mpi_args" key, i.e. something like ["mpiexec","-n","24"], the code complains. I added a print statement into executor.py right before the _run(args, output, cwd) to just print(args) and what prints is ['mpiexec', '-n', '24', 'openmc']. As I understand it, this means that OpenMC is trying to run the command mpiexec -n 24 openmc. Strangely, outside this script in the terminal, I can run mpiexec -n 24 openmc with my local model.xml present and it runs just fine with the correct number of MPI procs.

I was wondering if there was some issue with multiple python/multiple OpenMC installs, so I just used pyenv uninstall to destroy all non-python 3.13.1 executables. I’m still getting the issue, though.

Does anyone have any clue what is going on? Would be great to know if someone can try my code on their machine (even better if it 's python 3.13.1 and the same commit hash) to see if it’s some local install issue of mine or if the problem duplicated elsewhere. All we really need is to know if the error message is the same, different, or the criticality search just starts with the correct mpi_args.

A little more info, I tried

import openmc
import openmc.model

model = openmc.Model.from_model_xml()
model.run(mpi_args=["mpiexec","-n","24"])

and got the same error. I also had a colleague try my script with the v0.15.3 release commit and they had the same issue as me. I’m thinking this confirms that it’s not an environment issue.

I’m kind of surprsied that this would fail model.run(mpi_args=["mpiexec",-"n","24"]) because the docs for model.run has a similar example

Interestingly, I tried the above snippet with a different model and got the same error, so that makes me believe this is actually a bug.

Made a GH issue to track here