7#include <units/units.hpp>
14#ifdef POLYFEM_WITH_PYTHON
18#if defined(_MSC_VER) && !defined(NDEBUG) && !defined(PYBIND11_HANDLE_REF_DEBUG)
19#define POLYFEM_RESTORE_NDEBUG_AFTER_PYBIND11
22#include <pybind11/embed.h>
23#include <pybind11/pybind11.h>
24#include <pybind11/stl.h>
25#ifdef POLYFEM_RESTORE_NDEBUG_AFTER_PYBIND11
27#undef POLYFEM_RESTORE_NDEBUG_AFTER_PYBIND11
39#ifdef POLYFEM_WITH_PYTHON
40 namespace py = pybind11;
44 class PythonInterpreter
47 static PythonInterpreter &instance()
49 static PythonInterpreter interpreter;
53 PythonInterpreter(
const PythonInterpreter &) =
delete;
54 PythonInterpreter &operator=(
const PythonInterpreter &) =
delete;
59 if (!Py_IsInitialized())
64#ifdef POLYFEM_PYTHON_EXECUTABLE
66 PyConfig_InitPythonConfig(&config);
68 wchar_t *program_raw = Py_DecodeLocale(POLYFEM_PYTHON_EXECUTABLE,
nullptr);
69 if (program_raw ==
nullptr)
71 PyConfig_Clear(&config);
75 PyStatus status = PyConfig_SetString(&config, &config.program_name, program_raw);
76 PyMem_RawFree(program_raw);
77 if (PyStatus_Exception(status))
79 PyConfig_Clear(&config);
83 guard_ =
new py::scoped_interpreter(&config);
85 guard_ =
new py::scoped_interpreter();
90 ~PythonInterpreter() =
default;
92 py::scoped_interpreter *guard_ =
nullptr;
95 void ensure_python_interpreter()
99 (void)PythonInterpreter::instance();
101 catch (
const std::exception &e)
107 std::shared_ptr<py::object> make_python_object_holder(py::object value)
109 return std::shared_ptr<py::object>(
110 new py::object(std::move(value)),
111 [](py::object *
object) {
112 if (Py_IsInitialized())
114 py::gil_scoped_acquire gil;
119 (void)object->release();
125 py::object load_python_value_function(
const std::string &path,
const std::string &function_name)
127 ensure_python_interpreter();
128 py::gil_scoped_acquire gil;
130 py::module_ importlib_util = py::module_::import(
"importlib.util");
131 py::module_ pathlib = py::module_::import(
"pathlib");
133 py::object resolved_path = pathlib.attr(
"Path")(
path).attr(
"resolve")();
134 std::string module_name = resolved_path.attr(
"stem").cast<std::string>();
135 std::string resolved_path_str = py::str(resolved_path).cast<std::string>();
137 py::object spec = importlib_util.attr(
"spec_from_file_location")(module_name, resolved_path_str);
139 log_and_throw_error(fmt::format(
"Unable to create Python module spec from '{}'", resolved_path_str));
141 py::object loader = spec.attr(
"loader");
142 if (loader.is_none())
145 py::object
module = importlib_util.attr("module_from_spec")(spec);
146 loader.attr(
"exec_module")(module);
148 if (!py::hasattr(module, function_name.c_str()))
149 log_and_throw_error(fmt::format(
"Python expression file '{}' must define a function named '{}'", resolved_path_str, function_name));
151 py::object value =
module.attr(function_name.c_str());
152 if (!PyCallable_Check(value.ptr()))
153 log_and_throw_error(fmt::format(
"Python attribute '{}' in '{}' is not callable", function_name, resolved_path_str));
159 void ExpressionValue::init_python(
const std::string &path,
const std::string &function_name)
163 py::object value = load_python_value_function(path, function_name);
164 auto callable = make_python_object_holder(std::move(value));
166 sfunc_ = [callable](
double x,
double y,
double z,
double t,
int index) ->
double {
167 py::gil_scoped_acquire gil;
168 py::object out = (*callable)(
x,
y,
z, t, index);
172 return out.cast<
double>();
174 catch (
const py::cast_error &)
184 static double min(
double a,
double b) {
return a <
b ? a :
b; }
185 static double max(
double a,
double b) {
return a >
b ? a :
b; }
186 static double smoothstep(
double a)
193 return (3 * pow(a, 2)) - (2 * pow(a, 3));
195 static double half_smoothstep(
double a)
197 double b = (a + 1.) / 2.;
198 return 2. * smoothstep(b) - 1.;
200 static double deg2rad(
double d) {
return d * igl::PI / 180.0; }
201 static double rotate_2D_x(
double x,
double y,
double theta)
203 return x * cos(theta) -
y * sin(theta);
205 static double rotate_2D_y(
double x,
double y,
double theta)
207 return x * sin(theta) +
y * cos(theta);
209 static double smooth_abs(
double x,
double k)
211 return tanh(k *
x) *
x;
213 static double iflargerthanzerothenelse(
double check,
double ttrue,
double ffalse)
215 return check >= 0 ? ttrue : ffalse;
217 static double sign(
double x)
219 return (0 <
x) - (
x < 0);
221 static double compare(
double a,
double b)
223 return a <
b ? 1.0 : 0.0;
268 if (std::filesystem::is_regular_file(path))
274 catch (
const std::filesystem::filesystem_error &e)
280 double x = 0,
y = 0,
z = 0, t = 0;
282 std::vector<te_variable> vars = {
283 {
"x", &
x, TE_VARIABLE},
284 {
"y", &
y, TE_VARIABLE},
285 {
"z", &
z, TE_VARIABLE},
286 {
"t", &t, TE_VARIABLE},
287 {
"min", (
const void *)min, TE_FUNCTION2},
288 {
"max", (
const void *)max, TE_FUNCTION2},
289 {
"smoothstep", (
const void *)smoothstep, TE_FUNCTION1},
290 {
"half_smoothstep", (
const void *)half_smoothstep, TE_FUNCTION1},
291 {
"deg2rad", (
const void *)deg2rad, TE_FUNCTION1},
292 {
"rotate_2D_x", (
const void *)rotate_2D_x, TE_FUNCTION3},
293 {
"rotate_2D_y", (
const void *)rotate_2D_y, TE_FUNCTION3},
294 {
"if", (
const void *)iflargerthanzerothenelse, TE_FUNCTION3},
295 {
"compare", (
const void *)compare, TE_FUNCTION2},
296 {
"smooth_abs", (
const void *)smooth_abs, TE_FUNCTION2},
297 {
"sign", (
const void *)sign, TE_FUNCTION1},
301 te_expr *tmp = te_compile(expr.c_str(), vars.data(), vars.size(), &err);
304 logger().error(
"Unable to parse: {}", expr);
305 logger().error(
"Error near character {}.", err);
315 if (
vals.is_number())
319 else if (
vals.is_array())
321 if (
vals.empty() ||
vals[0].is_number())
325 for (
int i = 0; i <
mat_.size(); ++i)
327 if (!
vals[i].is_number())
328 log_and_throw_error(
"Expression arrays must contain either only numbers or only expressions.");
336 for (
int i = 0; i <
vals.size(); ++i)
349 logger().error(
"Specifying varying dirichlet over time, however 'time_reference' does not match dirichlet boundary conditions.");
351 else if (
vals.is_object())
353 units::precise_unit unit;
354 if (
vals.contains(
"unit"))
356 if (!
vals[
"unit"].is_string())
359 const std::string unit_str =
vals[
"unit"].get<std::string>();
360 if (!unit_str.empty())
361 unit = units::unit_from_string(unit_str);
364 if (
vals.contains(
"file_name"))
366#ifndef POLYFEM_WITH_PYTHON
368 "Python expression '{}' requested, but PolyFEM was built without Python support. "
369 "Reconfigure with -DPOLYFEM_WITH_PYTHON=ON to enable Python expressions.",
370 vals[
"file_name"].dump());
372 if (!
vals[
"file_name"].is_string())
374 if (!
vals.contains(
"function_name") || !
vals[
"function_name"].is_string())
375 log_and_throw_error(
"Python expression '{}' must include a string 'function_name'.",
vals[
"file_name"].get<std::string>());
378 const std::string function_name =
vals[
"function_name"].get<std::string>();
380 init_python(path, function_name);
386 if (!
vals.contains(
"value"))
394 init(
vals.get<std::string>(), root_path);
402 sfunc_ = [func](
double x,
double y,
double z,
double t,
int index) {
return func(
x,
y,
z); };
408 sfunc_ = [func](
double x,
double y,
double z,
double t,
int index) {
return func(
x,
y,
z, t); };
421 tfunc_ = [func](
double x,
double y,
double z,
double t) {
return func(
x,
y,
z); };
437 for (
int i = 0; i < t.size(); ++i)
439 t_index_[std::round(t[i].get<double>() * 1000.) / 1000.] = i;
443 logger().error(
"Specifying varying dirichlet over time, however 'time_reference' does not match dirichlet boundary conditions.");
454 t = std::round(t * 1000.) / 1000.;
464 logger().error(
"Cannot find dirichlet condition for time step {}.", t);
470 result =
mat_(index);
481 std::vector<te_variable> vars = {
482 {
"x", &
x, TE_VARIABLE},
483 {
"y", &
y, TE_VARIABLE},
484 {
"z", &
z, TE_VARIABLE},
485 {
"t", &t, TE_VARIABLE},
486 {
"min", (
const void *)min, TE_FUNCTION2},
487 {
"max", (
const void *)max, TE_FUNCTION2},
488 {
"smoothstep", (
const void *)smoothstep, TE_FUNCTION1},
489 {
"half_smoothstep", (
const void *)half_smoothstep, TE_FUNCTION1},
490 {
"deg2rad", (
const void *)deg2rad, TE_FUNCTION1},
491 {
"rotate_2D_x", (
const void *)rotate_2D_x, TE_FUNCTION3},
492 {
"rotate_2D_y", (
const void *)rotate_2D_y, TE_FUNCTION3},
493 {
"if", (
const void *)iflargerthanzerothenelse, TE_FUNCTION3},
494 {
"compare", (
const void *)compare, TE_FUNCTION2},
495 {
"smooth_abs", (
const void *)smooth_abs, TE_FUNCTION2},
496 {
"sign", (
const void *)sign, TE_FUNCTION1},
500 te_expr *tmp = te_compile(
expr_.c_str(), vars.data(), vars.size(), &err);
504 logger().error(
"Error near character {}.", err);
507 result = te_eval(tmp);
511 if (!
unit_.base_units().empty())
ElementAssemblyValues vals
void init(const json &vals, const std::string &root_path)
void set_t(const json &t)
std::vector< ExpressionValue > mat_expr_
units::precise_unit unit_
std::function< double(double x, double y, double z, double t, int index)> sfunc_
std::function< Eigen::MatrixXd(double x, double y, double z, double t)> tfunc_
std::map< double, int > t_index_
units::precise_unit unit_type_
double operator()(double x, double y, double z=0, double t=0, int index=-1) const
bool read_matrix(const std::string &path, Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > &mat)
Reads a matrix from a file. Determines the file format based on the path's extension.
std::string resolve_path(const std::string &path, const std::string &input_file_path, const bool only_if_exists=false)
spdlog::logger & logger()
Retrieves the current logger.
void log_and_throw_error(const std::string &msg)