From 91301590141d9f8ffda1a5ed77c7ad3a6b825641 Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Wed, 16 Sep 2020 21:38:53 +0100
Subject: [PATCH 01/10] docs: updated python examples
---
docs/example_estimation_py.md | 4 ++++
docs/example_rosenbrock_py.md | 14 +++++++++-----
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/docs/example_estimation_py.md b/docs/example_estimation_py.md
index db16d6e0..a16bcac8 100644
--- a/docs/example_estimation_py.md
+++ b/docs/example_estimation_py.md
@@ -219,7 +219,11 @@ meta = og.config.OptimizerMeta() \
.with_optimizer_name("estimator")
builder = og.builder.OpEnOptimizerBuilder(problem, meta, build_config)
builder.build()
+```
+
+The generated solver can be consumed over its TCP interface:
+```python
# Use TCP server
# ------------------------------------
mng = og.tcp.OptimizerTcpManager('python_test_build/estimator')
diff --git a/docs/example_rosenbrock_py.md b/docs/example_rosenbrock_py.md
index b23b4ec3..25112cec 100644
--- a/docs/example_rosenbrock_py.md
+++ b/docs/example_rosenbrock_py.md
@@ -42,11 +42,11 @@ problem = og.builder.Problem(u, p, phi) \
.with_penalty_constraints(c) \
.with_constraints(bounds)
build_config = og.config.BuildConfiguration() \
- .with_build_directory("python_test_build") \
+ .with_build_directory("my_optimizers") \
.with_build_mode("debug") \
.with_tcp_interface_config()
meta = og.config.OptimizerMeta() \
- .with_optimizer_name("tcp_enabled_optimizer")
+ .with_optimizer_name("rosenbrock")
solver_config = og.config.SolverConfiguration() \
.with_tolerance(1e-5) \
.with_delta_tolerance(1e-4) \
@@ -58,12 +58,16 @@ builder.build()
# Use TCP server
# ------------------------------------
-mng = og.tcp.OptimizerTcpManager('python_test_build/tcp_enabled_optimizer')
+mng = og.tcp.OptimizerTcpManager('my_optimizers/rosenbrock')
mng.start()
mng.ping()
-solution = mng.call([1.0, 50.0])
-print(solution)
+server_response = mng.call([1.0, 50.0])
+
+if server_response.is_ok():
+ solution = server_response.get()
+ u_star = solution.solution
+ status = solution.exit_status
mng.kill()
```
From 7287ccbba62d869b84b5ab0625ebbe2b77a90aa3 Mon Sep 17 00:00:00 2001
From: Pantelis Sopasakis
Date: Thu, 15 Oct 2020 02:56:40 +0100
Subject: [PATCH 02/10] empty commit (starting new branch)
From 30c7201a708da97222477ca38bb4d673d0684811 Mon Sep 17 00:00:00 2001
From: David Rusu
Date: Fri, 16 Oct 2020 16:56:04 -0400
Subject: [PATCH 03/10] Integrate pyo3 generated bindings
---
.../opengen/builder/optimizer_builder.py | 60 ++++++++
open-codegen/opengen/config/build_config.py | 20 +++
open-codegen/opengen/main.py | 51 ++++---
.../templates/python/python_bindings.rs | 142 ++++++++++++++++++
.../python/python_bindings_cargo.toml | 40 +++++
5 files changed, 291 insertions(+), 22 deletions(-)
create mode 100644 open-codegen/opengen/templates/python/python_bindings.rs
create mode 100644 open-codegen/opengen/templates/python/python_bindings_cargo.toml
diff --git a/open-codegen/opengen/builder/optimizer_builder.py b/open-codegen/opengen/builder/optimizer_builder.py
index 0d310926..15fb431c 100644
--- a/open-codegen/opengen/builder/optimizer_builder.py
+++ b/open-codegen/opengen/builder/optimizer_builder.py
@@ -18,6 +18,7 @@
_AUTOGEN_GRAD_FNAME = 'auto_casadi_grad.c'
_AUTOGEN_PNLT_CONSTRAINTS_FNAME = 'auto_casadi_mapping_f2.c'
_AUTOGEN_ALM_MAPPING_F1_FNAME = 'auto_casadi_mapping_f1.c'
+_PYTHON_BINDINGS_PREFIX = 'python_bindings_'
_TCP_IFACE_PREFIX = 'tcp_iface_'
_ICASADI_PREFIX = 'icasadi_'
_ROS_PREFIX = 'ros_node_'
@@ -421,6 +422,27 @@ def __build_optimizer(self):
if process_completion != 0:
raise Exception('Rust build failed')
+ def __build_python_bindings(self):
+ self.__logger.info("Building Python bindings")
+ target_dir = os.path.abspath(self.__target_dir())
+ optimizer_name = self.__meta.optimizer_name
+ python_bindings_dir_name = _PYTHON_BINDINGS_PREFIX + optimizer_name
+ python_bindings_dir = os.path.join(target_dir, python_bindings_dir_name)
+ command = self.__make_build_command()
+ p = subprocess.Popen(command, cwd=python_bindings_dir)
+ process_completion = p.wait()
+ if process_completion != 0:
+ raise Exception('Rust build of Python bindings failed')
+
+ if self.__build_config.build_mode.lower() == 'release':
+ build_dir = os.path.join(python_bindings_dir, 'target', 'release')
+ else:
+ build_dir = os.path.join(python_bindings_dir, 'target', 'debug')
+
+ generated_bindings = os.path.join(build_dir, f"lib{optimizer_name}.so")
+ assert os.path.isfile(generated_bindings)
+ shutil.copyfile(generated_bindings, os.path.join(os.getcwd(), f"{optimizer_name}.so"))
+
def __build_tcp_iface(self):
self.__logger.info("Building the TCP interface")
target_dir = os.path.abspath(self.__target_dir())
@@ -439,6 +461,37 @@ def __initialize(self):
def __check_user_provided_parameters(self):
self.__logger.info("Checking user parameters")
+ def __generate_code_python_bindings(self):
+ self.__logger.info("Generating code for Python bindings")
+ target_dir = self.__target_dir()
+ python_bindings_dir_name = _PYTHON_BINDINGS_PREFIX + self.__meta.optimizer_name
+ python_bindings_dir = os.path.join(target_dir, python_bindings_dir_name)
+ python_bindings_source_dir = os.path.join(python_bindings_dir, "src")
+ self.__logger.info(f"Generating code for Python bindings in {python_bindings_dir}")
+
+ # make python_bindings/ and python_bindings/src
+ make_dir_if_not_exists(python_bindings_dir)
+ make_dir_if_not_exists(python_bindings_source_dir)
+
+ # generate tcp_server.rs for python_bindings
+ python_rs_template = OpEnOptimizerBuilder.__get_template('python_bindings.rs', 'python')
+ python_rs_output_template = python_rs_template.render(
+ meta=self.__meta,
+ timestamp_created=datetime.datetime.now())
+ target_python_rs_path = os.path.join(python_bindings_source_dir, "lib.rs")
+ with open(target_python_rs_path, "w") as fh:
+ fh.write(python_rs_output_template)
+
+ # generate Cargo.toml for python_bindings
+ python_rs_template = OpEnOptimizerBuilder.__get_template('python_bindings_cargo.toml', 'python')
+ python_rs_output_template = python_rs_template.render(
+ meta=self.__meta,
+ build_config=self.__build_config,
+ timestamp_created=datetime.datetime.now())
+ target_python_rs_path = os.path.join(python_bindings_dir, "Cargo.toml")
+ with open(target_python_rs_path, "w") as fh:
+ fh.write(python_rs_output_template)
+
def __generate_code_tcp_interface(self):
self.__logger.info("Generating code for TCP/IP interface (tcp_iface/src/main.rs)")
self.__logger.info("TCP server will bind at %s:%d",
@@ -582,9 +635,16 @@ def build(self):
self.__build_tcp_iface()
if self.__build_config.build_c_bindings:
+ self.__logger.info("Generating C/C++ bindings")
self.__generate_c_bindings_example()
self.__generate_c_bindings_makefile()
+ if self.__build_config.build_python_bindings:
+ self.__logger.info("Generating Python bindings")
+ self.__generate_code_python_bindings()
+ if not self.__generate_not_build:
+ self.__build_python_bindings()
+
if self.__build_config.ros_config is not None:
ros_builder = RosBuilder(
self.__meta,
diff --git a/open-codegen/opengen/config/build_config.py b/open-codegen/opengen/config/build_config.py
index 29bd1662..b3ebfeda 100644
--- a/open-codegen/opengen/config/build_config.py
+++ b/open-codegen/opengen/config/build_config.py
@@ -36,6 +36,7 @@ def __init__(self, build_dir="."):
self.__build_dir = build_dir
self.__open_version = None
self.__build_c_bindings = False
+ self.__build_python_bindings = False
self.__ros_config = None
self.__tcp_interface_config = None
self.__local_path = None
@@ -103,6 +104,10 @@ def local_path(self):
def build_c_bindings(self):
return self.__build_c_bindings
+ @property
+ def build_python_bindings(self):
+ return self.__build_python_bindings
+
@property
def tcp_interface_config(self):
return self.__tcp_interface_config
@@ -204,6 +209,21 @@ def with_build_c_bindings(self, build_c_bindings=True):
self.__build_c_bindings = build_c_bindings
return self
+ def with_build_python_bindings(self, build_python_bindings=True):
+ """
+ If activated, OpEn will generate python bindings for the
+ auto-generated solver
+
+ :param build_python_bindings: whether to build python bindings for
+ auto-generated solver; default: `True`, i.e., it suffices
+ to call `build_config.with_build_python_bindings()` instead of
+ `build_config.with_build_python_bindings(True)`
+
+ :return: current instance of BuildConfiguration
+ """
+ self.__build_python_bindings = build_python_bindings
+ return self
+
def with_ros(self, ros_config: RosConfiguration):
"""
Activates the generation of a ROS package. The caller must provide an
diff --git a/open-codegen/opengen/main.py b/open-codegen/opengen/main.py
index 88e8de65..513228b5 100644
--- a/open-codegen/opengen/main.py
+++ b/open-codegen/opengen/main.py
@@ -22,7 +22,8 @@
build_config = og.config.BuildConfiguration() \
.with_build_directory('my_optimizers') \
.with_build_mode(og.config.BuildConfiguration.DEBUG_MODE) \
- .with_tcp_interface_config()
+ .with_build_python_bindings()
+ # .with_tcp_interface_config()
builder = og.builder.OpEnOptimizerBuilder(problem,
meta,
@@ -30,24 +31,30 @@
og.config.SolverConfiguration())
builder.build()
-all_managers = []
-for i in range(10):
- all_managers += [og.tcp.OptimizerTcpManager(
- optimizer_path='my_optimizers/halfspace_optimizer',
- ip='0.0.0.0',
- port=3311+i)]
-
-for m in all_managers:
- m.start()
-
-time.sleep(4)
-
-for m in all_managers:
- print(m.details)
- resp = m.call(p=[1., 2.])
- print(resp.get().solution)
-
-# mng.kill()
-time.sleep(6)
-for m in all_managers:
- m.kill()
+import halfspace_optimizer
+
+solver = halfspace_optimizer.build_solver()
+result = solver.run([1., 2.])
+print(result.solution)
+
+# all_managers = []
+# for i in range(10):
+# all_managers += [og.tcp.OptimizerTcpManager(
+# optimizer_path='my_optimizers/halfspace_optimizer',
+# ip='0.0.0.0',
+# port=3311+i)]
+#
+# for m in all_managers:
+# m.start()
+#
+# time.sleep(4)
+#
+# for m in all_managers:
+# print(m.details)
+# resp = m.call(p=[1., 2.])
+# print(resp.get().solution)
+#
+# # mng.kill()
+# time.sleep(6)
+# for m in all_managers:
+# m.kill()
diff --git a/open-codegen/opengen/templates/python/python_bindings.rs b/open-codegen/opengen/templates/python/python_bindings.rs
new file mode 100644
index 00000000..6fba7974
--- /dev/null
+++ b/open-codegen/opengen/templates/python/python_bindings.rs
@@ -0,0 +1,142 @@
+///
+/// Auto-generated python bindings for optimizer: {{ meta.optimizer_name }}
+/// Generated at: {{timestamp_created}}
+///
+use optimization_engine::alm::*;
+
+use pyo3::prelude::*;
+use pyo3::wrap_pyfunction;
+
+use {{ meta.optimizer_name }}::*;
+
+#[pymodule]
+fn {{ meta.optimizer_name }}(_py: Python, m: &PyModule) -> PyResult<()> {
+ m.add_function(wrap_pyfunction!(build_solver, m)?)?;
+ m.add_class::()?;
+ m.add_class::()?;
+ Ok(())
+}
+
+#[pyfunction]
+fn build_solver() -> PyResult {
+ let cache = initialize_solver();
+ Ok(Solver { cache })
+}
+
+/// Solution and solution status of optimizer
+#[pyclass]
+struct OptimizerSolution {
+ #[pyo3(get)]
+ exit_status: String,
+ #[pyo3(get)]
+ num_outer_iterations: usize,
+ #[pyo3(get)]
+ num_inner_iterations: usize,
+ #[pyo3(get)]
+ last_problem_norm_fpr: f64,
+ #[pyo3(get)]
+ delta_y_norm_over_c: f64,
+ #[pyo3(get)]
+ f2_norm: f64,
+ #[pyo3(get)]
+ solve_time_ms: f64,
+ #[pyo3(get)]
+ penalty: f64,
+ #[pyo3(get)]
+ solution: Vec,
+ #[pyo3(get)]
+ lagrange_multipliers: Vec,
+ #[pyo3(get)]
+ cost: f64,
+}
+
+#[pyclass]
+struct Solver {
+ cache: AlmCache,
+}
+
+#[pymethods]
+impl Solver {
+ /// Run solver
+ fn run(
+ &mut self,
+ parameter: Vec,
+ initial_guess: Option>,
+ initial_lagrange_multipliers: Option>,
+ initial_penalty: Option,
+ ) -> PyResult