Skip to content

Commit

Permalink
Merge pull request #161 from alphaville/fix/159
Browse files Browse the repository at this point in the history
Fix for #159: project-specific icasadi and tcp_iface
  • Loading branch information
alphaville authored Apr 13, 2020
2 parents 4a2d6d3 + fe687f9 commit d8c556f
Show file tree
Hide file tree
Showing 14 changed files with 80 additions and 42 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ target/
*.egg-info
*.pyc
open-codegen/opengen/icasadi/extern/Makefile
virt
data
my_optimizers
.python_test_build

# Python tests create this folder:
open-codegen/opengen/.python_test_build/
Expand Down
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ matrix:


# ------------------------------------------------------
# Linux Trusty distributions, Python 3.6 and 2.7
# Linux Trusty distribution, Python 3.6 and 2.7
# Stable Rust
# ------------------------------------------------------
#
Expand Down Expand Up @@ -67,8 +67,7 @@ matrix:
env:
- TARGET=i686-apple-darwin
- PYTHON_VERSION=3.7.5
rust: stable
# Note: Python 3.6 is available by default
rust: stable
python: "3.7.5"


Expand Down
4 changes: 1 addition & 3 deletions ci/install.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
set -euxo pipefail

main() {
if [ $TARGET != x86_64-unknown-linux-gnu ]; then
rustup target add $TARGET
fi
rustup toolchain remove stable && rustup toolchain install stable
sudo pip install --upgrade pip
sudo pip install virtualenv --upgrade --ignore-installed six
}
Expand Down
6 changes: 1 addition & 5 deletions ci/script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@ set -euxo pipefail
main() {
# Run Rust tests
# ------------------------------------
cargo test --target $TARGET

cargo test


# Run Python tests
# ------------------------------------

# NOTE: Temporarily deactivated
# TODO: Re-enable later

# Create virtual environment
cd open-codegen
export PYTHONPATH=.
Expand Down
8 changes: 3 additions & 5 deletions docs/python-tcp-ip.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@ generator creates a
```text
build_directory/
|-- optimizer/
|-- tcp_iface/
|-- tcp_iface_{optimizer_name}/
```

We can start the server from Python using the [`OptimizerTcpManager`], but we can
also start it by running `cargo run` from within `tcp_iface`. In order to see the
log messages of the server, start it with
We can start the server from Python using the [`OptimizerTcpManager`], but we can also start it by running `cargo run` from within `tcp_iface_{optimizer_name}` (where `{optimizer_name}` is the name of the optimizer). In order to see the log messages of the server, start it with

```shell
$ RUST_LOG=tcp_iface=info cargo run
Expand Down Expand Up @@ -132,7 +130,7 @@ $ echo '{ "Kill" : 1 }' | nc localhost 4598

### Error reporting

In case a request cannot be processed, e.g., because the provided JSON is malformed, the provided vectors have incompatible dimensions, the TCP server will return to the client an error report. This is a JSON with three attributes: (i) a key-value pair `"type": "Error"`, to allow the client to tell that an error has occured, (ii) a `code`, which can be used to uniquely identify the type of error and (iii) a `message`, which offers some human-readable details.
In case a request cannot be processed, e.g., because the provided JSON is malformed, the provided vectors have incompatible dimensions, the TCP server will return to the client an error report. This is a JSON with three attributes: (i) a key-value pair `"type": "Error"`, to allow the client to tell that an error has occurred, (ii) a `code`, which can be used to uniquely identify the type of error and (iii) a `message`, which offers some human-readable details.

For example, if the client provides an incompatible number of parameters, that is, if vector `parameter` is of the wrong length, then the server will return the following error:

Expand Down
8 changes: 6 additions & 2 deletions open-codegen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

Note: This is the Changelog file of `opengen` - the Python interface of OpEn

## [Unreleased]
## [0.4.1]

### Fixed

* Project-specific `icasadi` dependency
* Project-specific `tcp_iface` TCP interface
* Fixed `lbfgs` typo

### Added

### Changed

### Removed


[Unreleased]: https://github.com/alphaville/optimization-engine/compare/v0.6.1-alpha.2...master
[0.4.1]: https://github.com/alphaville/optimization-engine/compare/opengen-0.4.1...master
35 changes: 25 additions & 10 deletions open-codegen/opengen/builder/optimizer_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
_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'
_TCP_IFACE_PREFIX = 'tcp_iface_'
_ICASADI_PREFIX = 'icasadi_'


def make_dir_if_not_exists(directory):
Expand Down Expand Up @@ -122,10 +124,11 @@ def __icasadi_target_dir(self):
Returns icasadi target directory (instance of os.path)
"""
icasadi_target_dir_name = _ICASADI_PREFIX + self.__meta.optimizer_name
return os.path.abspath(
os.path.join(
self.__build_config.build_dir,
self.__meta.optimizer_name, "icasadi"))
self.__meta.optimizer_name, icasadi_target_dir_name))

def __prepare_target_project(self):
"""Creates folder structure
Expand Down Expand Up @@ -162,6 +165,21 @@ def __copy_icasadi_to_target(self):
ignore=shutil.ignore_patterns(
'*.lock', 'ci*', 'target', 'auto*'))

def __generate_icasadi_cargo_toml(self):
"""
Generate icasadi's Cargo.toml file
"""
self.__logger.info("Generating icasadi's Cargo.toml")
file_loader = jinja2.FileSystemLoader(og_dfn.templates_dir())
env = jinja2.Environment(loader=file_loader)
template = env.get_template('icasadi_cargo.toml.template')
output_template = template.render(meta=self.__meta)
icallocator_path = os.path.abspath(
os.path.join(self.__icasadi_target_dir(), "Cargo.toml"))
with open(icallocator_path, "w") as fh:
fh.write(output_template)
pass

def __generate_icasadi_c_interface(self):
"""
Generates the C interface file interface.c
Expand Down Expand Up @@ -355,13 +373,6 @@ def __generate_casadi_code(self):
self.__generate_memory_code(psi_fun, grad_psi_fun,
mapping_f1_fun, mapping_f2_fun)

# TODO: it seems that the following method is never used
def __build_icasadi(self):
icasadi_dir = self.__icasadi_target_dir()
command = self.__make_build_command()
p = subprocess.Popen(command, cwd=icasadi_dir)
p.wait()

def __generate_main_project_code(self):
self.__logger.info("Generating main code for target optimizer (lib.rs)")
target_dir = self.__target_dir()
Expand Down Expand Up @@ -403,7 +414,9 @@ def __build_optimizer(self):
def __build_tcp_iface(self):
self.__logger.info("Building the TCP interface")
target_dir = os.path.abspath(self.__target_dir())
tcp_iface_dir = os.path.join(target_dir, "tcp_iface")
optimizer_name = self.__meta.optimizer_name
tcp_iface_dir_name = _TCP_IFACE_PREFIX + optimizer_name
tcp_iface_dir = os.path.join(target_dir, tcp_iface_dir_name)
command = self.__make_build_command()
p = subprocess.Popen(command, cwd=tcp_iface_dir)
process_completion = p.wait()
Expand All @@ -422,7 +435,8 @@ def __generate_code_tcp_interface(self):
self.__build_config.tcp_interface_config.bind_ip,
self.__build_config.tcp_interface_config.bind_port)
target_dir = self.__target_dir()
tcp_iface_dir = os.path.join(target_dir, "tcp_iface")
tcp_iface_dir_name = _TCP_IFACE_PREFIX + self.__meta.optimizer_name
tcp_iface_dir = os.path.join(target_dir, tcp_iface_dir_name)
tcp_iface_source_dir = os.path.join(tcp_iface_dir, "src")

# make tcp_iface/ and tcp_iface/src
Expand Down Expand Up @@ -531,6 +545,7 @@ def build(self):
self.__check_user_provided_parameters() # check the provided parameters
self.__prepare_target_project() # create folders; init cargo project
self.__copy_icasadi_to_target() # copy icasadi/ files to target dir
self.__generate_icasadi_cargo_toml() # generate icasadi's Cargo.toml file
self.__generate_cargo_toml() # generate Cargo.toml using template
self.__generate_icasadi_lib() # generate icasadi lib.rs
self.__generate_casadi_code() # generate all necessary CasADi C files:
Expand Down
7 changes: 3 additions & 4 deletions open-codegen/opengen/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import casadi.casadi as cs
import opengen as og
import json

u = cs.SX.sym("u", 5) # decision variable (nu = 5)
p = cs.SX.sym("p", 2) # parameter (np = 2)
Expand All @@ -27,7 +26,7 @@
.with_optimizer_name("the_optimizer")

build_config = og.config.BuildConfiguration() \
.with_build_directory("python_build") \
.with_build_directory("my_optimizers") \
.with_build_mode("debug") \
.with_tcp_interface_config()

Expand All @@ -46,12 +45,12 @@
solver_configuration=solver_config)
builder.build()

mng = og.tcp.OptimizerTcpManager('python_build/the_optimizer')
mng = og.tcp.OptimizerTcpManager('my_optimizers/the_optimizer')
mng.start()

pong = mng.ping() # check if the server is alive
print(pong)
solution = mng.call([1.0, 50.0]) # call the solver over TCP
print(json.dumps(solution, indent=4, sort_keys=False))
print(solution.get().solution)

mng.kill()
6 changes: 4 additions & 2 deletions open-codegen/opengen/tcp/optimizer_tcp_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
from retry import retry
from .solver_response import SolverResponse


class OptimizerTcpManager:
"""Client for TCP interface of parametric optimizers
This class is used to start and stop a TCP server, which
has been generated by <code>opengen</code>.
"""

def __init__(self, optimizer_path = None, ip=None, port=None):
def __init__(self, optimizer_path=None, ip=None, port=None):
"""Constructs instance of <code>OptimizerTcpManager</code>
Args:
Expand Down Expand Up @@ -52,7 +53,8 @@ def __threaded_start(self):
command = ['cargo', 'run', '-q']
if optimizer_details['build']['build_mode'] == 'release':
command.append('--release')
tcp_iface_directory = os.path.join(self.__optimizer_path, "tcp_iface")
tcp_dir_name = "tcp_iface_" + optimizer_details['meta']['optimizer_name']
tcp_iface_directory = os.path.join(self.__optimizer_path, tcp_dir_name)
p = subprocess.Popen(command, cwd=tcp_iface_directory)
p.wait()

Expand Down
23 changes: 23 additions & 0 deletions open-codegen/opengen/templates/icasadi_cargo.toml.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
authors = ["Pantelis Sopasakis"]
name = "icasadi_{{meta.optimizer_name}}"
categories = ["api-bindings", "no-std", "science", "science::robotics", "simulation"]
keywords = ["casadi", "optimization", "interface"]
description = "Rust interface to CasADi functions"
documentation = "https://docs.rs/icasadi"
license = "MIT"
readme = "README.md"
version = "0.2.1"
edition = "2018"

[dependencies]
libc = { version = "0.2.0", default-features = false }

[build-dependencies]
cc = "1.0"

[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3
10 changes: 5 additions & 5 deletions open-codegen/opengen/templates/optimizer.rs.template
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Generated at: {{timestamp_created}}
//

use icasadi;
use icasadi_{{meta.optimizer_name}};
{% if activate_clib_generation -%}
use libc::{c_double, c_ulong, c_ulonglong};
{% endif %}
Expand Down Expand Up @@ -292,20 +292,20 @@ pub fn solve(
assert_eq!(u.len(), {{meta.optimizer_name|upper}}_NUM_DECISION_VARIABLES, "Wrong number of decision variables (u)");

let psi = |u: &[f64], xi: &[f64], cost: &mut f64| -> Result<(), SolverError> {
icasadi::cost(&u, &xi, &p, cost);
icasadi_{{meta.optimizer_name}}::cost(&u, &xi, &p, cost);
Ok(())
};
let grad_psi = |u: &[f64], xi: &[f64], grad: &mut [f64]| -> Result<(), SolverError> {
icasadi::grad(&u, &xi, &p, grad);
icasadi_{{meta.optimizer_name}}::grad(&u, &xi, &p, grad);
Ok(())
};
{% if problem.dim_constraints_aug_lagrangian() > 0 %}
let f1 = |u: &[f64], res: &mut [f64]| -> Result<(), SolverError> {
icasadi::mapping_f1(&u, &p, res);
icasadi_{{meta.optimizer_name}}::mapping_f1(&u, &p, res);
Ok(())
};{% endif %}
{% if problem.dim_constraints_penalty() %}let f2 = |u: &[f64], res: &mut [f64]| -> Result<(), SolverError> {
icasadi::mapping_f2(&u, &p, res);
icasadi_{{meta.optimizer_name}}::mapping_f2(&u, &p, res);
Ok(())
};{% endif -%}
let bounds = make_constraints();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ optimization_engine = {path = "{{build_config.local_path}}"}
optimization_engine = "{{build_config.open_version or '*'}}"
{% endif %}

icasadi = {path = "./icasadi/"}
icasadi_{{meta.optimizer_name}} = {path = "./icasadi_{{meta.optimizer_name}}/"}
{% if activate_clib_generation -%}
libc = "0.2.0"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# -----------------------------------------------------------------

[package]
name = "tcp_iface"
name = "tcp_iface_{{meta.optimizer_name}}"
version = "0.0.1"
license = "MIT"
authors = ["John Smith"]
Expand Down
2 changes: 1 addition & 1 deletion open-codegen/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import os

NAME = 'opengen'
VERSION = '0.3.2-alpha.2'
VERSION = '0.4.1-a1'
DESCRIPTION = 'Optimization Engine Code Generator'

here = os.path.abspath(os.path.dirname(__file__))
Expand Down

0 comments on commit d8c556f

Please sign in to comment.