Skip to content

Commit 9d8d687

Browse files
committed
Rearrange build so that Python package can be tested from the build directory without install
Update README with info on the Python package Don't re-use CVode solver memory between driver calls
1 parent c2ede23 commit 9d8d687

File tree

11 files changed

+230
-120
lines changed

11 files changed

+230
-120
lines changed

CMakeLists.txt

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -550,24 +550,36 @@ if(BUILD_PYTHON)
550550
COMMAND pytest -svx ${PROJECT_SOURCE_DIR}/tests/python)
551551

552552
# Installation
553+
# set(PYTHON_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR})
554+
cmake_path(
555+
APPEND CMAKE_CURRENT_BINARY_DIR ${PYTHON_PACKAGE_NAME}
556+
OUTPUT_VARIABLE PYTHON_BUILD_DIR
557+
)
553558
cmake_path(
554559
APPEND Python_SITEARCH ${PYTHON_PACKAGE_NAME}
555-
OUTPUT_VARIABLE EPHOTOSYNTHESIS_INSTALL_PYTHONDIR
560+
OUTPUT_VARIABLE PYTHON_INSTALL_DIR
556561
)
557562
message(STATUS "Python_SITEARCH = ${Python_SITEARCH}")
558563
message(STATUS "PYTHON_PACKAGE_NAME = ${PYTHON_PACKAGE_NAME}")
559-
message(STATUS "EPHOTOSYNTHESIS_INSTALL_PYTHONDIR = ${EPHOTOSYNTHESIS_INSTALL_PYTHONDIR}")
564+
message(STATUS "PYTHON_INSTALL_DIR = ${PYTHON_INSTALL_DIR}")
565+
message(STATUS "PYTHON_BUILD_DIR = ${PYTHON_BUILD_DIR}")
560566
install(
561567
TARGETS ${PYTHON_LIBRARY_NAME}
562-
DESTINATION "${EPHOTOSYNTHESIS_INSTALL_PYTHONDIR}"
568+
DESTINATION "${PYTHON_INSTALL_DIR}"
563569
COMPONENT Python
564-
EXCLUDE_FROM_ALL
565570
)
566-
configure_file(cmake/__init__.py.in __init__.py @ONLY)
571+
set_target_properties(
572+
${PYTHON_LIBRARY_NAME} PROPERTIES
573+
LIBRARY_OUTPUT_DIRECTORY ${PYTHON_BUILD_DIR}
574+
RUNTIME_OUTPUT_DIRECTORY ${PYTHON_BUILD_DIR}
575+
)
576+
configure_file(
577+
cmake/__init__.py.in
578+
${PYTHON_BUILD_DIR}/__init__.py @ONLY
579+
)
567580
install(
568-
FILES ${CMAKE_CURRENT_BINARY_DIR}/__init__.py
569-
DESTINATION "${EPHOTOSYNTHESIS_INSTALL_PYTHONDIR}"
581+
FILES ${PYTHON_BUILD_DIR}/__init__.py
582+
DESTINATION "${PYTHON_INSTALL_DIR}"
570583
COMPONENT Python
571-
EXCLUDE_FROM_ALL
572584
)
573585
endif()

README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ All of the packages required to build & run the library, tests, & documentation
1919
- [GoogleTest](https://google.github.io/googletest/) - (optional) needed to build the tests
2020
- [LCOV](https://wiki.documentfoundation.org/Development/Lcov) - (optional) needed to get test coverage
2121

22-
### Building
22+
### Building the C++ library & executable
2323
This can be built using a conda environment. This way, the cmake should be able to find the boost and sundials dependencies automatically. When using conda to manage the dependencies:
2424

2525
1. Be sure to activate the related env the next time after login.
@@ -37,6 +37,33 @@ cmake --build .
3737
cmake --install . (if desired)
3838
```
3939

40+
### Building the Python extension
41+
Similar to building the C++ library & executable, with the exception that the Python cmake target (pyPhotosynthesis) should be passed to the build command and the ``Python`` component should be specified in the install command if only the Python extension should be installed.
42+
43+
```
44+
mkdir build
45+
cd build
46+
cmake .. -DBUILD_PYTHON:BOOL=ON
47+
cmake --build . --target pyPhotosynthesis
48+
cmake --install . --component Python (if desired)
49+
```
50+
51+
The Python extension has the name ``ePhotosynthesis`` and can be used to run drivers from Python. E.g.
52+
53+
```
54+
import ePhotosynthesis
55+
theVars = ePhotosynthesis.Variables()
56+
theVars.readParam("InputEvn.txt")
57+
theVars.readParam("InputATPCost.txt")
58+
theVars.readEnzymeAct("InputEnzyme.txt")
59+
theVars.readGRN("InputGRNC.txt")
60+
theVars.RUBISCOMETHOD = 2
61+
theVars.debuglevel = 0
62+
ePhotosynthesis.modules.PR.setRUBISCOTOTAL(3)
63+
drv = ePhotosynthesis.drivers.EPSDriver(theVars, 0, 1, 5000, 750, 1e-5, 1e-4, 1, 1 25.0)
64+
results = drv.run()
65+
```
66+
4067
### Documentation
4168
The documentation is not automatically built. To build the docs run the following from the build directory (requires Doxygen)
4269
```
@@ -47,7 +74,7 @@ cmake --build . --target docs
4774
This will build the documentation and put the resulting files in the doc directory.
4875

4976
### Tests
50-
The tests are not automatically built. To build and run the tests, can be run the following from the build directory (requires GoogleTest)
77+
The tests are not automatically built. To build and run the tests, can be run the following from the build directory (requires GoogleTest). If ``-DBUILD_PYTHON:BOOL=ON`` is passed to the configuration step, the Python tests will also be run.
5178
```
5279
cmake .. -DBUILD_TESTS:BOOL=ON
5380
cmake --build .

include/Variables.hpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ namespace ePhotosynthesis {
6060
#ifdef SUNDIALS_CONTEXT_REQUIRED
6161
extern std::shared_ptr<SUNContext> global_context; //!< Global context
6262

63+
/**
64+
Create a new SUNContext instance.
65+
\returns SUNContext instance.
66+
*/
67+
SUNContext* create_sundials_context();
68+
6369
/**
6470
Initialize the global SUNContext.
6571
*/
@@ -69,7 +75,7 @@ void init_global_sundials_context();
6975
Destroy the global SUNContext.
7076
*/
7177
void cleanup_global_sundials_context();
72-
78+
7379
#endif // SUNDIALS_CONTEXT_REQUIRED
7480

7581
/**
@@ -90,8 +96,10 @@ class Variables {
9096
/**
9197
Construct a new variables instance for a given sundials context
9298
\param[in, out] ctx Sundials context.
99+
\param[in] flags Bitwise CONTEXT_FLAGS flags describing how the
100+
context was/should be created.
93101
*/
94-
Variables(SUNContext* ctx);
102+
Variables(SUNContext* ctx, const int flags = 0);
95103
/**
96104
Get the shared pointer to the Sundials context.
97105
\return Sundials context shared pointer.
@@ -111,6 +119,8 @@ class Variables {
111119
Construct a new variables instance for a given sundials context
112120
*/
113121
Variables();
122+
/** Destructor */
123+
~Variables();
114124
/**
115125
Copy constructor for a pointer to another variables instance.
116126
Some variables are not included in the default copy (e.g. alfa,

include/drivers/CVodeMem.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ class CVodeMem {
3232
\param y Specialized data vector containing the inputs for the solver.
3333
*/
3434
void cvode_mem_init(Driver* driver, realtype t0, N_Vector y);
35+
/**
36+
Destroy the ODE solver.
37+
*/
38+
void cvode_mem_free();
3539
private:
3640
CVodeMem() {
3741
initialized = false;

include/modules/BF.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class BF : public ModuleBase<BF, conditions::BFCondition> {
110110
FI_connect = false;
111111
PS_connect = false;
112112
RROEA_connect = false;
113-
N = 0;
113+
N = 1;
114114
TIME = 0.;
115115
conditions::BFCondition::reset();
116116
}

scripts/devtasks.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -229,17 +229,16 @@ def __init__(self, args, test_flags=None,
229229
pytest_flags += ['-v']
230230
if args.only_python:
231231
args.with_python = True
232-
if args.with_python:
233-
assert not args.dont_install
234-
# kwargs.setdefault('env', {})
235-
# kwargs['env'].setdefault(
236-
# 'PYTHONPATH', os.environ.get('PYTHONPATH', ''))
237-
# kwargs['env']['PYTHONPATH'] = os.pathsep.join([
238-
# x for x in
239-
# [args.install_dir,
240-
# kwargs['env']['PYTHONPATH']]
241-
# if x
242-
# ])
232+
if args.with_python and args.dont_install:
233+
kwargs.setdefault('env', {})
234+
kwargs['env'].setdefault(
235+
'PYTHONPATH', os.environ.get('PYTHONPATH', ''))
236+
kwargs['env']['PYTHONPATH'] = os.pathsep.join([
237+
x for x in
238+
[args.build_dir,
239+
kwargs['env']['PYTHONPATH']]
240+
if x
241+
])
243242
if args.only_python:
244243
testdir = os.path.join(_source_dir, 'tests', 'python')
245244
cmds = [f"python -m pytest {' '.join(pytest_flags)} {testdir}"]

src/Variables.cpp

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,19 @@
1111
#ifdef SUNDIALS_CONTEXT_REQUIRED
1212
std::shared_ptr<SUNContext> ePhotosynthesis::global_context(NULL);
1313

14+
SUNContext* ePhotosynthesis::create_sundials_context() {
15+
SUNContext* context = new SUNContext();
16+
SUNErrCode error_code = SUNContext_Create(SUN_COMM_NULL, context);
17+
if (error_code) {
18+
throw std::runtime_error("SUNContext_Create failed: " +
19+
std::string(SUNGetErrMsg(error_code)));
20+
}
21+
return context;
22+
}
23+
1424
void ePhotosynthesis::init_global_sundials_context() {
1525
if (!global_context) {
16-
SUNContext* context = new SUNContext();
17-
SUNErrCode error_code = SUNContext_Create(SUN_COMM_NULL, context);
18-
if (error_code) {
19-
throw std::runtime_error("SUNContext_Create failed: " +
20-
std::string(SUNGetErrMsg(error_code)));
21-
}
26+
SUNContext* context = create_sundials_context();
2227
global_context.reset(context);
2328
std::atexit(cleanup_global_sundials_context);
2429
}
@@ -37,11 +42,17 @@ using namespace ePhotosynthesis;
3742

3843
#ifdef SUNDIALS_CONTEXT_REQUIRED
3944

40-
Variables::Variables(SUNContext* ctx) : _context(), _context_flags(0) {
45+
Variables::Variables(SUNContext* ctx, const int flags) :
46+
_context(), _context_flags(flags) {
4147
if (_context == nullptr) {
42-
init_global_sundials_context();
43-
_context = global_context;
44-
_context_flags |= CONTEXT_FLAG_GLOBAL;
48+
if (flags & CONTEXT_FLAG_GLOBAL) {
49+
std::cerr << "Using global context" << std::endl;
50+
init_global_sundials_context();
51+
_context = global_context;
52+
_context_flags |= CONTEXT_FLAG_GLOBAL;
53+
} else {
54+
_context.reset(create_sundials_context());
55+
}
4556
} else {
4657
_context.reset(ctx);
4758
}
@@ -55,6 +66,13 @@ Variables::Variables(const Variables* other) :
5566
_context(other->_context), _context_flags(other->_context_flags) {
5667
*this = *other;
5768
}
69+
Variables::~Variables() {
70+
if (!(_context_flags & CONTEXT_FLAG_GLOBAL)) {
71+
if (_context.use_count() == 1)
72+
SUNContext_Free(_context.get());
73+
_context.reset();
74+
}
75+
}
5876
#else // SUNDIALS_CONTEXT_REQUIRED
5977
Variables::Variables() {}
6078
Variables::Variables(const Variables& other) : Variables() {
@@ -63,6 +81,7 @@ Variables::Variables(const Variables& other) : Variables() {
6381
Variables::Variables(const Variables* other) : Variables() {
6482
*this = *other;
6583
}
84+
Variables::~Variables() {}
6685
#endif // SUNDIALS_CONTEXT_REQUIRED
6786

6887
Variables* Variables::deepcopy() const {

src/drivers/CVodeMem.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ void CVodeMem::cvode_mem_init(Driver* driver, realtype t0, N_Vector y) {
5959
driver->cvode_mem = _cvode_mem;
6060

6161
}
62+
void CVodeMem::cvode_mem_free() {
63+
if (initialized) {
64+
CVodeFree(&_cvode_mem);
65+
delete data;
66+
data = nullptr;
67+
initialized = false;
68+
}
69+
}
6270

6371
CVodeMem::~CVodeMem() {
6472
if (initialized) {

src/drivers/driver.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ arr Driver::run() {
8484
y_ptr[i] = constraints[i];
8585
realtype t0 = start;
8686

87-
CVodeMem *cmem;
87+
CVodeMem *cmem = nullptr;
8888
try {
8989
cmem = &CVodeMem::create();
9090
cmem->cvode_mem_init(this, t0, y);
@@ -160,6 +160,10 @@ arr Driver::run() {
160160
Driver::~Driver() {
161161
if (origVars != nullptr)
162162
delete origVars;
163+
CVodeMem *cmem = nullptr;
164+
cmem = &CVodeMem::create();
165+
cmem->cvode_mem_free();
166+
cvode_mem = nullptr;
163167
}
164168

165169
int Driver::calculate(realtype t, N_Vector u, N_Vector u_dot, void *user_data) {

src/python/wrappers.cpp

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -137,28 +137,33 @@ void python::exportDrivers() {
137137
bp::scope driverScope = driverModule;
138138
bp::class_<std::vector<double> >("stl_vector_double")
139139
.def(bp::vector_indexing_suite<std::vector<double> >());
140+
#define ADD_DRIVER_METHODS(mod) \
141+
.def("run", &drivers::mod ## Driver::run) \
142+
.def("setup", &drivers::mod ## Driver::setup) \
143+
.def("getResults", &drivers::mod ## Driver::getResults) \
144+
.def_readwrite("abstol", &drivers::mod ## Driver::abstol) \
145+
.def_readwrite("reltol", &drivers::mod ## Driver::reltol) \
146+
.def_readwrite("start", &drivers::mod ## Driver::start) \
147+
.def_readwrite("step", &drivers::mod ## Driver::step) \
148+
.def_readwrite("endtime", &drivers::mod ## Driver::endtime) \
149+
.def_readwrite("initialStep", &drivers::mod ## Driver::initialStep) \
150+
.def_readwrite("time", &drivers::mod ## Driver::time) \
151+
.def_readwrite("maxStep", &drivers::mod ## Driver::maxStep) \
152+
.def_readwrite("results", &drivers::mod ## Driver::results)
140153
bp::class_<DriverWrap, boost::noncopyable>("Driver", bp::no_init)
141-
.def("run", &drivers::Driver::run)
142-
.def("setup", bp::pure_virtual(&drivers::Driver::setup))
143-
.def("getResults", bp::pure_virtual(&drivers::Driver::getResults))
144-
.def("MB", bp::pure_virtual(&drivers::Driver::MB));
154+
.def("run", &drivers::Driver::run)
155+
.def("setup", bp::pure_virtual(&drivers::Driver::setup))
156+
.def("getResults", bp::pure_virtual(&drivers::Driver::getResults))
157+
.def("MB", bp::pure_virtual(&drivers::Driver::MB));
145158
bp::class_<drivers::EPSDriver, bp::bases<DriverWrap>, boost::noncopyable>("EPSDriver", bp::init<Variables*, double, double, double, int, double, double, std::size_t, double, double, bool>()[bp::with_custodian_and_ward_postcall<0,2>()])
146-
.def("run", &drivers::EPSDriver::run)
147-
.def("setup", &drivers::EPSDriver::setup)
148-
.def("getResults", &drivers::EPSDriver::getResults);
159+
ADD_DRIVER_METHODS(EPS);
149160
bp::class_<drivers::trDynaPSDriver, bp::bases<DriverWrap>, boost::noncopyable>("trDynaPSDriver", bp::init<Variables*, double, double, double, int, double, double, std::size_t, double, bool>()[bp::with_custodian_and_ward_postcall<0,2>()])
150-
.def("run", &drivers::trDynaPSDriver::run)
151-
.def("setup", &drivers::trDynaPSDriver::setup)
152-
.def("getResults", &drivers::trDynaPSDriver::getResults);
161+
ADD_DRIVER_METHODS(trDynaPS);
153162
bp::class_<drivers::DynaPSDriver, bp::bases<DriverWrap>, boost::noncopyable>("DynaPSDriver", bp::init<Variables*, double, double, double, int, double, double, std::size_t, double, bool>()[bp::with_custodian_and_ward_postcall<0,2>()])
154-
.def("run", &drivers::DynaPSDriver::run)
155-
.def("setup", &drivers::DynaPSDriver::setup)
156-
.def("getResults", &drivers::DynaPSDriver::getResults);
163+
ADD_DRIVER_METHODS(DynaPS);
157164
bp::class_<drivers::CMDriver, bp::bases<DriverWrap>, boost::noncopyable>("CMDriver", bp::init<Variables*, double, double, double, int, double, double, bool>()[bp::with_custodian_and_ward_postcall<0,2>()])
158-
.def("run", &drivers::CMDriver::run)
159-
.def("setup", &drivers::CMDriver::setup)
160-
.def("getResults", &drivers::CMDriver::getResults);
161-
165+
ADD_DRIVER_METHODS(CM);
166+
#undef ADD_DRIVER_METHODS
162167
}
163168

164169
#endif

0 commit comments

Comments
 (0)