6#include <units/units.hpp>
13#ifdef POLYFEM_WITH_PYTHON
17#if defined(_MSC_VER) && !defined(NDEBUG) && !defined(PYBIND11_HANDLE_REF_DEBUG)
18#define POLYFEM_RESTORE_NDEBUG_AFTER_PYBIND11
21#include <pybind11/embed.h>
22#include <pybind11/pybind11.h>
23#include <pybind11/stl.h>
24#ifdef POLYFEM_RESTORE_NDEBUG_AFTER_PYBIND11
26#undef POLYFEM_RESTORE_NDEBUG_AFTER_PYBIND11
38#ifdef POLYFEM_WITH_PYTHON
39 namespace py = pybind11;
43 class PythonInterpreter
46 static PythonInterpreter &instance()
48 static PythonInterpreter interpreter;
52 PythonInterpreter(
const PythonInterpreter &) =
delete;
53 PythonInterpreter &operator=(
const PythonInterpreter &) =
delete;
58 if (!Py_IsInitialized())
63#ifdef POLYFEM_PYTHON_EXECUTABLE
65 PyConfig_InitPythonConfig(&config);
67 wchar_t *program_raw = Py_DecodeLocale(POLYFEM_PYTHON_EXECUTABLE,
nullptr);
68 if (program_raw ==
nullptr)
70 PyConfig_Clear(&config);
74 PyStatus status = PyConfig_SetString(&config, &config.program_name, program_raw);
75 PyMem_RawFree(program_raw);
76 if (PyStatus_Exception(status))
78 PyConfig_Clear(&config);
82 guard_ =
new py::scoped_interpreter(&config);
84 guard_ =
new py::scoped_interpreter();
89 ~PythonInterpreter() =
default;
91 py::scoped_interpreter *guard_ =
nullptr;
94 void ensure_python_interpreter()
98 (void)PythonInterpreter::instance();
100 catch (
const std::exception &e)
106 std::shared_ptr<py::object> make_python_object_holder(py::object value)
108 return std::shared_ptr<py::object>(
109 new py::object(std::move(value)),
110 [](py::object *
object) {
111 if (Py_IsInitialized())
113 py::gil_scoped_acquire gil;
118 (void)object->release();
124 py::object load_python_value_function(
const std::string &path,
const std::string &function_name)
126 ensure_python_interpreter();
127 py::gil_scoped_acquire gil;
129 py::module_ importlib_util = py::module_::import(
"importlib.util");
130 py::module_ pathlib = py::module_::import(
"pathlib");
132 py::object resolved_path = pathlib.attr(
"Path")(
path).attr(
"resolve")();
133 std::string module_name = resolved_path.attr(
"stem").cast<std::string>();
134 std::string resolved_path_str = py::str(resolved_path).cast<std::string>();
136 py::object spec = importlib_util.attr(
"spec_from_file_location")(module_name, resolved_path_str);
138 log_and_throw_error(fmt::format(
"Unable to create Python module spec from '{}'", resolved_path_str));
140 py::object loader = spec.attr(
"loader");
141 if (loader.is_none())
144 py::object
module = importlib_util.attr("module_from_spec")(spec);
145 loader.attr(
"exec_module")(module);
147 if (!py::hasattr(module, function_name.c_str()))
148 log_and_throw_error(fmt::format(
"Python expression file '{}' must define a function named '{}'", resolved_path_str, function_name));
150 py::object value =
module.attr(function_name.c_str());
151 if (!PyCallable_Check(value.ptr()))
152 log_and_throw_error(fmt::format(
"Python attribute '{}' in '{}' is not callable", function_name, resolved_path_str));
158 void ExpressionValue::init_python(
const std::string &path,
const std::string &function_name)
162 py::object value = load_python_value_function(path, function_name);
163 auto callable = make_python_object_holder(std::move(value));
165 sfunc_ = [callable](
double x,
double y,
double z,
double t,
int index) ->
double {
166 py::gil_scoped_acquire gil;
167 py::object out = (*callable)(
x,
y,
z, t, index);
171 return out.cast<
double>();
173 catch (
const py::cast_error &)
183 static double min(
double a,
double b) {
return a <
b ? a :
b; }
184 static double max(
double a,
double b) {
return a >
b ? a :
b; }
185 static double smoothstep(
double a)
192 return (3 * pow(a, 2)) - (2 * pow(a, 3));
194 static double half_smoothstep(
double a)
196 double b = (a + 1.) / 2.;
197 return 2. * smoothstep(b) - 1.;
199 static double deg2rad(
double d) {
return d * igl::PI / 180.0; }
200 static double rotate_2D_x(
double x,
double y,
double theta)
202 return x * cos(theta) -
y * sin(theta);
204 static double rotate_2D_y(
double x,
double y,
double theta)
206 return x * sin(theta) +
y * cos(theta);
208 static double smooth_abs(
double x,
double k)
210 return tanh(k *
x) *
x;
212 static double iflargerthanzerothenelse(
double check,
double ttrue,
double ffalse)
214 return check >= 0 ? ttrue : ffalse;
216 static double sign(
double x)
218 return (0 <
x) - (
x < 0);
220 static double compare(
double a,
double b)
222 return a <
b ? 1.0 : 0.0;
272 catch (
const std::exception &)
278 double x = 0,
y = 0,
z = 0, t = 0;
280 std::vector<te_variable> vars = {
281 {
"x", &
x, TE_VARIABLE},
282 {
"y", &
y, TE_VARIABLE},
283 {
"z", &
z, TE_VARIABLE},
284 {
"t", &t, TE_VARIABLE},
285 {
"min", (
const void *)min, TE_FUNCTION2},
286 {
"max", (
const void *)max, TE_FUNCTION2},
287 {
"smoothstep", (
const void *)smoothstep, TE_FUNCTION1},
288 {
"half_smoothstep", (
const void *)half_smoothstep, TE_FUNCTION1},
289 {
"deg2rad", (
const void *)deg2rad, TE_FUNCTION1},
290 {
"rotate_2D_x", (
const void *)rotate_2D_x, TE_FUNCTION3},
291 {
"rotate_2D_y", (
const void *)rotate_2D_y, TE_FUNCTION3},
292 {
"if", (
const void *)iflargerthanzerothenelse, TE_FUNCTION3},
293 {
"compare", (
const void *)compare, TE_FUNCTION2},
294 {
"smooth_abs", (
const void *)smooth_abs, TE_FUNCTION2},
295 {
"sign", (
const void *)sign, TE_FUNCTION1},
299 te_expr *tmp = te_compile(expr.c_str(), vars.data(), vars.size(), &err);
302 logger().error(
"Unable to parse: {}", expr);
303 logger().error(
"Error near character {}.", err);
313 if (
vals.is_number())
317 else if (
vals.is_boolean())
321 else if (
vals.is_array())
323 if (
vals.empty() ||
vals[0].is_number())
327 for (
int i = 0; i <
mat_.size(); ++i)
329 if (!
vals[i].is_number())
330 log_and_throw_error(
"Expression arrays must contain either only numbers or only expressions.");
338 for (
int i = 0; i <
vals.size(); ++i)
351 logger().error(
"Specifying varying dirichlet over time, however 'time_reference' does not match dirichlet boundary conditions.");
353 else if (
vals.is_object())
355 units::precise_unit unit;
356 if (
vals.contains(
"unit"))
358 if (!
vals[
"unit"].is_string())
361 const std::string unit_str =
vals[
"unit"].get<std::string>();
362 if (!unit_str.empty())
363 unit = units::unit_from_string(unit_str);
366 if (
vals.contains(
"file_name"))
368#ifndef POLYFEM_WITH_PYTHON
370 "Python expression '{}' requested, but PolyFEM was built without Python support. "
371 "Reconfigure with -DPOLYFEM_WITH_PYTHON=ON to enable Python expressions.",
372 vals[
"file_name"].dump());
374 if (!
vals[
"file_name"].is_string())
376 if (!
vals.contains(
"function_name") || !
vals[
"function_name"].is_string())
377 log_and_throw_error(
"Python expression '{}' must include a string 'function_name'.",
vals[
"file_name"].get<std::string>());
379 const std::string path = resources.
materialize(
vals[
"file_name"].get<std::string>()).string();
380 const std::string function_name =
vals[
"function_name"].get<std::string>();
382 init_python(path, function_name);
388 if (!
vals.contains(
"value"))
396 init(
vals.get<std::string>(), resources);
404 sfunc_ = [func](
double x,
double y,
double z,
double t,
int index) {
return func(
x,
y,
z); };
410 sfunc_ = [func](
double x,
double y,
double z,
double t,
int index) {
return func(
x,
y,
z, t); };
423 tfunc_ = [func](
double x,
double y,
double z,
double t) {
return func(
x,
y,
z); };
439 for (
int i = 0; i < t.size(); ++i)
441 t_index_[std::round(t[i].get<double>() * 1000.) / 1000.] = i;
445 logger().error(
"Specifying varying dirichlet over time, however 'time_reference' does not match dirichlet boundary conditions.");
456 t = std::round(t * 1000.) / 1000.;
466 logger().error(
"Cannot find dirichlet condition for time step {}.", t);
483 std::vector<te_variable> vars = {
484 {
"x", &
x, TE_VARIABLE},
485 {
"y", &
y, TE_VARIABLE},
486 {
"z", &
z, TE_VARIABLE},
487 {
"t", &t, TE_VARIABLE},
488 {
"min", (
const void *)min, TE_FUNCTION2},
489 {
"max", (
const void *)max, TE_FUNCTION2},
490 {
"smoothstep", (
const void *)smoothstep, TE_FUNCTION1},
491 {
"half_smoothstep", (
const void *)half_smoothstep, TE_FUNCTION1},
492 {
"deg2rad", (
const void *)deg2rad, TE_FUNCTION1},
493 {
"rotate_2D_x", (
const void *)rotate_2D_x, TE_FUNCTION3},
494 {
"rotate_2D_y", (
const void *)rotate_2D_y, TE_FUNCTION3},
495 {
"if", (
const void *)iflargerthanzerothenelse, TE_FUNCTION3},
496 {
"compare", (
const void *)compare, TE_FUNCTION2},
497 {
"smooth_abs", (
const void *)smooth_abs, TE_FUNCTION2},
498 {
"sign", (
const void *)sign, TE_FUNCTION1},
502 te_expr *tmp = te_compile(
expr_.c_str(), vars.data(), vars.size(), &err);
506 logger().error(
"Error near character {}.", err);
509 result = te_eval(tmp);
513 if (!
unit_.base_units().empty())
ElementAssemblyValues vals
Read-only interface to a hierarchical collection of simulation resources.
virtual std::filesystem::path materialize(const std::string &path) const =0
virtual bool exists(const std::string &path) const =0
virtual Eigen::MatrixXd read_matrix(const std::string &path) const =0
virtual bool is_group(const std::string &path) const =0
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
void init(const json &vals, const io::ResourceIO &resources)
spdlog::logger & logger()
Retrieves the current logger.
void log_and_throw_error(const std::string &msg)