Skip to content

Commit

Permalink
Make nmodl independent of nmodl. (#1305)
Browse files Browse the repository at this point in the history
The executable from the Python library.
  • Loading branch information
1uc committed Jun 10, 2024
1 parent 56b507c commit c5ecfea
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 90 deletions.
6 changes: 4 additions & 2 deletions python/nmodl/ode.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
known_functions = import_module("sympy.printing.c").known_functions_C99
else:
known_functions = import_module("sympy.printing.ccode").known_functions_C99
known_functions.pop("Abs")
known_functions["abs"] = "fabs"

if "Abs" in known_functions:
known_functions.pop("Abs")
known_functions["abs"] = "fabs"


if not ((major >= 1) and (minor >= 2)):
Expand Down
10 changes: 9 additions & 1 deletion src/pybind/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ endif()
# build nmodl python module under lib/nmodl
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl)

file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../../python/nmodl/ode.py NMODL_ODE_PY)
set_property(
DIRECTORY
APPEND
PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../../python/nmodl/ode.py)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ode_py.hpp.inc ${CMAKE_CURRENT_BINARY_DIR}/ode_py.hpp
@ONLY@)

add_library(pyembed pyembed.cpp)
set_property(TARGET pyembed PROPERTY POSITION_INDEPENDENT_CODE ON)
target_link_libraries(pyembed PRIVATE util)
Expand All @@ -35,7 +43,7 @@ endif()

target_include_directories(pyembed PRIVATE ${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS})
target_include_directories(pywrapper PRIVATE ${pybind11_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS})

target_include_directories(pywrapper PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
# ~~~
# pybind11::embed adds PYTHON_LIBRARIES to target_link_libraries. To avoid link to
# libpython, we can use `module` interface library from pybind11.
Expand Down
19 changes: 19 additions & 0 deletions src/pybind/ode_py.hpp.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include <string>

// This file is generated from `ode.py`.
//
// During code-generation NMODL needs to call SymPy, e.g. to compute
// derivatives symbolically. The code to do this can be found in `ode.py`.
//
// To avoid a dependency of the `nmodl` binary on the Python library `nmodl`.
// We embed `ode.py` like this.
//
// However, because we want to be able to test `ode.py` via pytest we can't
// move it here.

namespace nmodl::pybind_wrappers {
const std::string ode_py = R"jiowi(
@NMODL_ODE_PY@
)jiowi";

}
161 changes: 74 additions & 87 deletions src/pybind/wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <set>
#include <vector>

#include "ode_py.hpp"

namespace py = pybind11;
using namespace py::literals;

Expand All @@ -28,27 +30,24 @@ void SolveLinearSystemExecutor::operator()() {
"do_cse"_a = elimination,
"function_calls"_a = function_calls,
"tmp_unique_prefix"_a = tmp_unique_prefix);
py::exec(R"(
import builtins
builtins.nmodl_python_binding_check = False
from nmodl.ode import solve_lin_system
exception_message = ""
try:
solutions, new_local_vars = solve_lin_system(eq_strings,
state_vars,
vars,
function_calls,
tmp_unique_prefix,
small_system,
do_cse)
except Exception as e:
# if we fail, fail silently and return empty string
solutions = [""]
new_local_vars = [""]
exception_message = str(e)
)",
py::globals(),
locals);
std::string script = R"(
exception_message = ""
try:
solutions, new_local_vars = solve_lin_system(eq_strings,
state_vars,
vars,
function_calls,
tmp_unique_prefix,
small_system,
do_cse)
except Exception as e:
# if we fail, fail silently and return empty string
solutions = [""]
new_local_vars = [""]
exception_message = str(e)
)";

py::exec(nmodl::pybind_wrappers::ode_py + script, locals);
// returns a vector of solutions, i.e. new statements to add to block:
solutions = locals["solutions"].cast<std::vector<std::string>>();
// and a vector of new local variables that need to be declared in the block:
Expand All @@ -63,24 +62,21 @@ void SolveNonLinearSystemExecutor::operator()() {
"state_vars"_a = state_vars,
"vars"_a = vars,
"function_calls"_a = function_calls);
py::exec(R"(
import builtins
builtins.nmodl_python_binding_check = False
from nmodl.ode import solve_non_lin_system
exception_message = ""
try:
solutions = solve_non_lin_system(equation_strings,
state_vars,
vars,
function_calls)
except Exception as e:
# if we fail, fail silently and return empty string
solutions = [""]
new_local_vars = [""]
exception_message = str(e)
)",
py::globals(),
locals);
std::string script = R"(
exception_message = ""
try:
solutions = solve_non_lin_system(equation_strings,
state_vars,
vars,
function_calls)
except Exception as e:
# if we fail, fail silently and return empty string
solutions = [""]
new_local_vars = [""]
exception_message = str(e)
)";

py::exec(nmodl::pybind_wrappers::ode_py + script, locals);
// returns a vector of solutions, i.e. new statements to add to block:
solutions = locals["solutions"].cast<std::vector<std::string>>();
// may also return a python exception message:
Expand All @@ -98,39 +94,33 @@ void DiffeqSolverExecutor::operator()() {
// replace x' = f(x) differential equation
// with forwards Euler timestep:
// x = x + f(x) * dt
py::exec(R"(
import builtins
builtins.nmodl_python_binding_check = False
from nmodl.ode import forwards_euler2c
exception_message = ""
try:
solution = forwards_euler2c(equation_string, dt_var, vars, function_calls)
except Exception as e:
# if we fail, fail silently and return empty string
solution = ""
exception_message = str(e)
)",
py::globals(),
locals);
std::string script = R"(
exception_message = ""
try:
solution = forwards_euler2c(equation_string, dt_var, vars, function_calls)
except Exception as e:
# if we fail, fail silently and return empty string
solution = ""
exception_message = str(e)
)";

py::exec(nmodl::pybind_wrappers::ode_py + script, locals);
} else if (method == codegen::naming::CNEXP_METHOD) {
// replace x' = f(x) differential equation
// with analytic solution for x(t+dt) in terms of x(t)
// x = ...
py::exec(R"(
import builtins
builtins.nmodl_python_binding_check = False
from nmodl.ode import integrate2c
exception_message = ""
try:
solution = integrate2c(equation_string, dt_var, vars,
use_pade_approx)
except Exception as e:
# if we fail, fail silently and return empty string
solution = ""
exception_message = str(e)
)",
py::globals(),
locals);
std::string script = R"(
exception_message = ""
try:
solution = integrate2c(equation_string, dt_var, vars,
use_pade_approx)
except Exception as e:
# if we fail, fail silently and return empty string
solution = ""
exception_message = str(e)
)";

py::exec(nmodl::pybind_wrappers::ode_py + script, locals);
} else {
// nothing to do, but the caller should know.
return;
Expand All @@ -141,25 +131,22 @@ void DiffeqSolverExecutor::operator()() {

void AnalyticDiffExecutor::operator()() {
auto locals = py::dict("expressions"_a = expressions, "vars"_a = used_names_in_block);
py::exec(R"(
import builtins
builtins.nmodl_python_binding_check = False
from nmodl.ode import differentiate2c
exception_message = ""
try:
rhs = expressions[-1].split("=", 1)[1]
solution = differentiate2c(rhs,
"v",
vars,
expressions[:-1]
)
except Exception as e:
# if we fail, fail silently and return empty string
solution = ""
exception_message = str(e)
)",
py::globals(),
locals);
std::string script = R"(
exception_message = ""
try:
rhs = expressions[-1].split("=", 1)[1]
solution = differentiate2c(rhs,
"v",
vars,
expressions[:-1]
)
except Exception as e:
# if we fail, fail silently and return empty string
solution = ""
exception_message = str(e)
)";

py::exec(nmodl::pybind_wrappers::ode_py + script, locals);
solution = locals["solution"].cast<std::string>();
exception_message = locals["exception_message"].cast<std::string>();
}
Expand Down

0 comments on commit c5ecfea

Please sign in to comment.