diff --git a/src/phast/PhreeqcRM/.github/workflows/ci.yml b/src/phast/PhreeqcRM/.github/workflows/ci.yml index 4d777784..84578b69 100644 --- a/src/phast/PhreeqcRM/.github/workflows/ci.yml +++ b/src/phast/PhreeqcRM/.github/workflows/ci.yml @@ -390,16 +390,15 @@ jobs: compiler: intel-classic version: '2021.10' - - name: Unset CC/CXX (Windows) + - name: Set CC/CXX (Windows) if: runner.os == 'Windows' shell: bash run: | - echo "CC=" >> $GITHUB_ENV - echo "CXX=" >> $GITHUB_ENV + echo "CC=cl" >> $GITHUB_ENV + echo "CXX=cl" >> $GITHUB_ENV - name: Install ninja valgrind (Linux) if: ${{ runner.os == 'Linux' }} - #run: sudo apt-get install -y ninja-build valgrind libyaml-cpp-dev python3-numpy run: | sudo apt-get -y update sudo apt-get install -y ninja-build valgrind @@ -437,12 +436,6 @@ jobs: python -m pip install pytest # @todo if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Set up Visual Studio shell (Windows) - if: runner.os == 'Windows' - uses: egor-tensin/vs-shell@v2 - with: - arch: x64 - - name: Extract tarball run: tar xvzf phreeqcrm-${{ env.VER_STRING }}.tar.gz diff --git a/src/phast/PhreeqcRM/.github/workflows/wheels.yml b/src/phast/PhreeqcRM/.github/workflows/wheels.yml index e4b5f2ea..642f50ab 100644 --- a/src/phast/PhreeqcRM/.github/workflows/wheels.yml +++ b/src/phast/PhreeqcRM/.github/workflows/wheels.yml @@ -40,10 +40,12 @@ jobs: - [cp38, v141] - [cp39, v141] - [cp310, v141] - - [pp310, v141] - [cp311, v141] + - [cp312, v142] - [cp313, v142] # - [cp313t, v142] + - [pp310, v141] + exclude: # cp313(i686) on ubuntu-20.04 - buildplat: [ubuntu-20.04, manylinux_i686, i686] @@ -88,11 +90,19 @@ jobs: run: | python --version + # free some space to prevent reaching GHA disk space limits + - name: Clean docker images + if: runner.os == 'Linux' + run: | + docker system prune -a -f + df -h + - name: Setup QEMU if: runner.os == 'Linux' uses: docker/setup-qemu-action@v3 with: - platforms: all + image: tonistiigi/binfmt:qemu-v8.1.5 + #platforms: all - name: Build wheels (Linux) if: ${{ runner.os == 'Linux' }} diff --git a/src/phast/PhreeqcRM/CMakeLists.txt b/src/phast/PhreeqcRM/CMakeLists.txt index daa37857..e5c2f528 100644 --- a/src/phast/PhreeqcRM/CMakeLists.txt +++ b/src/phast/PhreeqcRM/CMakeLists.txt @@ -35,22 +35,12 @@ if(PHREEQCRM_FORTRAN_TESTING) enable_language(Fortran) endif() +# Include FetchContent module +include(FetchContent) + if(DEFINED SKBUILD) # SKBUILD is defined when scikit-build-core is used to build python wheels (see pyproject.toml) - # Include FetchContent module - include(FetchContent) - - # Fetch yaml-cpp - FetchContent_Declare( - yaml-cpp - URL https://github.com/jbeder/yaml-cpp/archive/refs/tags/0.8.0.tar.gz - ) - - # Make yaml-cpp available - FetchContent_MakeAvailable(yaml-cpp) - - # Fetch zlib FetchContent_Declare( zlib @@ -68,10 +58,28 @@ if(DEFINED SKBUILD) # SKBUILD is defined when scikit-build-core is used to build python wheels (see pyproject.toml) set(PHREEQCRM_WITH_YAML_CPP ON) endif() -if(PHREEQCRM_WITH_YAML_CPP AND NOT DEFINED SKBUILD) +if(PHREEQCRM_WITH_YAML_CPP) + # Initialize a static build for yaml-cpp + # w/o the FORCE option the user can override using -DYAML_BUILD_SHARED_LIBS=ON + set(YAML_BUILD_SHARED_LIBS OFF CACHE STRING "") + # w/o the FORCE option the user can override using -DYAML_CPP_INSTALL=OFF + set(YAML_CPP_INSTALL ON CACHE STRING "") + + # Fetch yaml-cpp + FetchContent_Declare( + yaml-cpp + URL https://github.com/jbeder/yaml-cpp/archive/refs/tags/0.8.0.tar.gz + OVERRIDE_FIND_PACKAGE + ) + + # The following will automatically forward through to FetchContent_MakeAvailable() find_package(yaml-cpp REQUIRED) if(yaml-cpp_FOUND) - message(STATUS "Found yaml-cpp: TRUE (found version \"${yaml-cpp_VERSION}\")") + if(DEFINED yaml-cpp_VERSION) + message(STATUS "Found yaml-cpp: TRUE (found version \"${yaml-cpp_VERSION}\")") + else() + message(STATUS "Found yaml-cpp: TRUE") + endif() endif() endif() @@ -314,8 +322,8 @@ target_include_directories(PhreeqcRM include(GenerateExportHeader) generate_export_header(PhreeqcRM BASE_NAME IRM_DLL) -# c++11 -target_compile_features(PhreeqcRM PUBLIC cxx_std_11) +# c++14 +target_compile_features(PhreeqcRM PUBLIC cxx_std_14) # iphreeqc defs target_compile_definitions(PhreeqcRM PRIVATE SWIG_SHARED_OBJ) @@ -526,13 +534,23 @@ if(STANDALONE_BUILD EQUAL 1 AND NOT DEFINED SKBUILD) DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PhreeqcRM ) - install(TARGETS PhreeqcRM - EXPORT PhreeqcRMTargets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - ) + if(PHREEQCRM_WITH_YAML_CPP AND yaml-cpp_FOUND) + install(TARGETS PhreeqcRM yaml-cpp + EXPORT PhreeqcRMTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) + else() + install(TARGETS PhreeqcRM + EXPORT PhreeqcRMTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) + endif() install(EXPORT PhreeqcRMTargets FILE PhreeqcRMTargets.cmake diff --git a/src/phast/PhreeqcRM/configure.ac b/src/phast/PhreeqcRM/configure.ac index 4a9390a8..12a9af69 100644 --- a/src/phast/PhreeqcRM/configure.ac +++ b/src/phast/PhreeqcRM/configure.ac @@ -69,8 +69,8 @@ AC_PROG_AWK AC_PROG_CPP AC_PROG_LN_S -# c++11 is required -AX_CXX_COMPILE_STDCXX(11, [ext], [mandatory]) +# c++14 is required +AX_CXX_COMPILE_STDCXX(14, [ext], [mandatory]) # Check if the fortran test should be included AC_MSG_CHECKING([if Fortran test is added]) diff --git a/src/phast/PhreeqcRM/mamba.environment.yml b/src/phast/PhreeqcRM/mamba.environment.yml index 3d957441..af005b0d 100644 --- a/src/phast/PhreeqcRM/mamba.environment.yml +++ b/src/phast/PhreeqcRM/mamba.environment.yml @@ -8,5 +8,4 @@ dependencies: - python - pyyaml - swig -- yaml-cpp - cmake diff --git a/src/phast/PhreeqcRM/pyproject.toml b/src/phast/PhreeqcRM/pyproject.toml index 64ad9c47..39ffecdc 100644 --- a/src/phast/PhreeqcRM/pyproject.toml +++ b/src/phast/PhreeqcRM/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "scikit_build_core.build" [project] name = "phreeqcrm" -version = "0.0.12" +version = "0.0.14" description = "A reaction module for transport simulators based on the geochemical model PHREEQC." readme = "README.md" requires-python = ">=3.8" diff --git a/src/phast/PhreeqcRM/src/BMIPhreeqcRM.cpp b/src/phast/PhreeqcRM/src/BMIPhreeqcRM.cpp index c1db528f..80049a8b 100644 --- a/src/phast/PhreeqcRM/src/BMIPhreeqcRM.cpp +++ b/src/phast/PhreeqcRM/src/BMIPhreeqcRM.cpp @@ -149,10 +149,10 @@ void BMIPhreeqcRM::ClearBMISelectedOutput(void) assert(this->var_man); this->var_man->BMISelectedOutput.clear(); } -void BMIPhreeqcRM::Construct(PhreeqcRM::Initializer i) +void BMIPhreeqcRM::Construct() { if (constructed) return; - this->PhreeqcRM::Construct(i); + this->PhreeqcRM::Construct(); this->var_man = new VarManager((PhreeqcRM*)this); #if defined(WITH_PYBIND11) this->_initialized = true; @@ -180,13 +180,13 @@ void BMIPhreeqcRM::Initialize(std::string config_file) std::string keyword = it1++->second.as(); if (keyword == "SetGridCellCount") { - this->initializer.nxyz_arg = it1++->second.as(); + set_nxyz(it1++->second.as()); found_nxyz = true; } if (keyword == "ThreadCount") { #if !defined(USE_MPI) - this->initializer.data_for_parallel_processing = it1++->second.as(); + set_data_for_parallel_processing(it1++->second.as()); #endif found_threads = true; } @@ -195,7 +195,7 @@ void BMIPhreeqcRM::Initialize(std::string config_file) } #endif - this->Construct(this->initializer); + this->Construct(); constructed = true; #ifdef USE_YAML if (config_file.size() != 0) @@ -1321,6 +1321,14 @@ RMVARS BMIPhreeqcRM::GetEnum(const std::string name) return RMVARS::NotFound; }; -////////////////// +/* ---------------------------------------------------------------------- */ +IRM_RESULT +BMIPhreeqcRM::LoadDatabase(const std::string& database) +/* ---------------------------------------------------------------------- */ +{ + // reqd for swig-python + return this->PhreeqcRM::LoadDatabase(database); +} +////////////////// diff --git a/src/phast/PhreeqcRM/src/BMIPhreeqcRM.h b/src/phast/PhreeqcRM/src/BMIPhreeqcRM.h index a6711570..970879df 100644 --- a/src/phast/PhreeqcRM/src/BMIPhreeqcRM.h +++ b/src/phast/PhreeqcRM/src/BMIPhreeqcRM.h @@ -1271,6 +1271,8 @@ class IRM_DLL_EXPORT BMIPhreeqcRM : public bmi::Bmi, public PhreeqcRM // std::set UpdateMap; // std::set& GetUpdateMap() { return UpdateMap; } + IRM_RESULT LoadDatabase(const std::string& database) override; + #if defined(WITH_PYBIND11) py::array BMIPhreeqcRM::get_value(std::string name, py::array arr); @@ -1297,7 +1299,7 @@ class IRM_DLL_EXPORT BMIPhreeqcRM : public bmi::Bmi, public PhreeqcRM #endif protected: - void Construct(Initializer initializer) override; + void Construct(void) override; private: //friend class RM_interface; diff --git a/src/phast/PhreeqcRM/src/PhreeqcRM.cpp b/src/phast/PhreeqcRM/src/PhreeqcRM.cpp index a4bc9fea..08a4080e 100644 --- a/src/phast/PhreeqcRM/src/PhreeqcRM.cpp +++ b/src/phast/PhreeqcRM/src/PhreeqcRM.cpp @@ -69,6 +69,19 @@ const MP_TYPE PhreeqcRM::default_data_for_parallel_processing = -1; #endif +// Pimpl for initialization +class PhreeqcRM::Initializer +{ + +public: + Initializer() : nxyz_arg(default_nxyz), data_for_parallel_processing(default_data_for_parallel_processing), io(nullptr) {} + Initializer(int nxyz, MP_TYPE data, PHRQ_io *pio) : nxyz_arg(nxyz), data_for_parallel_processing(data), io(pio) {} + +public: + int nxyz_arg; + MP_TYPE data_for_parallel_processing; + PHRQ_io *io; +}; //// static PhreeqcRM methods /* ---------------------------------------------------------------------- */ @@ -169,7 +182,7 @@ PhreeqcRM::PhreeqcRM(int nxyz_arg, MP_TYPE data_for_parallel_processing, PHRQ_io , mpi_worker_callback_c( nullptr ) , mpi_worker_callback_cookie( nullptr ) , species_save_on( false ) -, initializer( nxyz_arg, data_for_parallel_processing, io ) +, initializer(std::make_unique(nxyz_arg, data_for_parallel_processing, io)) { #ifdef USE_MPI phreeqcrm_comm = data_for_parallel_processing; @@ -188,7 +201,7 @@ PhreeqcRM::PhreeqcRM(int nxyz_arg, MP_TYPE data_for_parallel_processing, PHRQ_io #ifdef USE_MPI if (mpi_myself == 0) { - this->Construct(this->initializer); + this->Construct(); MpiWorkerBreak(); } else @@ -196,16 +209,16 @@ PhreeqcRM::PhreeqcRM(int nxyz_arg, MP_TYPE data_for_parallel_processing, PHRQ_io MpiWorker(); } #else - this->Construct(this->initializer); + this->Construct(); #endif } } -void PhreeqcRM::Construct(PhreeqcRM::Initializer i) +void PhreeqcRM::Construct() { - int nxyz_arg = i.nxyz_arg; - MP_TYPE data_for_parallel_processing = i.data_for_parallel_processing; - //PHRQ_io *io = i.io; + int nxyz_arg = this->initializer->nxyz_arg; + MP_TYPE data_for_parallel_processing = this->initializer->data_for_parallel_processing; + //PHRQ_io* io = this->initializer->io; #ifdef USE_MPI if (mpi_myself == 0) { @@ -5631,7 +5644,8 @@ IRM_RESULT PhreeqcRM::InitializeYAML(std::string config) if (keyword == "LoadDatabase") { std::string file = it1++->second.as< std::string >(); - this->LoadDatabase(file); + // no need to check for initialization just call base class + this->PhreeqcRM::LoadDatabase(file); continue; } if (keyword == "OpenFiles") @@ -7035,7 +7049,7 @@ PhreeqcRM::MpiWorker() case METHOD_CONSTRUCT: if (debug_worker) std::cerr << "METHOD_CONSTRUCT" << std::endl; { - this->Construct(this->initializer); + this->Construct(); } break; case METHOD_CREATEMAPPING: @@ -13028,6 +13042,27 @@ PhreeqcRM::WarningMessage(const std::string &str) this->phreeqcrm_io->warning_msg(str.c_str()); } } +/* ---------------------------------------------------------------------- */ +void +PhreeqcRM::set_data_for_parallel_processing(int value) +/* ---------------------------------------------------------------------- */ +{ + this->initializer->data_for_parallel_processing = value; +} +/* ---------------------------------------------------------------------- */ +void +PhreeqcRM::set_nxyz(int value) +/* ---------------------------------------------------------------------- */ +{ + this->initializer->nxyz_arg = value; +} +/* ---------------------------------------------------------------------- */ +void +PhreeqcRM::set_io(PHRQ_io *value) +/* ---------------------------------------------------------------------- */ +{ + this->initializer->io = value; +} diff --git a/src/phast/PhreeqcRM/src/PhreeqcRM.h b/src/phast/PhreeqcRM/src/PhreeqcRM.h index 9457719b..9db1f2ec 100644 --- a/src/phast/PhreeqcRM/src/PhreeqcRM.h +++ b/src/phast/PhreeqcRM/src/PhreeqcRM.h @@ -16,19 +16,23 @@ #else #define MP_TYPE int #endif -class IPhreeqcPhast; -class cxxStorageBin; -//class cxxNameDouble; -#include "NameDouble.h" +// forward declarations +class cxxNameDouble; class cxxSolution; +class cxxStorageBin; +class IPhreeqc; +class IPhreeqcPhast; class PHRQ_io; + +#include "NameDouble.h" #include #include #include #include #include #include +#include #include "RMVARS.h" #include "irm_dll_export.h" @@ -37,8 +41,6 @@ class PHRQ_io; #define IRM_DLL_EXPORT #endif -class PHRQ_io; -class IPhreeqc; //class BMI_Var; /** * @class PhreeqcRMStop @@ -3899,7 +3901,7 @@ status = phreeqc_rm.LoadDatabase("phreeqc.dat"); @par MPI: Called by root, workers must be in the loop of @ref MpiWorker. */ - IRM_RESULT LoadDatabase(const std::string &database); + virtual IRM_RESULT LoadDatabase(const std::string &database); /** Print a message to the log file. @param str String to be printed. @@ -5804,20 +5806,15 @@ Called by root and (or) workers; only root writes to the log file. protected: static const int default_nxyz = 10; static const MP_TYPE default_data_for_parallel_processing; - struct Initializer { - int nxyz_arg; - MP_TYPE data_for_parallel_processing; - PHRQ_io *io; - - Initializer() - : nxyz_arg(default_nxyz) , data_for_parallel_processing(default_data_for_parallel_processing), io(NULL) {} - Initializer(int nxyz_arg, MP_TYPE data_for_parallel_processing, PHRQ_io *io) - : nxyz_arg(nxyz_arg) , data_for_parallel_processing(data_for_parallel_processing), io(io) {} + class Initializer; // Forward declaration + std::unique_ptr initializer; - } initializer; + virtual void Construct(); - virtual void Construct(Initializer initializer); + void set_data_for_parallel_processing(int value); + void set_io(PHRQ_io* value); + void set_nxyz(int value); /** @endcond */ private: diff --git a/src/phast/PhreeqcRM/swig/PhreeqcRM.i b/src/phast/PhreeqcRM/swig/PhreeqcRM.i index 7ff9d50b..5f06e6cd 100644 --- a/src/phast/PhreeqcRM/swig/PhreeqcRM.i +++ b/src/phast/PhreeqcRM/swig/PhreeqcRM.i @@ -1,4 +1,4 @@ -// %module(directors="1") phreeqcrm +%module(directors="1") phreeqcrm %module phreeqcrm %begin %{ @@ -18,6 +18,9 @@ #endif %} +%feature("director") PhreeqcRM; // Enable directors for PhreeqcRM +%feature("director") BMIPhreeqcRM; // Enable directors for BMIPhreeqcRM + %pythoncode %{ import numpy as np @@ -694,6 +697,11 @@ def GetDoubleVector(self, v): raise RuntimeError("must call initialize first") %} +%feature("pythonprepend") BMIPhreeqcRM::LoadDatabase(const std::string& name) %{ + if self._state != State.INITIALIZED: + raise RuntimeError("must call initialize first") +%} + %feature("pythonprepend") BMIPhreeqcRM::Update() %{ if self._state != State.INITIALIZED: raise RuntimeError("must call initialize first") diff --git a/src/phast/PhreeqcRM/swig/python/CMakeLists.txt b/src/phast/PhreeqcRM/swig/python/CMakeLists.txt index e29a71d5..660e5e78 100644 --- a/src/phast/PhreeqcRM/swig/python/CMakeLists.txt +++ b/src/phast/PhreeqcRM/swig/python/CMakeLists.txt @@ -87,7 +87,7 @@ endif() set_property(TARGET ${PYTHON_TARGET_NAME} PROPERTY OUTPUT_NAME "phreeqcrm") -target_compile_features(${PYTHON_TARGET_NAME} PUBLIC cxx_std_11) +target_compile_features(${PYTHON_TARGET_NAME} PUBLIC cxx_std_14) target_compile_definitions(${PYTHON_TARGET_NAME} PRIVATE IRM_DLL_STATIC_DEFINE) diff --git a/src/phast/PhreeqcRM/swig/python/test_init.py b/src/phast/PhreeqcRM/swig/python/test_init.py index fe4dc55a..b8df5cce 100644 --- a/src/phast/PhreeqcRM/swig/python/test_init.py +++ b/src/phast/PhreeqcRM/swig/python/test_init.py @@ -240,6 +240,12 @@ def test_initialize_no_throw(): model = BMIPhreeqcRM() model.initialize() +# def LoadDatabase(self, database: str) +def test_LoadDatabase_raises_uninitialized(): + model = BMIPhreeqcRM() + with pytest.raises(RuntimeError, match=ERROR_MUST_INITIALIZE): + model.LoadDatabase(FilePaths.DATABASE) + # def set_value(self, name: str, src: np.ndarray) -> None: def test_set_value_raises_uninitialized(): model = BMIPhreeqcRM()