From 2cc15b441088b10498008763b76cf739f2ca1ee3 Mon Sep 17 00:00:00 2001 From: Meagan Lang Date: Wed, 18 Dec 2024 17:44:49 -0500 Subject: [PATCH] Added tests for Python extension and updated build system for Python extension Add methods for reading files into Variables that can be used by the python extension Add debug messages for duplicate input parameters and for absent EnzymeAct when useC3 is true --- .github/workflows/test-suite.yml | 65 ++++++---- .gitignore | 4 +- CMakeLists.txt | 69 +++++++++-- bin/ePhotosynthesis.cpp | 90 +++----------- cmake/__init__.py.in | 1 + environment.yml | 1 + include/Variables.hpp | 34 +++++- include/definitions.hpp | 2 + include/drivers/EPS_Driver.hpp | 3 + scripts/devtasks.py | 97 ++++++++++++--- src/Condition.cpp | 14 +++ src/Variables.cpp | 125 +++++++++++++++++++ src/drivers/driver.cpp | 2 + src/python/wrappers.cpp | 200 ++++++++++++++++--------------- tests/python/test_drivers.py | 116 ++++++++++++++++++ 15 files changed, 592 insertions(+), 231 deletions(-) create mode 100644 cmake/__init__.py.in create mode 100644 tests/python/test_drivers.py diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index af48e78..d8f76e9 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -22,6 +22,7 @@ jobs: with-elf: [false] with-valgrind: [false, true] with-mpi: [false] + with-python: [false] include: - sundials-version: "5.7.0" os: ubuntu-latest @@ -30,6 +31,7 @@ jobs: with-elf: false with-valgrind: false with-mpi: false + with-python: false - sundials-version: "6.7.0" os: ubuntu-latest with-coverage: false @@ -37,6 +39,7 @@ jobs: with-elf: false with-valgrind: false with-mpi: false + with-python: false - sundials-version: "7.1.1" os: ubuntu-latest with-coverage: false @@ -44,6 +47,7 @@ jobs: with-elf: false with-valgrind: false with-mpi: false + with-python: false - sundials-version: latest os: ubuntu-latest with-coverage: false @@ -51,6 +55,7 @@ jobs: with-elf: false with-valgrind: false with-mpi: true + with-python: false - sundials-version: "6.7.0" os: ubuntu-latest with-coverage: false @@ -58,6 +63,7 @@ jobs: with-elf: false with-valgrind: false with-mpi: true + with-python: false - sundials-version: "7.1.1" os: ubuntu-latest with-coverage: false @@ -65,6 +71,15 @@ jobs: with-elf: false with-valgrind: false with-mpi: true + with-python: false + - sundials-version: latest + os: ubuntu-latest + with-coverage: false + with-asan: false + with-elf: false + with-valgrind: false + with-mpi: false + with-python: true exclude: - os: windows-latest with-asan: true @@ -212,6 +227,11 @@ jobs: echo "CMAKE_ARGS=-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON" >> "$GITHUB_ENV" echo "CMAKE_ARGS_TEST=-DBUILD_TESTS=ON" >> "$GITHUB_ENV" echo "TEST_FLAGS=-C ${CMAKE_BUILD_TYPE_TEST} --output-on-failure -VV" >> "$GITHUB_ENV" + echo "BUILD_ARGS=--config ${CMAKE_BUILD_TYPE_TEST}" >> "$GITHUB_ENV" + - name: Set flags to build in parallel (UNIX) + if: matrix.os != 'windows-latest' + run: | + echo "BUILD_ARGS=${{ env.BUILD_ARGS }} -- -j 4" >> "$GITHUB_ENV" - name: Coverage config flags if: matrix.with-coverage == true && matrix.os != 'windows-latest' run: | @@ -257,21 +277,15 @@ jobs: mkdir build cd build which cmake - cmake .. ${{ env.CMAKE_ARGS }} ${{ env.CMAKE_ARGS_NO_PYTHON }} ${{ env.CMAKE_ARGS_TEST }} + cmake .. ${{ env.CMAKE_ARGS }} ${{ env.CMAKE_ARGS_TEST }} ################################### # Build CXX ################################### - - name: Build C++ & C in parallel (CONDA, UNIX) - if: matrix.os != 'windows-latest' + - name: Build C++ & C run: | cd build - cmake --build . --config ${{ env.CMAKE_BUILD_TYPE_TEST }} -- -j 4 - - name: Build C++ & C in serial (CONDA, WINDOWS) - if: matrix.os == 'windows-latest' - run: | - cd build - cmake --build . --config ${{ env.CMAKE_BUILD_TYPE_TEST }} + cmake --build . ${{ env.BUILD_ARGS }} ################################### # Test CXX @@ -299,26 +313,25 @@ jobs: ################################### # Build Python ################################### - # - name: Set CMAKE_ARGS for Python build - # run: | - # echo "OLD_CMAKE_ARGS=${{ env.CMAKE_ARGS }}" >> "$GITHUB_ENV" - # echo "CMAKE_ARGS=${{ env.CMAKE_ARGS }} -DBUILD_PYTHON=ON" >> "$GITHUB_ENV" - # - name: Build Python (CONDA) - # run: | - # cd build - # which cmake - # cmake .. ${{ env.CMAKE_ARGS }} ${{ env.CMAKE_ARGS_TEST }} - # - name: Unset CMAKE_ARGS for Python build - # run: | - # echo "CMAKE_ARGS=${{ env.OLD_CMAKE_ARGS }}" >> "$GITHUB_ENV" + - name: Configure Python + if: matrix.with-python == true + run: | + cd build + cmake .. -DBUILD_PYTHON=ON ${{ env.CMAKE_ARGS }} + - name: Build & install Python + if: matrix.with-python == true + run: | + cd build + cmake --build . --target pyPhotosynthesis ${{ env.BUILD_ARGS }} + cmake --install . --prefix /home/runner/miniconda3/envs/ephoto ################################### # Test Python ################################### - # - name: Test Python Interfaces (CONDA) - # if: matrix.with-elf == false && matrix.with-valgrind == false - # run: | - # pytest -sv tests/python + - name: Test Python Interfaces + if: matrix.with-python == true + run: | + pytest -sv tests/python ################################### # Docs tests @@ -335,7 +348,7 @@ jobs: if: matrix.with-coverage == true && matrix.os != 'windows-latest' uses: codecov/codecov-action@v4 with: - name: ${{ matrix.os }}-${{ matrix.with-coverage }}-${{ matrix.with-asan }}-${{ matrix.with-elf }}-${{ matrix.with-valgrind }} + name: ${{ matrix.os }}-${{ matrix.sundials-version }}-${{ matrix.with-coverage }}-${{ matrix.with-asan }}-${{ matrix.with-elf }}-${{ matrix.with-valgrind }}-${{ matrix.with-mpi }}-${{ matrix.with-python }} token: ${{ secrets.CODECOV_TOKEN }} file: coverage/coverage.info functionalities: gcov diff --git a/.gitignore b/.gitignore index 3005da0..d0629ed 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,6 @@ Doxyfile doc/html doc/latex -*~ \ No newline at end of file +*~ + +__pycache__ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 00e4585..7c8f134 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ else() endif() # Prohibit in-source build if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") - message(FATAL_ERROR "In-source build prohibited.") + message(FATAL_ERROR "In-source build prohibited.\nCMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}\nCMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}") endif("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${PROJECT_SOURCE_DIR}/cmake") @@ -243,12 +243,14 @@ install( ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} PUBLIC_HEADER DESTINATION EPHOTOSYNTHESIS_INSTALL_INCLUDEDIR + COMPONENT CXX ) install( EXPORT ePhotosynthesisTargets FILE ePhotosynthesisTargets.cmake NAMESPACE ePhotosynthesis:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ePhotosynthesis + COMPONENT CXX ) include(CMakePackageConfigHelpers) write_basic_package_version_file( @@ -264,10 +266,12 @@ install( FILES "${CMAKE_CURRENT_BINARY_DIR}/ePhotosynthesisConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/ePhotosynthesisConfigVersion.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ePhotosynthesis + COMPONENT CXX ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/ePhotosynthesis_export.h DESTINATION ${EPHOTOSYNTHESIS_INSTALL_INCLUDEDIR} + COMPONENT CXX ) # install( # DIRECTORY param @@ -504,15 +508,56 @@ endif() ########## if(BUILD_PYTHON) - find_package(PythonInterp 3) - find_package(PythonLibs 3 REQUIRED) - add_library(pyPhotosynthesis MODULE ${SOURCES}) - include_directories(${PYTHON_INCLUDE_DIRS}) - find_package(Boost 1.36.0 REQUIRED COMPONENTS regex python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}) - target_link_libraries(pyPhotosynthesis boost_python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}) - target_link_libraries(pyPhotosynthesis boost_regex) - target_link_libraries(pyPhotosynthesis ${PYTHON_LIBRARIES}) - target_link_libraries(pyPhotosynthesis ${SUNDIALS_LIBRARIES}) - target_compile_options(pyPhotosynthesis PRIVATE -DBUILD_PYTHON) - set_target_properties(pyPhotosynthesis PROPERTIES PREFIX "") + set(PYTHON_PACKAGE_NAME ePhotosynthesis) + set(PYTHON_LIBRARY_NAME pyPhotosynthesis) + find_package( + Python REQUIRED COMPONENTS + Interpreter Development.Module + ) + find_package( + Boost 1.36.0 REQUIRED COMPONENTS + regex python${Python_VERSION_MAJOR}${Python_VERSION_MINOR} + ) + add_library(${PYTHON_LIBRARY_NAME} MODULE ${SOURCES}) + target_link_libraries( + ${PYTHON_LIBRARY_NAME} + ${Boost_LIBRARIES} + Python::Module + ${SUNDIALS_LIBRARIES} + ) + target_include_directories( + ${PYTHON_LIBRARY_NAME} PUBLIC + ${Boost_INCLUDE_DIRS} + ${Python_INCLUDE_DIRS} + ${SUNDIALS_INCLUDE_DIRS} + $ + $ + $ + ) + target_compile_options( + ${PYTHON_LIBRARY_NAME} PRIVATE -DBUILD_PYTHON + -DPYTHON_LIBRARY_NAME=${PYTHON_LIBRARY_NAME} + ) + set_target_properties(${PYTHON_LIBRARY_NAME} PROPERTIES PREFIX "") + + # Tests (requires the Python package actually be installed) + add_test(NAME python_tests + COMMAND pytest -svx ${PROJECT_SOURCE_DIR}/tests/python) + + # Installation + cmake_path( + APPEND Python_SITEARCH ${PYTHON_PACKAGE_NAME} + OUTPUT_VARIABLE EPHOTOSYNTHESIS_INSTALL_PYTHONDIR + ) + install( + TARGETS ${PYTHON_LIBRARY_NAME} + DESTINATION "${EPHOTOSYNTHESIS_INSTALL_PYTHONDIR}" + COMPONENT Python + ) + configure_file(cmake/__init__.py.in __init__.py @ONLY) + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/__init__.py + DESTINATION "${EPHOTOSYNTHESIS_INSTALL_PYTHONDIR}" + COMPONENT Python + ) endif() diff --git a/bin/ePhotosynthesis.cpp b/bin/ePhotosynthesis.cpp index 4c21aab..1347960 100644 --- a/bin/ePhotosynthesis.cpp +++ b/bin/ePhotosynthesis.cpp @@ -55,29 +55,6 @@ using namespace ePhotosynthesis; if (result.count(#x) == 0 && inputs.count(#x) > 0) \ x = inputs.at(#x); -#define displayInputVar(fmt, src, val) \ - printf("Input variable \"%s\" = " #fmt "\n", #src, val) - -#define assignInputVarD(src, dst) \ - if (inputs.count(#src) > 0) { \ - theVars-> dst = static_cast(stof(inputs.at(#src), nullptr)); \ - displayInputVar(%lf, src, theVars-> dst); \ - } else \ - printf("Input variable \"%s\" not set\n", #src) -#define assignInputVarI(src, dst) \ - if (inputs.count(#src) > 0) { \ - theVars-> dst = stoi(inputs.at(#src), nullptr); \ - displayInputVar(%d, src, theVars-> dst); \ - } else \ - printf("Input variable \"%s\" not set\n", #src) - -#define setInputVarB(src, mod, dst) \ - if (inputs.count(#src) > 0) { \ - modules::mod::set ## dst ((bool)(stoi(inputs.at(#src), nullptr))); \ - displayInputVar(%d, src, stoi(inputs.at(#src), nullptr)); \ - } else \ - printf("Input variable \"%s\" not set\n", #src) - enum DriverType { None, @@ -125,9 +102,9 @@ int main(int argc, const char* argv[]) { ("c,c3", "Use the C3 model, automatically set to true for EPS driver", cxxopts::value(useC3)->default_value("false")) ("rubiscomethod", "The method to use for rubisco calculations. Choices are:\n" - "1 - (default) Use enzyme concentration for\n" + "1 - Use enzyme concentration for\n" " calculation\n" - "2 - Use the michaelis menton and enzyme\n" + "2 - (default) Use the michaelis menton and enzyme\n" " concentration together for calculation", cxxopts::value(RUBISCOMETHOD)) ("t,abstol", "Absolute tolerance for calculations", cxxopts::value(abstol)->default_value("1e-5")) @@ -171,68 +148,29 @@ int main(int argc, const char* argv[]) { if (driverChoice == EPS) useC3 = true; + Variables *theVars = new Variables(); + // Ensure that command line argument takes precedence + if (result.count("Tp") != 0) { + theVars->Tp = Tp; + inputs["Tp_SET"] = std::to_string(Tp); + } if (fileProvided(evn, evn)) - readFile(evn, inputs); + theVars->readParam(evn, inputs); if (fileProvided(atpcost, atpcost)) - readFile(atpcost, inputs); - Variables *theVars = new Variables(); + theVars->readParam(atpcost, inputs); if (fileProvided(enzyme, enzymeFile)) { - std::cerr << "ENZYME DATA PROVIDED" << std::endl; - readFile(enzymeFile, theVars->EnzymeAct, true); + theVars->readEnzymeAct(enzymeFile); } else { if (useC3) throw std::runtime_error("Enzyme data required if --c3 set (automatically true for EPS driver)"); } - assignInputVarD(CO2, CO2_in); - assignInputVarD(PAR, TestLi); - assignInputVarD(ATPCost, TestATPCost); - if (result.count("Tp") == 0) { - assignInputVarD(WeatherTemperature, Tp); - Tp = theVars->Tp; - } - assignInputVarI(GRNC, GRNC); - setInputVarB(SucPath, CM, TestSucPath); - // Read the GRN data and assign it into the correct positions // based on the expected order - if (fileProvided(grn, grnFile)) { - std::cerr << "GRN DATA PROVIDED" << std::endl; - std::map glymaID_map; - readFile(grnFile, glymaID_map); - double pcfactor = 1.0 / 0.973; - if (inputs.count("ProteinTotalRatio") > 0) - pcfactor = 1.0 / static_cast(stof(inputs.at("ProteinTotalRatio"), nullptr)); - size_t i = 0; - for (auto it = glymaID_order.begin(); it != glymaID_order.end(); it ++, i++) { - double iVfactor = pcfactor; - try { - if ((i >= 33) && (theVars->GRNC == 0)) - iVfactor = 1.0; - else - iVfactor = pcfactor * glymaID_map.at(*it); - } catch (const std::out_of_range&) { - // Do nothing - printf("GlymaID \"%s\" not present.\n", it->c_str()); - } - if (i < 33) { - theVars->VfactorCp[i] = iVfactor; - } else if (i == 33) { - modules::BF::setcATPsyn(iVfactor); - } else if (i == 34) { - modules::BF::setCPSi(iVfactor); - } else if (i == 35) { - modules::FI::setcpsii(iVfactor); - } else if (i == 36) { - modules::BF::setcNADPHsyn(iVfactor); - } else { - printf("More GlymaIDs than expected.\n"); - exit(EXIT_FAILURE); - } - } - } + if (fileProvided(grn, grnFile)) + theVars->readGRN(grnFile); - theVars->Tp = Tp; + Tp = theVars->Tp; theVars->record = record; theVars->useC3 = useC3; theVars->RUBISCOMETHOD = RUBISCOMETHOD; diff --git a/cmake/__init__.py.in b/cmake/__init__.py.in new file mode 100644 index 0000000..071a82d --- /dev/null +++ b/cmake/__init__.py.in @@ -0,0 +1 @@ +from .pyPhotosynthesis import Variables, modules, drivers diff --git a/environment.yml b/environment.yml index e0fb6cf..e4ee280 100644 --- a/environment.yml +++ b/environment.yml @@ -10,4 +10,5 @@ dependencies: - pytest - python - sundials + - numpy diff --git a/include/Variables.hpp b/include/Variables.hpp index 835bbd4..40495b1 100644 --- a/include/Variables.hpp +++ b/include/Variables.hpp @@ -91,7 +91,7 @@ class Variables { Construct a new variables instance for a given sundials context \param[in, out] ctx Sundials context. */ - Variables(SUNContext* ctx = nullptr); + Variables(SUNContext* ctx); /** Get the shared pointer to the Sundials context. \return Sundials context shared pointer. @@ -106,12 +106,11 @@ class Variables { SUNContext& context() { return *(_context.get()); } -#else // SUNDIALS_CONTEXT_REQUIRED +#endif // SUNDIALS_CONTEXT_REQUIRED /** Construct a new variables instance for a given sundials context */ - Variables() {} -#endif // SUNDIALS_CONTEXT_REQUIRED + Variables(); /** Copy constructor for a pointer to another variables instance. Some variables are not included in the default copy (e.g. alfa, @@ -125,6 +124,30 @@ class Variables { Variables& operator=(const Variables& other); friend std::ostream& operator<<(std::ostream &out, const Variables *in); + /** + Read parameters from a file. + \param[in] File to read. + */ + void readParam(const std::string& fname); + /** + Read parameters from a file, checking for duplicates + \param[in] File to read. + \param[in, out] Map that read variables should be checked + against for duplicates and copied into. + */ + void readParam(const std::string& fname, + std::map& inputs); + /** + Read Enzyme activities from a file. + \param[in] File to read. + */ + void readEnzymeAct(const std::string& fname); + /** + Read genetic regulatory network expression data from a file. + \param[in] File to read. + */ + void readGRN(const std::string& fname); + bool record = false; bool BF_FI_com = false; bool EPS_SUCS_com = false; @@ -168,7 +191,8 @@ class Variables { double lightParam = 0.; const double alpha1 = 0.87; const double alpha2 = 1.03; - double sensitivity_sf = 1.0; //a scaling factor for enzymes + double sensitivity_sf = 1.0; //!< a scaling factor for enzymes + double ProteinTotalRatio = 0.973; //!< Scaling factor for CM GRN expression levels // Parameters arr PR_Param = zeros(2); diff --git a/include/definitions.hpp b/include/definitions.hpp index 3961766..ef9093e 100644 --- a/include/definitions.hpp +++ b/include/definitions.hpp @@ -36,6 +36,8 @@ #include #include +#define STRINGIZE(x) #x + #if ((SUNDIALS_VERSION_MAJOR < 6) || (SUNDIALS_VERSION_MAJOR == 6 && SUNDIALS_VERSION_MINOR <= 7)) #include #endif diff --git a/include/drivers/EPS_Driver.hpp b/include/drivers/EPS_Driver.hpp index 1a6b0cf..8937f62 100644 --- a/include/drivers/EPS_Driver.hpp +++ b/include/drivers/EPS_Driver.hpp @@ -61,6 +61,9 @@ class EPSDriver : public Driver { ParaNum = para; Ratio = ratio; this->Tp = Tp; + inputVars->useC3 = true; + if (inputVars->useC3 && inputVars->EnzymeAct.empty()) + throw std::runtime_error("EnzymeAct must be set if useC3 is True (automatically set for EPS driver)"); } ~EPSDriver() override; diff --git a/scripts/devtasks.py b/scripts/devtasks.py index 85498d3..ff28ca3 100644 --- a/scripts/devtasks.py +++ b/scripts/devtasks.py @@ -4,6 +4,7 @@ import argparse import pprint import subprocess +import glob _source_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) @@ -67,11 +68,15 @@ def __init__(self, args, cmds=None, output_file=None, **kwargs): cmdS = '\n\t'.join(cmds) print(f"Running\n{cmdS}\n" f"with {pprint.pformat(kwargs)}") + if kwargs.get('env', None): + kwargs['env'] = dict(os.environ, **kwargs['env']) output_str = b'' if output_file: kwargs.update(capture_output=True) for x in cmds: ires = subprocess.run(x.split(), **kwargs) + if ires.returncode != 0: + raise RuntimeError(f'Error in running \'{x}\'') if output_file: output_str += ires.stdout if output_file: @@ -86,10 +91,11 @@ def adjust_args(cls, args): class BuildSubTask(SubTask): def __init__(self, args, config_args=None, build_args=None, - build_kwargs=None, **kwargs): + build_kwargs=None, build_env=None, **kwargs): self.adjust_args(args) if build_kwargs is None: build_kwargs = {} + build_kwargs.setdefault('env', build_env) if not args.dont_build: build(args, config_args=config_args, build_args=build_args, **build_kwargs) @@ -106,15 +112,26 @@ def adjust_args(cls, args): class build(SubTask): - def __init__(self, args, config_args=None, build_args=None): + def __init__(self, args, config_args=None, build_args=None, + install_args=None, **kwargs): BuildSubTask.adjust_args(args) if config_args is None: config_args = [] if build_args is None: build_args = [] + if install_args is None: + install_args = [] config_args += ['-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON'] if args.with_asan: config_args += ['-DWITH_ASAN=ON'] + if args.only_python: + build_args += ['--target pyPhotosynthesis'] + install_args += ['--component', 'Python'] + args.with_python = True + if args.with_python: + config_args += ['-DBUILD_PYTHON:BOOL=ON'] + if not args.dont_install: + config_args += [f'-DCMAKE_INSTALL_PREFIX={args.install_dir}'] if sys.platform != 'win32': build_args += ['--', '-j', str(args.njobs)] cmds = [ @@ -122,16 +139,35 @@ def __init__(self, args, config_args=None, build_args=None): f"cmake --build . --config {args.build_type}" f" {' '.join(build_args)}", ] + if args.with_python and not args.only_python: + cmds += [ + f"cmake --build . --config {args.build_type} " + f"--target pyPhotosynthesis" + ] if not args.dont_install: + if not os.path.isdir(args.install_dir): + os.mkdir(args.install_dir) cmds += [ - f"cmake --install . --prefix {args.install_dir}" + f"cmake --install . --prefix {args.install_dir} " + f"{' '.join(install_args)}" ] + if args.with_python and not getattr(args, 'only_python', False): + cmds += [ + f"cmake --install . --prefix {args.install_dir} " + f"--component Python {' '.join(install_args)}" + ] + if ((args.rebuild and (not args.dont_install) and + os.path.isdir(args.install_dir))): + subdir = glob.glob(os.path.join(args.install_dir, '*')) + if (not subdir) or subdir == [os.path.join(args.install_dir, + 'ePhotosynthesis')]: + shutil.rmtree(args.install_dir) if args.rebuild and os.path.isdir(args.build_dir): shutil.rmtree(args.build_dir) if not os.path.isdir(args.build_dir): os.mkdir(args.build_dir) - super(build, self).__init__(args, cmds=cmds, - cwd=args.build_dir) + kwargs.setdefault('cwd', args.build_dir) + super(build, self).__init__(args, cmds=cmds, **kwargs) class update_readme(BuildSubTask): @@ -172,7 +208,7 @@ def __init__(self, args): class test(BuildSubTask): def __init__(self, args, test_flags=None, - config_args=None, build_args=None): + config_args=None, build_args=None, **kwargs): if test_flags is None: test_flags = [] if config_args is None: @@ -181,23 +217,44 @@ def __init__(self, args, test_flags=None, build_args = [] config_args += ['-DBUILD_TESTS:BOOL=ON'] test_flags += ['-C', args.build_type] + pytest_flags = ['-sv'] if args.preserve_output: config_args += ['-DPRESERVE_TEST_OUTPUT:BOOL=ON'] if args.stop_on_error: test_flags += ['--stop-on-failure'] + pytest_flags += ['-x'] if args.verbose: test_flags += ['--output-on-failure', '-VV'] - if args.show_tests: - cmds = [ - 'ctest -N' - ] + pytest_flags += ['-v'] + if args.only_python: + args.with_python = True + if args.with_python: + assert not args.dont_install + # kwargs.setdefault('env', {}) + # kwargs['env'].setdefault( + # 'PYTHONPATH', os.environ.get('PYTHONPATH', '')) + # kwargs['env']['PYTHONPATH'] = os.pathsep.join([ + # x for x in + # [args.install_dir, + # kwargs['env']['PYTHONPATH']] + # if x + # ]) + if args.only_python: + testdir = os.path.join(_source_dir, 'tests', 'python') + cmds = [f"python -m pytest {' '.join(pytest_flags)} {testdir}"] else: - cmds = [ - f"ctest {' '.join(test_flags)}" - ] + if args.show_tests: + cmds = [ + 'ctest -N' + ] + else: + cmds = [ + f"ctest {' '.join(test_flags)}" + ] super(test, self).__init__(args, cmds=cmds, config_args=config_args, - build_args=build_args) + build_args=build_args, + **kwargs) class ephoto(BuildSubTask): @@ -320,10 +377,10 @@ def __init__(self, args): '--show-tests', action='store_true', help="Show the set of discovered tests") parser_test.add_argument( - '--verbose', action='store_true', + '--verbose', '-v', action='store_true', help="Turn on verbose test output") parser_test.add_argument( - '--stop-on-error', action='store_true', + '--stop-on-error', '-x', action='store_true', help="Stop running tests after the first error") parser_test.add_argument( '--preserve-output', action='store_true', @@ -414,6 +471,14 @@ def __init__(self, args): help="Type of build", choices=["Debug", "Release"], subparsers={'task': build_tasks}) + parser.add_argument( + '--with-python', action='store_true', + help="Build the Python interface", + subparsers={'task': build_tasks + ['docs']}) + parser.add_argument( + '--only-python', action='store_true', + help="Only run the Python tests", + subparsers={'task': build_tasks}) parser.add_argument( '--dont-build', action='store_true', diff --git a/src/Condition.cpp b/src/Condition.cpp index 2dac70b..6e84f2b 100644 --- a/src/Condition.cpp +++ b/src/Condition.cpp @@ -136,6 +136,7 @@ void ePhotosynthesis::readFile(const std::string &filename, std::map tempVec; std::string input; std::ifstream inputfile(filename); + typename std::map::iterator existing; if(inputfile.fail()) { std::string errmsg = "Could not open " + filename + " for reading"; throw std::runtime_error(errmsg); @@ -144,6 +145,12 @@ void ePhotosynthesis::readFile(const std::string &filename, std::map(tempVec[0], tempVec[1])); } } @@ -152,6 +159,7 @@ void ePhotosynthesis::readFile(const std::string &filename, std::map tempVec; std::string input; std::ifstream inputfile(filename); + typename std::map::iterator existing; if(inputfile.fail()) { std::string errmsg = "Could not open " + filename + " for reading"; throw std::runtime_error(errmsg); @@ -167,6 +175,12 @@ void ePhotosynthesis::readFile(const std::string &filename, std::map(tempVec[0], d)); } } diff --git a/src/Variables.cpp b/src/Variables.cpp index 3d64d73..7256dd1 100644 --- a/src/Variables.cpp +++ b/src/Variables.cpp @@ -1,5 +1,10 @@ #include #include "Variables.hpp" +#include "modules/CM.hpp" +#include "modules/BF.hpp" +#include "modules/FI.hpp" +#include "globals.hpp" + #ifdef SUNDIALS_CONTEXT_REQUIRED std::shared_ptr ePhotosynthesis::global_context(NULL); @@ -39,6 +44,7 @@ Variables::Variables(SUNContext* ctx) : _context(), _context_flags(0) { _context.reset(ctx); } } +Variables::Variables() : Variables((SUNContext*)nullptr) {} Variables::Variables(const Variables& other) : _context(other._context), _context_flags(other._context_flags) { *this = other; @@ -48,6 +54,7 @@ Variables::Variables(const Variables* other) : *this = *other; } #else // SUNDIALS_CONTEXT_REQUIRED +Variables::Variables() {} Variables::Variables(const Variables& other) : Variables() { *this = other; } @@ -153,6 +160,7 @@ Variables& Variables::operator=(const Variables &other) { FluxTR = other.FluxTR; RROEA_KE = other.RROEA_KE; useC3 = other.useC3; + ProteinTotalRatio = other.ProteinTotalRatio; return *this; } @@ -287,3 +295,120 @@ out << std::endl; out << "useC3 = " << in->useC3 << std::endl; return out; } + +void Variables::readParam(const std::string& fname) { + std::map inputs; + readParam(fname, inputs); +} +void Variables::readParam(const std::string& fname, + std::map& inputs) { + if (fname.empty()) + return; + std::cout << "PARAMETER FILE PROVIDED: " << fname << std::endl; +#define convD(x) static_cast(stof(x, nullptr)) +#define convI(x) stoi(x, nullptr) +#define convB(x) ((bool)(stoi(x, nullptr))) +#define setInputVar(dst, x, dummy) this-> dst = x +#define setModVar(dst, x, mod) modules::mod::set ## dst(x) +#define assign(src, dst, set, conv, ...) \ + { \ + std::string ikey, ival; \ + if (inputs.count(#src) > 0) { \ + ikey = #src; \ + } else if (inputs.count(#dst) > 0) { \ + ikey = #dst; \ + } \ + if (!ikey.empty()) { \ + ival = inputs.at(ikey); \ + if (inputs.count(#dst "_SET") > 0) { \ + if (ival != inputs.at(#dst "_SET")) { \ + std::cout << "readParam: " << \ + "Parameter \"" << #dst << "\" already set." << \ + " Using previous value." << std::endl << \ + " Previous value : " << \ + inputs.at(#dst "_SET") << std::endl << \ + " Discarded value: " << ival << std::endl; \ + } \ + inputs.erase(ikey); \ + } else { \ + set(dst, conv(inputs.at(#src)), __VA_ARGS__); \ + std::cout << "readParam: " << \ + "Parameter \"" << #dst << "\" (" << \ + ival << ") read from \"" << ikey << \ + "\"" << std::endl; \ + inputs[#dst "_SET"] = ival; \ + } \ + } \ +} +#define assignInputVar(src, dst, conv) \ + assign(src, dst, setInputVar, conv, ) +#define assignModVar(src, mod, dst, conv) \ + assign(src, dst, setModVar, conv, mod) +#define assignInputVarD(src, dst) assignInputVar(src, dst, convD) +#define assignInputVarI(src, dst) assignInputVar(src, dst, convI) +#define setInputVarB(src, mod, dst) assignModVar(src, mod, dst, convB) + readFile(fname, inputs); + assignInputVarD(CO2, CO2_in); + assignInputVarD(PAR, TestLi); + assignInputVarD(ATPCost, TestATPCost); + assignInputVarD(WeatherTemperature, Tp); + assignInputVarD(ProteinTotalRatio, ProteinTotalRatio); + assignInputVarI(GRNC, GRNC); + setInputVarB(SucPath, CM, TestSucPath); +#undef setInputVarB +#undef assignInputVarD +#undef assignInputVarI +#undef assignModVar +#undef assignInputVar +#undef assign +#undef setInputVar +#undef setModVar +#undef convD +#undef convI +#undef convB +} +void Variables::readEnzymeAct(const std::string& fname) { + if (fname.empty()) + return; + std::cout << "ENZYME DATA PROVIDED: " << fname << std::endl; + readFile(fname, EnzymeAct, true); +} +void Variables::readGRN(const std::string& fname) { + if (fname.empty()) + return; + // Read the GRN data and assign it into the correct positions + // based on the expected order + std::cout << "GRN DATA PROVIDED: " << fname << std::endl; + std::map glymaID_map; + readFile(fname, glymaID_map); + double pcfactor = 1.0 / this->ProteinTotalRatio; + size_t i = 0; + for (auto it = glymaID_order.begin(); it != glymaID_order.end(); it ++, i++) { + double iVfactor = pcfactor; + try { + if ((i >= 33) && (this->GRNC == 0)) + iVfactor = 1.0; + else + iVfactor = pcfactor * glymaID_map.at(*it); + } catch (const std::out_of_range&) { + // Do nothing + std::cerr << "GlymaID \"" << *it << "\" not present." << std::endl; + } + if (i < 33) { + this->VfactorCp[i] = iVfactor; + } else if (i == 33) { + modules::BF::setcATPsyn(iVfactor); + } else if (i == 34) { + modules::BF::setCPSi(iVfactor); + } else if (i == 35) { + modules::FI::setcpsii(iVfactor); + } else if (i == 36) { + modules::BF::setcNADPHsyn(iVfactor); + } else { + throw std::runtime_error("More GlymaIDs than expected " + " (expected <= 37, provided " + + std::to_string(glymaID_order.size()) + + ")"); + } + } +} diff --git a/src/drivers/driver.cpp b/src/drivers/driver.cpp index 67db131..3b8ff5c 100644 --- a/src/drivers/driver.cpp +++ b/src/drivers/driver.cpp @@ -58,6 +58,8 @@ Driver::Driver(Variables *theVars, const double startTime, const double stepSize data = nullptr; origVars = nullptr; intermediateRes = nullptr; + if (inputVars->useC3 && inputVars->EnzymeAct.empty()) + throw std::runtime_error("EnzymeAct must be set if useC3 is True (automatically set for EPS driver)"); } arr Driver::run() { diff --git a/src/python/wrappers.cpp b/src/python/wrappers.cpp index b9a6556..602426d 100644 --- a/src/python/wrappers.cpp +++ b/src/python/wrappers.cpp @@ -10,107 +10,117 @@ using namespace ePhotosynthesis; namespace bp = boost::python; -BOOST_PYTHON_MODULE(pyPhotosynthesis) { - bp::object package = bp::scope(); - package.attr("__path__") = "pyPhotosynthesis"; +#ifndef PYTHON_LIBRARY_NAME +#define PYTHON_LIBRARY_NAME pyPhotosynthesis +#endif + +BOOST_PYTHON_MODULE(PYTHON_LIBRARY_NAME) { + // bp::object package = bp::scope(); + // package.attr("__path__") = STRINGIZE(PYTHON_LIBRARY_NAME); + void (Variables::*readParam1)(const std::string&) = &Variables::readParam; + void (Variables::*readParam2)(const std::string&, std::map&) = &Variables::readParam; bp::class_("Variables") - .def_readwrite("record", &Variables::record) - .def_readwrite("BF_FI_com", &Variables::BF_FI_com) - .def_readwrite("EPS_SUCS_com", &Variables::EPS_SUCS_com) - .def_readwrite("FIBF_PSPR_com", &Variables::FIBF_PSPR_com) - .def_readwrite("PR_PS_com", &Variables::PR_PS_com) - .def_readwrite("PSPR_SUCS_com", &Variables::PSPR_SUCS_com) - .def_readwrite("RROEA_EPS_com", &Variables::RROEA_EPS_com) - .def_readwrite("RedoxReg_RA_com", &Variables::RedoxReg_RA_com) - .def_readwrite("RuACT_EPS_com", &Variables::RuACT_EPS_com) - .def_readwrite("XanCycle_BF_com", &Variables::XanCycle_BF_com) - .def_readwrite("GP", &Variables::GP) - .def_readwrite("GRNC", &Variables::GRNC) - .def_readwrite("GRNT", &Variables::GRNT) - .def_readwrite("RUBISCOMETHOD", &Variables::RUBISCOMETHOD) - .def_readonly("AVR", &Variables::AVR) - .def_readonly("HPR", &Variables::HPR) - .def_readonly("O2", &Variables::O2) - .def_readwrite("CO2_cond", &Variables::CO2_cond) - .def_readwrite("GLight", &Variables::GLight) - .def_readwrite("O2_cond", &Variables::O2_cond) - .def_readwrite("PS12ratio", &Variables::PS12ratio) - .def_readwrite("ADP", &Variables::ADP) - .def_readwrite("Pi", &Variables::Pi) - .def_readwrite("TestATPCost", &Variables::TestATPCost) - .def_readwrite("CO2_in", &Variables::CO2_in) - .def_readwrite("TestLi", &Variables::TestLi) - .def_readwrite("PS2BF_Pi", &Variables::PS2BF_Pi) - .def_readwrite("PS_PR_Param", &Variables::PS_PR_Param) - .def_readwrite("Tp", &Variables::Tp) - .def_readwrite("alfa", &Variables::alfa) - .def_readwrite("fc", &Variables::fc) - .def_readwrite("lightParam", &Variables::lightParam) - .def_readonly("alpha1", &Variables::alpha1) - .def_readonly("alpha2", &Variables::alpha2) - .def_readwrite("PR_Param", &Variables::PR_Param) - .def_readwrite("BF_Param", &Variables::BF_Param) - .def_readwrite("FI_PARAM", &Variables::FI_PARAM) - .def_readwrite("FI_Param", &Variables::FI_Param) - .def_readwrite("RROEA_Param", &Variables::RROEA_Param) - .def_readwrite("RuACT_Param", &Variables::RuACT_Param) - .def_readwrite("SUCS_Param", &Variables::SUCS_Param) - .def_readwrite("XanCycle_Param", &Variables::XanCycle_Param) - .def_readwrite("BF_Vel", &Variables::BF_Vel) - .def_readwrite("FI_Vel", &Variables::FI_Vel) - .def_readwrite("PR_Vel", &Variables::PR_Vel) - .def_readwrite("PS_Vel", &Variables::PS_Vel) - .def_readwrite("RROEA_Vel", &Variables::RROEA_Vel) - .def_readwrite("RedoxReg_Vel", &Variables::RedoxReg_Vel) - .def_readwrite("RuACT_Vel", &Variables::RuACT_Vel) - .def_readwrite("SUCS_Vel", &Variables::SUCS_Vel) - .def_readwrite("XanCycle_Vel", &Variables::XanCycle_Vel) - .def_readwrite("BFRatio", &Variables::BFRatio) - .def_readwrite("FIRatio", &Variables::FIRatio) - .def_readwrite("PRRatio", &Variables::PRRatio) - .def_readwrite("PSRatio", &Variables::PSRatio) - .def_readwrite("RacRatio", &Variables::RacRatio) - .def_readwrite("SUCRatio", &Variables::SUCRatio) - .def_readwrite("XanRatio", &Variables::XanRatio) - .def_readwrite("EnzymeAct", &Variables::EnzymeAct) - .def_readwrite("VfactorCp", &Variables::VfactorCp) - .def_readwrite("VfactorT", &Variables::VfactorT) - .def_readwrite("BF_Pool", &Variables::BF_Pool) - .def_readwrite("FIBF_Pool", &Variables::FIBF_Pool) - .def_readwrite("FI_Pool", &Variables::FI_Pool) - .def_readwrite("RROEA_Pool", &Variables::RROEA_Pool) - .def_readwrite("RuACT_Pool", &Variables::RuACT_Pool) - .def_readwrite("SUCS_Pool", &Variables::SUCS_Pool) - .def_readwrite("BF_RC", &Variables::BF_RC) - .def_readwrite("FI_RC", &Variables::FI_RC) - .def_readwrite("RROEA_RC", &Variables::RROEA_RC) - .def_readwrite("RuACT_RC", &Variables::RuACT_RC) - .def_readwrite("BF2OUT", &Variables::BF2OUT) - .def_readwrite("PR2OUT", &Variables::PR2OUT) - .def_readwrite("PS2OUT", &Variables::PS2OUT) - .def_readwrite("SUCS2OUT", &Variables::SUCS2OUT) - .def_readwrite("XanCycle2OUT", &Variables::XanCycle2OUT) - .def_readwrite("FluxTR", &Variables::FluxTR) - .def_readwrite("RROEA_KE", &Variables::RROEA_KE) - .def_readwrite("BF_VEL", &Variables::BF_VEL) - .def_readwrite("CO2A", &Variables::CO2A) - .def_readwrite("FI_VEL", &Variables::FI_VEL) - .def_readwrite("PR_VEL", &Variables::PR_VEL) - .def_readwrite("PS_VEL", &Variables::PS_VEL) - .def_readwrite("RROEA_VEL", &Variables::RROEA_VEL) - .def_readwrite("RedoxReg_MP", &Variables::RedoxReg_MP) - .def_readwrite("RedoxReg_VEL", &Variables::RedoxReg_VEL) - .def_readwrite("RuACT_VEL", &Variables::RuACT_VEL) - .def_readwrite("SUCS_VEL", &Variables::SUCS_VEL) - .def_readwrite("XanCycle_VEL", &Variables::XanCycle_VEL) - .def_readwrite("useC3", &Variables::useC3); + .def("readParam", readParam1) + .def("readParam", readParam2) + .def("readEnzymeAct", &Variables::readEnzymeAct) + .def("readGRN", &Variables::readGRN) + .def_readwrite("record", &Variables::record) + .def_readwrite("BF_FI_com", &Variables::BF_FI_com) + .def_readwrite("EPS_SUCS_com", &Variables::EPS_SUCS_com) + .def_readwrite("FIBF_PSPR_com", &Variables::FIBF_PSPR_com) + .def_readwrite("PR_PS_com", &Variables::PR_PS_com) + .def_readwrite("PSPR_SUCS_com", &Variables::PSPR_SUCS_com) + .def_readwrite("RROEA_EPS_com", &Variables::RROEA_EPS_com) + .def_readwrite("RedoxReg_RA_com", &Variables::RedoxReg_RA_com) + .def_readwrite("RuACT_EPS_com", &Variables::RuACT_EPS_com) + .def_readwrite("XanCycle_BF_com", &Variables::XanCycle_BF_com) + .def_readwrite("GP", &Variables::GP) + .def_readwrite("GRNC", &Variables::GRNC) + .def_readwrite("GRNT", &Variables::GRNT) + .def_readwrite("RUBISCOMETHOD", &Variables::RUBISCOMETHOD) + .def_readonly("AVR", &Variables::AVR) + .def_readonly("HPR", &Variables::HPR) + .def_readonly("O2", &Variables::O2) + .def_readwrite("CO2_cond", &Variables::CO2_cond) + .def_readwrite("GLight", &Variables::GLight) + .def_readwrite("O2_cond", &Variables::O2_cond) + .def_readwrite("PS12ratio", &Variables::PS12ratio) + .def_readwrite("ADP", &Variables::ADP) + .def_readwrite("Pi", &Variables::Pi) + .def_readwrite("TestATPCost", &Variables::TestATPCost) + .def_readwrite("CO2_in", &Variables::CO2_in) + .def_readwrite("TestLi", &Variables::TestLi) + .def_readwrite("PS2BF_Pi", &Variables::PS2BF_Pi) + .def_readwrite("PS_PR_Param", &Variables::PS_PR_Param) + .def_readwrite("Tp", &Variables::Tp) + .def_readwrite("alfa", &Variables::alfa) + .def_readwrite("fc", &Variables::fc) + .def_readwrite("lightParam", &Variables::lightParam) + .def_readonly("alpha1", &Variables::alpha1) + .def_readonly("alpha2", &Variables::alpha2) + .def_readwrite("PR_Param", &Variables::PR_Param) + .def_readwrite("BF_Param", &Variables::BF_Param) + .def_readwrite("FI_PARAM", &Variables::FI_PARAM) + .def_readwrite("FI_Param", &Variables::FI_Param) + .def_readwrite("RROEA_Param", &Variables::RROEA_Param) + .def_readwrite("RuACT_Param", &Variables::RuACT_Param) + .def_readwrite("SUCS_Param", &Variables::SUCS_Param) + .def_readwrite("XanCycle_Param", &Variables::XanCycle_Param) + .def_readwrite("BF_Vel", &Variables::BF_Vel) + .def_readwrite("FI_Vel", &Variables::FI_Vel) + .def_readwrite("PR_Vel", &Variables::PR_Vel) + .def_readwrite("PS_Vel", &Variables::PS_Vel) + .def_readwrite("RROEA_Vel", &Variables::RROEA_Vel) + .def_readwrite("RedoxReg_Vel", &Variables::RedoxReg_Vel) + .def_readwrite("RuACT_Vel", &Variables::RuACT_Vel) + .def_readwrite("SUCS_Vel", &Variables::SUCS_Vel) + .def_readwrite("XanCycle_Vel", &Variables::XanCycle_Vel) + .def_readwrite("BFRatio", &Variables::BFRatio) + .def_readwrite("FIRatio", &Variables::FIRatio) + .def_readwrite("PRRatio", &Variables::PRRatio) + .def_readwrite("PSRatio", &Variables::PSRatio) + .def_readwrite("RacRatio", &Variables::RacRatio) + .def_readwrite("SUCRatio", &Variables::SUCRatio) + .def_readwrite("XanRatio", &Variables::XanRatio) + .def_readwrite("EnzymeAct", &Variables::EnzymeAct) + .def_readwrite("VfactorCp", &Variables::VfactorCp) + .def_readwrite("VfactorT", &Variables::VfactorT) + .def_readwrite("BF_Pool", &Variables::BF_Pool) + .def_readwrite("FIBF_Pool", &Variables::FIBF_Pool) + .def_readwrite("FI_Pool", &Variables::FI_Pool) + .def_readwrite("RROEA_Pool", &Variables::RROEA_Pool) + .def_readwrite("RuACT_Pool", &Variables::RuACT_Pool) + .def_readwrite("SUCS_Pool", &Variables::SUCS_Pool) + .def_readwrite("BF_RC", &Variables::BF_RC) + .def_readwrite("FI_RC", &Variables::FI_RC) + .def_readwrite("RROEA_RC", &Variables::RROEA_RC) + .def_readwrite("RuACT_RC", &Variables::RuACT_RC) + .def_readwrite("BF2OUT", &Variables::BF2OUT) + .def_readwrite("PR2OUT", &Variables::PR2OUT) + .def_readwrite("PS2OUT", &Variables::PS2OUT) + .def_readwrite("SUCS2OUT", &Variables::SUCS2OUT) + .def_readwrite("XanCycle2OUT", &Variables::XanCycle2OUT) + .def_readwrite("FluxTR", &Variables::FluxTR) + .def_readwrite("RROEA_KE", &Variables::RROEA_KE) + .def_readwrite("BF_VEL", &Variables::BF_VEL) + .def_readwrite("CO2A", &Variables::CO2A) + .def_readwrite("FI_VEL", &Variables::FI_VEL) + .def_readwrite("PR_VEL", &Variables::PR_VEL) + .def_readwrite("PS_VEL", &Variables::PS_VEL) + .def_readwrite("RROEA_VEL", &Variables::RROEA_VEL) + .def_readwrite("RedoxReg_MP", &Variables::RedoxReg_MP) + .def_readwrite("RedoxReg_VEL", &Variables::RedoxReg_VEL) + .def_readwrite("RuACT_VEL", &Variables::RuACT_VEL) + .def_readwrite("SUCS_VEL", &Variables::SUCS_VEL) + .def_readwrite("XanCycle_VEL", &Variables::XanCycle_VEL) + .def_readwrite("useC3", &Variables::useC3); python::exportModules(); python::exportDrivers(); } void python::exportModules() { - bp::object modModule(bp::handle<>(bp::borrowed(PyImport_AddModule("pyPhotosynthesis.modules")))); + bp::object modModule(bp::handle<>(bp::borrowed(PyImport_AddModule(STRINGIZE(PYTHON_LIBRARY_NAME) ".modules")))); bp::scope().attr("modules") = modModule; bp::scope modScope = modModule; bp::class_("CM", bp::no_init) @@ -122,7 +132,7 @@ void python::exportModules() { } void python::exportDrivers() { - bp::object driverModule(bp::handle<>(bp::borrowed(PyImport_AddModule("pyPhotosynthesis.drivers")))); + bp::object driverModule(bp::handle<>(bp::borrowed(PyImport_AddModule(STRINGIZE(PYTHON_LIBRARY_NAME) ".drivers")))); bp::scope().attr("drivers") = driverModule; bp::scope driverScope = driverModule; bp::class_ >("stl_vector_double") diff --git a/tests/python/test_drivers.py b/tests/python/test_drivers.py new file mode 100644 index 0000000..d78326e --- /dev/null +++ b/tests/python/test_drivers.py @@ -0,0 +1,116 @@ +import os +import pytest +import numpy as np +import ePhotosynthesis +from ePhotosynthesis import Variables + + +def test_Variables(): + x = Variables() + assert not x.useC3 + + +def test_Modules(): + assert hasattr(ePhotosynthesis, 'modules') + ePhotosynthesis.modules.CM.setTestSucPath(1) + ePhotosynthesis.modules.PR.setRUBISCOTOTAL(3) + + +def test_drivers(): + assert hasattr(ePhotosynthesis, 'drivers') + + +@pytest.fixture(scope="module") +def datadir(): + testsdir = os.path.dirname(os.path.dirname(__file__)) + return os.path.join(testsdir, 'data') + + +@pytest.fixture(scope="module") +def fname_InputEnzyme(datadir): + return os.path.join(datadir, "InputEnzyme.txt") + + +@pytest.fixture(scope="module") +def fname_InputEvn(datadir): + return os.path.join(datadir, "InputEvn.txt") + + +@pytest.fixture(scope="module") +def fname_InputATPCost(datadir): + return os.path.join(datadir, "InputATPCost.txt") + + +@pytest.fixture(scope="module") +def fname_InputGRNC(datadir): + return os.path.join(datadir, "InputGRNC.txt") + + +@pytest.fixture(scope="module") +def fname_expected_output(datadir): + + def wrapped_fname_expected_output(driver): + return os.path.join(datadir, f"ePhotoOutput_{driver}.txt") + + return wrapped_fname_expected_output + + +@pytest.fixture +def default_vars(): + x = Variables() + x.RUBISCOMETHOD = 2 + return x + + +@pytest.fixture +def C3_vars(fname_InputEvn, fname_InputATPCost, + fname_InputEnzyme, fname_InputGRNC): + x = Variables() + x.useC3 = True + x.readParam(fname_InputEvn) + x.readParam(fname_InputATPCost) + x.readEnzymeAct(fname_InputEnzyme) + x.readGRN(fname_InputGRNC) + x.RUBISCOMETHOD = 2 + x.debuglevel = 0 + ePhotosynthesis.modules.PR.setRUBISCOTOTAL(3) + return x + + +def test_EPS_error(default_vars): + startTime = 0.0 + stepSize = 1.0 + endTime = 5000.0 + maxSubsteps = 750 + atol = 1e-5 + rtol = 1e-4 + param = 1 + ratio = 1.0 + Tp = 25.0 + showWarn = False + with pytest.raises(RuntimeError): + ePhotosynthesis.drivers.EPSDriver( + default_vars, startTime, stepSize, endTime, maxSubsteps, + atol, rtol, param, ratio, Tp, showWarn) + + +def test_EPS(C3_vars, fname_expected_output): + startTime = 0.0 + stepSize = 1.0 + endTime = 5000.0 + maxSubsteps = 750 + atol = 1e-5 + rtol = 1e-4 + param = 1 + ratio = 1.0 + Tp = C3_vars.Tp + showWarn = False + # TODO: Actually read the file? + # expectedFile = fname_expected_output("EPS") + expected = np.array([30.3325, 33.50430665237476, 3.1717867365048513]) + x = ePhotosynthesis.drivers.EPSDriver( + C3_vars, startTime, stepSize, endTime, maxSubsteps, + atol, rtol, param, ratio, Tp, showWarn) + result = list(x.run()) + actual = np.array(result) + np.testing.assert_allclose(actual, expected, rtol=rtol, atol=atol)