diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000000..6f0f8fbc6ab --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +**/__pycache__ +*.pyc +build/ +dist/ +statepoint.* +summary.h5 +mgxs.h5 +vendor/xtensor +vendor/xtl +q diff --git a/.github/workflows/build-wheels-linux.yml b/.github/workflows/build-wheels-linux.yml new file mode 100644 index 00000000000..56e1067719d --- /dev/null +++ b/.github/workflows/build-wheels-linux.yml @@ -0,0 +1,74 @@ +name: Build Linux Wheels + +on: + push: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 120 + strategy: + matrix: + python-abi: ["cp312-cp312", "cp313-cp313", "cp314-cp314"] + include: + - python-abi: "cp312-cp312" + python-version: "3.12" + - python-abi: "cp313-cp313" + python-version: "3.13" + - python-abi: "cp314-cp314" + python-version: "3.14" + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build wheel + run: | + docker build \ + --build-arg Python_ABI=${{ matrix.python-abi }} \ + --build-arg OPENMC_USE_DAGMC=ON \ + --build-arg OPENMC_USE_XDG=ON \ + -t openmc_wheel:python${{ matrix.python-version }} \ + -f manylinux.Dockerfile . + + - name: Extract wheel + run: | + mkdir -p wheelhouse + docker create --name wheel_container openmc_wheel:python${{ matrix.python-version }} + docker cp wheel_container:/root/openmc/dist/. wheelhouse + docker rm wheel_container + ls -la wheelhouse/*manylinux*.whl + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: linux-wheel-${{ matrix.python-version }} + path: wheelhouse/*manylinux*.whl + + test: + needs: build + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.12", "3.13", "3.14"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download wheel + uses: actions/download-artifact@v4 + with: + name: linux-wheel-${{ matrix.python-version }} + path: wheelhouse + + - name: Test wheel in Docker + run: | + docker build \ + --progress=plain \ + -f wheeltest.Dockerfile \ + --build-arg python_version=${{ matrix.python-version }} \ + . diff --git a/.github/workflows/build-wheels-macos.yml b/.github/workflows/build-wheels-macos.yml new file mode 100644 index 00000000000..b41bfd3452c --- /dev/null +++ b/.github/workflows/build-wheels-macos.yml @@ -0,0 +1,114 @@ +name: Build macOS ARM Wheels + +on: + push: + workflow_dispatch: + +env: + MOAB_TAG: "5.6.0" + DAGMC_TAG: "v3.2.4" + DEP_PREFIX: /usr/local/openmc_deps + +jobs: + build: + runs-on: macos-14 + strategy: + matrix: + python-version: ["3.12", "3.13", "3.14"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install system dependencies + run: | + brew install hdf5 netcdf eigen zlib + pip install cmake + + - name: Build MOAB + run: | + sudo mkdir -p ${{ env.DEP_PREFIX }} + git clone --depth 1 -b ${{ env.MOAB_TAG }} https://bitbucket.org/fathomteam/moab.git + cd moab + mkdir build && cd build + cmake .. \ + -DCMAKE_INSTALL_PREFIX=${{ env.DEP_PREFIX }} \ + -DENABLE_MPI=OFF \ + -DENABLE_FORTRAN=OFF \ + -DENABLE_HDF5=ON \ + -DHDF5_ROOT=$(brew --prefix hdf5) \ + -DENABLE_NETCDF=OFF \ + -DBUILD_SHARED_LIBS=ON \ + -DENABLE_BLASLAPACK=OFF \ + -DENABLE_PYMOAB=OFF + make -j$(sysctl -n hw.ncpu) + sudo make install + + - name: Build DAGMC + run: | + git clone --depth 1 -b ${{ env.DAGMC_TAG }} https://github.com/svalinn/DAGMC.git + cd DAGMC + mkdir build && cd build + cmake .. \ + -DCMAKE_INSTALL_PREFIX=${{ env.DEP_PREFIX }} \ + -DMOAB_DIR=${{ env.DEP_PREFIX }} \ + -DDOUBLE_DOWN=OFF \ + -DBUILD_TALLY=OFF \ + -DBUILD_UWUW=OFF \ + -DBUILD_STATIC_LIBS=OFF \ + -DBUILD_RPATH=OFF \ + -DCMAKE_PREFIX_PATH=$(brew --prefix zlib) + make -j$(sysctl -n hw.ncpu) + sudo make install + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Python build tools + run: | + pip install build delocate + + - name: Build wheel + run: | + export SKBUILD_CMAKE_ARGS="-DOPENMC_USE_DAGMC=ON;-DOPENMC_USE_LIBMESH=OFF;-DOPENMC_USE_MPI=OFF;-DOPENMC_USE_OPENMP=OFF;-DHDF5_ROOT=$(brew --prefix hdf5);-DCMAKE_PREFIX_PATH=${{ env.DEP_PREFIX }}" + python -m build . -w + + - name: Repair wheel + run: | + delocate-wheel -w wheelhouse dist/openmc-*.whl + ls -la wheelhouse/ + + - name: Upload wheel + uses: actions/upload-artifact@v4 + with: + name: macos-arm-wheel-${{ matrix.python-version }} + path: wheelhouse/*.whl + + test: + needs: build + runs-on: macos-14 + strategy: + matrix: + python-version: ["3.12", "3.13", "3.14"] + + steps: + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Download wheel + uses: actions/download-artifact@v4 + with: + name: macos-arm-wheel-${{ matrix.python-version }} + path: wheelhouse + + - name: Test wheel + run: | + python -m venv test_env + source test_env/bin/activate + pip install wheelhouse/openmc-*.whl + python -c "import openmc.lib; print('openmc.lib imported successfully')" + openmc --version diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8c14279b92..715034254c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,7 @@ jobs: mpi: [n, y] omp: [n, y] dagmc: [n] + xdg: [n] libmesh: [n] event: [n] @@ -64,6 +65,11 @@ jobs: python-version: "3.12" mpi: y omp: y + - xdg: y + python-version: "3.12" + libmesh: y + mpi: n + omp: y - libmesh: y python-version: "3.12" mpi: y @@ -78,13 +84,15 @@ jobs: mpi: n name: "Python ${{ matrix.python-version }} (omp=${{ matrix.omp }}, mpi=${{ matrix.mpi }}, dagmc=${{ matrix.dagmc }}, - libmesh=${{ matrix.libmesh }}, event=${{ matrix.event }}" + xdg=${{ matrix.xdg }}, libmesh=${{ matrix.libmesh }}, + event=${{ matrix.event }}" env: MPI: ${{ matrix.mpi }} PHDF5: ${{ matrix.mpi }} OMP: ${{ matrix.omp }} DAGMC: ${{ matrix.dagmc }} + XDG: ${{ matrix.xdg }} EVENT: ${{ matrix.event }} LIBMESH: ${{ matrix.libmesh }} NPY_DISABLE_CPU_FEATURES: "AVX512F AVX512_SKX" @@ -115,6 +123,7 @@ jobs: run: | echo "OPENMC_CROSS_SECTIONS=$HOME/nndc_hdf5/cross_sections.xml" >> $GITHUB_ENV echo "OPENMC_ENDF_DATA=$HOME/endf-b-vii.1" >> $GITHUB_ENV + echo "LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH" >> $GITHUB_ENV # get the sha of the last branch commit # for push and workflow_dispatch events, use the current reference head BRANCH_SHA=HEAD @@ -146,6 +155,12 @@ jobs: sudo update-alternatives --set mpirun /usr/bin/mpirun.mpich sudo update-alternatives --set mpi-x86_64-linux-gnu /usr/include/x86_64-linux-gnu/mpich + - name: Optional apt dependencies for XDG + shell: bash + if: ${{ matrix.xdg == 'y' }} + run: | + sudo apt install -y libembree-dev + - name: install shell: bash run: | diff --git a/.gitignore b/.gitignore index dd8dfb14a96..f5a069bf368 100644 --- a/.gitignore +++ b/.gitignore @@ -43,9 +43,6 @@ results_test.dat # Test .pytest_cache/ -# HDF5 files -*.h5 - # Build files src/CMakeCache.txt src/CMakeFiles/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 61301bdb499..efa6292be14 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,6 +4,10 @@ build: os: "ubuntu-24.04" tools: python: "3.12" + apt_packages: + - g++ + - libhdf5-dev + - libpng-dev jobs: post_checkout: - git fetch --unshallow || true diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fe133a22e3..45d65824890 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.16 FATAL_ERROR) +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) project(openmc C CXX) # Set module path @@ -39,6 +39,7 @@ option(OPENMC_BUILD_TESTS "Build tests" option(OPENMC_ENABLE_PROFILE "Compile with profiling flags" OFF) option(OPENMC_ENABLE_COVERAGE "Compile with coverage analysis flags" OFF) option(OPENMC_USE_DAGMC "Enable support for DAGMC (CAD) geometry" OFF) +option(OPENMC_USE_XDG "Enable support for XDG discretized CAD geometry" OFF) option(OPENMC_USE_LIBMESH "Enable support for libMesh unstructured mesh tallies" OFF) option(OPENMC_USE_MPI "Enable MPI" OFF) option(OPENMC_USE_UWUW "Enable UWUW" OFF) @@ -87,6 +88,9 @@ foreach(OLD_BLD in ITEMS "debug" "optimize") endif() endforeach() +# Include the GenerateScript.cmake file +include(cmake/GenerateScript.cmake) + #=============================================================================== # Set a default build configuration if not explicitly specified #=============================================================================== @@ -156,6 +160,15 @@ if(OPENMC_USE_DAGMC) endif() endif() +#=============================================================================== +# XDG Mesh and Geometry Support +#=============================================================================== + +if(OPENMC_USE_XDG) + find_package(XDG REQUIRED PATH_SUFFIXES lib/cmake) + message(STATUS "Found XDG: ${XDG_DIR} (version ${XDG_VERSION})") +endif() + #=============================================================================== # libMesh Unstructured Mesh Support #=============================================================================== @@ -333,23 +346,48 @@ include(GNUInstallDirs) # installed one in CMAKE_INSTALL_PREFIX. Ref: # https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/RPATH-handling -# use, i.e. don't skip the full RPATH for the build tree -set(CMAKE_SKIP_BUILD_RPATH FALSE) -# when building, don't use the install RPATH already -# (but later on when installing) -set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +if(SKBUILD) + # By default, scikit-build will install everything to ${SKBUILD_PLATLIB_DIR}/openmc + # Install include and bin directories to ${SKBUILD_PLATLIB_DIR}/openmc/SKBUILD_SUBDIR + set(SKBUILD_SUBDIR core/) + set(CMAKE_INSTALL_BINDIR ${SKBUILD_SUBDIR}${CMAKE_INSTALL_BINDIR}) + set(CMAKE_INSTALL_INCLUDEDIR ${SKBUILD_SUBDIR}${CMAKE_INSTALL_INCLUDEDIR}) + set(CMAKE_INSTALL_DATADIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_DATADIR}) + set(CMAKE_INSTALL_DOCDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_DOCDIR}) + set(CMAKE_INSTALL_INFODIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_INFODIR}) + set(CMAKE_INSTALL_MANDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_MANDIR}) + set(CMAKE_INSTALL_LOCALEDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_LOCALEDIR}) + set(CMAKE_INSTALL_LOCALSTATEDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_LOCALSTATEDIR}) + set(CMAKE_INSTALL_RUNSTATEDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_RUNSTATEDIR}) + + # Set RPATH + if(APPLE) + set(CMAKE_MACOSX_RPATH ON) + set(OPENMC_LIBRARY_RPATH "@loader_path") + set(OPENMC_BINARY_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR}") + elseif(UNIX) + set(OPENMC_LIBRARY_RPATH "$ORIGIN") + set(OPENMC_BINARY_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}") + endif() +else() + # use, i.e. don't skip the full RPATH for the build tree + set(CMAKE_SKIP_BUILD_RPATH FALSE) + + # when building, don't use the install RPATH already + # (but later on when installing) + set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + + # the RPATH to be used when installing, but only if it's not a system directory + list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_FULL_LIBDIR}" isSystemDir) + if("${isSystemDir}" STREQUAL "-1") + set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") + endif() +endif() # add the automatically determined parts of the RPATH # which point to directories outside the build tree to the install RPATH set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) - -# the RPATH to be used when installing, but only if it's not a system directory -list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_FULL_LIBDIR}" isSystemDir) -if("${isSystemDir}" STREQUAL "-1") - set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_FULL_LIBDIR}") -endif() - #=============================================================================== # libopenmc #=============================================================================== @@ -428,6 +466,7 @@ list(APPEND libopenmc_SOURCES src/string_utils.cpp src/summary.cpp src/surface.cpp + src/xdg.cpp src/tallies/derivative.cpp src/tallies/filter.cpp src/tallies/filter_azimuthal.cpp @@ -501,7 +540,7 @@ endif() target_include_directories(libopenmc PUBLIC - $ + $ $ ${HDF5_INCLUDE_DIRS} ) @@ -509,6 +548,14 @@ target_include_directories(libopenmc # Set compile flags target_compile_options(libopenmc PRIVATE ${cxxflags}) +if(OPENMC_LIBRARY_RPATH) + set_target_properties( + libopenmc + PROPERTIES + INSTALL_RPATH "${OPENMC_LIBRARY_RPATH}" + ) +endif() + # Add include directory for configured version file target_include_directories(libopenmc PUBLIC $) @@ -544,6 +591,11 @@ elseif(OPENMC_USE_UWUW) message(FATAL_ERROR "DAGMC must be enabled when UWUW is enabled.") endif() +if(OPENMC_USE_XDG) + target_compile_definitions(libopenmc PRIVATE OPENMC_XDG_ENABLED) + target_link_libraries(libopenmc xdg::xdg) +endif() + if(OPENMC_USE_LIBMESH) target_compile_definitions(libopenmc PRIVATE OPENMC_LIBMESH_ENABLED) target_link_libraries(libopenmc PkgConfig::LIBMESH) @@ -598,15 +650,16 @@ target_compile_features(openmc PUBLIC cxx_std_17) target_compile_features(libopenmc PUBLIC cxx_std_17) set_target_properties(openmc libopenmc PROPERTIES CXX_EXTENSIONS OFF) -#=============================================================================== -# Python package -#=============================================================================== +generate_and_install_python_script(openmc) -add_custom_command(TARGET libopenmc POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - $ - ${CMAKE_CURRENT_SOURCE_DIR}/openmc/lib/$ - COMMENT "Copying libopenmc to Python module directory") +# Set rpath for openmc executable +if(OPENMC_BINARY_RPATH) + set_target_properties( + openmc + PROPERTIES + INSTALL_RPATH "${OPENMC_BINARY_RPATH}" + ) +endif() #=============================================================================== # Install executable, scripts, manpage, license @@ -615,17 +668,35 @@ add_custom_command(TARGET libopenmc POST_BUILD configure_file(cmake/OpenMCConfig.cmake.in "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMCConfig.cmake" @ONLY) configure_file(cmake/OpenMCConfigVersion.cmake.in "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMCConfigVersion.cmake" @ONLY) -set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/OpenMC) +if(SKBUILD) + set(INSTALL_CONFIGDIR ${SKBUILD_DATA_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/OpenMC) +else() + set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/OpenMC) +endif() + install(TARGETS openmc libopenmc EXPORT openmc-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${SKBUILD_SUBDIR}${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${SKBUILD_SUBDIR}${CMAKE_INSTALL_LIBDIR} ) + +set(INSTALL_TARGETDIR ${SKBUILD_SUBDIR}${CMAKE_INSTALL_LIBDIR}/cmake/OpenMC) install(EXPORT openmc-targets FILE OpenMCTargets.cmake NAMESPACE OpenMC:: - DESTINATION ${INSTALL_CONFIGDIR}) + DESTINATION ${INSTALL_TARGETDIR} +) + +# Collect scripts +file(GLOB SCRIPTS "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*") + +# Install scripts to bin directory +if(SKBUILD) + install(PROGRAMS ${SCRIPTS} DESTINATION ${SKBUILD_SCRIPTS_DIR}) +else() + install(PROGRAMS ${SCRIPTS} DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() install(FILES "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/OpenMCConfig.cmake" diff --git a/Dockerfile b/Dockerfile index 688e5a370f8..c3c19348c39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -184,40 +184,21 @@ ENV LIBMESH_INSTALL_DIR=$HOME/LIBMESH # clone and install openmc RUN mkdir -p ${HOME}/OpenMC && cd ${HOME}/OpenMC \ && git clone --shallow-submodules --recurse-submodules --single-branch -b ${openmc_branch} ${OPENMC_REPO} \ - && mkdir build && cd build ; \ - if [ ${build_dagmc} = "on" ] && [ ${build_libmesh} = "on" ]; then \ - cmake ../openmc \ - -DCMAKE_CXX_COMPILER=mpicxx \ - -DOPENMC_USE_MPI=on \ - -DHDF5_PREFER_PARALLEL=on \ - -DOPENMC_USE_DAGMC=on \ - -DOPENMC_USE_LIBMESH=on \ - -DCMAKE_PREFIX_PATH="${DAGMC_INSTALL_DIR};${LIBMESH_INSTALL_DIR}" ; \ + && cd openmc ; \ + export SKBUILD_CMAKE_ARGS="-DCMAKE_CXX_COMPILER=mpicxx; \ + -DOPENMC_USE_MPI=on; \ + -DHDF5_PREFER_PARALLEL=on" \ + if [ ${build_dagmc} = "on" ]; then \ + SKBUILD_CMAKE_ARGS="${SKBUILD_CMAKE_ARGS}; \ + -DOPENMC_USE_DAGMC=on; \ + -DCMAKE_PREFIX_PATH=${DAGMC_INSTALL_DIR}" ; \ fi ; \ - if [ ${build_dagmc} = "on" ] && [ ${build_libmesh} = "off" ]; then \ - cmake ../openmc \ - -DCMAKE_CXX_COMPILER=mpicxx \ - -DOPENMC_USE_MPI=on \ - -DHDF5_PREFER_PARALLEL=on \ - -DOPENMC_USE_DAGMC=ON \ - -DCMAKE_PREFIX_PATH=${DAGMC_INSTALL_DIR} ; \ + if [ ${build_libmesh} = "on" ]; then \ + SKBUILD_CMAKE_ARGS="${SKBUILD_CMAKE_ARGS}; \ + -DOPENMC_USE_LIBMESH=on; \ + -DCMAKE_PREFIX_PATH=${LIBMESH_INSTALL_DIR}" ; \ fi ; \ - if [ ${build_dagmc} = "off" ] && [ ${build_libmesh} = "on" ]; then \ - cmake ../openmc \ - -DCMAKE_CXX_COMPILER=mpicxx \ - -DOPENMC_USE_MPI=on \ - -DHDF5_PREFER_PARALLEL=on \ - -DOPENMC_USE_LIBMESH=on \ - -DCMAKE_PREFIX_PATH=${LIBMESH_INSTALL_DIR} ; \ - fi ; \ - if [ ${build_dagmc} = "off" ] && [ ${build_libmesh} = "off" ]; then \ - cmake ../openmc \ - -DCMAKE_CXX_COMPILER=mpicxx \ - -DOPENMC_USE_MPI=on \ - -DHDF5_PREFER_PARALLEL=on ; \ - fi ; \ - make 2>/dev/null -j${compile_cores} install \ - && cd ../openmc && pip install .[test,depletion-mpi] \ + pip -v install .[test,depletion-mpi] \ && python -c "import openmc" FROM build AS release diff --git a/Li.h5 b/Li.h5 new file mode 100644 index 00000000000..003633cbda3 Binary files /dev/null and b/Li.h5 differ diff --git a/Li7.h5 b/Li7.h5 new file mode 100644 index 00000000000..e91a9021fa2 Binary files /dev/null and b/Li7.h5 differ diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index cdc7e2abcf0..00000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,47 +0,0 @@ -include CMakeLists.txt -include LICENSE -include CODE_OF_CONDUCT.md -include CODEOWNERS -include CONTRIBUTING.md -include Dockerfile -include schemas.xml -include pyproject.toml -include pytest.ini -include docs/source/_templates/layout.html -global-include *.cmake -global-include *.cmake.in -global-include *.rst -recursive-include docs *.css -recursive-include docs *.dia -recursive-include docs *.png -recursive-include docs *.py -recursive-include docs *.svg -recursive-include docs *.tex -recursive-include docs *.txt -recursive-include docs Makefile -recursive-include examples *.cpp -recursive-include examples *.py -recursive-include examples *.xml -recursive-include include *.h -recursive-include include *.h.in -recursive-include include *.hh -recursive-include man *.1 -recursive-include src *.cc -recursive-include src *.cpp -recursive-include src *.rnc -recursive-include src *.rng -recursive-include tests *.dat -recursive-include tests *.h5 -recursive-include tests *.h5m -recursive-include tests *.py -recursive-include tests *.xml -recursive-include vendor CMakeLists.txt -recursive-include vendor *.cc -recursive-include vendor *.cpp -recursive-include vendor *.h -recursive-include vendor *.hh -recursive-include vendor *.hpp -recursive-include vendor *.pc.in -recursive-include vendor *.natvis -prune docs/build -prune docs/source/pythonapi/generated/ diff --git a/cmake/GenerateScript.cmake b/cmake/GenerateScript.cmake new file mode 100644 index 00000000000..553c3dc1627 --- /dev/null +++ b/cmake/GenerateScript.cmake @@ -0,0 +1,33 @@ +# Function to generate and install a Python script +# This function creates a Python script with a specified name, writes content to it, +# and installs it to a specified location. +# +# Arguments: +# SCRIPT_NAME - The name of the script to be generated (e.g., 'my_script'). +# +function(generate_and_install_python_script SCRIPT_NAME) + if(SKBUILD) + # Strip any leading/trailing whitespace from the script name + string(STRIP "${SCRIPT_NAME}" CLEAN_SCRIPT_NAME) + + # Define the output path for the generated script using the cleaned name + set(GENERATED_SCRIPT "${CMAKE_BINARY_DIR}/${CLEAN_SCRIPT_NAME}") + + # Generate the script content directly + file(WRITE "${GENERATED_SCRIPT}" "#!/usr/bin/env python3\n\n") + file(APPEND "${GENERATED_SCRIPT}" "import os\n") + file(APPEND "${GENERATED_SCRIPT}" "import sys\n") + file(APPEND "${GENERATED_SCRIPT}" "import sysconfig\n\n") + file(APPEND "${GENERATED_SCRIPT}" "if __name__ == '__main__':\n") + file(APPEND "${GENERATED_SCRIPT}" " os.execv(\n") + file(APPEND "${GENERATED_SCRIPT}" " os.path.join(sysconfig.get_path('platlib'), '${SKBUILD_PROJECT_NAME}', '${CMAKE_INSTALL_BINDIR}', '${CLEAN_SCRIPT_NAME}'),\n") + file(APPEND "${GENERATED_SCRIPT}" " sys.argv,\n") + file(APPEND "${GENERATED_SCRIPT}" " )\n") + + # Install the generated script + install( + PROGRAMS "${GENERATED_SCRIPT}" + DESTINATION "${SKBUILD_SCRIPTS_DIR}" + ) + endif() +endfunction() diff --git a/cmake/OpenMCConfig.cmake.in b/cmake/OpenMCConfig.cmake.in index cc837bba711..7f6dcc88703 100644 --- a/cmake/OpenMCConfig.cmake.in +++ b/cmake/OpenMCConfig.cmake.in @@ -1,12 +1,10 @@ -get_filename_component(OpenMC_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) -# Compute the install prefix from this file's location -get_filename_component(_OPENMC_PREFIX "${OpenMC_CMAKE_DIR}/../../.." ABSOLUTE) +include(CMakeFindDependencyMacro) -find_package(fmt CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) -find_package(pugixml CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) -if(@OPENMC_USE_DAGMC@) - find_package(DAGMC REQUIRED HINTS @DAGMC_DIR@) +if(@OPENMC_USE_XDG@) + if(NOT TARGET xdg::xdg) + find_dependency(XDG CONFIG REQUIRED HINTS "@XDG_DIR@") + endif() endif() if(@OPENMC_USE_LIBMESH@) @@ -16,18 +14,81 @@ if(@OPENMC_USE_LIBMESH@) pkg_check_modules(LIBMESH REQUIRED @LIBMESH_PC_FILE@>=1.7.0 IMPORTED_TARGET) endif() -find_package(PNG) +if(@OPENMC_USE_DAGMC@) + if(NOT TARGET dagmc-shared) + find_dependency(DAGMC REQUIRED HINTS "@DAGMC_DIR@") + endif() +endif() -if(NOT TARGET OpenMC::libopenmc) - include("${OpenMC_CMAKE_DIR}/OpenMCTargets.cmake") +if(NOT TARGET fmt::fmt) + find_dependency(fmt CONFIG REQUIRED) +endif() + +if(NOT TARGET pugixml::pugixml AND NOT TARGET pugixml) + find_dependency(pugixml CONFIG REQUIRED) +endif() + +if(@PNG_FOUND@) + if(NOT TARGET PNG::PNG) + find_dependency(PNG REQUIRED) + endif() endif() if(@OPENMC_USE_MPI@) - find_package(MPI REQUIRED) + if(NOT TARGET MPI::MPI_CXX) + find_dependency(MPI REQUIRED) + endif() endif() if(@OPENMC_USE_OPENMP@) - find_package(OpenMP REQUIRED) + if(NOT TARGET OpenMP::OpenMP_CXX) + find_dependency(OpenMP REQUIRED) + endif() +endif() + +if(@SKBUILD@) + # Find the Python interpreter and ensure it's available. + find_package(Python COMPONENTS Interpreter REQUIRED) + + # Function to run Python commands and validate their execution. + function(run_python_command output_var command) + execute_process( + COMMAND ${Python_EXECUTABLE} -c "${command}" + OUTPUT_VARIABLE ${output_var} + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE result + ) + # Check if the command was successful + if(NOT result EQUAL 0) + message(FATAL_ERROR "Failed to run Python command: ${command}") + else() + # Add the output variable to the parent scope + set(${output_var} "${${output_var}}" PARENT_SCOPE) + endif() + endfunction() + + # Extract MOAB include paths, library paths, and extra libraries + run_python_command(OpenMC_INCLUDE_DIRS "import openmc; print(' '.join(openmc.include_path))") + run_python_command(OpenMC_LIBRARY_DIRS "import openmc; print(' '.join(openmc.lib_path))") + run_python_command(OpenMC_EXTRA_LIBRARIES "import openmc; print(' '.join(openmc.extra_lib))") + + # Check if the wheel was repaired using auditwheel or delocate + if(OpenMC_EXTRA_LIBRARIES) + message(FATAL_ERROR + "This build of OpenMC is not supported. " + "It appears that the wheel was repaired using tools like auditwheel or delocate, " + "that modifies the shared libraries, which may cause problems.\n" + "OpenMC_EXTRA_LIBRARIES is not empty: ${OpenMC_EXTRA_LIBRARIES}.\n" + "To resolve this, please build OpenMC from scratch. " + "For more information, visit: https://docs.openmc.org/" + ) + endif() + + # Add OpenMC targets + file(TO_CMAKE_PATH "${OpenMC_LIBRARY_DIRS}/cmake/OpenMC/OpenMCTargets.cmake" OpenMC_TARGETS_FILE) + include(${OpenMC_TARGETS_FILE}) +elseif(NOT TARGET OpenMC::libopenmc) + include("${OpenMC_CMAKE_DIR}/OpenMCTargets.cmake") endif() if(@OPENMC_USE_UWUW@ AND NOT ${DAGMC_BUILD_UWUW}) diff --git a/cross_sections.xml b/cross_sections.xml new file mode 100644 index 00000000000..5c97c2789f1 --- /dev/null +++ b/cross_sections.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/source/devguide/workflow.rst b/docs/source/devguide/workflow.rst index c49326a2090..8fea63ea033 100644 --- a/docs/source/devguide/workflow.rst +++ b/docs/source/devguide/workflow.rst @@ -63,9 +63,9 @@ features and bug fixes. The general steps for contributing are as follows: .. code-block:: sh - git clone --recurse-submodules git@github.com:yourusername/openmc.git - cd openmc - git checkout -b newbranch develop + git clone --recurse-submodules git@github.com:yourusername/openmc.git + cd openmc + git checkout -b newbranch develop 3. Run ``tools/dev/install-commit-hooks.sh`` to install a post-commit hook that runs clang-format on C++ files to apply :ref:`automatic code formatting @@ -140,15 +140,14 @@ pip_. From the root directory of the OpenMC repository, run: .. code-block:: sh - python -m pip install -e .[test] + python -m pip install --no-build-isolation \ + -Ceditable.rebuild=true \ + -Ccmake.build-type=Debug \ + -Cbuild-dir=build \ + -ve ".[test]" -This installs the OpenMC Python package in `"editable" mode -`_ so that 1) -it can be imported from a Python interpreter and 2) any changes made are -immediately reflected in the installed version (that is, you don't need to keep -reinstalling it). While the same effect can be achieved using the -:envvar:`PYTHONPATH` environment variable, this is generally discouraged as it -can interfere with virtual environments. +This feature allows for rebuilding on initial import, providing +flexibility for iterative development or testing changes to the codebase. .. _git: https://git-scm.com/ .. _GitHub: https://github.com/ diff --git a/docs/source/quickinstall.rst b/docs/source/quickinstall.rst index 6cb774b5b07..ac600a08dd5 100644 --- a/docs/source/quickinstall.rst +++ b/docs/source/quickinstall.rst @@ -135,38 +135,66 @@ source below. Building Source on Linux or macOS --------------------------------- -All OpenMC source code is hosted on `GitHub -`_. If you have `git -`_, a modern C++ compiler, `CMake `_, -and `HDF5 `_ installed, you can -download and install OpenMC by entering the following commands in a terminal: +All OpenMC source code is hosted on GitHub (`OpenMC GitHub `_). +Depending on your needs, you can either: -.. code-block:: sh +- Build only the OpenMC executable (using CMake). +- Build both the OpenMC executable and Python package (using pip). + +1. Building the OpenMC Executable Only (CMake) +============================================== + +If you only need the OpenMC executable without Python bindings, you can build it using +the following steps. You will need `git `_, a modern C++ compiler, +`CMake `_, and `HDF5 `_ installed: + +.. code-block:: bash git clone --recurse-submodules https://github.com/openmc-dev/openmc.git cd openmc - mkdir build && cd build - cmake .. - make - sudo make install + python -m pip install . -This will build an executable named ``openmc`` and install it (by default in -/usr/local/bin). If you do not have administrator privileges, the cmake command -should specify an installation directory where you have write access, e.g. +The easiest way to install it is using `pip `_. +This `pip` command will install the `openmc` Python package and compile an executable named ``openmc`` +and install it (by default in the bin folder of the Python package directory). .. code-block:: sh cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local .. + make + make install -The :mod:`openmc` Python package must be installed separately. The easiest way -to install it is using `pip `_. -From the root directory of the OpenMC repository, run: +2. Building the OpenMC Executable with Python Support (pip) +=========================================================== + +If you also want to install the OpenMC Python package,you can use +`pip `_ to build both the executable and +the Python package at the same time. This requires a Python environment +and pip installed. From the root directory of the OpenMC repository, run: .. code-block:: sh python -m pip install . +This will build the ``openmc`` executable and install it along with the Python bindings. +There is no need to manually run ``cmake`` or ``make``, as the pip command handles +both the C++ and Python build processes. + +Custom Build Options +~~~~~~~~~~~~~~~~~~~~ + +If you need to customize the build options (e.g., enabling MPI, DAGMC, or LibMesh), +you can pass CMake arguments using the ``SKBUILD_CMAKE_ARGS`` environment variable +before running pip. For example: + +.. code-block:: bash + + export SKBUILD_CMAKE_ARGS="-DOPENMC_USE_MPI=on;-DOPENMC_USE_DAGMC=on" + python -m pip install ".[test,depletion-mpi]" + +This allows you to configure your build just like you would with CMake. + By default, OpenMC will be built with multithreading support. To build -distributed-memory parallel versions of OpenMC using MPI or to configure other -options, directions can be found in the :ref:`detailed installation instructions +distributed-memory parallel versions of OpenMC using MPI the above command can be run. +There are other options that can be set, more details can be found in the :ref:`detailed installation instructions `. diff --git a/docs/source/usersguide/install.rst b/docs/source/usersguide/install.rst index 1f9a07b80c6..4a9be08e643 100644 --- a/docs/source/usersguide/install.rst +++ b/docs/source/usersguide/install.rst @@ -161,9 +161,9 @@ feature can be used to access the installed packages. .. _install_source: ----------------------- -Installing from Source ----------------------- +-------------------------------- +Compiling from source with CMake +-------------------------------- .. _prerequisites: @@ -352,6 +352,8 @@ Note that first a build directory is created as a subdirectory of the source directory. The Makefile in the top-level directory will automatically perform an out-of-source build with default options. +.. _cmake_arguemnts: + CMakeLists.txt Options ++++++++++++++++++++++ @@ -505,29 +507,72 @@ To run the test suite, you will first need to download a pre-generated cross section library along with windowed multipole data. Please refer to our :ref:`devguide_tests` documentation for further details. ---------------------- -Installing Python API ---------------------- +----------------------------------------------- +Installing Python API and compiling from source +----------------------------------------------- If you installed OpenMC using :ref:`Conda `, no further steps are -necessary in order to use OpenMC's :ref:`Python API `. However, if -you are :ref:`installing from source `, the Python API is not -installed by default when ``make install`` is run because in many situations it -doesn't make sense to install a Python package in the same location as the -``openmc`` executable (for example, if you are installing the package into a -`virtual environment `_). The -easiest way to install the :mod:`openmc` Python package is to use pip_, which is -included by default in Python 3.4+. From the root directory of the OpenMC -distribution/repository, run: +necessary to use OpenMC's :ref:`Python API `. However, if you are +:ref:`installing from source `, the Python API is not installed +by default when building OpenMC because, in many cases, it doesn't make sense to +install a Python package in the same location as the OpenMC executable (for example, +if you are using a virtual environment `virtualenv `_). -.. code-block:: sh +To install OpenMC and its Python API, the recommended method is to use pip, +which includes both the Python package and the OpenMC executable. +From the root directory of the OpenMC repository, run: + +.. code-block:: bash + + python -m pip install ".[test,depletion-mpi]" + +This command installs both the OpenMC executable and the Python API together. +There's no need to manually run ``cmake`` or ``make install`` as pip automatically +handles the build process, including any CMake configuration. + +Custom Build Options +==================== + +If you need to customize the build (for example, to enable MPI, DAGMC, or LibMesh), +you can pass the necessary CMake arguments through the ``SKBUILD_CMAKE_ARGS`` +environment variable before running pip install: + +.. code-block:: bash + + export SKBUILD_CMAKE_ARGS="-DOPENMC_USE_MPI=on;-DOPENMC_USE_DAGMC=on" + python -m pip install ".[test,depletion-mpi]" + +Alternatively, pip provides additional ways to configure the build using +``--config-settings`` or ``-C``: - python -m pip install . +.. code-block:: bash + + python -m pip install . --config-settings=cmake.args="-DOPENMC_USE_MPI=ON;-DOPENMC_USE_MCPL=ON" pip will first check that all :ref:`required third-party packages ` have been installed, and if they are not present, they will be installed by downloading the appropriate packages from the Python -Package Index (`PyPI `_). +Package Index (`PyPI `_). The pip command will also compile +an executable named ``openmc`` and install it (by default in the bin folder of +the Python package directory). + +Passing CMake arguments via pip +-------------------------------- + +If you need to pass CMake options to the build process, you can do so by +running pip install with some additional options. All the CMake arguments +covered in the :ref:`CMakeLists.txt Options` are supported. +For example, to build OpenMC with MPI support, you can run: + +.. code-block:: sh + + python -m pip install . --config-settings=cmake.args=-DOPENMC_USE_MPI=ON + +To build OpenMC with DAGMC support two CMake arguments are needed, you can run: + +.. code-block:: sh + + python -m pip install . --config-settings=cmake.args="-DOPENMC_USE_DAGMC=ON;DDAGMC_ROOT=/path/to/dagmc/installation" Installing in "Development" Mode -------------------------------- diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 0d8189caa1d..26e7fe0c3ec 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -47,6 +47,12 @@ namespace openmc { enum class ElementType { UNSUPPORTED = -1, LINEAR_TET, LINEAR_HEX }; +struct NextMeshCell { + double distance {INFTY}; + int face_idx {-1}; + std::array next_ijk; +}; + //============================================================================== // Global variables //============================================================================== @@ -771,6 +777,9 @@ class UnstructuredMesh : public Mesh { //! Get the library used for this unstructured mesh virtual std::string library() const = 0; + //! Get the mesh filename + virtual const std::string& filename() const { return filename_; } + // Data members bool output_ { true}; //!< Write tallies onto the unstructured mesh at the end of a run @@ -797,7 +806,36 @@ class UnstructuredMesh : public Mesh { //! \param[in] coords Coordinates of the tetrahedron //! \param[in] seed Random number generation seed //! \return Sampled position within the tetrahedron - Position sample_tet(std::array coords, uint64_t* seed) const; + template + Position sample_tet(span coords, uint64_t* seed) const + { + // Uniform distribution + double s = prn(seed); + double t = prn(seed); + double u = prn(seed); + + // From PyNE implementation of moab tet sampling C. Rocchini & P. Cignoni + // (2000) Generating Random Points in a Tetrahedron, Journal of Graphics + // Tools, 5:4, 9-12, DOI: 10.1080/10867651.2000.10487528 + if (s + t > 1) { + s = 1.0 - s; + t = 1.0 - t; + } + if (s + t + u > 1) { + if (t + u > 1) { + double old_t = t; + t = 1.0 - u; + u = 1.0 - s - old_t; + } else if (t + u <= 1) { + double old_s = s; + s = 1.0 - t - u; + u = old_s + t + u - 1; + } + } + V result = s * (coords[1] - coords[0]) + t * (coords[2] - coords[0]) + + u * (coords[3] - coords[0]) + coords[0]; + return {result[0], result[1], result[2]}; + } // Data members double length_multiplier_ { diff --git a/include/openmc/xdg.h b/include/openmc/xdg.h new file mode 100644 index 00000000000..fbb6a4d36ba --- /dev/null +++ b/include/openmc/xdg.h @@ -0,0 +1,113 @@ +#ifndef OPENMC_XDG_H +#define OPENMC_XDG_H + +namespace openmc { +extern "C" const bool XDG_ENABLED; +} + +// always include the XML interface header +#include "openmc/xml_interface.h" + +#ifdef OPENMC_XDG_ENABLED + +#include "xdg/xdg.h" + +#include "openmc/mesh.h" +#include "openmc/position.h" +namespace openmc { + +class XDGMesh : public UnstructuredMesh { + +public: + // Constructors + XDGMesh() = default; + XDGMesh(pugi::xml_node node); + XDGMesh(hid_t group); + XDGMesh(const std::string& filename, double length_multiplier = 1.0); + XDGMesh(std::shared_ptr external_xdg); + + static const std::string mesh_lib_type; + + const std::shared_ptr& xdg_instance() const { return xdg_; } + + // Overridden Methods + + //! Perform any preparation needed to support use in mesh filters + void prepare_for_point_location() override; + + Position sample_element(int32_t bin, uint64_t* seed) const override; + + void bins_crossed(Position r0, Position r1, const Direction& u, + vector& bins, vector& lengths) const override; + + int get_bin(Position r) const override; + + bool bin_is_valid(int bin) const { return bin >= 0 && bin < n_bins(); } + + xdg::MeshID bin_to_mesh_id(int bin) const; + + int mesh_id_to_bin(xdg::MeshID id) const; + + int n_bins() const override; + + int n_surface_bins() const override; + + std::pair, vector> plot( + Position plot_ll, Position plot_ur) const override; + + std::string library() const override; + + std::string mesh_library() const; + + //! Add a score to the mesh instance + void add_score(const std::string& score) override {}; + + //! Remove all scores from the mesh instance + void remove_scores() override {}; + + //! Set data for a score + void set_score_data(const std::string& score, const vector& values, + const vector& std_dev) override {}; + + //! Write the mesh with any current tally data + void write(const std::string& base_filename) const override; + + Position centroid(int bin) const override; + + int n_vertices() const override; + + Position vertex(int id) const override; + + std::vector connectivity(int id) const override; + + //! Get the volume of a mesh bin + // + //! \param[in] bin Bin to return the volume for + //! \return Volume of the bin + double volume(int bin) const override; + + //! Get the distance to the nearest boundary for a given position and + //! direction \param[in] g GeometryState object containing position and + //! direction \return NextMeshCell struct containing distance, face index, and + //! next indices + NextMeshCell distance_to_bin_boundary(GeometryState& g) const; + + //! Get the distance to the nearest boundary for a given position and + //! direction \param[in] r Position to check \param[in] u Direction to check + //! \return Distance to the nearest boundary + NextMeshCell distance_to_bin_boundary( + int bin, const Position& r, const Direction& u) const; + +private: + void initialize() override; + + std::shared_ptr xdg_; //!< XDG instance + xdg::MeshLibrary mesh_library_ { + xdg::MeshLibrary::LIBMESH}; //!< Mesh library type +}; + +} // namespace openmc + +#endif // OPENMC_XDG_ENABLED + +#endif // OPENMC_XDG_H \ No newline at end of file diff --git a/make_wheels.sh b/make_wheels.sh new file mode 100644 index 00000000000..b735f73217a --- /dev/null +++ b/make_wheels.sh @@ -0,0 +1,32 @@ +set -e + +# delete old images +docker rmi openmc_wheel:python3.12 -f +docker rmi openmc_wheel:python3.13 -f +docker rmi openmc_wheel:python3.14 -f + +# build manylinux image +docker build -t openmc -f manylinux.Dockerfile . + +docker build --no-cache --build-arg Python_ABI=cp312-cp312 --build-arg=OPENMC_USE_DAGMC=ON --build-arg=OPENMC_USE_XDG=ON -t openmc_wheel:python3.12 -f manylinux.Dockerfile . +docker build --no-cache --build-arg Python_ABI=cp313-cp313 --build-arg=OPENMC_USE_DAGMC=ON --build-arg=OPENMC_USE_XDG=ON -t openmc_wheel:python3.13 -f manylinux.Dockerfile . +docker build --no-cache --build-arg Python_ABI=cp314-cp314 --build-arg=OPENMC_USE_DAGMC=ON --build-arg=OPENMC_USE_XDG=ON -t openmc_wheel:python3.14 -f manylinux.Dockerfile . + +rm -rf wheelhouse +mkdir wheelhouse + +docker create --name openmc_wheel_container_3.12 openmc_wheel:python3.12 +docker cp openmc_wheel_container_3.12:/root/openmc/dist/. wheelhouse +docker rm openmc_wheel_container_3.12 + +docker create --name openmc_wheel_container_3.13 openmc_wheel:python3.13 +docker cp openmc_wheel_container_3.13:/root/openmc/dist/. wheelhouse +docker rm openmc_wheel_container_3.13 + +docker create --name openmc_wheel_container_3.14 openmc_wheel:python3.14 +docker cp openmc_wheel_container_3.14:/root/openmc/dist/. wheelhouse +docker rm openmc_wheel_container_3.14 + +docker build -f wheeltest.Dockerfile --build-arg python_version=3.12 . +docker build -f wheeltest.Dockerfile --build-arg python_version=3.13 . +docker build -f wheeltest.Dockerfile --build-arg python_version=3.14 . \ No newline at end of file diff --git a/manylinux.Dockerfile b/manylinux.Dockerfile new file mode 100644 index 00000000000..251b0c73e46 --- /dev/null +++ b/manylinux.Dockerfile @@ -0,0 +1,417 @@ +# ---------------------------------------------------------------------------- +# Dockerfile for building the OpenMC project with support for various dependencies +# and configurations. This Dockerfile allows you to build OpenMC with support +# for GCC or OpenMPI compilers, along with several libraries like NJOY2016, HDF5, +# NetCDF, MOAB, EMBREE, Double Down, DAGMC, PyBind11, Xtensor, Vectfit and +# libMesh. Each of these dependencies is installed from their respective +# repositories and tags. + +# The build process is split into stages: +# 1. Base Stage: Sets up a Manylinux base image and installs necessary dependencies. +# 2. Compiler Configuration: Defines the compilers (GCC or OpenMPI) to be used. +# 3. Dependencies Stage: Downloads and builds all external dependencies. +# 4. Python Dependencies Stage: Downloads and installs Python dependencies. +# 5. OpenMC Stage: Copies OpenMC source code, build wheel with specific +# flags and installs it in the container. Then, runs tests. + +# Arguments and environment variables can be customized for different compiler and +# dependency versions. +# +# To build the Docker image, use the following command from the repository's root: +# docker build -t openmc -f tools/ci/manylinux.dockerfile . +# +# For more information about each step, refer to the inline comments. +# ---------------------------------------------------------------------------- + +# Configure base image +ARG MANYLINUX_IMAGE=manylinux_2_28_x86_64 +# ARG MANYLINUX_IMAGE=manylinux_2_24_x86_64 +# ARG MANYLINUX_IMAGE=manylinux2014_x86_64 + +# Configure Compiler to use (gcc or openmpi) +ARG COMPILER="gcc" + +# Configure Python ABI to use +ARG Python_ABI="cp312-cp312" + +# OpenMC options +ARG OPENMC_USE_OPENMP="ON" +ARG OPENMC_BUILD_TESTS="ON" +ARG OPENMC_ENABLE_PROFILE="OFF" +ARG OPENMC_ENABLE_COVERAGE="OFF" +ARG OPENMC_USE_DAGMC="ON" +ARG OPENMC_USE_XDG="ON" +ARG OPENMC_USE_LIBMESH="OFF" +ARG OPENMC_USE_UWUW="OFF" + +# Configure dependencies tags +ARG GSL_LITE_TAG="v0.41.0" +ARG XTL_TAG="0.7.7" +ARG XTENSOR_TAG="0.25.0" +ARG XTENSOR_BLAS_TAG="0.21.0" +ARG CATCH2_TAG="v3.7.1" +ARG NJOY2016_TAG="2016.76" +ARG HDF5_TAG="hdf5_1.14.4.3" +ARG NETCDF_TAG="v4.9.3" +ARG MOAB_TAG="5.5.0" +ARG EMBREE_TAG="v4.3.3" +ARG DD_TAG="v1.1.0" +ARG DAGMC_TAG="v3.2.4" +ARG PYBIND_TAG="v2.13.6" +ARG XTENSOR_PYTHON_TAG="0.27.0" +ARG VECTFIT_TAG="master" +ARG LIBMESH_TAG="v1.7.7" +ARG XDG_TAG="main" + + +# Base stage +FROM quay.io/pypa/${MANYLINUX_IMAGE} AS base + +# Set timezone +ENV TZ=America/Chicago +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# Set Home directory +ENV HOME=/root +WORKDIR $HOME + +# Setup Epel repository and install build dependencies +RUN yum install -y epel-release && \ + yum config-manager --enable epel && \ + yum install -y \ + wget \ + git \ + gcc \ + gcc-c++ \ + gcc-gfortran \ + make \ + python3.12-devel \ + # python3.10-devel \ + # python3.10-dev \ + # python3.10-venv \ + zlib-devel \ + curl-devel \ + eigen3-devel \ + lapack-devel \ + libpng-devel \ + pugixml-devel \ + openmpi-devel \ + fmt-devel && \ + yum clean all + +RUN pipx uninstall cmake +RUN pipx install cmake==3.31.6 + +# Set up environment variables for shared libraries +ENV LD_LIBRARY_PATH=/usr/lib:/usr/lib64:$LD_LIBRARY_PATH + +ENV CC=gcc +ENV CXX=g++ +ENV FC=gfortran +ENV F77=gfortran + +ENV CC=mpicc +ENV CXX=mpicxx +ENV FC=mpif90 +ENV F77=mpif77 + +# Set up OpenMPI environment variables +ENV PATH=/usr/lib64/openmpi/bin:$PATH +ENV LD_LIBRARY_PATH=/usr/lib64/openmpi/lib:$LD_LIBRARY_PATH + + +ARG COMPILER + +# Build and install HDF5 +ARG HDF5_TAG +RUN git clone --depth 1 -b ${HDF5_TAG} https://github.com/HDFGroup/hdf5.git hdf5 && \ + cd hdf5 && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DHDF5_ENABLE_PARALLEL=$([ ${COMPILER} == "openmpi" ] && echo "ON" || echo "OFF") \ + -DHDF5_BUILD_HL_LIB=ON \ + -DBUILD_SHARED_LIBS=ON && \ + make -j$(nproc) && make install && \ + cd ../.. + +# Build and install NetCDF +ARG NETCDF_TAG +RUN git clone --depth 1 -b ${NETCDF_TAG} https://github.com/Unidata/netcdf-c.git netcdf && \ + cd netcdf && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_SHARED_LIBS=ON \ + -DENABLE_DAP=ON \ + -DENABLE_TESTS=OFF && \ + make -j$(nproc) && make install + + +# Build and install MOAB +ARG MOAB_TAG +# RUN git clone https://bitbucket.org/ahnaf-tahmid-chowdhury/moab.git moab && \ +# cd moab && \ +# git checkout c10c1ef343cbd45bd61bec626eb77a9c4741cd68 && \ +# mkdir build && cd build && \ +# export NETCDF_DIR=/usr && \ +# cmake .. \ +# -DCMAKE_INSTALL_PREFIX=/usr \ +# -DENABLE_MPI=$([ ${COMPILER} == "openmpi" ] && echo "ON" || echo "OFF") \ +# -DENABLE_HDF5=ON \ +# -DHDF5_ROOT=/usr \ +# -DENABLE_NETCDF=ON \ +# -DNETCDF_ROOT=/usr \ +# -DBUILD_SHARED_LIBS=ON \ +# -DENABLE_BLASLAPACK=OFF \ +# -DENABLE_PYMOAB=OFF && \ +# make -j$(nproc) && make install && \ +# cd ../.. && \ +# rm -rf moab + +RUN git clone --depth 1 -b ${MOAB_TAG} https://bitbucket.org/fathomteam/moab.git moab && \ + cd moab && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DENABLE_MPI=$([ ${COMPILER} == "openmpi" ] && echo "ON" || echo "OFF") \ + -DENABLE_HDF5=ON \ + -DHDF5_ROOT=/usr \ + -DENABLE_NETCDF=ON \ + -DNETCDF_ROOT=/usr \ + -DBUILD_SHARED_LIBS=ON \ + -DENABLE_BLASLAPACK=OFF \ + -DENABLE_PYMOAB=OFF && \ + make -j$(nproc) && make install && \ + cd ../.. && \ + rm -rf moab + +# Build and install gsl-lite +ARG GSL_LITE_TAG +RUN git clone --depth 1 -b ${GSL_LITE_TAG} https://github.com/gsl-lite/gsl-lite.git gsl-lite && \ + cd gsl-lite && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/usr && \ + make -j$(nproc) && make install && \ + cd ../.. && \ + rm -rf gsl-lite + +# Build and install xtl +ARG XTL_TAG +RUN git clone --depth 1 -b ${XTL_TAG} https://github.com/xtensor-stack/xtl.git xtl && \ + cd xtl && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/usr && \ + make -j$(nproc) && make install && \ + cd ../.. && \ + rm -rf xtl + +# Build and install xtensor +ARG XTENSOR_TAG +RUN git clone --depth 1 -b ${XTENSOR_TAG} https://github.com/xtensor-stack/xtensor.git xtensor && \ + cd xtensor && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/usr && \ + make -j$(nproc) && make install && \ + cd ../.. && \ + rm -rf xtensor + +# Build and install xtensor-blas +ARG XTENSOR_BLAS_TAG +RUN git clone --depth 1 -b ${XTENSOR_BLAS_TAG} https://github.com/xtensor-stack/xtensor-blas.git xtensor-blas && \ + cd xtensor-blas && \ + mkdir build && cd build && \ + cmake .. && \ + make -j$(nproc) && make install && \ + cd ../.. && \ + rm -rf xtensor-blas + +# Build and install Catch2 +ARG CATCH2_TAG +RUN git clone --depth 1 -b ${CATCH2_TAG} https://github.com/catchorg/Catch2.git catch2 && \ + cd catch2 && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/usr && \ + make -j$(nproc) && make install && \ + cd ../.. && \ + rm -rf catch2 + +# Build and install NJOY2016 +# ARG NJOY2016_TAG +# RUN git clone --depth 1 -b ${NJOY2016_TAG} https://github.com/njoy/njoy2016.git njoy && \ +# cd njoy && \ +# mkdir build && cd build && \ +# cmake .. \ +# -DCMAKE_INSTALL_PREFIX=/usr \ +# -Dstatic=ON && \ +# make -j$(nproc) && make install && \ +# cd ../.. && \ +# rm -rf njoy + +# Build and install Embree +ARG EMBREE_TAG +RUN git clone --depth 1 -b ${EMBREE_TAG} https://github.com/embree/embree.git embree && \ + cd embree && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DEMBREE_TASKING_SYSTEM=INTERNAL \ + -DEMBREE_ISPC_SUPPORT=OFF \ + -DEMBREE_TUTORIALS=OFF && \ + make -j$(nproc) && make install && \ + cd ../.. && \ + rm -rf embree + + +# Build and install Double Down +ARG DD_TAG +RUN git clone --depth 1 -b ${DD_TAG} https://github.com/pshriwise/double-down.git dd && \ + cd dd && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/usr && \ + make -j$(nproc) && make install && \ + cd ../.. && \ + rm -rf dd + +# Build and install DAGMC +ARG DAGMC_TAG +RUN git clone --depth 1 -b ${DAGMC_TAG} https://github.com/svalinn/DAGMC.git dagmc && \ + cd dagmc && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DMOAB_DIR=/usr \ + -Ddd_ROOT=/usr \ + -DBUILD_TALLY=ON \ + -DBUILD_UWUW=ON \ + -DDOUBLE_DOWN=ON \ + -DBUILD_STATIC_LIBS=OFF \ + -DBUILD_RPATH=OFF && \ + make -j$(nproc) && make install && \ + cd ../.. && \ + rm -rf dagmc + +# Build and install XDG +ARG XDG_TAG +RUN git clone --depth 1 -b ${XDG_TAG} https://github.com/xdg-org/xdg.git xdg && \ + cd xdg && \ + git submodule update --init vendor/fmt vendor/linalg vendor/indicators vendor/argparse && \ + mkdir build && cd build && \ + cmake .. \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DXDG_BUILD_TESTS=OFF \ + -DXDG_ENABLE_MOAB=ON \ + -DMOAB_DIR=/usr \ + -DXDG_ENABLE_LIBMESH=OFF \ + -DCMAKE_DISABLE_FIND_PACKAGE_fmt=ON && \ + make -j$(nproc) && make install && \ + cd ../.. && \ + rm -rf xdg + + +# Download and extract HDF5 data +# RUN wget -q -O - https://anl.box.com/shared/static/teaup95cqv8s9nn56hfn7ku8mmelr95p.xz | tar -C $HOME -xJ +# ENV OPENMC_CROSS_SECTIONS=$HOME/nndc_hdf5/cross_sections.xml + +# # Download and extract ENDF/B-VII.1 distribution +# RUN wget -q -O - https://anl.box.com/shared/static/4kd2gxnf4gtk4w1c8eua5fsua22kvgjb.xz | tar -C $HOME -xJ +# ENV OPENMC_ENDF_DATA=$HOME/endf-b-vii.1 + + +# Python dependencies stage + +ARG Python_ABI + +# Use Python from manylinux as the default Python +ENV PYTHONHOME="" +# ENV PYTHONHOME="/opt/python/${Python_ABI}" +ENV PATH="/opt/python/${Python_ABI}/bin:${PATH}" + + +# Build and install pybind +# ARG PYBIND_TAG +# RUN git clone --depth 1 -b ${PYBIND_TAG} https://github.com/pybind/pybind11.git pybind11 && \ +# cd pybind11 && \ +# mkdir build && cd build && \ +# cmake .. \ +# -DCMAKE_INSTALL_PREFIX=/usr && \ +# make -j$(nproc) && make install && \ +# cd .. && \ +# python -m pip install . && \ +# cd .. && \ +# rm -rf pybind11 + +# # Build and install xtensor-python +# ARG XTENSOR_PYTHON_TAG +# RUN git clone --depth 1 -b ${XTENSOR_PYTHON_TAG} https://github.com/xtensor-stack/xtensor-python.git xtensor-python && \ +# cd xtensor-python && \ +# mkdir build && cd build && \ +# python -m pip install numpy && \ +# cmake .. \ +# -DCMAKE_INSTALL_PREFIX=/usr \ +# -DNUMPY_INCLUDE_DIRS=$(python -c "import numpy; print(numpy.get_include())") && \ +# make -j$(nproc) && make install && \ +# cd ../.. && \ +# rm -rf xtensor-python + +# # Build and install vectfit +# ARG VECTFIT_TAG +# RUN git clone --depth 1 -b ${VECTFIT_TAG} https://github.com/liangjg/vectfit.git vectfit && \ +# cd vectfit && \ +# python -m pip install . && \ +# cd .. && \ +# rm -rf vectfit + + +# OpenMC stage + +ARG COMPILER +ARG Python_ABI +ARG OPENMC_USE_OPENMP +ARG OPENMC_BUILD_TESTS +ARG OPENMC_ENABLE_PROFILE +ARG OPENMC_ENABLE_COVERAGE +ARG OPENMC_USE_DAGMC +ARG OPENMC_USE_XDG +ARG OPENMC_USE_LIBMESH +ARG OPENMC_USE_UWUW + +# Copy OpenMC source to docker image +COPY . $HOME/openmc + +# Configure SKBUILD CMake arguments +RUN export SKBUILD_CMAKE_ARGS="-DOPENMC_USE_MPI=$([ ${COMPILER} == 'openmpi' ] && echo 'ON' || echo 'OFF'); \ + -DOPENMC_USE_OPENMP=${OPENMC_USE_OPENMP}; \ + -DOPENMC_BUILD_TESTS=${OPENMC_BUILD_TESTS}; \ + -DOPENMC_ENABLE_PROFILE=${OPENMC_ENABLE_PROFILE}; \ + -DOPENMC_ENABLE_COVERAGE=${OPENMC_ENABLE_COVERAGE}; \ + -DOPENMC_USE_DAGMC=${OPENMC_USE_DAGMC}; \ + -DOPENMC_USE_XDG=${OPENMC_USE_XDG}; \ + -DOPENMC_USE_LIBMESH=${OPENMC_USE_LIBMESH}; \ + -DOPENMC_USE_UWUW=${OPENMC_USE_UWUW}" && \ + cd $HOME/openmc && \ + python -m build . -w + +# Install OpenMC wheel +RUN python -m pip install \ + "$(echo $HOME/openmc/dist/*.whl)[$([ ${COMPILER} == 'openmpi' ] && echo 'depletion-mpi,')test,ci]" + +# Test OpenMC +# RUN cd $HOME/openmc && \ + # eval $(ncrystal-config --setup) && \ + # nctool --test && \ + # pytest --cov=openmc -v $([ ${COMPILER} == 'openmpi' ] && echo '--mpi') --event tests + +# Repair wheel +RUN auditwheel repair $HOME/openmc/dist/openmc-*.whl -w $HOME/openmc/dist/ + +# Test repaired wheel +RUN python -m pip uninstall openmc -y +RUN ls $HOME/openmc/dist/*manylinux**.whl +RUN python -m pip install $HOME/openmc/dist/*manylinux**.whl +RUN openmc --version \ No newline at end of file diff --git a/minimal_test.py b/minimal_test.py new file mode 100644 index 00000000000..3eb724b4af6 --- /dev/null +++ b/minimal_test.py @@ -0,0 +1,68 @@ +import openmc + +openmc.config['cross_sections'] = 'cross_sections.xml' + +mat1 = openmc.Material(name='mat1') +mat1.add_nuclide('Li7', 0.95) +mat1.set_density('g/cm3', 1.0) + +materials = openmc.Materials([mat1]) + +bound_dag_univ = openmc.DAGMCUniverse(filename='small_dagmc_file.h5m').bounded_universe() +my_geometry = openmc.Geometry(root=bound_dag_univ) + +umesh = openmc.UnstructuredMesh(filename='small_um.vtk', library='moab') +umesh.id = 1 + +mesh_filter = openmc.MeshFilter(umesh) + +# Create flux mesh tally to score alpha production +mesh_tally = openmc.Tally(tally_id=1, name='alpha_production_on_mesh') # note the tally_id is specified +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ['(n,Xa)'] # where X is a wild card + + +tallies = openmc.Tallies([mesh_tally]) + + +my_source = openmc.IndependentSource() +my_source.space = openmc.stats.Point((0.4, 0, 0.4)) + + +settings = openmc.Settings() +settings.run_mode = 'fixed source' +settings.batches = 3 +settings.particles = 1000 +settings.source = my_source +settings.photon_transport=True + +my_model = openmc.Model( + materials=materials, + geometry=my_geometry, + settings=settings, + tallies=tallies +) + +statepoint_file = my_model.run() + +statepoint = openmc.StatePoint(statepoint_file) + +my_tally = statepoint.get_tally(name='alpha_production_on_mesh') + +umesh_from_sp = statepoint.meshes[1] # note to self we can add a function to openmc to get the mesh by type or name + +# needed to trigger internal mesh data loading with openmc v0.15, fixed on dev +# note to self we can add a function to openmc to get this automated when writting to vtk +centroids = umesh_from_sp.centroids +mesh_vols = umesh_from_sp.volumes + +umesh_from_sp.write_data_to_vtk( + datasets={'mean': my_tally.mean.flatten()}, + filename = "shape_alpha_production_on_mesh.vtkhdf", +) +# vtk and vtk are not used as package does not have optional vtk dependency +# umesh_from_sp.write_data_to_vtk( +# datasets={'mean': my_tally.mean.flatten()}, +# filename = "shape_alpha_production_on_mesh.vtk", +# filename = "shape_alpha_production_on_mesh.vtu", +# ) \ No newline at end of file diff --git a/minimal_test_umesh.py b/minimal_test_umesh.py new file mode 100644 index 00000000000..e0c8afc2f82 --- /dev/null +++ b/minimal_test_umesh.py @@ -0,0 +1,50 @@ +import openmc + +openmc.config['cross_sections'] = 'cross_sections.xml' + +mat1 = openmc.Material(name='mat1') +mat1.add_nuclide('Li7', 0.95) +mat1.set_density('g/cm3', 1.0) + +materials = openmc.Materials([mat1]) + +bound_dag_univ = openmc.DAGMCUniverse(filename='small_dagmc_file.h5m').bounded_universe() +my_geometry = openmc.Geometry(root=bound_dag_univ) + +umesh = openmc.UnstructuredMesh(filename='small_um.vtk', library='moab') +umesh.id = 1 + +mesh_filter = openmc.MeshFilter(umesh) + +mesh_tally = openmc.Tally(tally_id=1, name='alpha_production_on_mesh') +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ['(n,Xa)'] + +tallies = openmc.Tallies([mesh_tally]) + +my_source = openmc.IndependentSource() +my_source.space = openmc.stats.Point((0.4, 0, 0.4)) + +settings = openmc.Settings() +settings.run_mode = 'fixed source' +settings.batches = 3 +settings.particles = 1000 +settings.source = my_source +settings.photon_transport = True + +my_model = openmc.Model( + materials=materials, + geometry=my_geometry, + settings=settings, + tallies=tallies +) + +statepoint_file = my_model.run() + +statepoint = openmc.StatePoint(statepoint_file) +my_tally = statepoint.get_tally(name='alpha_production_on_mesh') + +print("=== UnstructuredMesh (moab) ===") +for key, value in statepoint.runtime.items(): + print(f" {key}: {value:.4f}s") +print(f"Tally mean sum: {my_tally.mean.sum():.6e}") diff --git a/minimal_test_xdg.py b/minimal_test_xdg.py new file mode 100644 index 00000000000..336aabbfb2e --- /dev/null +++ b/minimal_test_xdg.py @@ -0,0 +1,51 @@ +import openmc +from openmc.xdg import XDGMesh + +openmc.config['cross_sections'] = 'cross_sections.xml' + +mat1 = openmc.Material(name='mat1') +mat1.add_nuclide('Li7', 0.95) +mat1.set_density('g/cm3', 1.0) + +materials = openmc.Materials([mat1]) + +bound_dag_univ = openmc.DAGMCUniverse(filename='small_dagmc_file.h5m').bounded_universe() +my_geometry = openmc.Geometry(root=bound_dag_univ) + +xdg_mesh = XDGMesh(filename='small_um.vtk', library='moab') +xdg_mesh.id = 1 + +mesh_filter = openmc.MeshFilter(xdg_mesh) + +mesh_tally = openmc.Tally(tally_id=1, name='alpha_production_on_mesh') +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ['(n,Xa)'] + +tallies = openmc.Tallies([mesh_tally]) + +my_source = openmc.IndependentSource() +my_source.space = openmc.stats.Point((0.4, 0, 0.4)) + +settings = openmc.Settings() +settings.run_mode = 'fixed source' +settings.batches = 3 +settings.particles = 1000 +settings.source = my_source +settings.photon_transport = True + +my_model = openmc.Model( + materials=materials, + geometry=my_geometry, + settings=settings, + tallies=tallies +) + +statepoint_file = my_model.run() + +statepoint = openmc.StatePoint(statepoint_file) +my_tally = statepoint.get_tally(name='alpha_production_on_mesh') + +print("=== XDGMesh (moab) ===") +for key, value in statepoint.runtime.items(): + print(f" {key}: {value:.4f}s") +print(f"Tally mean sum: {my_tally.mean.sum():.6e}") diff --git a/openmc/__init__.py b/openmc/__init__.py index c204929c840..d4762d45c80 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -1,3 +1,5 @@ +import os +import glob import importlib.metadata from openmc.arithmetic import * from openmc.bounding_box import * @@ -34,6 +36,7 @@ from openmc.search import * from openmc.polynomial import * from openmc.tracks import * +from openmc.xdg import * from .config import * # Import a few names from the model module @@ -43,3 +46,61 @@ __version__ = importlib.metadata.version("openmc") + +try: + OPENMC_CORE_BASE_PATH = os.path.join(__path__[0], "core") +except NameError: + OPENMC_CORE_BASE_PATH = None + +if not OPENMC_CORE_BASE_PATH or not os.path.exists(OPENMC_CORE_BASE_PATH): + import sysconfig + OPENMC_CORE_BASE_PATH = os.path.join(sysconfig.get_path("platlib"), "openmc", "core") + if not os.path.exists(OPENMC_CORE_BASE_PATH): + raise ImportError("OpenMC is not installed. Please run 'pip install openmc'.") + warnings.warn( + "It seems OpenMC is being run from its source directory. " + "This setup is not recommended as it may lead to unexpected behavior, " + "such as conflicts between source and installed versions. " + "Please run your script from outside the OpenMC source tree.", + RuntimeWarning + ) + +def get_paths(subdir, pattern="*", recursive=False): + """ + Helper function to return paths that match a given pattern within a subdirectory. + + Args: + subdir (str): The subdirectory within the 'core' directory. + pattern (str): The pattern to match files or directories. + recursive (bool): Whether to search recursively in subdirectories. + + Returns: + list: A list of matched paths. + """ + search_pattern = os.path.join(OPENMC_CORE_BASE_PATH, subdir, "**", pattern) if recursive else os.path.join(OPENMC_CORE_BASE_PATH, subdir, pattern) + return glob.glob(search_pattern, recursive=recursive) + +def get_include_path(): + """Return includes and include path for OpenMC headers.""" + include = get_paths("include", "*", recursive=True) + include_path = get_paths("include", "", recursive=False) + return include, include_path + +def get_core_libraries(): + """Return libraries and library paths for OpenMC.""" + lib = [lib_file for lib in ["lib", "lib64"] for lib_file in get_paths(lib, "libopenmc*", recursive=True)] + lib_path = [lib_file for lib in ["lib", "lib64"] for lib_file in get_paths(lib, "", recursive=False)] + return lib, lib_path + +def get_extra_libraries(): + """Return the extra libraries installed by auditwheel or delocate.""" + libs_path = os.path.join(__path__[0], ".dylibs") if sys.platform == "darwin" else os.path.normpath(os.path.join(__path__[0], "..", "openmc.libs")) + return (glob.glob(os.path.join(libs_path, "*")), libs_path) if os.path.exists(libs_path) else ([], []) + +# Setup variables +include, include_path = get_include_path() +lib, lib_path = get_core_libraries() +extra_lib, extra_lib_path = get_extra_libraries() + +# Export variables for easy access +__all__ = ["include", "include_path", "lib", "lib_path", "extra_lib", "extra_lib_path"] \ No newline at end of file diff --git a/openmc/filter.py b/openmc/filter.py index 87aeb70c3a7..6d166f2709b 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -925,6 +925,8 @@ def mesh(self, mesh): self.bins = list(range(len(mesh.volumes))) else: self.bins = [] + elif isinstance(mesh, openmc.XDGMesh): + self.bins = [] else: self.bins = list(mesh.indices) diff --git a/openmc/lib/__init__.py b/openmc/lib/__init__.py index 9b135370fe9..83e8cf817e0 100644 --- a/openmc/lib/__init__.py +++ b/openmc/lib/__init__.py @@ -13,20 +13,12 @@ """ from ctypes import CDLL, c_bool, c_int -import importlib.resources import os -import sys - - -# Determine shared-library suffix -if sys.platform == 'darwin': - _suffix = 'dylib' -else: - _suffix = 'so' if os.environ.get('READTHEDOCS', None) != 'True': # Open shared library - _filename = importlib.resources.files(__name__) / f'libopenmc.{_suffix}' + import openmc + _filename = openmc.lib[0] _dll = CDLL(str(_filename)) # TODO: Remove str() when Python 3.12+ else: # For documentation builds, we don't actually have the shared library @@ -40,6 +32,9 @@ def _dagmc_enabled(): return c_bool.in_dll(_dll, "DAGMC_ENABLED").value +def _xdg_enabled(): + return c_bool.in_dll(_dll, "XDG_ENABLED").value + def _coord_levels(): return c_int.in_dll(_dll, "n_coord_levels").value diff --git a/openmc/mesh.py b/openmc/mesh.py index 3c3c0a1ac5d..377e988184e 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -337,6 +337,8 @@ def from_xml_element(cls, elem: ET.Element): an openmc mesh object """ + from .xdg import XDGMesh + mesh_type = get_text(elem, 'type') if mesh_type == 'regular' or mesh_type is None: @@ -347,6 +349,8 @@ def from_xml_element(cls, elem: ET.Element): mesh = CylindricalMesh.from_xml_element(elem) elif mesh_type == 'spherical': mesh = SphericalMesh.from_xml_element(elem) + elif mesh_type == 'xdg': + mesh = XDGMesh.from_xml_element(elem) elif mesh_type == 'unstructured': mesh = UnstructuredMesh.from_xml_element(elem) else: @@ -2769,7 +2773,7 @@ def library(self): @library.setter def library(self, lib: str): - cv.check_value('Unstructured mesh library', lib, ('moab', 'libmesh')) + cv.check_value('Unstructured mesh library', lib, ('xdg','moab', 'libmesh')) self._library = lib @property diff --git a/openmc/xdg.py b/openmc/xdg.py new file mode 100644 index 00000000000..f25597668c4 --- /dev/null +++ b/openmc/xdg.py @@ -0,0 +1,541 @@ +from collections.abc import Iterable, Mapping +from numbers import Integral + + +import h5py +import lxml.etree as ET +import numpy as np +import warnings + +import openmc +import openmc.checkvalue as cv +from ._xml import get_text +from .checkvalue import check_type, check_value, PathLike +from .mesh import MeshBase +from .surface import _BOUNDARY_TYPES +from .bounding_box import BoundingBox +from .utility_funcs import input_path + + +class XDGMesh(openmc.MeshBase): + """A 3D unstructured mesh + + Parameters + ---------- + filename : path-like + Location of the unstructured mesh file. Supported files for 'moab' + library are .h5 and .vtk. Supported files for 'libmesh' library are + exodus mesh files .exo. + library : {'moab', 'libmesh'} + Mesh library used for the unstructured mesh tally + mesh_id : int + Unique identifier for the mesh + name : str + Name of the mesh + + Attributes + ---------- + id : int + Unique identifier for the mesh + name : str + Name of the mesh + filename : str + Name of the file containing the unstructured mesh + library : {'moab', 'libmesh'} + Mesh library used for the unstructured mesh tally + """ + def __init__(self, filename: PathLike, library: str, mesh_id: int | None = None, + name: str = ''): + super().__init__(mesh_id, name) + self.filename = filename + self.library = library + + @property + def filename(self): + return self._filename + + @filename.setter + def filename(self, filename): + cv.check_type('Unstructured Mesh filename', filename, PathLike) + self._filename = input_path(filename) + + @property + def library(self): + return self._library + + @library.setter + def library(self, lib: str): + cv.check_value('Unstructured mesh library', lib, ('moab', 'libmesh')) + self._library = lib + + @property + def dimension(self): + return (self.n_elements,) + + @property + def n_dimension(self): + return 3 + + @property + def lower_left(self): + raise NotImplementedError("XDGMesh.lower_left is not implemented") + + @property + def upper_right(self): + raise NotImplementedError("XDGMesh.upper_right is not implemented") + + @property + def n_elements(self): + raise NotImplementedError("XDGMesh.n_elements is not implemented") + + @property + def indices(self): + raise NotImplementedError("XDGMesh.indices is not implemented") + + + def __repr__(self): + string = super().__repr__() + string += '{: <16}=\t{}\n'.format('\tFilename', self.filename) + string += '{: <16}=\t{}\n'.format('\tMesh Library', self.library) + return string + + @classmethod + def from_hdf5(cls, group: h5py.Group, mesh_id: int, name: str): + raise NotImplementedError("XDGMesh.from_hdf5 is not implemented") + # TODO: add length_multiplier + + def to_xml_element(self): + """Return XML representation of the mesh + + Returns + ------- + element : lxml.etree._Element + XML element containing mesh data + + """ + element = super().to_xml_element() + element.set("type", "xdg") + element.set("library", self._library) + subelement = ET.SubElement(element, "filename") + subelement.text = str(self.filename) + + return element + + @classmethod + def from_xml_element(cls, elem: ET.Element): + """Generate unstructured mesh object from XML element + + Parameters + ---------- + elem : lxml.etree._Element + XML element + + Returns + ------- + openmc.UnstructuredMesh + UnstructuredMesh generated from an XML element + """ + mesh_id = int(get_text(elem, 'id')) + filename = get_text(elem, 'filename') + library = get_text(elem, 'library') + return cls(filename, library, mesh_id) + + +class XDGUniverse(openmc.UniverseBase): + """A reference to a XDG file to be used in the model. + + Parameters + ---------- + mesh : openmc.XDGMesh + Mesh to use for the XDG universe + type : str + Type of XDG file to use. Options are 'volume_mesh' or 'surface_mesh'. + universe_id : int, optional + Unique identifier of the universe. If not specified, an identifier will + automatically be assigned. + name : str, optional + Name of the universe. If not specified, the name is the empty string. + auto_geom_ids : bool + Set IDs automatically on initialization (True) or report overlaps in ID + space between CSG and DAGMC (False) + + Attributes + ---------- + id : int + Unique identifier of the universe + name : str + Name of the universe + mesh : openmc.XDGMesh + Mesh to use for the XDG universe + auto_geom_ids : bool + Set IDs automatically on initialization (True) or report overlaps in ID + space between CSG and XDG geometry (False) + """ + + def __init__(self, + mesh: XDGMesh, + universe_id=None, + name='', + auto_geom_ids=False, + auto_mat_ids=False): + super().__init__(universe_id, name) + # Initialize class attributes + self.mesh = mesh + self.auto_geom_ids = auto_geom_ids + self._type = 'surface_mesh' + self._background_material = None + + def __repr__(self): + string = super().__repr__() + string += '{: <16}=\t{}\n'.format('\tGeom', 'XDG') + string += '{: <16}=\t{}\n'.format('\tAuto Geom IDs', self.auto_geom_ids) + string += '{: <16}=\t{}\n'.format('\tType', self._type) + string += '{: <16}=\t{}\n'.format('\tMesh', self._mesh) + return string + + @property + def bounding_box(self): + return BoundingBox.infinite() + + @property + def filename(self): + return self._filename + + @filename.setter + def filename(self, val: cv.PathLike): + cv.check_type('XDG filename', val, cv.PathLike) + self._filename = input_path(val) + + @property + def material_overrides(self): + raise NotImplementedError("Material overrides are not implemented for XDG") + + @property + def mesh(self): + return self._mesh + + @mesh.setter + def mesh(self, val): + cv.check_type('XDG mesh', val, XDGMesh) + self._mesh = val + + @property + def type(self): + return self._type + + @type.setter + def type(self, val): + cv.check_value('XDG type', val, ('surface_mesh', 'volume_mesh')) + self._type = val + + def replace_material_assignment(self, material_name: str, material: openmc.Material): + """Replace a material assignment within the DAGMC universe. + + Replace the material assignment of all cells filled with a material in + the DAGMC universe. The universe must be synchronized in an initialized + Model (see :meth:`~openmc.DAGMCUniverse.sync_dagmc_cells`) before + calling this method. + + .. versionadded:: 0.15.1 + + Parameters + ---------- + material_name : str + Material name to replace + material : openmc.Material + Material to replace the material_name with + + """ + raise NotImplementedError("Material overrides are not implemented for XDG") + + def add_material_override(self, key, overrides=None): + """Add a material override to the universe. + + .. versionadded:: 0.15 + + Parameters + ---------- + key : openmc.DAGMCCell or int + Cell object or ID of the Cell to override + value : openmc.Material or Iterable of openmc.Material + Material(s) to be applied to the Cell passed as the key + + """ + raise NotImplementedError("Material overrides are not implemented for XDG") + + @property + def auto_geom_ids(self): + return self._auto_geom_ids + + @auto_geom_ids.setter + def auto_geom_ids(self, val): + cv.check_type('DAGMC automatic geometry ids', val, bool) + self._auto_geom_ids = val + + @property + def material_names(self): + raise NotImplementedError("Material names is not implemented for XDG") + + @property + def background_material(self): + return self._background_material + + @background_material.setter + def background_material(self, val): + cv.check_type('XDG background material', val, openmc.Material) + self._background_material = val + + def _n_geom_elements(self, geom_type): + """ + Helper function for retrieving the number geometric entities in a DAGMC + file + + Parameters + ---------- + geom_type : str + The type of geometric entity to count. One of {'Volume', 'Surface'}. Returns + the runtime number of voumes in the DAGMC model (includes implicit complement). + + Returns + ------- + int + Number of geometry elements of the specified type + """ + raise NotImplementedError("Number of cells is not implemented for XDG") + + @property + def n_cells(self): + raise NotImplementedError("Number of cells is not implemented for XDG") + + @property + def n_surfaces(self): + raise NotImplementedError("Number of surfaces is not implemented for XDG") + + def create_xml_subelement(self, xml_element, memo=None): + if memo is None: + memo = set() + + if self in memo: + return + + memo.add(self) + + # Set xml element values + xdg_element = ET.Element('xdg_universe') + xdg_element.set('id', str(self.id)) + + if self.auto_geom_ids: + xdg_element.set('auto_geom_ids', 'true') + + xdg_element.set('mesh', str(self.mesh.id)) + + xdg_element.set('type', self._type) + + if self.background_material is not None: + xdg_element.set('background_material', str(self.background_material.id)) + + # add mesh element + xml_element.append(xdg_element) + + # if this mesh has already been added to the XML element + if self.mesh in memo: + return + memo.add(self.mesh) + + xml_element.append(self.mesh.to_xml_element()) + + def bounding_region( + self, + bounded_type: str = 'box', + boundary_type: str = 'vacuum', + starting_id: int = 10000, + padding_distance: float = 0. + ): + """Creates a either a spherical or box shaped bounding region around + the DAGMC geometry. + + .. versionadded:: 0.13.1 + + Parameters + ---------- + bounded_type : str + The type of bounding surface(s) to use when constructing the region. + Options include a single spherical surface (sphere) or a rectangle + made from six planes (box). + boundary_type : str + Boundary condition that defines the behavior for particles hitting + the surface. Defaults to vacuum boundary condition. Passed into the + surface construction. + starting_id : int + Starting ID of the surface(s) used in the region. For bounded_type + 'box', the next 5 IDs will also be used. Defaults to 10000 to reduce + the chance of an overlap of surface IDs with the DAGMC geometry. + padding_distance : float + Distance between the bounding region surfaces and the minimal + bounding box. Allows for the region to be larger than the DAGMC + geometry. + + Returns + ------- + openmc.Region + Region instance + """ + raise NotImplementedError("Bounded region is not implemented for XDG") + + def bounded_universe(self, bounding_cell_id=10000, **kwargs): + """Returns an openmc.Universe filled with this DAGMCUniverse and bounded + with a cell. Defaults to a box cell with a vacuum surface however this + can be changed using the kwargs which are passed directly to + DAGMCUniverse.bounding_region(). + + Parameters + ---------- + bounding_cell_id : int + The cell ID number to use for the bounding cell, defaults to 10000 to reduce + the chance of overlapping ID numbers with the DAGMC geometry. + + Returns + ------- + openmc.Universe + Universe instance + """ + raise NotImplementedError("Bounded universe is not implemented for XDG") + + @classmethod + def from_hdf5(cls, group): + """Create DAGMC universe from HDF5 group + + Parameters + ---------- + group : h5py.Group + Group in HDF5 file + + Returns + ------- + openmc.XDGUniverse + XDGUniverse instance + + """ + id = int(group.name.split('/')[-1].lstrip('universe ')) + fname = group['filename'][()].decode() + name = group['name'][()].decode() if 'name' in group else None + + out = cls(fname, universe_id=id, name=name) + + out.auto_geom_ids = bool(group.attrs['auto_geom_ids']) + out.auto_mat_ids = bool(group.attrs['auto_mat_ids']) + + return out + + @classmethod + def from_xml_element(cls, elem, meshes=None): + """Generate XDG universe from XML element + + Parameters + ---------- + elem : lxml.etree._Element + `` element + meshes : dict + Dictionary mapping mesh ID strings to :class:`openmc.XDGMesh` + instances (defined in :meth:`openmc.Geometry.from_xml`) + + Returns + ------- + openmc.XDGUniverse + XDGUniverse instance + + """ + id = int(get_text(elem, 'id')) + mesh_id = int(get_text(elem, 'mesh')) + + out = cls(meshes[mesh_id], universe_id=id) + + name = get_text(elem, 'name') + if name is not None: + out.name = name + + out.auto_geom_ids = bool(elem.get('auto_geom_ids')) + + if type := elem.get('type'): + out.type = type + else: + raise ValueError("XDG type is not specified in the XML element") + + if library := elem.get('library'): + out.library = library + + if background_material := elem.get('background_material'): + out.background_material = background_material + + return out + + def _partial_deepcopy(self): + """Clone all of the openmc.DAGMCUniverse object's attributes except for + its cells, as they are copied within the clone function. This should + only to be used within the openmc.UniverseBase.clone() context. + """ + clone = openmc.XDGUniverse(name=self.name, filename=self.filename) + clone.auto_geom_ids = self.auto_geom_ids + clone.auto_mat_ids = self.auto_mat_ids + clone.library = self.library + clone.type = self.type + return clone + + def add_cell(self, cell): + """Add a cell to the universe. + + Parameters + ---------- + cell : openmc.XDGCell + Cell to add + + """ + raise NotImplementedError("Add cell is not implemented for XDG") + + def remove_cell(self, cell): + """Remove a cell from the universe. + + Parameters + ---------- + cell : openmc.Cell + Cell to remove + + """ + raise NotImplementedError("Remove cell is not implemented for XDG") + + def sync_dagmc_cells(self, mats: Iterable[openmc.Material]): + """Synchronize DAGMC cell information between Python and C API + + .. versionadded:: 0.15.1 + + Parameters + ---------- + mats : iterable of openmc.Material + Iterable of materials to assign to the DAGMC cells + + """ + raise NotImplementedError("Sync cells is not implemented for XDG") + +class XDGCell(openmc.Cell): + """A cell class for XDG-based geometries. + + Parameters + ---------- + cell_id : int or None, optional + Unique identifier for the cell. If None, an identifier will be + automatically assigned. + name : str, optional + Name of the cell. + fill : openmc.Material or None, optional + Material filling the cell. If None, the cell is filled with vacuum. + + Attributes + ---------- + id : int + Unique identifier of the cell + name : str + Name of the cell + fill : openmc.Material or None, optional + Material filling the cell. If None, the cell is filled with vacuum. + """ + pass \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index bf40113445b..499e6862505 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["setuptools", "setuptools-scm", "wheel"] -build-backend = "setuptools.build_meta" +requires = ["scikit-build-core", "setuptools_scm"] +build-backend = "scikit_build_core.build" [project] name = "openmc" @@ -8,7 +8,7 @@ authors = [ {name = "The OpenMC Development Team", email = "openmc@anl.gov"}, ] description = "OpenMC" -dynamic = ["version"] +version = "0.15.3" requires-python = ">=3.12" license = {file = "LICENSE"} classifiers = [ @@ -64,13 +64,12 @@ Documentation = "https://docs.openmc.org" Repository = "https://github.com/openmc-dev/openmc" Issues = "https://github.com/openmc-dev/openmc/issues" -[tool.setuptools.packages.find] -include = ['openmc*'] -exclude = ['tests*'] - -[tool.setuptools.package-data] -"openmc.data.dose" = ["**/*.txt"] -"openmc.data" = ["*.txt", "*.DAT", "*.json", "*.h5"] -"openmc.lib" = ["libopenmc.dylib", "libopenmc.so"] +# Scikit-Build Configuration +[tool.scikit-build] +build.verbose = true +logging.level = "INFO" +wheel.install-dir = "openmc" +wheel.packages = ["openmc"] [tool.setuptools_scm] +fallback_version = "0.15.2" diff --git a/small_dagmc_file.h5m b/small_dagmc_file.h5m new file mode 100644 index 00000000000..b060ce228ef Binary files /dev/null and b/small_dagmc_file.h5m differ diff --git a/small_um.vtk b/small_um.vtk new file mode 100644 index 00000000000..9acdf3e486a --- /dev/null +++ b/small_um.vtk @@ -0,0 +1,159 @@ +# vtk DataFile Version 2.0 +made_with_cad_to_dagmc_package, Created by Gmsh 4.13.1 +ASCII +DATASET UNSTRUCTURED_GRID +POINTS 14 double +-0.5 -0.5 0.5 +-0.5 -0.5 -0.5 +-0.5 0.5 0.5 +-0.5 0.5 -0.5 +0.5 -0.5 0.5 +0.5 -0.5 -0.5 +0.5 0.5 0.5 +0.5 0.5 -0.5 +-0.5 0 0 +0.5 0 0 +0 -0.5 0 +0 0.5 0 +0 0 -0.5 +0 0 0.5 + +CELLS 68 268 +1 0 +1 1 +1 2 +1 3 +1 4 +1 5 +1 6 +1 7 +2 1 0 +2 0 2 +2 3 2 +2 1 3 +2 5 4 +2 4 6 +2 7 6 +2 5 7 +2 1 5 +2 0 4 +2 3 7 +2 2 6 +3 1 0 8 +3 8 0 2 +3 8 3 1 +3 2 3 8 +3 5 9 4 +3 9 6 4 +3 9 5 7 +3 6 9 7 +3 0 1 10 +3 10 4 0 +3 10 1 5 +3 5 4 10 +3 2 11 3 +3 11 2 6 +3 11 7 3 +3 7 11 6 +3 12 1 3 +3 5 1 12 +3 3 7 12 +3 12 7 5 +3 13 2 0 +3 4 13 0 +3 2 13 6 +3 13 4 6 +4 10 12 8 9 +4 12 11 8 9 +4 13 8 11 9 +4 8 13 10 9 +4 13 0 10 4 +4 3 1 12 8 +4 12 3 11 7 +4 8 11 3 2 +4 13 2 8 0 +4 1 8 0 10 +4 11 2 13 6 +4 10 1 12 5 +4 6 4 13 9 +4 7 6 11 9 +4 9 4 10 5 +4 9 12 7 5 +4 11 3 12 8 +4 8 1 12 10 +4 2 8 11 13 +4 8 13 0 10 +4 4 10 13 9 +4 13 11 6 9 +4 11 12 7 9 +4 9 10 12 5 + +CELL_TYPES 68 +1 +1 +1 +1 +1 +1 +1 +1 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 diff --git a/src/initialize.cpp b/src/initialize.cpp index efb462f5c01..df2c7a6962b 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -42,6 +42,10 @@ #include "libmesh/libmesh.h" #endif +#ifdef OPENMC_XDG_ENABLED +#include "xdg/config.h" +#endif + int openmc_init(int argc, char* argv[], const void* intracomm) { using namespace openmc; @@ -85,6 +89,16 @@ int openmc_init(int argc, char* argv[], const void* intracomm) settings::libmesh_comm = &(settings::libmesh_init->comm()); } +#ifdef OPENMC_XDG_ENABLED +#ifdef XDG_ENABLE_LIBMESH + // Set the external libMesh initialization and communicator for XDG if + // libMesh was initialized externally. If libMesh was initialized internally, + // the XDG config will use the internal initialization and communicator. + xdg::config::external_libmesh_init = settings::libmesh_init.get(); + xdg::config::external_libmesh_comm = settings::libmesh_comm; +#endif +#endif + #endif // Start total and initialization timer diff --git a/src/mesh.cpp b/src/mesh.cpp index 5ab7ac3988b..898c8fb5fbc 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -55,6 +55,8 @@ #include "moab/FileOptions.hpp" #endif +#include "openmc/xdg.h" + namespace openmc { //============================================================================== @@ -347,6 +349,10 @@ const std::unique_ptr& Mesh::create( mesh_library == MOABMesh::mesh_lib_type) { model::meshes.push_back(make_unique(dataset)); #endif +#ifdef OPENMC_XDG_ENABLED + } else if (mesh_type == "xdg") { + model::meshes.push_back(make_unique(dataset)); +#endif #ifdef OPENMC_LIBMESH_ENABLED } else if (mesh_type == UnstructuredMesh::mesh_type && mesh_library == LibMesh::mesh_lib_type) { @@ -798,12 +804,12 @@ UnstructuredMesh::UnstructuredMesh(pugi::xml_node node) : Mesh(node) n_dimension_ = 3; // check the mesh type - if (check_for_node(node, "type")) { - auto temp = get_node_value(node, "type", true, true); - if (temp != mesh_type) { - fatal_error(fmt::format("Invalid mesh type: {}", temp)); - } - } + // if (check_for_node(node, "type")) { + // auto temp = get_node_value(node, "type", true, true); + // if (temp != mesh_type) { + // fatal_error(fmt::format("Invalid mesh type: {}", temp)); + // } + // } // check if a length unit multiplier was specified if (check_for_node(node, "length_multiplier")) { @@ -894,36 +900,6 @@ void UnstructuredMesh::determine_bounds() upper_right_ = {xmax, ymax, zmax}; } -Position UnstructuredMesh::sample_tet( - std::array coords, uint64_t* seed) const -{ - // Uniform distribution - double s = prn(seed); - double t = prn(seed); - double u = prn(seed); - - // From PyNE implementation of moab tet sampling C. Rocchini & P. Cignoni - // (2000) Generating Random Points in a Tetrahedron, Journal of Graphics - // Tools, 5:4, 9-12, DOI: 10.1080/10867651.2000.10487528 - if (s + t > 1) { - s = 1.0 - s; - t = 1.0 - t; - } - if (s + t + u > 1) { - if (t + u > 1) { - double old_t = t; - t = 1.0 - u; - u = 1.0 - s - old_t; - } else if (t + u <= 1) { - double old_s = s; - s = 1.0 - t - u; - u = old_s + t + u - 1; - } - } - return s * (coords[1] - coords[0]) + t * (coords[2] - coords[0]) + - u * (coords[3] - coords[0]) + coords[0]; -} - const std::string UnstructuredMesh::mesh_type = "unstructured"; std::string UnstructuredMesh::get_mesh_type() const @@ -991,7 +967,6 @@ void UnstructuredMesh::to_hdf5_inner(hid_t mesh_group) const connectivity.slice(i) = -1; } } - // warn users that some elements were skipped if (num_elem_skipped > 0) { warning(fmt::format("The connectivity of {} elements " @@ -3159,7 +3134,6 @@ std::string MOABMesh::library() const // Sample position within a tet for MOAB type tets Position MOABMesh::sample_element(int32_t bin, uint64_t* seed) const { - moab::EntityHandle tet_ent = get_ent_handle_from_bin(bin); // Get vertex coordinates for MOAB tet @@ -3177,12 +3151,8 @@ Position MOABMesh::sample_element(int32_t bin, uint64_t* seed) const fatal_error("Failed to get tet coords"); } - std::array tet_verts; - for (int i = 0; i < 4; i++) { - tet_verts[i] = {p[i][0], p[i][1], p[i][2]}; - } // Samples position within tet using Barycentric stuff - return this->sample_tet(tet_verts, seed); + return this->sample_tet({p, 4}, seed); } double MOABMesh::tet_volume(moab::EntityHandle tet) const @@ -3674,7 +3644,8 @@ Position LibMesh::sample_element(int32_t bin, uint64_t* seed) const tet_verts[i] = {node_ref(0), node_ref(1), node_ref(2)}; } // Samples position within tet using Barycentric coordinates - Position sampled_position = this->sample_tet(tet_verts, seed); + Position sampled_position = + this->sample_tet({tet_verts.begin(), tet_verts.end()}, seed); if (length_multiplier_ > 0.0) { return length_multiplier_ * sampled_position; } else { diff --git a/src/output.cpp b/src/output.cpp index c66c313dce5..af4c5d9ae8c 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -317,6 +317,7 @@ void print_build_info() std::string mpi(n); std::string phdf5(n); std::string dagmc(n); + std::string xdg(n); std::string libmesh(n); std::string png(n); std::string profiling(n); @@ -334,6 +335,9 @@ void print_build_info() #ifdef OPENMC_DAGMC_ENABLED dagmc = y; #endif +#ifdef OPENMC_XDG_ENABLED + xdg = y; +#endif #ifdef OPENMC_LIBMESH_ENABLED libmesh = y; #endif @@ -368,6 +372,7 @@ void print_build_info() fmt::print("Parallel HDF5 enabled: {}\n", phdf5); fmt::print("PNG support: {}\n", png); fmt::print("DAGMC support: {}\n", dagmc); + fmt::print("XDG support: {}\n", xdg); fmt::print("libMesh support: {}\n", libmesh); fmt::print("MCPL support: {}\n", mcpl); fmt::print("Coverage testing: {}\n", coverage); diff --git a/src/xdg.cpp b/src/xdg.cpp new file mode 100644 index 00000000000..0bc69ad1850 --- /dev/null +++ b/src/xdg.cpp @@ -0,0 +1,228 @@ +#include +#include +#include +#include + +#include "openmc/xdg.h" + +#include "openmc/constants.h" +#include "openmc/container_util.h" +#include "openmc/error.h" +#include "openmc/file_utils.h" +#include "openmc/geometry.h" +#include "openmc/geometry_aux.h" +#include "openmc/hdf5_interface.h" +#include "openmc/material.h" +#include "openmc/settings.h" +#include "openmc/string_utils.h" + +#include + +#ifdef OPENMC_XDG_ENABLED +#include "xdg/xdg.h" +#endif + +namespace openmc { + +#ifdef OPENMC_XDG_ENABLED +const bool XDG_ENABLED = true; +#else +const bool XDG_ENABLED = false; +#endif + +} // namespace openmc + +#ifdef OPENMC_XDG_ENABLED + +namespace openmc { + +//============================================================================== +// XDG Mesh implementation +//============================================================================== + +const std::string XDGMesh::mesh_lib_type = "xdg"; + +XDGMesh::XDGMesh(pugi::xml_node node) : UnstructuredMesh(node) +{ + std::string mesh_lib = get_node_value(node, "library", true, true); + if (mesh_lib == "moab") { + mesh_library_ = xdg::MeshLibrary::MOAB; + } else if (mesh_lib == "libmesh") { + mesh_library_ = xdg::MeshLibrary::LIBMESH; + } + initialize(); +} + +XDGMesh::XDGMesh(hid_t group) : UnstructuredMesh(group) +{ + std::string mesh_lib; + read_dataset(group, "library", mesh_lib); + if (mesh_lib == "moab") { + mesh_library_ = xdg::MeshLibrary::MOAB; + } else if (mesh_lib == "libmesh") { + mesh_library_ = xdg::MeshLibrary::LIBMESH; + } + initialize(); +} + +XDGMesh::XDGMesh(const std::string& filename, double length_multiplier) +{ + filename_ = filename; + set_length_multiplier(length_multiplier); + initialize(); +} + +XDGMesh::XDGMesh(std::shared_ptr external_xdg) +{ + xdg_ = external_xdg; + filename_ = "unknown (external file)"; + initialize(); +} + +void XDGMesh::initialize() +{ + if (xdg_) + return; + + // create XDGMesh instance + xdg_ = xdg::XDG::create(mesh_library_); + + // load XDGMesh file + if (!file_exists(filename_)) { + fatal_error(fmt::format("Mesh file \"{}\" does not exist", filename_)); + } + + xdg_->mesh_manager()->load_file(filename_); + xdg_->mesh_manager()->init(); + xdg_->mesh_manager()->parse_metadata(); +} + +void XDGMesh::prepare_for_point_location() +{ + xdg_->prepare_raytracer(); +} + +Position XDGMesh::sample_element(int32_t bin, uint64_t* seed) const +{ + // MeshIDs are 1-indexed, so we add 1 to the bin, which is 0-indexed + auto vertices = xdg_->mesh_manager()->element_vertices(bin_to_mesh_id(bin)); + return this->sample_tet(vertices, seed); +} + +void XDGMesh::bins_crossed(Position r0, Position r1, const Direction& u, + vector& bins, vector& lengths) const +{ + // TODO: Make more robust (including mesh entrance/re-entrance) + xdg::Position p0 {r0.x, r0.y, r0.z}; + xdg::Position p1 {r1.x, r1.y, r1.z}; + double length_rcp = 1 / (p1 - p0).length(); + auto track_segments = xdg_->segments(p0, p1); + // remove elements with lengths of zero + track_segments.erase( + std::remove_if(track_segments.begin(), track_segments.end(), + [](const std::pair& p) { return p.second == 0.0; }), + track_segments.end()); + for (const auto& track_segment : track_segments) { + bins.push_back(mesh_id_to_bin(track_segment.first)); + lengths.push_back(track_segment.second * length_rcp); + } +} + +int XDGMesh::get_bin(Position r) const +{ + xdg::Position p {r.x, r.y, r.z}; + return mesh_id_to_bin(xdg_->find_element(p)); +} + +int XDGMesh::n_bins() const +{ + return xdg_->mesh_manager()->num_volume_elements(); +} + +int XDGMesh::n_surface_bins() const +{ + return 4 * n_bins(); +} + +std::pair, vector> XDGMesh::plot( + Position plot_ll, Position plot_ur) const +{ + fatal_error("Plot of XDGMesh mesh not implemented"); + return {}; +} + +std::string XDGMesh::library() const +{ + return mesh_lib_type; +} + +std::string XDGMesh::mesh_library() const +{ + if (mesh_library_ == xdg::MeshLibrary::LIBMESH) { + return "libmesh"; + } else if (mesh_library_ == xdg::MeshLibrary::MOAB) { + return "moab"; + } +} + +void XDGMesh::write(const std::string& base_filename) const +{ + warning("XDGMesh mesh write from C++ not implemented"); +} + +Position XDGMesh::centroid(int bin) const +{ + auto element_vertices = + xdg_->mesh_manager()->element_vertices(bin_to_mesh_id(bin)); + + xdg::Vertex centroid {0.0, 0.0, 0.0}; + for (const auto& v : element_vertices) { + centroid += v; + } + + centroid /= double(element_vertices.size()); + + return {centroid[0], centroid[1], centroid[2]}; +} + +int XDGMesh::n_vertices() const +{ + return xdg_->mesh_manager()->num_vertices(); +} + +Position XDGMesh::vertex(int id) const +{ + xdg::MeshID mesh_id = xdg_->mesh_manager()->vertex_id(id); + auto v = xdg_->mesh_manager()->vertex_coordinates(mesh_id); + return {v[0], v[1], v[2]}; +} + +std::vector XDGMesh::connectivity(int id) const +{ + auto conn = xdg_->mesh_manager()->element_connectivity(bin_to_mesh_id(id)); + for (auto& c : conn) { + c = xdg_->mesh_manager()->vertex_index(c); + } + return conn; +} + +double XDGMesh::volume(int bin) const +{ + return xdg_->mesh_manager()->element_volume(bin_to_mesh_id(bin)); +} + +xdg::MeshID XDGMesh::bin_to_mesh_id(int bin) const +{ + return xdg_->mesh_manager()->element_id(bin); +} + +int32_t XDGMesh::mesh_id_to_bin(xdg::MeshID id) const +{ + if (id < 0) + return -1; + return xdg_->mesh_manager()->element_index(id); +} + +} // namespace openmc + +#endif // XDG diff --git a/tests/regression_tests/xdg/__init__.py b/tests/regression_tests/xdg/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/xdg/tallies/__init__.py b/tests/regression_tests/xdg/tallies/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/xdg/tallies/inputs_true0_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true0_libmesh.dat new file mode 100644 index 00000000000..baafa1ec5be --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true0_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true0_moab.dat b/tests/regression_tests/xdg/tallies/inputs_true0_moab.dat new file mode 100644 index 00000000000..87ac4b871de --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true0_moab.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true1_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true1_libmesh.dat new file mode 100644 index 00000000000..f47b11f8b84 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true1_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets.exo + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true2_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true2_libmesh.dat new file mode 100644 index 00000000000..996a6439cb8 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true2_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true2_moab.dat b/tests/regression_tests/xdg/tallies/inputs_true2_moab.dat new file mode 100644 index 00000000000..a739e2fd209 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true2_moab.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true3_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true3_libmesh.dat new file mode 100644 index 00000000000..ff90c4cb751 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true3_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.exo + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true4_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true4_libmesh.dat new file mode 100644 index 00000000000..9e0c8b7a4d8 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true4_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true4_moab.dat b/tests/regression_tests/xdg/tallies/inputs_true4_moab.dat new file mode 100644 index 00000000000..ae53681788f --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true4_moab.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true5_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true5_libmesh.dat new file mode 100644 index 00000000000..d0647600dba --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true5_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets.exo + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true6_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true6_libmesh.dat new file mode 100644 index 00000000000..e33a0a7c4a5 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true6_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true6_moab.dat b/tests/regression_tests/xdg/tallies/inputs_true6_moab.dat new file mode 100644 index 00000000000..8d5b3970f9d --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true6_moab.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true7_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true7_libmesh.dat new file mode 100644 index 00000000000..64983e4319d --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true7_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.exo + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/test.py b/tests/regression_tests/xdg/tallies/test.py new file mode 100644 index 00000000000..b8659148c56 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test.py @@ -0,0 +1,382 @@ +import glob +import os + +import numpy as np +import pytest + +import openmc +import openmc.lib +from openmc.xdg import XDGMesh + +from tests.testing_harness import PyAPITestHarness + + +class XDGMeshTallyTest(PyAPITestHarness): + + ELEM_PER_VOXEL = 12 + + def __init__( + self, + statepoint_name, + model, + inputs_true, + mesh_filename, + mesh_kind, + holes=None, + scale_factor=10.0, + ): + super().__init__(statepoint_name, model, inputs_true) + print(f"Running test with mesh file: {mesh_filename} and inputs file: {inputs_true}") + self.mesh_filename = mesh_filename + self.mesh_kind = mesh_kind + self.holes = holes + self.scale_bounding_cell(scale_factor) + + def scale_bounding_cell(self, scale_factor): + geometry = self._model.geometry + for surface in geometry.get_all_surfaces().values(): + if surface.boundary_type != 'vacuum': + continue + for coeff in surface._coefficients: + surface._coefficients[coeff] *= scale_factor + + def _compare_results(self): + with openmc.StatePoint(self._sp_name) as sp: + xdg_mesh = None + for mesh in sp.meshes.values(): + if isinstance(mesh, openmc.UnstructuredMesh) and mesh.library == "xdg": + xdg_mesh = mesh + break + + assert xdg_mesh is not None + assert os.path.basename(xdg_mesh.filename) == os.path.basename( + self.mesh_filename + ) + + if self.mesh_kind == "tet": + exp_vertex = (-10.0, -10.0, -10.0) + exp_centroid = (-8.75, -9.75, -9.25) + else: + exp_vertex = (-10.0, -10.0, 10.0) + exp_centroid = (-9.0, -9.0, 9.0) + + np.testing.assert_array_equal(xdg_mesh.vertices[0], exp_vertex) + np.testing.assert_array_equal(xdg_mesh.centroid(0), exp_centroid) + + reg_mesh_data = None + xdg_mesh_data = None + xdg_tally = None + for tally in sp.tallies.values(): + if tally.contains_filter(openmc.MeshFilter): + flt = tally.find_filter(openmc.MeshFilter) + if isinstance(flt.mesh, openmc.RegularMesh): + reg_mesh_data = self.get_mesh_tally_data(tally) + else: + xdg_tally = tally + xdg_mesh_data = self.get_mesh_tally_data(tally, structured=True) + + assert reg_mesh_data is not None + assert xdg_mesh_data is not None + + if self.holes: + reg_mesh_data = np.delete(reg_mesh_data, self.holes) + + decimals = 10 if xdg_tally.estimator == 'collision' else 8 + np.testing.assert_array_almost_equal( + np.sort(xdg_mesh_data), + np.sort(reg_mesh_data), + decimals + ) + + def get_mesh_tally_data(self, tally, structured=False): + data = tally.get_reshaped_data(value='mean') + if structured: + data = data.reshape((-1, self.ELEM_PER_VOXEL)) + else: + data.shape = (data.size, 1) + return np.sum(data, axis=1) + + def update_results(self): + """Update inputs_true.dat without storing results_true.dat.""" + try: + self._build_inputs() + inputs = self._get_inputs() + self._write_inputs(inputs) + self._overwrite_inputs() + self._run_openmc() + self._test_output_created() + finally: + self._cleanup() + + def _cleanup(self): + super()._cleanup() + output = glob.glob('tally*.vtk') + output += glob.glob('tally*.e') + for f in output: + if os.path.exists(f): + os.remove(f) + + +@pytest.fixture +def model(): + openmc.reset_auto_ids() + + model = openmc.Model() + + ### Materials ### + materials = openmc.Materials() + + fuel_mat = openmc.Material(name="fuel") + fuel_mat.add_nuclide("U235", 1.0) + fuel_mat.set_density('g/cc', 4.5) + materials.append(fuel_mat) + + zirc_mat = openmc.Material(name="zircaloy") + zirc_mat.add_element("Zr", 1.0) + zirc_mat.set_density("g/cc", 5.77) + materials.append(zirc_mat) + + water_mat = openmc.Material(name="water") + water_mat.add_nuclide("H1", 2.0) + water_mat.add_nuclide("O16", 1.0) + water_mat.set_density("atom/b-cm", 0.07416) + materials.append(water_mat) + + model.materials = materials + + ### Geometry ### + fuel_min_x = openmc.XPlane(-5.0, name="minimum x") + fuel_max_x = openmc.XPlane(5.0, name="maximum x") + + fuel_min_y = openmc.YPlane(-5.0, name="minimum y") + fuel_max_y = openmc.YPlane(5.0, name="maximum y") + + fuel_min_z = openmc.ZPlane(-5.0, name="minimum z") + fuel_max_z = openmc.ZPlane(5.0, name="maximum z") + + fuel_cell = openmc.Cell(name="fuel") + fuel_cell.region = +fuel_min_x & -fuel_max_x & \ + +fuel_min_y & -fuel_max_y & \ + +fuel_min_z & -fuel_max_z + fuel_cell.fill = fuel_mat + + clad_min_x = openmc.XPlane(-6.0, name="minimum x") + clad_max_x = openmc.XPlane(6.0, name="maximum x") + + clad_min_y = openmc.YPlane(-6.0, name="minimum y") + clad_max_y = openmc.YPlane(6.0, name="maximum y") + + clad_min_z = openmc.ZPlane(-6.0, name="minimum z") + clad_max_z = openmc.ZPlane(6.0, name="maximum z") + + clad_cell = openmc.Cell(name="clad") + clad_cell.region = (-fuel_min_x | +fuel_max_x | + -fuel_min_y | +fuel_max_y | + -fuel_min_z | +fuel_max_z) & \ + (+clad_min_x & -clad_max_x & + +clad_min_y & -clad_max_y & + +clad_min_z & -clad_max_z) + clad_cell.fill = zirc_mat + + # set bounding cell dimension to one + # this will be updated later according to the test case parameters + water_min_x = openmc.XPlane(x0=-1.0, + name="minimum x", + boundary_type='vacuum') + water_max_x = openmc.XPlane(x0=1.0, + name="maximum x", + boundary_type='vacuum') + + water_min_y = openmc.YPlane(y0=-1.0, + name="minimum y", + boundary_type='vacuum') + water_max_y = openmc.YPlane(y0=1.0, + name="maximum y", + boundary_type='vacuum') + + water_min_z = openmc.ZPlane(z0=-1.0, + name="minimum z", + boundary_type='vacuum') + water_max_z = openmc.ZPlane(z0=1.0, + name="maximum z", + boundary_type='vacuum') + + water_cell = openmc.Cell(name="water") + water_cell.region = (-clad_min_x | +clad_max_x | + -clad_min_y | +clad_max_y | + -clad_min_z | +clad_max_z) & \ + (+water_min_x & -water_max_x & + +water_min_y & -water_max_y & + +water_min_z & -water_max_z) + water_cell.fill = water_mat + + # create a containing universe + model.geometry = openmc.Geometry([fuel_cell, clad_cell, water_cell]) + + ### Reference Tally ### + + # create meshes and mesh filters + regular_mesh = openmc.RegularMesh() + regular_mesh.dimension = (10, 10, 10) + regular_mesh.lower_left = (-10.0, -10.0, -10.0) + regular_mesh.upper_right = (10.0, 10.0, 10.0) + + regular_mesh_filter = openmc.MeshFilter(mesh=regular_mesh) + regular_mesh_tally = openmc.Tally(name="regular mesh tally") + regular_mesh_tally.filters = [regular_mesh_filter] + regular_mesh_tally.scores = ['flux'] + + model.tallies = openmc.Tallies([regular_mesh_tally]) + + ### Settings ### + settings = openmc.Settings() + settings.run_mode = 'fixed source' + settings.particles = 1000 + settings.batches = 10 + + # source setup + r = openmc.stats.Uniform(a=0.0, b=0.0) + cos_theta = openmc.stats.Discrete(x=[1.0], p=[1.0]) + phi = openmc.stats.Discrete(x=[0.0], p=[1.0]) + + space = openmc.stats.SphericalIndependent(r, cos_theta, phi) + energy = openmc.stats.Discrete(x=[15.e+06], p=[1.0]) + source = openmc.IndependentSource(space=space, energy=energy) + settings.source = source + + model.settings = settings + + return model + + +MESH_CASES = ( + { + "mesh_filename": "test_mesh_tets.e", + "mesh_kind": "tet", + "holes": None, + "elem_per_voxel": 12, + "external_geom": False, + "libraries": ("moab", "libmesh"), + }, + { + "mesh_filename": "test_mesh_tets.exo", + "mesh_kind": "tet", + "holes": None, + "elem_per_voxel": 12, + "external_geom": False, + "libraries": ("libmesh",), + }, + { + "mesh_filename": "test_mesh_tets_w_holes.e", + "mesh_kind": "tet", + "holes": (333, 90, 77), + "elem_per_voxel": 12, + "external_geom": False, + "libraries": ("moab", "libmesh"), + }, + { + "mesh_filename": "test_mesh_tets_w_holes.exo", + "mesh_kind": "tet", + "holes": (333, 90, 77), + "elem_per_voxel": 12, + "external_geom": False, + "libraries": ("libmesh",), + }, + { + "mesh_filename": "test_mesh_tets.e", + "mesh_kind": "tet", + "holes": None, + "elem_per_voxel": 12, + "external_geom": True, + "libraries": ("moab", "libmesh"), + }, + { + "mesh_filename": "test_mesh_tets.exo", + "mesh_kind": "tet", + "holes": None, + "elem_per_voxel": 12, + "external_geom": True, + "libraries": ("libmesh",), + }, + { + "mesh_filename": "test_mesh_tets_w_holes.e", + "mesh_kind": "tet", + "holes": (333, 90, 77), + "elem_per_voxel": 12, + "external_geom": True, + "libraries": ("moab", "libmesh"), + }, + { + "mesh_filename": "test_mesh_tets_w_holes.exo", + "mesh_kind": "tet", + "holes": (333, 90, 77), + "elem_per_voxel": 12, + "external_geom": True, + "libraries": ("libmesh",), + }, + # { + # "mesh_filename": "test_mesh_hexes.e", + # "mesh_kind": "hex", + # "holes": None, + # "elem_per_voxel": 1, + # "libraries": ("libmesh",), + # }, + # { + # "mesh_filename": "test_mesh_hexes.exo", + # "mesh_kind": "hex", + # "holes": None, + # "elem_per_voxel": 1, + # "libraries": ("libmesh",), + # }, +) + +test_cases = [] +test_case_ids = [] +for i, mesh_case in enumerate(MESH_CASES): + for library in mesh_case["libraries"]: + test_cases.append({ + "inputs_true": f"inputs_true{i}_{library}.dat", + "library": library, + **{k: v for k, v in mesh_case.items() if k != "libraries"}, + }) + mesh_stem = os.path.splitext(mesh_case["mesh_filename"])[0] + geom_mode = "external-geom" if mesh_case["external_geom"] else "bounded-geom" + holes = "holes" if mesh_case["holes"] else "solid" + test_case_ids.append(f"{library}-{mesh_stem}-{holes}-{geom_mode}") + +@pytest.mark.parametrize("test_opts", test_cases, ids=test_case_ids) +def test_xdg_mesh_tallies(model, test_opts): + if not openmc.lib._xdg_enabled(): + pytest.skip("XDG is not enabled in this build.") + + if test_opts["library"] == "moab" and not openmc.lib._dagmc_enabled(): + pytest.skip("DAGMC (and MOAB) mesh not enabled in this build.") + + if test_opts["library"] == "libmesh" and not openmc.lib._libmesh_enabled(): + pytest.skip("LibMesh is not enabled in this build.") + + # reference mesh tally + regular_mesh_tally = model.tallies[0] + regular_mesh_tally.estimator = 'collision' + + # add analogous XDG mesh tally + xdg_mesh = XDGMesh(test_opts["mesh_filename"], test_opts["library"]) + xdg_filter = openmc.MeshFilter(mesh=xdg_mesh) + + xdg_tally = openmc.Tally(name="xdg mesh tally") + xdg_tally.filters = [xdg_filter] + xdg_tally.scores = ['flux'] + xdg_tally.estimator = 'collision' + model.tallies.append(xdg_tally) + + harness = XDGMeshTallyTest( + 'statepoint.10.h5', + model, + test_opts["inputs_true"], + test_opts["mesh_filename"], + test_opts["mesh_kind"], + test_opts["holes"], + scale_factor=15.0 if test_opts["external_geom"] else 10.0, + ) + harness.ELEM_PER_VOXEL = test_opts["elem_per_voxel"] + harness.main() diff --git a/tests/regression_tests/xdg/tallies/test_mesh_hexes.e b/tests/regression_tests/xdg/tallies/test_mesh_hexes.e new file mode 120000 index 00000000000..32a7bc68fcd --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_hexes.e @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_hexes.e \ No newline at end of file diff --git a/tests/regression_tests/xdg/tallies/test_mesh_hexes.exo b/tests/regression_tests/xdg/tallies/test_mesh_hexes.exo new file mode 120000 index 00000000000..723a379f7bc --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_hexes.exo @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_hexes.exo \ No newline at end of file diff --git a/tests/regression_tests/xdg/tallies/test_mesh_tets.e b/tests/regression_tests/xdg/tallies/test_mesh_tets.e new file mode 120000 index 00000000000..65723ab8170 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_tets.e @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_tets.e \ No newline at end of file diff --git a/tests/regression_tests/xdg/tallies/test_mesh_tets.exo b/tests/regression_tests/xdg/tallies/test_mesh_tets.exo new file mode 120000 index 00000000000..676f4d799d7 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_tets.exo @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_tets.exo \ No newline at end of file diff --git a/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.e b/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.e new file mode 120000 index 00000000000..2dae88efc80 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.e @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_tets_w_holes.e \ No newline at end of file diff --git a/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.exo b/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.exo new file mode 120000 index 00000000000..fbd15ce88aa --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.exo @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_tets_w_holes.exo \ No newline at end of file diff --git a/tools/ci/gha-build-moab.sh b/tools/ci/gha-build-moab.sh new file mode 100755 index 00000000000..e9ebd75a720 --- /dev/null +++ b/tools/ci/gha-build-moab.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -ex + +MOAB_BRANCH='5.5.1' +MOAB_REPO='https://bitbucket.org/fathomteam/moab/' +MOAB_INSTALL_DIR=$HOME/MOAB/ + +pushd $HOME + +if [[ -d $MOAB_INSTALL_DIR/lib ]]; then + popd + exit 0 +fi + +mkdir -p MOAB +cd MOAB +git clone -b $MOAB_BRANCH $MOAB_REPO moab +mkdir build +cd build +cmake ../moab \ + -DENABLE_HDF5=ON \ + -DENABLE_NETCDF=ON \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_INSTALL_PREFIX=$MOAB_INSTALL_DIR +make -j4 +make -j4 install +rm -rf $HOME/MOAB/moab $HOME/MOAB/build + +popd diff --git a/tools/ci/gha-install-dagmc.sh b/tools/ci/gha-install-dagmc.sh index 8d0648a5041..21133c6d3cf 100755 --- a/tools/ci/gha-install-dagmc.sh +++ b/tools/ci/gha-install-dagmc.sh @@ -2,34 +2,23 @@ #!/bin/bash set -ex -# MOAB Variables -MOAB_BRANCH='5.5.1' -MOAB_REPO='https://bitbucket.org/fathomteam/moab/' -MOAB_INSTALL_DIR=$HOME/MOAB/ - # DAGMC Variables DAGMC_BRANCH='develop' DAGMC_REPO='https://github.com/svalinn/dagmc' DAGMC_INSTALL_DIR=$HOME/DAGMC/ -CURRENT_DIR=$(pwd) - -# MOAB Install -cd $HOME -mkdir MOAB && cd MOAB -git clone -b $MOAB_BRANCH $MOAB_REPO -mkdir build && cd build -cmake ../moab -DENABLE_HDF5=ON -DENABLE_NETCDF=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=$MOAB_INSTALL_DIR -make -j && make -j install -rm -rf $HOME/MOAB/moab $HOME/MOAB/build - # DAGMC Install -cd $HOME -mkdir DAGMC && cd DAGMC -git clone -b $DAGMC_BRANCH $DAGMC_REPO -mkdir build && cd build -cmake ../dagmc -DBUILD_TALLY=ON -DCMAKE_INSTALL_PREFIX=$DAGMC_INSTALL_DIR -DBUILD_STATIC_LIBS=OFF -DMOAB_DIR=$MOAB_INSTALL_DIR -make -j install +pushd $HOME +mkdir -p DAGMC +cd DAGMC +git clone -b $DAGMC_BRANCH $DAGMC_REPO dagmc +mkdir build +cd build +cmake ../dagmc \ + -DBUILD_TALLY=ON \ + -DCMAKE_INSTALL_PREFIX=$DAGMC_INSTALL_DIR \ + -DBUILD_STATIC_LIBS=OFF \ + -DMOAB_DIR=$HOME/MOAB +make -j4 install rm -rf $HOME/DAGMC/dagmc $HOME/DAGMC/build - -cd $CURRENT_DIR +popd diff --git a/tools/ci/gha-install-xdg.sh b/tools/ci/gha-install-xdg.sh new file mode 100755 index 00000000000..f783fb0b324 --- /dev/null +++ b/tools/ci/gha-install-xdg.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -ex + +XDG_BRANCH='main' +XDG_REPO='https://github.com/xdg-org/xdg.git' +XDG_INSTALL_DIR=$HOME/XDG/ + +pushd $HOME + +mkdir -p XDG +cd XDG +git clone -b $XDG_BRANCH --recurse-submodules $XDG_REPO xdg +mkdir build +cd build + +cmake_args=( + ../xdg + -DCMAKE_INSTALL_PREFIX=$XDG_INSTALL_DIR + -DXDG_BUILD_TESTS=OFF + -DXDG_BUILD_TOOLS=OFF + -DXDG_ENABLE_MOAB=ON + -DMOAB_DIR=$HOME/MOAB +) + +if [[ $LIBMESH == 'y' || $XDG == 'y' ]]; then + cmake_args+=( + -DXDG_ENABLE_LIBMESH=ON + -DCMAKE_PREFIX_PATH=$HOME/LIBMESH + ) +else + cmake_args+=(-DXDG_ENABLE_LIBMESH=OFF) +fi + +if [[ $MPI == 'y' ]]; then + cmake_args+=(-DXDG_LINK_MPI=ON) +fi + +cmake "${cmake_args[@]}" +make -j4 +make -j4 install +rm -rf $HOME/XDG/xdg $HOME/XDG/build + +popd diff --git a/tools/ci/gha-install.py b/tools/ci/gha-install.py index 5488b95476e..bd538ed3902 100644 --- a/tools/ci/gha-install.py +++ b/tools/ci/gha-install.py @@ -3,52 +3,69 @@ import subprocess -def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False): - # Create build directory and change to it - shutil.rmtree('build', ignore_errors=True) - os.mkdir('build') - os.chdir('build') - - # Build in RelWithDebInfo mode by default with support for MCPL - cmake_cmd = ['cmake', '-DCMAKE_BUILD_TYPE=RelWithDebInfo', '-DOPENMC_USE_MCPL=on'] +def install(omp=False, mpi=False, phdf5=False, dagmc=False, xdg=False, + libmesh=False): + # List to store the CMake arguments + cmake_args = ['-DCMAKE_BUILD_TYPE=Debug', '-DOPENMC_USE_MCPL=on'] # Turn off OpenMP if specified if not omp: - cmake_cmd.append('-DOPENMC_USE_OPENMP=off') + cmake_args.append('-DOPENMC_USE_OPENMP=off') # Use MPI wrappers when building in parallel if mpi: - cmake_cmd.append('-DOPENMC_USE_MPI=on') + cmake_args.append('-DOPENMC_USE_MPI=on') # Tell CMake to prefer parallel HDF5 if specified if phdf5: if not mpi: - raise ValueError('Parallel HDF5 must be used in ' - 'conjunction with MPI.') - cmake_cmd.append('-DHDF5_PREFER_PARALLEL=ON') + raise ValueError('Parallel HDF5 must be used in conjunction with MPI.') + cmake_args.append('-DHDF5_PREFER_PARALLEL=ON') else: - cmake_cmd.append('-DHDF5_PREFER_PARALLEL=OFF') + cmake_args.append('-DHDF5_PREFER_PARALLEL=OFF') + + prefix_paths = [] if dagmc: - cmake_cmd.append('-DOPENMC_USE_DAGMC=ON') - cmake_cmd.append('-DOPENMC_USE_UWUW=ON') + cmake_args.append('-DOPENMC_USE_DAGMC=ON') + cmake_args.append('-DOPENMC_USE_UWUW=ON') dagmc_path = os.environ.get('HOME') + '/DAGMC' - cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + dagmc_path) + prefix_paths.append(dagmc_path) - if libmesh: - cmake_cmd.append('-DOPENMC_USE_LIBMESH=ON') + if xdg: + cmake_args.append('-DOPENMC_USE_XDG=ON') + xdg_path = os.environ.get('HOME') + '/XDG' + prefix_paths.append(xdg_path) + + if libmesh or xdg: + cmake_args.append('-DOPENMC_USE_LIBMESH=ON') libmesh_path = os.environ.get('HOME') + '/LIBMESH' - cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + libmesh_path) + prefix_paths.append(libmesh_path) + + if prefix_paths: + cmake_args.append('-DCMAKE_PREFIX_PATH=' + ';'.join(prefix_paths)) # Build in coverage mode for coverage testing - cmake_cmd.append('-DOPENMC_ENABLE_COVERAGE=on') + cmake_args.append('-DOPENMC_ENABLE_COVERAGE=on') - # Enable strict FP for cross-platform reproducibility in CI - cmake_cmd.append('-DOPENMC_ENABLE_STRICT_FP=on') + # Set environment variable for SKBUILD + os.environ['SKBUILD_CMAKE_ARGS'] = ';'.join(cmake_args) - # Build and install - cmake_cmd.append('..') + # Run pip to build and install + pip_suffix = '--config-settings=cmake.args="' + ';'.join(cmake_args) + '"' + subprocess.check_call(['pip', '-v', 'install', '.[test,vtk,ci]', pip_suffix]) + + # Using standard CMake method + # Create build directory and change to it + shutil.rmtree('build', ignore_errors=True) + os.mkdir('build') + os.chdir('build') + + # Add CMake arguments for standard method + cmake_cmd = ['cmake', '..'] + cmake_args print(' '.join(cmake_cmd)) + + # Run CMake and build subprocess.check_call(cmake_cmd) subprocess.check_call(['make', '-j4']) subprocess.check_call(['sudo', 'make', 'install']) @@ -59,10 +76,11 @@ def main(): mpi = (os.environ.get('MPI') == 'y') phdf5 = (os.environ.get('PHDF5') == 'y') dagmc = (os.environ.get('DAGMC') == 'y') + xdg = (os.environ.get('XDG') == 'y') libmesh = (os.environ.get('LIBMESH') == 'y') # Build and install - install(omp, mpi, phdf5, dagmc, libmesh) + install(omp, mpi, phdf5, dagmc, xdg, libmesh) if __name__ == '__main__': main() diff --git a/tools/ci/gha-install.sh b/tools/ci/gha-install.sh index 4cf62afbb71..602e997702c 100755 --- a/tools/ci/gha-install.sh +++ b/tools/ci/gha-install.sh @@ -9,20 +9,30 @@ pip install --upgrade numpy # Install NJOY 2016 ./tools/ci/gha-install-njoy.sh -# Install DAGMC if needed -if [[ $DAGMC = 'y' ]]; then - ./tools/ci/gha-install-dagmc.sh +# Build MOAB if needed +if [[ $DAGMC = 'y' || $XDG = 'y' ]]; then + ./tools/ci/gha-build-moab.sh fi # Install NCrystal and verify installation pip install 'ncrystal>=4.1.0' nctool --test +# Install DAGMC if needed +if [[ $DAGMC = 'y' ]]; then + ./tools/ci/gha-install-dagmc.sh +fi + # Install libMesh if needed -if [[ $LIBMESH = 'y' ]]; then +if [[ $LIBMESH = 'y' || $XDG = 'y' ]]; then ./tools/ci/gha-install-libmesh.sh fi +# Install XDG if needed +if [[ $XDG = 'y' ]]; then + ./tools/ci/gha-install-xdg.sh +fi + # Install MCPL pip install mcpl @@ -39,8 +49,6 @@ if [[ $MPI == 'y' ]]; then pip install --no-build-isolation --no-binary=h5py h5py fi -# Build and install OpenMC executable +# Build and install OpenMC python tools/ci/gha-install.py -# Install Python API in editable mode -pip install -e .[test,vtk,ci] diff --git a/wheeltest.Dockerfile b/wheeltest.Dockerfile new file mode 100644 index 00000000000..cc7d77f4e76 --- /dev/null +++ b/wheeltest.Dockerfile @@ -0,0 +1,34 @@ + + + +FROM ubuntu:24.04 + +# ARG openmc_version=0.15.1.dev0 +ARG openmc_version=0.15.3 +ARG python_version + +ENV python_version_no_dot=${python_version//./} + +RUN apt update -y && apt upgrade -y && \ + apt install -y software-properties-common && \ + add-apt-repository ppa:deadsnakes/ppa && \ + apt update -y && \ + apt install -y python${python_version} python${python_version}-venv python3-pip python${python_version}-dev + +RUN apt install libhdf5-dev -y + +RUN python${python_version} -m venv openmc_venv +ENV PATH=/openmc_venv/bin:$PATH +COPY wheelhouse/openmc-${openmc_version}-cp${python_version_no_dot}-cp${python_version_no_dot}-manylinux_2_28_x86_64.whl . +RUN python${python_version} -m pip install openmc-${openmc_version}-cp${python_version_no_dot}-cp${python_version_no_dot}-manylinux_2_28_x86_64.whl +RUN python${python_version} -c "import openmc.lib" +COPY minimal_test_umesh.py . +COPY minimal_test_xdg.py . +COPY small_dagmc_file.h5m . +COPY cross_sections.xml . +COPY Li.h5 . +COPY Li7.h5 . +COPY small_um.vtk . +RUN apt install libxrender1 -y +RUN python${python_version} minimal_test_umesh.py +RUN python${python_version} minimal_test_xdg.py \ No newline at end of file