diff --git a/docs/source/pythonapi/capi.rst b/docs/source/pythonapi/capi.rst index 6c31bf82f86..ec3fed94811 100644 --- a/docs/source/pythonapi/capi.rst +++ b/docs/source/pythonapi/capi.rst @@ -16,6 +16,7 @@ Functions current_batch export_properties export_weight_windows + feature_enabled finalize find_cell find_material diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 9f6987d74ed..6d2e7970343 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -352,6 +352,15 @@ int openmc_properties_export(const char* filename); // \return Error code int openmc_properties_import(const char* filename); +//! Get whether an optional build feature is enabled. +//! +//! Supported feature names are ``dagmc``, ``libmesh``, ``strict_fp``, and +//! ``uwuw``. +//! \param feature Name of the feature to query +//! \param enabled Whether the feature is enabled +//! \return Error code +int openmc_get_feature_enabled(const char* feature, bool* enabled); + // Error codes extern int OPENMC_E_UNASSIGNED; extern int OPENMC_E_ALLOCATE; diff --git a/include/openmc/dagmc.h b/include/openmc/dagmc.h index 89c5962781c..1031f5fbcb9 100644 --- a/include/openmc/dagmc.h +++ b/include/openmc/dagmc.h @@ -1,11 +1,6 @@ #ifndef OPENMC_DAGMC_H #define OPENMC_DAGMC_H -namespace openmc { -extern "C" const bool DAGMC_ENABLED; -extern "C" const bool UWUW_ENABLED; -} // namespace openmc - // always include the XML interface header #include "openmc/xml_interface.h" diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 3d11d39017a..1c6044514bd 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -51,8 +51,6 @@ enum class ElementType { UNSUPPORTED = -1, LINEAR_TET, LINEAR_HEX }; // Global variables //============================================================================== -extern "C" const bool LIBMESH_ENABLED; - class Mesh; namespace model { diff --git a/include/openmc/output.h b/include/openmc/output.h index 0ad8b2fe507..a43bb8db1e0 100644 --- a/include/openmc/output.h +++ b/include/openmc/output.h @@ -10,8 +10,6 @@ namespace openmc { -extern "C" const bool STRICT_FP_ENABLED; - //! \brief Display the main title banner as well as information about the //! program developers, version, and date/time which the problem was run. void title(); diff --git a/openmc/lib/__init__.py b/openmc/lib/__init__.py index 9b135370fe9..4d79622447a 100644 --- a/openmc/lib/__init__.py +++ b/openmc/lib/__init__.py @@ -12,7 +12,7 @@ """ -from ctypes import CDLL, c_bool, c_int +from ctypes import CDLL, byref, c_bool, c_char_p, c_int, POINTER import importlib.resources import os import sys @@ -36,21 +36,38 @@ from unittest.mock import Mock _dll = Mock() +from .error import _error_handler -def _dagmc_enabled(): - return c_bool.in_dll(_dll, "DAGMC_ENABLED").value +_dll.openmc_get_feature_enabled.argtypes = [c_char_p, POINTER(c_bool)] +_dll.openmc_get_feature_enabled.restype = c_int +_dll.openmc_get_feature_enabled.errcheck = _error_handler -def _coord_levels(): - return c_int.in_dll(_dll, "n_coord_levels").value +def feature_enabled(feature: str) -> bool: + """Return whether OpenMC was built with an optional feature. + + Parameters + ---------- + feature : {'dagmc', 'libmesh', 'strict_fp', 'uwuw'} + Feature to query. + + Returns + ------- + bool + Whether the feature is enabled. -def _libmesh_enabled(): - return c_bool.in_dll(_dll, "LIBMESH_ENABLED").value + Raises + ------ + InvalidArgumentError + If *feature* is not recognized. -def _uwuw_enabled(): - return c_bool.in_dll(_dll, "UWUW_ENABLED").value + """ + enabled = c_bool() + _dll.openmc_get_feature_enabled(feature.encode(), byref(enabled)) + return enabled.value -def _strict_fp_enabled(): - return c_bool.in_dll(_dll, "STRICT_FP_ENABLED").value + +def _coord_levels(): + return c_int.in_dll(_dll, "n_coord_levels").value from .error import * diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 54e7b917fb5..caa0549ac74 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -25,22 +25,6 @@ #include #include -namespace openmc { - -#ifdef OPENMC_DAGMC_ENABLED -const bool DAGMC_ENABLED = true; -#else -const bool DAGMC_ENABLED = false; -#endif - -#ifdef OPENMC_UWUW_ENABLED -const bool UWUW_ENABLED = true; -#else -const bool UWUW_ENABLED = false; -#endif - -} // namespace openmc - #ifdef OPENMC_DAGMC_ENABLED namespace openmc { diff --git a/src/mesh.cpp b/src/mesh.cpp index a0e497613c0..4d6179fe8c2 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -61,12 +61,6 @@ namespace openmc { // Global variables //============================================================================== -#ifdef OPENMC_LIBMESH_ENABLED -const bool LIBMESH_ENABLED = true; -#else -const bool LIBMESH_ENABLED = false; -#endif - // Value used to indicate an empty slot in the hash table. We use -2 because // the value -1 is used to indicate a void material. constexpr int32_t EMPTY = -2; diff --git a/src/output.cpp b/src/output.cpp index dd7222c30fa..99032d69046 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -44,11 +44,44 @@ namespace openmc { +extern "C" int openmc_get_feature_enabled(const char* feature, bool* enabled) +{ + if (!feature || !enabled) { + set_errmsg("Feature name and output pointer must not be null."); + return OPENMC_E_INVALID_ARGUMENT; + } + + if (strcmp(feature, "dagmc") == 0) { +#ifdef OPENMC_DAGMC_ENABLED + *enabled = true; +#else + *enabled = false; +#endif + } else if (strcmp(feature, "libmesh") == 0) { +#ifdef OPENMC_LIBMESH_ENABLED + *enabled = true; +#else + *enabled = false; +#endif + } else if (strcmp(feature, "strict_fp") == 0) { #ifdef OPENMC_ENABLE_STRICT_FP -const bool STRICT_FP_ENABLED = true; + *enabled = true; +#else + *enabled = false; +#endif + } else if (strcmp(feature, "uwuw") == 0) { +#ifdef OPENMC_UWUW_ENABLED + *enabled = true; #else -const bool STRICT_FP_ENABLED = false; + *enabled = false; #endif + } else { + set_errmsg(fmt::format("Unknown build feature '{}'.", feature)); + return OPENMC_E_INVALID_ARGUMENT; + } + + return 0; +} //============================================================================== diff --git a/tests/conftest.py b/tests/conftest.py index 8dda9f7565d..087b7795cd8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,7 +16,7 @@ def _check_build_environment(): """Check STRICT_FP and cross section data, collecting any warnings.""" - if not openmc.lib._strict_fp_enabled(): + if not openmc.lib.feature_enabled('strict_fp'): _environment_warnings.append( "OpenMC was NOT built with -DOPENMC_ENABLE_STRICT_FP=on. " "Regression test results may not match reference values due to " diff --git a/tests/regression_tests/dagmc/external/test.py b/tests/regression_tests/dagmc/external/test.py index 3580bfa11e6..c9551eb139b 100644 --- a/tests/regression_tests/dagmc/external/test.py +++ b/tests/regression_tests/dagmc/external/test.py @@ -12,7 +12,7 @@ from tests.testing_harness import PyAPITestHarness pytestmark = pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC is not enabled.") # Test that an external DAGMC instance can be passed in through the C API diff --git a/tests/regression_tests/dagmc/legacy/test.py b/tests/regression_tests/dagmc/legacy/test.py index b6b1e376b00..35cdbe01ea1 100644 --- a/tests/regression_tests/dagmc/legacy/test.py +++ b/tests/regression_tests/dagmc/legacy/test.py @@ -10,7 +10,7 @@ from tests.testing_harness import PyAPITestHarness, config pytestmark = pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC CAD geometry is not enabled.") @pytest.fixture diff --git a/tests/regression_tests/dagmc/refl/test.py b/tests/regression_tests/dagmc/refl/test.py index a13acc0256a..1b0af16314c 100644 --- a/tests/regression_tests/dagmc/refl/test.py +++ b/tests/regression_tests/dagmc/refl/test.py @@ -6,7 +6,7 @@ from tests.testing_harness import PyAPITestHarness pytestmark = pytest.mark.skipif( - not openmc.lib._uwuw_enabled(), + not openmc.lib.feature_enabled('uwuw'), reason="UWUW is not enabled.") class UWUWTest(PyAPITestHarness): diff --git a/tests/regression_tests/dagmc/universes/test.py b/tests/regression_tests/dagmc/universes/test.py index d68c6b11cfa..3a84950df5a 100644 --- a/tests/regression_tests/dagmc/universes/test.py +++ b/tests/regression_tests/dagmc/universes/test.py @@ -7,7 +7,7 @@ from tests.testing_harness import PyAPITestHarness pytestmark = pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC CAD geometry is not enabled.") diff --git a/tests/regression_tests/dagmc/uwuw/test.py b/tests/regression_tests/dagmc/uwuw/test.py index bea464cfabc..d0e000b2669 100644 --- a/tests/regression_tests/dagmc/uwuw/test.py +++ b/tests/regression_tests/dagmc/uwuw/test.py @@ -6,7 +6,7 @@ from tests.testing_harness import PyAPITestHarness pytestmark = pytest.mark.skipif( - not openmc.lib._uwuw_enabled(), + not openmc.lib.feature_enabled('uwuw'), reason="UWUW is not enabled.") class UWUWTest(PyAPITestHarness): diff --git a/tests/regression_tests/external_moab/test.py b/tests/regression_tests/external_moab/test.py index 2d64eb14bb9..fb3c902fcbd 100644 --- a/tests/regression_tests/external_moab/test.py +++ b/tests/regression_tests/external_moab/test.py @@ -16,7 +16,7 @@ from tests.testing_harness import PyAPITestHarness pytestmark = pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC is not enabled.") TETS_PER_VOXEL = 12 diff --git a/tests/regression_tests/surface_source_write/test.py b/tests/regression_tests/surface_source_write/test.py index 094df1f8b84..d1b3f54e550 100644 --- a/tests/regression_tests/surface_source_write/test.py +++ b/tests/regression_tests/surface_source_write/test.py @@ -1075,7 +1075,7 @@ def model_dagmc_2(): @pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled." + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC CAD geometry is not enabled." ) @pytest.mark.skipif(config["event"] is True, reason="Results from history-based mode.") @pytest.mark.parametrize( diff --git a/tests/regression_tests/unstructured_mesh/test.py b/tests/regression_tests/unstructured_mesh/test.py index 7607531d8ba..fc43e9f2c6d 100644 --- a/tests/regression_tests/unstructured_mesh/test.py +++ b/tests/regression_tests/unstructured_mesh/test.py @@ -255,10 +255,10 @@ def model(): @pytest.mark.parametrize("test_opts", test_cases) def test_unstructured_mesh_tets(model, test_opts): # skip the test if the library is not enabled - if test_opts['library'] == 'moab' and not openmc.lib._dagmc_enabled(): + if test_opts['library'] == 'moab' and not openmc.lib.feature_enabled('dagmc'): pytest.skip("DAGMC (and MOAB) mesh not enabled in this build.") - if test_opts['library'] == 'libmesh' and not openmc.lib._libmesh_enabled(): + if test_opts['library'] == 'libmesh' and not openmc.lib.feature_enabled('libmesh'): pytest.skip("LibMesh is not enabled in this build.") # skip the tracklength test for libmesh @@ -302,7 +302,7 @@ def test_unstructured_mesh_tets(model, test_opts): harness.main() -@pytest.mark.skipif(not openmc.lib._libmesh_enabled(), +@pytest.mark.skipif(not openmc.lib.feature_enabled('libmesh'), reason='LibMesh is not enabled in this build.') def test_unstructured_mesh_hexes(model): regular_mesh_tally = model.tallies[0] diff --git a/tests/unit_tests/dagmc/test.py b/tests/unit_tests/dagmc/test.py index e84b5317ede..8ac5726065e 100644 --- a/tests/unit_tests/dagmc/test.py +++ b/tests/unit_tests/dagmc/test.py @@ -10,7 +10,7 @@ from tests import cdtemp pytestmark = pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC CAD geometry is not enabled.") diff --git a/tests/unit_tests/dagmc/test_convert_to_multigroup.py b/tests/unit_tests/dagmc/test_convert_to_multigroup.py index 57c93727e7f..07661e4fdfc 100644 --- a/tests/unit_tests/dagmc/test_convert_to_multigroup.py +++ b/tests/unit_tests/dagmc/test_convert_to_multigroup.py @@ -8,7 +8,7 @@ import openmc.lib pytestmark = pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC CAD geometry is not enabled.") diff --git a/tests/unit_tests/dagmc/test_h5m_subdir.py b/tests/unit_tests/dagmc/test_h5m_subdir.py index dd9c6b043b6..0a1b4e903a0 100644 --- a/tests/unit_tests/dagmc/test_h5m_subdir.py +++ b/tests/unit_tests/dagmc/test_h5m_subdir.py @@ -6,7 +6,7 @@ import pytest pytestmark = pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled." + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC CAD geometry is not enabled." ) diff --git a/tests/unit_tests/dagmc/test_lost_particles.py b/tests/unit_tests/dagmc/test_lost_particles.py index 48b6cf16573..a5187551a61 100644 --- a/tests/unit_tests/dagmc/test_lost_particles.py +++ b/tests/unit_tests/dagmc/test_lost_particles.py @@ -7,7 +7,7 @@ import pytest pytestmark = pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC CAD geometry is not enabled.") diff --git a/tests/unit_tests/dagmc/test_model.py b/tests/unit_tests/dagmc/test_model.py index 498fdbdcc42..eed8d7df96f 100644 --- a/tests/unit_tests/dagmc/test_model.py +++ b/tests/unit_tests/dagmc/test_model.py @@ -9,7 +9,7 @@ from openmc.utility_funcs import change_directory pytestmark = pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC CAD geometry is not enabled.") diff --git a/tests/unit_tests/dagmc/test_plot.py b/tests/unit_tests/dagmc/test_plot.py index 6ce1d79a22d..014c4602a90 100644 --- a/tests/unit_tests/dagmc/test_plot.py +++ b/tests/unit_tests/dagmc/test_plot.py @@ -4,7 +4,7 @@ pytestmark = pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled." + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC CAD geometry is not enabled." ) def test_plotting_dagmc_model(request): diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index 4cfae0df289..8f1900eaa87 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -315,6 +315,15 @@ def test_settings(lib_init): settings.seed = 11 +def test_feature_enabled(): + assert isinstance(openmc.lib.feature_enabled('dagmc'), bool) + assert isinstance(openmc.lib.feature_enabled('libmesh'), bool) + assert isinstance(openmc.lib.feature_enabled('strict_fp'), bool) + assert isinstance(openmc.lib.feature_enabled('uwuw'), bool) + with pytest.raises(exc.InvalidArgumentError, match="Unknown build feature"): + openmc.lib.feature_enabled('not-a-feature') + + def test_tally_mapping(lib_init): tallies = openmc.lib.tallies assert isinstance(tallies, Mapping) diff --git a/tests/unit_tests/test_mesh.py b/tests/unit_tests/test_mesh.py index 9b1469fc590..6d66b938649 100644 --- a/tests/unit_tests/test_mesh.py +++ b/tests/unit_tests/test_mesh.py @@ -453,7 +453,9 @@ def simple_umesh(request): return sp.meshes[1] -@pytest.mark.skipif(not openmc.lib._dagmc_enabled(), reason="DAGMC not enabled.") +@pytest.mark.skipif( + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC not enabled." +) @pytest.mark.parametrize('export_type', ('.vtk', '.vtu')) def test_umesh(run_in_tmpdir, simple_umesh, export_type): """Performs a minimal UnstructuredMesh simulation, reads in the resulting @@ -507,9 +509,9 @@ def test_write_vtkhdf(mesh_file, mesh_library, request, run_in_tmpdir): necessary to read in the unstructured mesh from a statepoint file to ensure it has all the required attributes """ - if mesh_library == 'moab' and not openmc.lib._dagmc_enabled(): + if mesh_library == 'moab' and not openmc.lib.feature_enabled('dagmc'): pytest.skip("DAGMC not enabled.") - if mesh_library == 'libmesh' and not openmc.lib._libmesh_enabled(): + if mesh_library == 'libmesh' and not openmc.lib.feature_enabled('libmesh'): pytest.skip("LibMesh not enabled.") model = openmc.Model() diff --git a/tests/unit_tests/test_source_mesh.py b/tests/unit_tests/test_source_mesh.py index 2550cb87ebb..86b39f6a270 100644 --- a/tests/unit_tests/test_source_mesh.py +++ b/tests/unit_tests/test_source_mesh.py @@ -71,10 +71,10 @@ def ids(params): @pytest.mark.parametrize("test_cases", test_cases, ids=ids) def test_unstructured_mesh_sampling(model, request, test_cases): # skip the test if the library is not enabled - if test_cases['library'] == 'moab' and not openmc.lib._dagmc_enabled(): + if test_cases['library'] == 'moab' and not openmc.lib.feature_enabled('dagmc'): pytest.skip("DAGMC (and MOAB) mesh not enabled in this build.") - if test_cases['library'] == 'libmesh' and not openmc.lib._libmesh_enabled(): + if test_cases['library'] == 'libmesh' and not openmc.lib.feature_enabled('libmesh'): pytest.skip("LibMesh is not enabled in this build.") # setup mesh source ### @@ -162,8 +162,8 @@ def test_strengths_size_failure(request, model): model.settings.source = source # skip the test if unstructured mesh is not available - if not openmc.lib._libmesh_enabled(): - if openmc.lib._dagmc_enabled(): + if not openmc.lib.feature_enabled('libmesh'): + if openmc.lib.feature_enabled('dagmc'): source.space.mesh.library = 'moab' else: pytest.skip("Unstructured mesh support unavailable.") @@ -179,13 +179,16 @@ def test_strengths_size_failure(request, model): def test_roundtrip(run_in_tmpdir, model, request): - if not openmc.lib._libmesh_enabled() and not openmc.lib._dagmc_enabled(): + if ( + not openmc.lib.feature_enabled('libmesh') + and not openmc.lib.feature_enabled('dagmc') + ): pytest.skip("Unstructured mesh is not enabled in this build.") mesh_filename = Path(request.fspath).parent / 'test_mesh_tets.e' ucd_mesh = openmc.UnstructuredMesh(mesh_filename, library='libmesh') - if not openmc.lib._libmesh_enabled(): + if not openmc.lib.feature_enabled('libmesh'): ucd_mesh.library = 'moab' n_cells = len(model.geometry.get_all_cells()) @@ -322,10 +325,10 @@ def test_mesh_source_independent(run_in_tmpdir, void_model, mesh_type): def test_umesh_source_independent(run_in_tmpdir, request, void_model, library): import openmc.lib # skip the test if the library is not enabled - if library == 'moab' and not openmc.lib._dagmc_enabled(): + if library == 'moab' and not openmc.lib.feature_enabled('dagmc'): pytest.skip("DAGMC (and MOAB) mesh not enabled in this build.") - if library == 'libmesh' and not openmc.lib._libmesh_enabled(): + if library == 'libmesh' and not openmc.lib.feature_enabled('libmesh'): pytest.skip("LibMesh is not enabled in this build.") model = void_model diff --git a/tests/unit_tests/test_surface_source_write.py b/tests/unit_tests/test_surface_source_write.py index e229fbb72e4..766ddcf5d25 100644 --- a/tests/unit_tests/test_surface_source_write.py +++ b/tests/unit_tests/test_surface_source_write.py @@ -278,7 +278,7 @@ def model_dagmc(request): @pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled." + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC CAD geometry is not enabled." ) @pytest.mark.parametrize( "parameter", diff --git a/tests/unit_tests/weightwindows/dagmc/test.py b/tests/unit_tests/weightwindows/dagmc/test.py index ed01a93ed3d..4f73d1aa9e5 100644 --- a/tests/unit_tests/weightwindows/dagmc/test.py +++ b/tests/unit_tests/weightwindows/dagmc/test.py @@ -4,7 +4,7 @@ import openmc.lib pytestmark = pytest.mark.skipif( - not openmc.lib._dagmc_enabled(), + not openmc.lib.feature_enabled('dagmc'), reason="DAGMC CAD geometry is not enabled.", ) diff --git a/tests/unit_tests/weightwindows/test.py b/tests/unit_tests/weightwindows/test.py index c9b24e3059f..59486b57ee0 100644 --- a/tests/unit_tests/weightwindows/test.py +++ b/tests/unit_tests/weightwindows/test.py @@ -354,9 +354,9 @@ def test_unstructured_mesh_applied_wws(request, run_in_tmpdir, library): they aren't part of a tally or weight window generator """ - if library == 'libmesh' and not openmc.lib._libmesh_enabled(): + if library == 'libmesh' and not openmc.lib.feature_enabled('libmesh'): pytest.skip('LibMesh not enabled in this build.') - if library == 'moab' and not openmc.lib._dagmc_enabled(): + if library == 'moab' and not openmc.lib.feature_enabled('dagmc'): pytest.skip('DAGMC (and MOAB) mesh not enabled in this build.') water = openmc.Material(name='water') diff --git a/tests/unit_tests/weightwindows/test_ww_gen.py b/tests/unit_tests/weightwindows/test_ww_gen.py index 9cb61f0ef04..ed66ca69cef 100644 --- a/tests/unit_tests/weightwindows/test_ww_gen.py +++ b/tests/unit_tests/weightwindows/test_ww_gen.py @@ -335,7 +335,10 @@ def test_ww_bounds_set_in_memory(run_in_tmpdir, model): openmc.lib.finalize() -@pytest.mark.skipif(not openmc.lib._dagmc_enabled(), reason="DAGMC CAD geometry is not enabled.") +@pytest.mark.skipif( + not openmc.lib.feature_enabled('dagmc'), + reason="DAGMC CAD geometry is not enabled." +) def test_ww_generation_with_dagmc(run_in_tmpdir): mat1 = openmc.Material(name="1") mat1.add_nuclide("H1", 1, percent_type="ao")