diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38a49b60219..0cbb31def30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -177,8 +177,75 @@ jobs: cpp-coveralls -i src -i include -e src/external --exclude-pattern "/usr/*" --dump cpp_cov.json coveralls --merge=cpp_cov.json --service=github + win: + runs-on: windows-latest + strategy: + matrix: + python-version: ["3.11"] + omp: [n, y] + + include: + - python-version: "3.11" + omp: n + - python-version: "3.11" + omp: y + name: "Windows Python ${{ matrix.python-version }} (omp=${{ matrix.omp }})" + + env: + OMP: ${{ matrix.omp }} + EVENT: ${{ matrix.event }} + NPY_DISABLE_CPU_FEATURES: "AVX512F AVX512_SKX" + OPENBLAS_NUM_THREADS: 1 + # libfabric complains about fork() as a result of using Python multiprocessing. + # We can work around it with RDMAV_FORK_SAFE=1 in libfabric < 1.13 and with + # FI_EFA_FORK_SAFE=1 in more recent versions. + RDMAV_FORK_SAFE: 1 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Environment Variables + shell: pwsh + run: | + [Environment]::SetEnvironmentVariable("OPENMC_CROSS_SECTIONS", "$Env:GITHUB_WORKSPACE\nndc_hdf5\cross_sections.xml", 'Machine') + [Environment]::SetEnvironmentVariable("OPENMC_ENDF_DATA", "$Env:GITHUB_WORKSPACE\endf-b-vii.1", 'Machine') + + - name: Cache XS + uses: actions/cache@v4 + with: + path: | + ${{github.workspace}}\nndc_hdf5 + ${{github.workspace}}\endf-b-vii.1 + key: ${{ runner.os }}-build-xs-cache + + - name: Install + shell: pwsh + run: | + ${{github.workspace}}\tools\ci\gha-install.ps1 + + - name: Before + shell: pwsh + run: ${{github.workspace}}\tools\ci\download-xs.ps1 + + - name: Test + shell: pwsh + run: | + ctest --output-on-failure -C Release ${{ github.workspace }}\build\ + ${{github.workspace}}\tools\ci\gha-script.ps1 + + - name: Setup tmate debug session + continue-on-error: true + if: ${{ contains(env.COMMIT_MESSAGE, '[gha-debug]') }} + uses: mxschmitt/action-tmate@v3 + timeout-minutes: 10 + finish: - needs: main + needs: [win, main] runs-on: ubuntu-latest steps: - name: Coveralls Finished diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dff35418f0..00e3dea83ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,11 @@ endif() if(OPENMC_USE_OPENMP) find_package(OpenMP REQUIRED) + + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + # Special flag to get OpenMP to work on Windows with MSVC + list(APPEND cxxflags /openmp:llvm) + endif() endif() #=============================================================================== @@ -167,6 +172,12 @@ if(NOT DEFINED HDF5_PREFER_PARALLEL) endif() endif() +if (MSVC) + # To avoid problems with loading DLLs on Windows in the Python API, + # we should try to link libhdf5 statically. + set(HDF5_USE_STATIC_LIBRARIES TRUE) +endif() + find_package(HDF5 REQUIRED COMPONENTS C HL) # Remove HDF5 transitive dependencies that are system libraries @@ -211,24 +222,22 @@ endif() # Skip for Visual Studio which has its own configurations through GUI if(NOT MSVC) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - -if(OPENMC_ENABLE_PROFILE) - list(APPEND cxxflags -g -fno-omit-frame-pointer) -endif() + if(OPENMC_ENABLE_PROFILE) + list(APPEND cxxflags -g -fno-omit-frame-pointer) + endif() -if(OPENMC_ENABLE_COVERAGE) - list(APPEND cxxflags --coverage) - list(APPEND ldflags --coverage) + if(OPENMC_ENABLE_COVERAGE) + list(APPEND cxxflags --coverage) + list(APPEND ldflags --coverage) + endif() endif() # Show flags being used message(STATUS "OpenMC C++ flags: ${cxxflags}") message(STATUS "OpenMC Linker flags: ${ldflags}") -endif() - #=============================================================================== # Update git submodules as needed #=============================================================================== @@ -446,20 +455,31 @@ list(APPEND libopenmc_SOURCES src/external/quartic_solver.cpp src/external/Faddeeva.cc) -# For Visual Studio compilers +#=============================================================================== +# openmc library +#=============================================================================== +add_library(libopenmc SHARED ${libopenmc_SOURCES}) +add_library(OpenMC::libopenmc ALIAS libopenmc) + +# For compilers on Windows if(MSVC) - # Use static library (otherwise explicit symbol portings are needed) - add_library(libopenmc STATIC ${libopenmc_SOURCES}) + # This ensures that all function/class method symbols are exported, without + # needing to add the export macros in the source. + set_target_properties(libopenmc PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) + + # This compile definition is needed to make sure that the correct macros + # are used for exporting symbols in the dll. While the + # WINDOWS_EXPORT_ALL_SYMBOLS exports all function/class symbols, it + # unfortunately does not work on global variables, which still need the macro. + target_compile_definitions(libopenmc PRIVATE -DOPENMC_DLL_EXPORTS) # To use the shared HDF5 libraries on Windows, the H5_BUILT_AS_DYNAMIC_LIB # compile definition must be specified. - target_compile_definitions(libopenmc PRIVATE -DH5_BUILT_AS_DYNAMIC_LIB) -else() - add_library(libopenmc SHARED ${libopenmc_SOURCES}) + if (NOT HDF5_USE_STATIC_LIBRARIES) + target_compile_definitions(libopenmc PRIVATE -DH5_BUILT_AS_DYNAMIC_LIB) + endif() endif() -add_library(OpenMC::libopenmc ALIAS libopenmc) - # Avoid vs error lnk1149 :output filename matches input filename if(NOT MSVC) set_target_properties(libopenmc PROPERTIES OUTPUT_NAME openmc) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 54257d09385..b462ea6e7b2 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -284,8 +284,17 @@ extern int OPENMC_E_DATA; extern int OPENMC_E_PHYSICS; extern int OPENMC_E_WARNING; +// Macro to ensure global variables are exported in Windows DLLs +#if defined(_WIN32) && defined(OPENMC_DLL_EXPORTS) +#define OPENMC_API __declspec(dllexport) +#elif defined(_WIN32) +#define OPENMC_API __declspec(dllimport) +#else +#define OPENMC_API +#endif + // Global variables -extern char openmc_err_msg[256]; +extern char OPENMC_API openmc_err_msg[256]; #ifdef __cplusplus } diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 252194528c7..afc7ac28fb9 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -365,6 +365,19 @@ enum class GeometryType { CSG, DAG }; // representations. This value represents no surface. constexpr int32_t SURFACE_NONE {0}; +//============================================================================== +// Shared Libraries on Windows need the functions/classes/variables to be +// exposed to be declared with this special keyword "__declspec(dllexport)". +// We use a macro for this, so that it is only exported on Windows. + +#if defined(_WIN32) && defined(OPENMC_DLL_EXPORTS) +#define OPENMC_API __declspec(dllexport) +#elif defined(_WIN32) +#define OPENMC_API __declspec(dllimport) +#else +#define OPENMC_API +#endif + } // namespace openmc #endif // OPENMC_CONSTANTS_H diff --git a/include/openmc/dagmc.h b/include/openmc/dagmc.h index 82ef1b644f3..bd91090eb28 100644 --- a/include/openmc/dagmc.h +++ b/include/openmc/dagmc.h @@ -1,9 +1,11 @@ #ifndef OPENMC_DAGMC_H #define OPENMC_DAGMC_H +#include "openmc/constants.h" // Needed for OPENMC_API + namespace openmc { -extern "C" const bool DAGMC_ENABLED; -extern "C" const bool UWUW_ENABLED; +extern "C" const bool OPENMC_API DAGMC_ENABLED; +extern "C" const bool OPENMC_API UWUW_ENABLED; } // namespace openmc // always include the XML interface header diff --git a/include/openmc/event.h b/include/openmc/event.h index 2d215a10e46..6004d0ffbcd 100644 --- a/include/openmc/event.h +++ b/include/openmc/event.h @@ -7,6 +7,8 @@ #include "openmc/particle.h" #include "openmc/shared_array.h" +#include + namespace openmc { //============================================================================== diff --git a/include/openmc/geometry.h b/include/openmc/geometry.h index 107cc7d1f3e..2e31f7c49e1 100644 --- a/include/openmc/geometry.h +++ b/include/openmc/geometry.h @@ -10,7 +10,7 @@ namespace openmc { -class BoundaryInfo; +struct BoundaryInfo; class GeometryState; //============================================================================== diff --git a/include/openmc/mcpl_interface.h b/include/openmc/mcpl_interface.h index e5c182280c8..d67abcb02d8 100644 --- a/include/openmc/mcpl_interface.h +++ b/include/openmc/mcpl_interface.h @@ -1,6 +1,7 @@ #ifndef OPENMC_MCPL_INTERFACE_H #define OPENMC_MCPL_INTERFACE_H +#include "openmc/constants.h" #include "openmc/particle_data.h" #include "openmc/span.h" #include "openmc/vector.h" @@ -13,7 +14,7 @@ namespace openmc { // Constants //============================================================================== -extern "C" const bool MCPL_ENABLED; +extern "C" const bool OPENMC_API MCPL_ENABLED; //============================================================================== // Functions diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 5a727c2b6f1..65e32b33eeb 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -11,6 +11,7 @@ #include "xtensor/xtensor.hpp" #include "openmc/bounding_box.h" +#include "openmc/constants.h" // for OPENMC_API #include "openmc/error.h" #include "openmc/memory.h" // for unique_ptr #include "openmc/particle.h" @@ -50,7 +51,7 @@ enum class ElementType { UNSUPPORTED = -1, LINEAR_TET, LINEAR_HEX }; // Global variables //============================================================================== -extern "C" const bool LIBMESH_ENABLED; +extern "C" const bool OPENMC_API LIBMESH_ENABLED; class Mesh; @@ -578,7 +579,7 @@ class CylindricalMesh : public PeriodicStructuredMesh { inline int sanitize_angular_index(int idx, bool full, int N) const { - if ((idx > 0) and (idx <= N)) { + if ((idx > 0) && (idx <= N)) { return idx; } else if (full) { return (idx + N - 1) % N + 1; @@ -640,7 +641,7 @@ class SphericalMesh : public PeriodicStructuredMesh { inline int sanitize_angular_index(int idx, bool full, int N) const { - if ((idx > 0) and (idx <= N)) { + if ((idx > 0) && (idx <= N)) { return idx; } else if (full) { return (idx + N - 1) % N + 1; diff --git a/include/openmc/message_passing.h b/include/openmc/message_passing.h index a1641a9069e..23b79b4f676 100644 --- a/include/openmc/message_passing.h +++ b/include/openmc/message_passing.h @@ -7,6 +7,7 @@ #include #endif +#include "openmc/constants.h" #include "openmc/vector.h" namespace openmc { @@ -14,7 +15,7 @@ namespace mpi { extern int rank; extern int n_procs; -extern bool master; +extern bool OPENMC_API master; #ifdef OPENMC_MPI extern MPI_Datatype source_site; diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 9017b2d080b..039f3e264a3 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -136,9 +136,10 @@ extern ResScatMethod res_scat_method; //!< resonance upscattering method extern double res_scat_energy_min; //!< Min energy in [eV] for res. upscattering extern double res_scat_energy_max; //!< Max energy in [eV] for res. upscattering extern vector - res_scat_nuclides; //!< Nuclides using res. upscattering treatment -extern RunMode run_mode; //!< Run mode (eigenvalue, fixed src, etc.) -extern SolverType solver_type; //!< Solver Type (Monte Carlo or Random Ray) + res_scat_nuclides; //!< Nuclides using res. upscattering treatment +extern RunMode OPENMC_API run_mode; //!< Run mode (eigenvalue, fixed src, etc.) +extern SolverType OPENMC_API + solver_type; //!< Solver Type (Monte Carlo or Random Ray) extern std::unordered_set sourcepoint_batch; //!< Batches when source should be written extern std::unordered_set diff --git a/include/openmc/shared_array.h b/include/openmc/shared_array.h index 7e9ef28c580..fc9a975c781 100644 --- a/include/openmc/shared_array.h +++ b/include/openmc/shared_array.h @@ -72,13 +72,23 @@ class SharedArray { { // Atomically capture the index we want to write to int64_t idx; +#if _WIN32 && !__INTEL_COMPILER +#pragma omp atomic capture + idx = size_++; +#else #pragma omp atomic capture seq_cst idx = size_++; +#endif // Check that we haven't written off the end of the array if (idx >= capacity_) { +#if _WIN32 && !__INTEL_COMPILER +#pragma omp atomic write + size_ = capacity_; +#else #pragma omp atomic write seq_cst size_ = capacity_; +#endif return -1; } diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h index ee635b18327..019e6f8a375 100644 --- a/include/openmc/tallies/filter.h +++ b/include/openmc/tallies/filter.h @@ -150,7 +150,7 @@ class Filter { namespace model { extern "C" int32_t n_filters; -extern std::unordered_map filter_map; +extern std::unordered_map OPENMC_API filter_map; extern vector> tally_filters; } // namespace model diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index 27ecaa4dd8b..705980ec67b 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -10,10 +10,17 @@ from openmc.mpi import comm +import sys + # Configurable switch that enables / disables the use of # multiprocessing routines during depletion USE_MULTIPROCESSING = True +# Not sure why, but using multiprocessing on Windows leads to many transport +# simulations being run over eachother and leads to catastrophe. +if sys.platform == 'win32': + USE_MULTIPROCESSING = False + # Allow user to override the number of worker processes to use for depletion # calculations NUM_PROCESSES = None diff --git a/openmc/lib/__init__.py b/openmc/lib/__init__.py index 15642b42be3..20cf8691c50 100644 --- a/openmc/lib/__init__.py +++ b/openmc/lib/__init__.py @@ -19,12 +19,20 @@ # Determine shared-library suffix -if sys.platform == 'darwin': +if sys.platform == 'win32': + _suffix = 'dll' +elif sys.platform == 'darwin': _suffix = 'dylib' else: _suffix = 'so' if os.environ.get('READTHEDOCS', None) != 'True': + # To load the DLL on Windows without error, we need to add the path of + # the DLL. We get the directory from the path of this file. + if sys.platform == 'win32': + dll_path = os.path.dirname(os.path.abspath(__file__)) + os.add_dll_directory(dll_path) + # Open shared library _filename = importlib.resources.files(__name__) / f'libopenmc.{_suffix}' _dll = CDLL(str(_filename)) # TODO: Remove str() when Python 3.12+ diff --git a/openmc/model/model.py b/openmc/model/model.py index c19c8ac9f15..7446a62dfbe 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -1027,7 +1027,7 @@ def plot( if outline: # Combine R, G, B values into a single int - rgb = (img * 256).astype(int) + rgb = img.astype(int) * 256 image_value = (rgb[..., 0] << 16) + \ (rgb[..., 1] << 8) + (rgb[..., 2]) diff --git a/pyproject.toml b/pyproject.toml index 6e8ed798e78..9ecceed457a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,6 @@ exclude = ['tests*'] [tool.setuptools.package-data] "openmc.data.effective_dose" = ["**/*.txt"] "openmc.data" = ["*.txt", "*.DAT", "*.json", "*.h5"] -"openmc.lib" = ["libopenmc.dylib", "libopenmc.so"] +"openmc.lib" = ["libopenmc.dylib", "libopenmc.so", "libopenmc.dll"] [tool.setuptools_scm] diff --git a/src/cell.cpp b/src/cell.cpp index 4b26992299e..3640a4a501c 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -482,7 +482,9 @@ Region::Region(std::string region_spec, int32_t cell_id) auto it = std::find(expression_.begin(), expression_.end(), OP_COMPLEMENT); while (it != expression_.end()) { // Erase complement - expression_.erase(it); + it = expression_.erase(it); + if (it == expression_.end()) + break; // Shouldn't happen, but to be safe // Define stop given left parenthesis or not auto stop = it; @@ -536,7 +538,7 @@ Region::Region(std::string region_spec, int32_t cell_id) if (simple_) { for (auto it = expression_.begin(); it != expression_.end(); it++) { if (*it == OP_INTERSECTION || *it > OP_COMPLEMENT) { - expression_.erase(it); + it = expression_.erase(it); it--; } } diff --git a/src/cross_sections.cpp b/src/cross_sections.cpp index b1bfde03d13..a7350f45029 100644 --- a/src/cross_sections.cpp +++ b/src/cross_sections.cpp @@ -71,16 +71,14 @@ Library::Library(pugi::xml_node node, const std::string& directory) if (!check_for_node(node, "path")) { fatal_error("Missing library path"); } - std::string path = get_node_value(node, "path"); - - if (starts_with(path, "/")) { - path_ = path; - } else if (ends_with(directory, "/")) { - path_ = directory + path; - } else if (!directory.empty()) { - path_ = directory + "/" + path; + std::filesystem::path path(get_node_value(node, "path")); + std::filesystem::path dir(directory); + if (path.is_absolute()) { + path_ = path.string(); + } else if (std::filesystem::is_directory(dir)) { + path_ = (dir / path).string(); } else { - path_ = path; + path_ = path.string(); } if (!file_exists(path_)) { @@ -144,11 +142,11 @@ void read_cross_sections_xml(pugi::xml_node root) } else { settings::path_cross_sections = get_node_value(root, "cross_sections"); - // If no '/' found, the file is probably in the input directory - auto pos = settings::path_cross_sections.rfind("/"); - if (pos == std::string::npos && !settings::path_input.empty()) { - settings::path_cross_sections = - settings::path_input + "/" + settings::path_cross_sections; + // If the path is relative, it is probably in the input directory + std::filesystem::path p(settings::path_cross_sections); + if (p.is_relative() && !settings::path_input.empty()) { + std::filesystem::path dir(settings::path_input); + settings::path_cross_sections = (dir / p).string(); } } diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 13436088652..6d6274a0765 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -27,9 +27,9 @@ namespace openmc { #ifdef DAGMC -const bool DAGMC_ENABLED = true; +const bool OPENMC_API DAGMC_ENABLED = true; #else -const bool DAGMC_ENABLED = false; +const bool OPENMC_API DAGMC_ENABLED = false; #endif #ifdef OPENMC_UWUW diff --git a/src/error.cpp b/src/error.cpp index 566950a973c..467bc67c184 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -30,7 +30,7 @@ int OPENMC_E_PHYSICS {-10}; int OPENMC_E_WARNING {1}; // Error message -char openmc_err_msg[256]; +char OPENMC_API openmc_err_msg[256]; //============================================================================== // Functions diff --git a/src/external/quartic_solver.cpp b/src/external/quartic_solver.cpp index 915020ffaa3..34772463130 100644 --- a/src/external/quartic_solver.cpp +++ b/src/external/quartic_solver.cpp @@ -7,6 +7,9 @@ namespace oqs { +// TODO: replace with when we go for C++20 +constexpr double PI {3.141592653589793238462643383279502884L}; + // pow(DBL_MAX,1.0/3.0)/1.618034; constexpr double CUBIC_RESCAL_FACT = 3.488062113727083e+102; // pow(DBL_MAX,1.0/4.0)/1.618034; @@ -36,10 +39,10 @@ double solve_cubic_analytic_depressed_handle_inf(double b, double c) if (KK < 0.0) { double sqrtQ = std::sqrt(Q); double theta = std::acos((R / std::abs(Q)) / sqrtQ); - if (2.0 * theta < M_PI) + if (2.0 * theta < PI) return -2.0 * sqrtQ * std::cos(theta / 3.0); else - return -2.0 * sqrtQ * std::cos((theta + 2.0 * M_PI) / 3.0); + return -2.0 * sqrtQ * std::cos((theta + 2.0 * PI) / 3.0); } else { double A; if (std::abs(Q) < std::abs(R)) @@ -68,10 +71,10 @@ double solve_cubic_analytic_depressed(double b, double c) if (R2 < Q3) { double theta = std::acos(R / std::sqrt(Q3)); double sqrtQ = -2.0 * std::sqrt(Q); - if (2.0 * theta < M_PI) + if (2.0 * theta < PI) return sqrtQ * std::cos(theta / 3.0); else - return sqrtQ * std::cos((theta + 2.0 * M_PI) / 3.0); + return sqrtQ * std::cos((theta + 2.0 * PI) / 3.0); } else { double A = -std::copysign(1.0, R) * std::pow(std::abs(R) + std::sqrt(R2 - Q3), 1.0 / 3.0); diff --git a/src/mcpl_interface.cpp b/src/mcpl_interface.cpp index 83ef6332097..d4b5608d0ad 100644 --- a/src/mcpl_interface.cpp +++ b/src/mcpl_interface.cpp @@ -22,9 +22,9 @@ namespace openmc { //============================================================================== #ifdef OPENMC_MCPL -const bool MCPL_ENABLED = true; +const bool OPENMC_API MCPL_ENABLED = true; #else -const bool MCPL_ENABLED = false; +const bool OPENMC_API MCPL_ENABLED = false; #endif //============================================================================== diff --git a/src/mesh.cpp b/src/mesh.cpp index 8280177ac13..1c4b7f04b92 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -64,9 +64,9 @@ namespace openmc { //============================================================================== #ifdef LIBMESH -const bool LIBMESH_ENABLED = true; +const bool OPENMC_API LIBMESH_ENABLED = true; #else -const bool LIBMESH_ENABLED = false; +const bool OPENMC_API LIBMESH_ENABLED = false; #endif // Value used to indicate an empty slot in the hash table. We use -2 because @@ -1492,7 +1492,7 @@ StructuredMesh::MeshIndex CylindricalMesh::get_indices( } else { mapped_r[1] = std::atan2(r.y, r.x); if (mapped_r[1] < 0) - mapped_r[1] += 2 * M_PI; + mapped_r[1] += 2 * PI; } MeshIndex idx = StructuredMesh::get_indices(mapped_r, in_mesh); @@ -1771,7 +1771,7 @@ StructuredMesh::MeshIndex SphericalMesh::get_indices( mapped_r[1] = std::acos(r.z / mapped_r.x); mapped_r[2] = std::atan2(r.y, r.x); if (mapped_r[2] < 0) - mapped_r[2] += 2 * M_PI; + mapped_r[2] += 2 * PI; } MeshIndex idx = StructuredMesh::get_indices(mapped_r, in_mesh); diff --git a/src/message_passing.cpp b/src/message_passing.cpp index 374c1aa7257..fa246a5d7d2 100644 --- a/src/message_passing.cpp +++ b/src/message_passing.cpp @@ -5,7 +5,7 @@ namespace mpi { int rank {0}; int n_procs {1}; -bool master {true}; +bool OPENMC_API master {true}; #ifdef OPENMC_MPI MPI_Comm intracomm {MPI_COMM_NULL}; diff --git a/src/plot.cpp b/src/plot.cpp index dbc25b21e4d..514a0e4b5e7 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -1,7 +1,6 @@ #include "openmc/plot.h" #include -#define _USE_MATH_DEFINES // to make M_PI declared in Intel and MSVC compilers #include #include #include @@ -1180,7 +1179,7 @@ std::pair RayTracePlot::get_pixel_ray( int horiz, int vert) const { // Compute field of view in radians - constexpr double DEGREE_TO_RADIAN = M_PI / 180.0; + constexpr double DEGREE_TO_RADIAN = PI / 180.0; double horiz_fov_radians = horizontal_field_of_view_ * DEGREE_TO_RADIAN; double p0 = static_cast(pixels_[0]); double p1 = static_cast(pixels_[1]); @@ -1535,9 +1534,12 @@ void SolidRayTracePlot::create_output() const size_t height = pixels_[1]; ImageData data({width, height}, not_found_); + const auto pixels_0 = pixels_[0]; + const auto pixels_1 = pixels_[1]; + #pragma omp parallel for schedule(dynamic) collapse(2) - for (int horiz = 0; horiz < pixels_[0]; ++horiz) { - for (int vert = 0; vert < pixels_[1]; ++vert) { + for (int horiz = 0; horiz < pixels_0; ++horiz) { + for (int vert = 0; vert < pixels_1; ++vert) { // RayTracePlot implements camera ray generation std::pair ru = get_pixel_ray(horiz, vert); PhongRay ray(ru.first, ru.second, *this); diff --git a/src/random_ray/flat_source_domain.cpp b/src/random_ray/flat_source_domain.cpp index cd073f35077..d45dd24a3ce 100644 --- a/src/random_ray/flat_source_domain.cpp +++ b/src/random_ray/flat_source_domain.cpp @@ -1012,13 +1012,15 @@ void FlatSourceDomain::apply_external_source_to_cell_and_children( void FlatSourceDomain::count_external_source_regions() { - n_external_source_regions_ = 0; -#pragma omp parallel for reduction(+ : n_external_source_regions_) + // Must use temporary accumulator variable for OpenMP on Windows + int64_t tmp_n_external_source_regions {0}; +#pragma omp parallel for reduction(+ : tmp_n_external_source_regions) for (int64_t sr = 0; sr < n_source_regions(); sr++) { if (source_regions_.external_source_present(sr)) { - n_external_source_regions_++; + tmp_n_external_source_regions++; } } + n_external_source_regions_ = tmp_n_external_source_regions; } void FlatSourceDomain::convert_external_sources() diff --git a/src/random_ray/random_ray_simulation.cpp b/src/random_ray/random_ray_simulation.cpp index 40a08c3af7a..58787909f67 100644 --- a/src/random_ray/random_ray_simulation.cpp +++ b/src/random_ray/random_ray_simulation.cpp @@ -465,14 +465,17 @@ void RandomRaySimulation::simulate() // Start timer for transport simulation::time_transport.start(); -// Transport sweep over all random rays for the iteration + // Transport sweep over all random rays for the iteration + // Must use temporary accumulator variable for OpenMP on Windows + uint64_t tmp_total_geometric_intersections {0}; #pragma omp parallel for schedule(dynamic) \ - reduction(+ : total_geometric_intersections_) + reduction(+ : tmp_total_geometric_intersections) for (int i = 0; i < settings::n_particles; i++) { RandomRay ray(i, domain_.get()); - total_geometric_intersections_ += + tmp_total_geometric_intersections += ray.transport_history_based_single_ray(); } + total_geometric_intersections_ = tmp_total_geometric_intersections; simulation::time_transport.stop(); diff --git a/src/secondary_correlated.cpp b/src/secondary_correlated.cpp index 0e4891dd3e7..0674c89fa74 100644 --- a/src/secondary_correlated.cpp +++ b/src/secondary_correlated.cpp @@ -208,7 +208,7 @@ void CorrelatedAngleEnergy::sample( } // Continuous portion - double c_k1; + double c_k1 = 0.; for (int j = n_discrete; j < end; ++j) { k = j; c_k1 = distribution_[l].c[k + 1]; diff --git a/src/settings.cpp b/src/settings.cpp index c8230a10f1d..1282c6ab939 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -120,8 +120,8 @@ ResScatMethod res_scat_method {ResScatMethod::rvs}; double res_scat_energy_min {0.01}; double res_scat_energy_max {1000.0}; vector res_scat_nuclides; -RunMode run_mode {RunMode::UNSET}; -SolverType solver_type {SolverType::MONTE_CARLO}; +RunMode OPENMC_API run_mode {RunMode::UNSET}; +SolverType OPENMC_API solver_type {SolverType::MONTE_CARLO}; std::unordered_set sourcepoint_batch; std::unordered_set statepoint_batch; std::unordered_set source_write_surf_id; diff --git a/src/source.cpp b/src/source.cpp index 8e6eb4f11fd..bb95dddc822 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -381,7 +381,7 @@ SourceSite IndependentSource::sample(uint64_t* seed) const site.E = energy_->sample(seed); // Resample if energy falls above maximum particle energy - if (site.E < data::energy_max[p] and + if (site.E < data::energy_max[p] && (satisfies_energy_constraints(site.E))) break; diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index 17e57a987c7..83c0dd21467 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -50,7 +50,7 @@ namespace openmc { //============================================================================== namespace model { -std::unordered_map filter_map; +std::unordered_map OPENMC_API filter_map; vector> tally_filters; } // namespace model diff --git a/tests/cpp_unit_tests/test_file_utils.cpp b/tests/cpp_unit_tests/test_file_utils.cpp index 8b0d99d76da..cce8672907f 100644 --- a/tests/cpp_unit_tests/test_file_utils.cpp +++ b/tests/cpp_unit_tests/test_file_utils.cpp @@ -5,6 +5,18 @@ using namespace openmc; TEST_CASE("Test get_file_extension") { +#if defined(_WIN32) || defined(_WIN64) + REQUIRE(get_file_extension("rememberthealamo.png") == "png"); + REQUIRE(get_file_extension("statepoint.20.h5") == "h5"); + REQUIRE(get_file_extension("wEiRDNaa_ame.h4") == "h4"); + REQUIRE(get_file_extension("has_directory\\asdf.20.h5") == "h5"); + REQUIRE(get_file_extension("wasssssup_lol") == ""); + REQUIRE(get_file_extension("has_directory\\secret_file") == ""); + REQUIRE(get_file_extension("lovely.dir\\extensionless_file") == ""); + REQUIRE(get_file_extension("lovely.dir\\statepoint.20.h5") == "h5"); + REQUIRE(get_file_extension("lovely.dir\\asdf123.cpp") == "cpp"); + +#else REQUIRE(get_file_extension("rememberthealamo.png") == "png"); REQUIRE(get_file_extension("statepoint.20.h5") == "h5"); REQUIRE(get_file_extension("wEiRDNaa_ame.h4") == "h4"); @@ -14,21 +26,35 @@ TEST_CASE("Test get_file_extension") REQUIRE(get_file_extension("lovely.dir/extensionless_file") == ""); REQUIRE(get_file_extension("lovely.dir/statepoint.20.h5") == "h5"); REQUIRE(get_file_extension("lovely.dir/asdf123.cpp") == "cpp"); +#endif } TEST_CASE("Test dir_exists") { +#if defined(_WIN32) || defined(_WIN64) + // If this doesn't exist on a Windows system, I have no clue what is happening + REQUIRE(dir_exists("C:\\")); + + // if this exists on your system... you deserve for this test to fail + REQUIRE(!dir_exists("C:\\asdfa\\asdfasdf\\asdgasodgosuihasjkgh")); +#else // not sure how to test this when running on windows? REQUIRE(dir_exists("/")); // if this exists on your system... you deserve for this test to fail REQUIRE(!dir_exists("/asdfa/asdfasdf/asdgasodgosuihasjkgh/")); +#endif } TEST_CASE("Test file_exists") { +#if defined(_WIN32) || defined(_WIN64) + // Note: not clear how to portably test where a file should exist. + REQUIRE(!file_exists("C:\\should_not_exist\\really_do_not_make_this_please")); +#else // Note: not clear how to portably test where a file should exist. REQUIRE(!file_exists("./should_not_exist/really_do_not_make_this_please")); +#endif } TEST_CASE("Test dir_name") diff --git a/tests/regression_tests/cpp_driver/test.py b/tests/regression_tests/cpp_driver/test.py index b80e82ee0e1..286f0a7377f 100644 --- a/tests/regression_tests/cpp_driver/test.py +++ b/tests/regression_tests/cpp_driver/test.py @@ -1,5 +1,6 @@ from pathlib import Path import os +import sys import shutil import subprocess import textwrap @@ -11,6 +12,9 @@ from tests.regression_tests import config from tests.testing_harness import PyAPITestHarness +pytestmark = pytest.mark.skipif( + sys.platform == 'win32', + reason="Cannot use compiled tests on Windows.") @pytest.fixture def cpp_driver(request): diff --git a/tests/regression_tests/deplete_decay_only/test.py b/tests/regression_tests/deplete_decay_only/test.py index 4345b86b898..7b9f9af9714 100644 --- a/tests/regression_tests/deplete_decay_only/test.py +++ b/tests/regression_tests/deplete_decay_only/test.py @@ -50,7 +50,7 @@ def model(): @pytest.fixture(scope="module") def micro_xs(): micro_xs_file = Path(__file__).parents[2] / 'micro_xs_simple.csv' - return MicroXS.from_csv(micro_xs_file) + return MicroXS.from_csv(micro_xs_file, lineterminator='\n') @pytest.fixture(scope="module") diff --git a/tests/regression_tests/deplete_no_transport/test.py b/tests/regression_tests/deplete_no_transport/test.py index 63ae584e116..b76c4fa01d8 100644 --- a/tests/regression_tests/deplete_no_transport/test.py +++ b/tests/regression_tests/deplete_no_transport/test.py @@ -28,7 +28,7 @@ def fuel(): @pytest.fixture(scope="module") def micro_xs(): micro_xs_file = Path(__file__).parents[2] / 'micro_xs_simple.csv' - return MicroXS.from_csv(micro_xs_file) + return MicroXS.from_csv(micro_xs_file, lineterminator='\n') @pytest.fixture(scope="module") diff --git a/tests/regression_tests/diff_tally/test.py b/tests/regression_tests/diff_tally/test.py index 89460df9152..5ab1f52f621 100644 --- a/tests/regression_tests/diff_tally/test.py +++ b/tests/regression_tests/diff_tally/test.py @@ -109,7 +109,7 @@ def _get_results(self): # Extract the relevant data as a CSV string. cols = ('d_material', 'd_nuclide', 'd_variable', 'score', 'mean', 'std. dev.') - return df.to_csv(None, columns=cols, index=False, float_format='%.7e') + return df.to_csv(None, columns=cols, index=False, float_format='%.7e', lineterminator='\n') def test_diff_tally(): diff --git a/tests/regression_tests/microxs/test.py b/tests/regression_tests/microxs/test.py index a35150a1b80..bf36c37099f 100644 --- a/tests/regression_tests/microxs/test.py +++ b/tests/regression_tests/microxs/test.py @@ -60,8 +60,8 @@ def test_from_model(model, domain_type): 'Xe136', 'Cs135', 'Gd157', 'Gd156'] _, test_xs = get_microxs_and_flux(model, domains, nuclides, chain_file=CHAIN_FILE) if config['update']: - test_xs[0].to_csv(f'test_reference_{domain_type}.csv') + test_xs[0].to_csv(f'test_reference_{domain_type}.csv', lineterminator='\n') - ref_xs = MicroXS.from_csv(f'test_reference_{domain_type}.csv') + ref_xs = MicroXS.from_csv(f'test_reference_{domain_type}.csv', lineterminator='\n') np.testing.assert_allclose(test_xs[0].data, ref_xs.data, rtol=1e-11) diff --git a/tests/regression_tests/source_dlopen/test.py b/tests/regression_tests/source_dlopen/test.py index 0581d6deec4..c3308a0b8fc 100644 --- a/tests/regression_tests/source_dlopen/test.py +++ b/tests/regression_tests/source_dlopen/test.py @@ -1,5 +1,6 @@ from pathlib import Path import os +import sys import shutil import subprocess import textwrap @@ -9,6 +10,9 @@ from tests.testing_harness import PyAPITestHarness +pytestmark = pytest.mark.skipif( + sys.platform == 'win32', + reason="Cannot use compiled tests on Windows.") @pytest.fixture def compile_source(request): diff --git a/tests/regression_tests/source_parameterized_dlopen/test.py b/tests/regression_tests/source_parameterized_dlopen/test.py index 151fb37356e..441a125e85f 100644 --- a/tests/regression_tests/source_parameterized_dlopen/test.py +++ b/tests/regression_tests/source_parameterized_dlopen/test.py @@ -1,5 +1,6 @@ from pathlib import Path import os +import sys import shutil import subprocess import textwrap @@ -9,6 +10,9 @@ from tests.testing_harness import PyAPITestHarness +pytestmark = pytest.mark.skipif( + sys.platform == 'win32', + reason="Cannot use compiled tests on Windows.") @pytest.fixture def compile_source(request): diff --git a/tests/regression_tests/surface_tally/test.py b/tests/regression_tests/surface_tally/test.py index e496ac0f65c..0f2507db480 100644 --- a/tests/regression_tests/surface_tally/test.py +++ b/tests/regression_tests/surface_tally/test.py @@ -168,7 +168,7 @@ def _get_results(self): # Extract the relevant data as a CSV string. cols = ('mean', 'std. dev.') - return df.to_csv(None, columns=cols, index=False, float_format='%.7e') + return df.to_csv(None, columns=cols, index=False, float_format='%.7e', lineterminator='\n') return outstr diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py index 4e87de4b7e9..d0e569d2cf5 100644 --- a/tests/unit_tests/test_config.py +++ b/tests/unit_tests/test_config.py @@ -4,6 +4,7 @@ import openmc import pytest +from pathlib import Path @pytest.fixture(autouse=True, scope='module') @@ -45,8 +46,8 @@ def test_config_basics(): def test_config_patch(): openmc.config['cross_sections'] = '/path/to/cross_sections.xml' with openmc.config.patch('cross_sections', '/path/to/other.xml'): - assert str(openmc.config['cross_sections']) == '/path/to/other.xml' - assert str(openmc.config['cross_sections']) == '/path/to/cross_sections.xml' + assert str(openmc.config['cross_sections']) == str(Path('/path/to/other.xml')) + assert str(openmc.config['cross_sections']) == str(Path('/path/to/cross_sections.xml')) def test_config_set_envvar(): diff --git a/tests/unit_tests/test_deplete_independent_operator.py b/tests/unit_tests/test_deplete_independent_operator.py index c765d065009..88d3f8e8546 100644 --- a/tests/unit_tests/test_deplete_independent_operator.py +++ b/tests/unit_tests/test_deplete_independent_operator.py @@ -24,7 +24,7 @@ def test_operator_init(): 'O16': 4.639065406771322e+22, 'O17': 1.7588724018066158e+19} flux = 1.0 - micro_xs = MicroXS.from_csv(ONE_GROUP_XS) + micro_xs = MicroXS.from_csv(ONE_GROUP_XS, lineterminator='\n') IndependentOperator.from_nuclides( volume, nuclides, flux, micro_xs, CHAIN_PATH, nuc_units='atom/cm3') @@ -41,7 +41,7 @@ def test_operator_init(): def test_error_handling(): - micro_xs = MicroXS.from_csv(ONE_GROUP_XS) + micro_xs = MicroXS.from_csv(ONE_GROUP_XS, lineterminator='\n') fuel = Material(name="oxygen") fuel.add_element("O", 2) fuel.set_density("g/cc", 1) diff --git a/tests/unit_tests/test_deplete_microxs.py b/tests/unit_tests/test_deplete_microxs.py index 073b3f162d1..aebc7c6ff8e 100644 --- a/tests/unit_tests/test_deplete_microxs.py +++ b/tests/unit_tests/test_deplete_microxs.py @@ -51,9 +51,9 @@ def test_from_array(): def test_csv(): - ref_xs = MicroXS.from_csv(ONE_GROUP_XS) - ref_xs.to_csv('temp_xs.csv') - temp_xs = MicroXS.from_csv('temp_xs.csv') + ref_xs = MicroXS.from_csv(ONE_GROUP_XS, lineterminator='\n') + ref_xs.to_csv('temp_xs.csv', lineterminator='\n') + temp_xs = MicroXS.from_csv('temp_xs.csv', lineterminator='\n') assert np.all(ref_xs.data == temp_xs.data) remove('temp_xs.csv') diff --git a/tools/ci/download-xs.ps1 b/tools/ci/download-xs.ps1 new file mode 100644 index 00000000000..c94cc0f2dd4 --- /dev/null +++ b/tools/ci/download-xs.ps1 @@ -0,0 +1,14 @@ +$ProgressPreference = 'SilentlyContinue' + +# Download HDF5 data +if (-not (Test-Path "$Env:GITHUB_WORKSPACE\nndc_hdf5")) { + Invoke-WebRequest https://anl.box.com/shared/static/teaup95cqv8s9nn56hfn7ku8mmelr95p.xz -OutFile hdf5.xz -UseBasicParsing + tar -xvzf hdf5.xz +} + +# Download ENDF/B-VII.1 distribution +$Env:ENDF = "$Env:GITHUB_WORKSPACE\endf-b-vii.1" +if (-not (Test-Path $Env:ENDF)) { + Invoke-WebRequest https://anl.box.com/shared/static/4kd2gxnf4gtk4w1c8eua5fsua22kvgjb.xz -OutFile endf.xz -UseBasicParsing + tar -xvzf endf.xz +} diff --git a/tools/ci/gha-install.ps1 b/tools/ci/gha-install.ps1 new file mode 100644 index 00000000000..600d8310ecc --- /dev/null +++ b/tools/ci/gha-install.ps1 @@ -0,0 +1,26 @@ +# Install VCPKG +git clone https://github.com/microsoft/vcpkg.git "$Env:GITHUB_WORKSPACE\..\vcpkg" + +cd "$Env:GITHUB_WORKSPACE\..\vcpkg" +.\bootstrap-vcpkg.bat + +[Environment]::SetEnvironmentVariable("VCPKG_ROOT", "$Env:GITHUB_WORKSPACE\..\vcpkg", [System.EnvironmentVariableTarget]::User) +$Env:VCPKG_ROOT = "$Env:GITHUB_WORKSPACE\..\vcpkg" +$Env:Path += ";$Env:VCPKG_ROOT" + +# Install HDF5 +vcpkg install hdf5:x64-windows-static + +# Go back to main directory for install +cd "$Env:GITHUB_WORKSPACE" + +# Upgrade pip, pytest, numpy before doing anything else. +pip install --upgrade pip +pip install --upgrade pytest +pip install --upgrade numpy + +# Build and install OpenMC executable +python tools/ci/gha-install.py + +# Install Python API in editable mode +pip install -e .[test,vtk,ci] \ No newline at end of file diff --git a/tools/ci/gha-install.py b/tools/ci/gha-install.py index 1cc792f8d78..88f5b1d4441 100644 --- a/tools/ci/gha-install.py +++ b/tools/ci/gha-install.py @@ -1,4 +1,5 @@ import os +import sys import shutil import subprocess @@ -10,7 +11,11 @@ def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False): os.chdir('build') # Build in debug mode by default with support for MCPL - cmake_cmd = ['cmake', '-DCMAKE_BUILD_TYPE=Debug', '-DOPENMC_USE_MCPL=on'] + if sys.platform == 'win32': + work_dir = os.environ.get('GITHUB_WORKSPACE') + cmake_cmd = ['cmake', '-DCMAKE_BUILD_TYPE=Release', '-DCMAKE_TOOLCHAIN_FILE='+work_dir+'\\..\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake', '-DVCPKG_TARGET_TRIPLET=x64-windows-static'] + else: + cmake_cmd = ['cmake', '-DCMAKE_BUILD_TYPE=Debug', '-DOPENMC_USE_MCPL=on'] # Turn off OpenMP if specified if not omp: @@ -41,14 +46,20 @@ def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False): cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + libmesh_path) # Build in coverage mode for coverage testing - cmake_cmd.append('-DOPENMC_ENABLE_COVERAGE=on') + if sys.platform != 'win32': + cmake_cmd.append('-DOPENMC_ENABLE_COVERAGE=on') # Build and install cmake_cmd.append('..') print(' '.join(cmake_cmd)) subprocess.check_call(cmake_cmd) - subprocess.check_call(['make', '-j4']) - subprocess.check_call(['sudo', 'make', 'install']) + + if sys.platform == 'win32': + subprocess.check_call(['cmake', '--build', '.', '--config=Release']) + subprocess.check_call(['cmake', '--install', '.', '--config=Release']) + else: + subprocess.check_call(['make', '-j4']) + subprocess.check_call(['sudo', 'make', 'install']) def main(): # Convert Travis matrix environment variables into arguments for install() diff --git a/tools/ci/gha-script.ps1 b/tools/ci/gha-script.ps1 new file mode 100644 index 00000000000..bd3f89b856b --- /dev/null +++ b/tools/ci/gha-script.ps1 @@ -0,0 +1,10 @@ +# Argument list +$Env:args = "" + +# Check for event-based +if ($Env:EVENT) { + $Env:args = $Env:args + " --event " +} + +# Run regression and unit tests +pytest --cov=openmc -v $Env:args tests \ No newline at end of file