diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c284a26e..33922b3b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ubuntu-20.04, ubuntu-latest]
+ os: [ubuntu-latest]
env:
DO_DOCKER: 0
steps:
@@ -28,9 +28,25 @@ jobs:
with:
python-version: '3.12'
architecture: 'x64'
- - run: cargo test --features rp
- - run: cargo test --features jem
- - run: bash ./ci/script.sh
+ - name: Install ARM cross-compiler and C libraries
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y gcc-arm-linux-gnueabihf libc6-dev-armhf-cross
+ # If icasadi_rosenbrock or other deps need C++:
+ # sudo apt-get install -y g++-arm-linux-gnueabihf
+ - name: Cargo tests (RP and JEM)
+ run: |
+ cargo test --features rp
+ cargo test --features jem
+ - name: Run tests (script.sh)
+ # Set environment variables for the cc crate
+ env:
+ CC_arm_unknown_linux_gnueabihf: arm-linux-gnueabihf-gcc
+ AR_arm_unknown_linux_gnueabihf: arm-linux-gnueabihf-ar
+ # If C++ is involved and you installed g++-arm-linux-gnueabihf:
+ # CXX_arm_unknown_linux_gnueabihf: arm-linux-gnueabihf-g++
+ run: |
+ bash ./ci/script.sh
ci_macos:
runs-on: ${{ matrix.os }}
@@ -55,4 +71,30 @@ jobs:
python-version: '3.12'
- run: cargo test --features rp
- run: cargo test --features jem
- - run: bash ./ci/script.sh
+ - name: Install ARM cross-compiler toolchain (via Homebrew)
+ run: |
+ # Tap the repository that provides the cross-compiler
+ brew tap messense/macos-cross-toolchains
+ # Update brew to ensure the tap is recognized (can sometimes be needed)
+ brew update
+ # Install the full toolchain (includes gcc, binutils, sysroot)
+ # This specific formula provides the entire toolchain.
+ brew install arm-unknown-linux-gnueabihf
+
+ # The above `brew install` might have linking conflicts if other partial
+ # toolchains were somehow pre-installed or installed by other steps.
+ # If it fails with link errors, you might need:
+ # brew link --overwrite arm-unknown-linux-gnueabihf
+
+ # Verify the compiler is found
+ which arm-linux-gnueabihf-gcc || (echo "arm-linux-gnueabihf-gcc not found in PATH" && exit 1)
+ - name: Run tests (script.sh)
+ # Set environment variables for the cc crate and PyO3 (if needed)
+ env:
+ CC_arm_unknown_linux_gnueabihf: arm-linux-gnueabihf-gcc
+ AR_arm_unknown_linux_gnueabihf: arm-linux-gnueabihf-ar
+ # If you are building PyO3 bindings and need to specify Python libs from the sysroot:
+ # PYO3_CROSS_LIB_DIR: "/opt/homebrew/opt/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/sysroot/usr/lib" # Adjust path and Python version
+ # PYO3_CROSS_INCLUDE_DIR: "/opt/homebrew/opt/arm-unknown-linux-gnueabihf/arm-unknown-linux-gnueabihf/sysroot/usr/include/python3.x" # Adjust path and Python version
+ run: |
+ bash ./ci/script.sh
diff --git a/.github/workflows/dox.yml b/.github/workflows/dox.yml
index 2ebe1d25..f23c2214 100644
--- a/.github/workflows/dox.yml
+++ b/.github/workflows/dox.yml
@@ -8,7 +8,7 @@ on:
jobs:
deploy:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
diff --git a/ci/script.sh b/ci/script.sh
index 91a72407..f9e68a43 100755
--- a/ci/script.sh
+++ b/ci/script.sh
@@ -44,10 +44,15 @@ regular_test() {
# --- install opengen
pip install .
+ # --- rust dependencies
+ rustup update
+ rustup target add arm-unknown-linux-gnueabihf
+
# --- run the tests
export PYTHONPATH=.
python -W ignore test/test_constraints.py -v
python -W ignore test/test.py -v
+ python -W ignore test/test_raspberry_pi.py -v
# Run Clippy for generated optimizers
diff --git a/docs/python-advanced.md b/docs/python-advanced.md
index ccf45165..9f679158 100644
--- a/docs/python-advanced.md
+++ b/docs/python-advanced.md
@@ -81,6 +81,8 @@ A complete list of solver options is given in the following table
## Build options
+### Build mode
+
During the design phase, one needs to experiment with the problem
formulation and solver parameters. This is way the default build
mode is the "debug" mode, which compiles fast, but it suboptimal.
@@ -99,8 +101,10 @@ build_config.with_build_mode(
og.config.BuildConfiguration.RELEASE_MODE)
```
+### Cross-compilation
+
You can either compile for your own system, or cross-compile for a
-different target system. For example, to cross-compile for a Raspberry Pi,
+different target system. For example, to cross-compile for a **Raspberry Pi**,
set the following option
```python
@@ -113,8 +117,83 @@ or
build_config.with_target_system("rpi") # Raspberry Pi
```
-Note that you need to install the necessary target first.
+Note that you need to install the necessary target first.
+
+
+See setup details
+To cross-compile for a Raspberry Pi you need to run the following in your terminal
+
+```bash
+rustup target add arm-unknown-linux-gnueabihf
+```
+
+You also need to install the following dependencies
+
+
+
+
+```bash
+sudo apt-get update
+sudo apt-get install -y gcc-arm-linux-gnueabihf libc6-dev-armhf-cross
+```
+
+
+```bash
+# Tap the repository that provides the cross-compiler
+brew tap messense/macos-cross-toolchains
+# Update brew to ensure the tap is recognized (can sometimes be needed)
+brew update
+# Install the full toolchain (includes gcc, binutils, sysroot)
+# This specific formula provides the entire toolchain.
+brew install arm-unknown-linux-gnueabihf
+
+# Verify the compiler is found
+which arm-linux-gnueabihf-gcc || (echo "arm-linux-gnueabihf-gcc not found in PATH" && exit 1)
+```
+
+
+
+
+If you need to compile for a target other than `arm-linux-gnueabihf-gcc` (`rpi`)
+some manual configuration may be needed (you may need to install the target
+and/or a compiler/linker) and you may need to edit the auto-generated
+`.cargo/config.toml` files you will find in your auto-generated solvers.
+
+
+Non-supported targets
+The auto-generated `.cargo/config.toml` files contain entries like
+
+```toml
+[target.arm-unknown-linux-gnueabihf]
+linker="arm-linux-gnueabihf-gcc"
+```
+
+Here you may have to insert manually your own target.
+Feel free to open an [issue](https://github.com/alphaville/optimization-engine/issues)
+on GitHub if you would like us to add support for a particular target (create a feature
+request); see the [contributing guidelines](https://alphaville.github.io/optimization-engine/docs/contributing).
+
+
+
+When cross-compiling for a Raspberry Pi you may want to configure a TCP server
+so you can call the optimizer remotely. You can find more information about this
+[below](#tcpip-interface).
+Once you have cross-compiled, locate the file
+```text
+{your_optimizer}/tcp_iface_{your_optimizer}/target/arm-unknown-linux-gnueabihf/release/tcp_iface_{your_optimizer}
+```
+—where `{your_optimizer}` is the name of your optimizer—and copy it to your Raspberry Pi.
+On your Raspberry, change the permissions so you can execute this file
+```bash
+chmod u+x ./tcp_iface_{your_optimizer}
+```
+and [run it](https://alphaville.github.io/optimization-engine/docs/python-tcp-ip). Your OpEn server is live.
+Read also the [documentation](https://alphaville.github.io/optimization-engine/docs/python-tcp-ip)
+on the TCP sockets protocol of OpEn servers.
+
+### Other build options
+All build options are shown below
| Method | Explanation |
|-------------------------------|---------------------------------------------|
diff --git a/open-codegen/CHANGELOG.md b/open-codegen/CHANGELOG.md
index 59843c4b..ce7f82d7 100644
--- a/open-codegen/CHANGELOG.md
+++ b/open-codegen/CHANGELOG.md
@@ -7,6 +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
+## [0.9.4] - 2025-05-08
+
+
+### Fixed
+
+- Fixed issues with cross compilation (each sub-project has its own `.cargo/config.toml`) and updated [documentation](https://alphaville.github.io/optimization-engine/docs/python-advanced#cross-compilation)
+
+### Changed
+
+- Rename auto-generated bindings file from `.cargo/config` to `.cargo/config.toml` (backwards compatible change)
+- Updated min cmake version from 2.8 to 3.5
+- Updated auto-generated example C/C++ bindings
+
## [0.9.3] - 2024-12-06
@@ -226,6 +239,7 @@ Note: This is the Changelog file of `opengen` - the Python interface of OpEn
* Fixed `lbfgs` typo
+[0.9.4]: https://github.com/alphaville/optimization-engine/compare/opengen-0.9.3...opengen-0.9.4
[0.9.3]: https://github.com/alphaville/optimization-engine/compare/opengen-0.9.2...opengen-0.9.3
[0.9.2]: https://github.com/alphaville/optimization-engine/compare/opengen-0.9.1...opengen-0.9.2
[0.9.1]: https://github.com/alphaville/optimization-engine/compare/opengen-0.9.0...opengen-0.9.1
diff --git a/open-codegen/VERSION b/open-codegen/VERSION
index b3ec1638..2bd77c74 100644
--- a/open-codegen/VERSION
+++ b/open-codegen/VERSION
@@ -1 +1 @@
-0.9.3
\ No newline at end of file
+0.9.4
\ No newline at end of file
diff --git a/open-codegen/opengen/builder/optimizer_builder.py b/open-codegen/opengen/builder/optimizer_builder.py
index 8a43c30d..67956171 100644
--- a/open-codegen/opengen/builder/optimizer_builder.py
+++ b/open-codegen/opengen/builder/optimizer_builder.py
@@ -160,8 +160,6 @@ def __prepare_target_project(self):
"""Creates folder structure
Creates necessary folders
- Runs `cargo init` in that folder
-
"""
self.__logger.info("Creating necessary folders")
@@ -173,6 +171,13 @@ def __prepare_target_project(self):
os.makedirs(target_dir)
else:
make_dir_if_not_exists(target_dir)
+
+ # make folder {root}/.cargo
+ dot_cargo_dir = os.path.join(target_dir, ".cargo")
+ make_dir_if_not_exists(dot_cargo_dir)
+ # copy cargo_config.toml into .cargo/config.toml
+ cargo_config_file = os.path.join(og_dfn.templates_dir(), 'cargo_config.toml')
+ shutil.copy(cargo_config_file, os.path.join(dot_cargo_dir, 'config.toml'))
def __copy_icasadi_to_target(self):
"""
@@ -181,13 +186,21 @@ def __copy_icasadi_to_target(self):
self.__logger.info("Copying icasadi interface to target directory")
origin_icasadi_dir = og_dfn.original_icasadi_dir()
target_icasadi_dir = self.__icasadi_target_dir()
- if not os.path.exists(target_icasadi_dir):
- os.makedirs(target_icasadi_dir)
- shutil.rmtree(target_icasadi_dir)
+ target_icasadi_cargo_config_dir = os.path.join(target_icasadi_dir, ".cargo")
+
+ if os.path.exists(target_icasadi_dir):
+ shutil.rmtree(target_icasadi_dir)
+
shutil.copytree(origin_icasadi_dir,
target_icasadi_dir,
ignore=shutil.ignore_patterns(
'*.lock', 'ci*', 'target', 'auto*'))
+
+ # Copy cargo_config.toml into .cargo/config.toml
+ make_dir_if_not_exists(target_icasadi_cargo_config_dir)
+ cargo_config_file = os.path.join(og_dfn.templates_dir(), 'cargo_config.toml')
+ shutil.copy(cargo_config_file, os.path.join(
+ target_icasadi_cargo_config_dir, 'config.toml'))
def __generate_icasadi_cargo_toml(self):
"""
@@ -571,7 +584,7 @@ def __generate_build_rs(self):
def __build_optimizer(self):
target_dir = os.path.abspath(self.__target_dir())
command = self.__make_build_command()
- p = subprocess.Popen(command, cwd=target_dir)
+ p = subprocess.Popen(command, cwd=target_dir, shell=False)
process_completion = p.wait()
if process_completion != 0:
raise Exception('Rust build failed')
@@ -676,13 +689,13 @@ def __generate_code_python_bindings(self):
with open(target_python_rs_path, "w") as fh:
fh.write(python_rs_output_template)
- # move cargo_config into .cargo/config
+ # copy cargo_config into .cargo/config
target_cargo_config_dir = os.path.join(python_bindings_dir, '.cargo')
make_dir_if_not_exists(target_cargo_config_dir)
cargo_config_file = os.path.join(
- og_dfn.templates_dir(), 'python', 'cargo_config')
+ og_dfn.templates_dir(), 'cargo_config.toml')
shutil.copy(cargo_config_file, os.path.join(
- target_cargo_config_dir, 'config'))
+ target_cargo_config_dir, 'config.toml'))
def __generate_code_tcp_interface(self):
self.__logger.info(
@@ -694,10 +707,12 @@ def __generate_code_tcp_interface(self):
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")
+ tcp_iface_cargo_config_dir = os.path.join(tcp_iface_dir, ".cargo")
# make tcp_iface/ and tcp_iface/src
make_dir_if_not_exists(tcp_iface_dir)
make_dir_if_not_exists(tcp_iface_source_dir)
+ make_dir_if_not_exists(tcp_iface_cargo_config_dir)
# generate tcp_server.rs for tcp_iface
tcp_rs_template = OpEnOptimizerBuilder.__get_template(
@@ -718,6 +733,12 @@ def __generate_code_tcp_interface(self):
target_tcp_rs_path = os.path.join(tcp_iface_dir, "Cargo.toml")
with open(target_tcp_rs_path, "w") as fh:
fh.write(tcp_rs_output_template)
+
+ # Copy cargo_config.toml into .cargo/config.toml
+ cargo_config_file = os.path.join(
+ og_dfn.templates_dir(), 'cargo_config.toml')
+ shutil.copy(cargo_config_file, os.path.join(
+ tcp_iface_cargo_config_dir, 'config.toml'))
def __generate_yaml_data_file(self):
self.__logger.info("Generating YAML configuration file")
@@ -850,7 +871,7 @@ def build(self):
"""
self.__initialize() # initialize default value (if not provided)
self.__check_user_provided_parameters() # check the provided parameters
- self.__prepare_target_project() # create folders; init cargo project
+ self.__prepare_target_project() # create folders
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
diff --git a/open-codegen/opengen/templates/c/example_cmakelists.txt b/open-codegen/opengen/templates/c/example_cmakelists.txt
index d4da009c..13a79032 100644
--- a/open-codegen/opengen/templates/c/example_cmakelists.txt
+++ b/open-codegen/opengen/templates/c/example_cmakelists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 2.8)
+cmake_minimum_required(VERSION 3.5)
# Project name
project({{meta.optimizer_name}})
diff --git a/open-codegen/opengen/templates/c/example_optimizer_c_bindings.c b/open-codegen/opengen/templates/c/example_optimizer_c_bindings.c
index 79895b15..64f3b69a 100644
--- a/open-codegen/opengen/templates/c/example_optimizer_c_bindings.c
+++ b/open-codegen/opengen/templates/c/example_optimizer_c_bindings.c
@@ -3,11 +3,20 @@
*
* Compile with:
*
- * gcc -Wall -std=c99 -pedantic \
+ * $ gcc -Wall -std=c99 -pedantic \
example_optimizer.c -l:lib{{meta.optimizer_name}}.a \
-L./target/{{build_config.build_mode}} -pthread -lm -ldl \
-o optimizer
*
+ * OR ...
+ *
+ * $ gcc -Wall -std=c99 -pedantic \
+ example_optimizer.c -l{{meta.optimizer_name}} \
+ -L./target/{{build_config.build_mode}} -pthread -lm -ldl \
+ -o optimizer
+ *
+ * Or simply do:
+ * cmake .; make run
*/
#include
@@ -17,7 +26,7 @@
* Feel free to customize the following code...
*/
-int main() {
+int main(void) {
int i;
/* parameters */
diff --git a/open-codegen/opengen/templates/cargo_config.toml b/open-codegen/opengen/templates/cargo_config.toml
new file mode 100644
index 00000000..7ded92cd
--- /dev/null
+++ b/open-codegen/opengen/templates/cargo_config.toml
@@ -0,0 +1,25 @@
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+# Autogenerated Cargo.toml configuration file
+# This file was generated by OptimizationEngine
+#
+# See https://alphaville.github.io/optimization-engine/
+#
+# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+
+[target.x86_64-apple-darwin]
+rustflags = [
+ "-C", "link-arg=-undefined",
+ "-C", "link-arg=dynamic_lookup",
+]
+
+[target.aarch64-apple-darwin]
+rustflags = [
+ "-C", "link-arg=-undefined",
+ "-C", "link-arg=dynamic_lookup",
+]
+
+# This is for cross-compiling to ARM architecture (e.g., Raspberry Pi)
+[target.arm-unknown-linux-gnueabihf]
+linker="arm-linux-gnueabihf-gcc"
diff --git a/open-codegen/opengen/templates/python/cargo_config b/open-codegen/opengen/templates/python/cargo_config
deleted file mode 100644
index 49314f34..00000000
--- a/open-codegen/opengen/templates/python/cargo_config
+++ /dev/null
@@ -1,10 +0,0 @@
-[target.x86_64-apple-darwin]
-rustflags = [
- "-C", "link-arg=-undefined",
- "-C", "link-arg=dynamic_lookup",
-]
-[target.aarch64-apple-darwin]
-rustflags = [
- "-C", "link-arg=-undefined",
- "-C", "link-arg=dynamic_lookup",
-]
\ No newline at end of file
diff --git a/open-codegen/test/test_raspberry_pi.py b/open-codegen/test/test_raspberry_pi.py
new file mode 100644
index 00000000..e6236241
--- /dev/null
+++ b/open-codegen/test/test_raspberry_pi.py
@@ -0,0 +1,43 @@
+import opengen as og
+import unittest
+import casadi.casadi as cs
+import numpy as np
+import math
+
+
+class RaspberryPiTest(unittest.TestCase):
+
+ # -----------------------------------------------------------------------
+ # Cross-compile to Raspberry Pi
+ # -----------------------------------------------------------------------
+ def test_compile_rpi(self):
+ optimizers_dir = "my_optimizers"
+ optimizer_name = "rosenbrock"
+ nu, np = 5, 2
+ u = cs.SX.sym("u", nu) # decision variable (nu = 5)
+ p = cs.SX.sym("p", np) # parameter (np = 2)
+ phi = og.functions.rosenbrock(u, p) + 1500*cs.sum1(u)
+ c_f2 = cs.vertcat(0.2 + 1.5 * u[0] - u[1], u[2] - u[3] - 0.1)
+ bounds = og.constraints.Ball2(None, 1.5)
+ problem = og.builder.Problem(u, p, phi) \
+ .with_penalty_constraints(c_f2)\
+ .with_constraints(bounds)
+ meta = og.config.OptimizerMeta()\
+ .with_optimizer_name(optimizer_name)
+ build_config = og.config.BuildConfiguration() \
+ .with_build_directory(optimizers_dir) \
+ .with_build_mode(og.config.BuildConfiguration.DEBUG_MODE) \
+ .with_tcp_interface_config() \
+ .with_target_system("rpi")
+ solver_cfg = og.config.SolverConfiguration() \
+ .with_penalty_weight_update_factor(1.5) \
+ .with_preconditioning(True)
+ builder = og.builder.OpEnOptimizerBuilder(problem,
+ meta,
+ build_config,
+ solver_cfg)
+ builder.build()
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/website/package.json b/website/package.json
index cec29100..06c92b34 100644
--- a/website/package.json
+++ b/website/package.json
@@ -16,5 +16,6 @@
},
"dependencies": {
"remark-admonitions": "^1.2.1"
- }
+ },
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}