Skip to content

Commit

Permalink
Merge pull request #28 from niklasdewally/minion-bindings
Browse files Browse the repository at this point in the history
Represent and run a minion model in Rust
  • Loading branch information
ozgurakgun authored Oct 30, 2023
2 parents 0606264 + 3e3e0a9 commit 515d869
Show file tree
Hide file tree
Showing 17 changed files with 533 additions and 64 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/minion-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ jobs:
run: rustup update stable && rustup default stable

- working-directory: ./solvers/minion
run: cargo test
run: cargo test -- --test-threads=1



Expand Down
3 changes: 2 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[submodule "minion-bindings/vendor"]
path = solvers/minion/vendor
url = https://github.com/minion/minion
url = https://github.com/niklasdewally/minion
branch = conjure-oxide-devel
ignore = dirty

[submodule "solvers/chuffed/vendor"]
Expand Down
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions solvers/minion/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
debug/
target/
vendor/build
*.log
6 changes: 2 additions & 4 deletions solvers/minion/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
This directory contains in progress bindings for the [Minion solver](https://github.com/minion/minion).

Currently, this builds `minion`, but provides no functionality.

# Installation / Usage

TODO
Note that minion is single threaded only so tests must be ran with
cargo test -- --test-threads=1
37 changes: 33 additions & 4 deletions solvers/minion/build.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// adapted from
// - https://github.com/gokberkkocak/rust_glucose/blob/master/build.rs
// e https://github.com/gokberkkocak/rust_glucose/blob/master/build.rs
// - https://rust-lang.github.io/rust-bindgen/non-system-libraries.html
// - https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed
//

use std::env;
use std::path::PathBuf;
Expand Down Expand Up @@ -61,11 +62,39 @@ fn bind() {
// Tell cargo to invalidate the built crate whenever any of the
// included header files changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
// Must manually give allow list to stop bindgen accidentally binding something complicated
// in C++ stdlib that will make it crash.
.allowlist_function("minion_main")
// Make all templates opaque as reccomended by bindgen
.opaque_type("std::.*")
// Manually allow C++ functions to stop bindgen getting confused.
.allowlist_function("resetMinion")
.allowlist_function("runMinion")
.allowlist_function("constantAsVar")
.allowlist_function("newSearchOptions")
.allowlist_function("newSearchMethod")
.allowlist_function("newInstance")
.allowlist_function("newConstraintBlob")
.allowlist_function("newSearchOrder")
.allowlist_function("getVarByName")
.allowlist_function("searchOptions_free")
.allowlist_function("searchMethod_free")
.allowlist_function("instance_free")
.allowlist_function("constraint_free")
.allowlist_function("searchOrder_free")
.allowlist_function("newVar_ffi")
.allowlist_function("instance_addSearchOrder")
.allowlist_function("instance_addConstraint")
.allowlist_function("printMatrix_addVar")
.allowlist_function("printMatrix_getValue")
.allowlist_function("constraint_addVarList")
.allowlist_function("constraint_addConstantList")
.allowlist_function("vec_var_new")
.allowlist_function("vec_var_push_back")
.allowlist_function("vec_var_free")
.allowlist_function("vec_int_new")
.allowlist_function("vec_int_push_back")
.allowlist_function("vec_int_free")
.clang_arg("-Ivendor/build/src/") // generated from configure.py
.clang_arg("-Ivendor/minion/")
.clang_arg("-DLIBMINION")
.clang_arg(r"--std=gnu++11")
.clang_arg(r"-xc++")
// Finish the builder and generate the bindings.
Expand Down
4 changes: 2 additions & 2 deletions solvers/minion/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ if [ -d vendor/build ]; then
else
mkdir -p vendor/build
cd vendor/build
python3 ../configure.py --library
python3 ../configure.py --lib
fi

echo "------ BUILD STEP ------"
cd "$SCRIPT_DIR"
cd vendor/build
make all
make
93 changes: 93 additions & 0 deletions solvers/minion/src/ast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//! The Model Syntax tree for the Minion bindings.

use std::collections::HashMap;

pub type VarName = String;

pub struct Model {
/// A lookup table of all named variables.
pub named_variables: SymbolTable,
pub constraints: Vec<Constraint>,
}

impl Model {
pub fn new() -> Model {
Model {
named_variables: SymbolTable::new(),
constraints: Vec::new(),
}
}
}

pub enum Constraint {
SumLeq(Vec<Var>, Var),
SumGeq(Vec<Var>, Var),
Ineq(Var, Var, Constant),
}

/// A variable can either be a named variable, or an anomynous "constant as a variable".
///
/// The latter is not stored in the symbol table, or counted in Minions internal list of all
/// variables, but is used to allow the use of a constant in the place of a variable in a
/// constraint.
pub enum Var {
NameRef(VarName),
ConstantAsVar(i32),
}

pub enum Constant {
Bool(bool),
Integer(i32),
}

#[derive(Copy, Clone)]
pub enum VarDomain {
Bound(i32, i32),
Discrete(i32, i32),
SparseBound(i32, i32),
Bool(bool),
}

pub struct SymbolTable {
table: HashMap<VarName, VarDomain>,

// for now doubles both as Minion's SearchOrder and print order
var_order: Vec<VarName>,
}

impl SymbolTable {
fn new() -> SymbolTable {
SymbolTable {
table: HashMap::new(),
var_order: Vec::new(),
}
}

/// Creates a new variable and adds it to the symbol table.
/// If a variable already exists with the given name, an error is thrown.
pub fn add_var(&mut self, name: VarName, vartype: VarDomain) -> Option<()> {
if self.table.contains_key(&name) {
return None;
}

self.table.insert(name.clone(), vartype);
self.var_order.push(name);

return Some(());
}

pub fn get_vartype(&self, name: VarName) -> Option<VarDomain> {
match self.table.get(&name) {
Some(m) => Some(*m),
None => None,
}
}

pub fn get_variable_order(&self) -> Vec<VarName> {
self.var_order.clone()
}

pub fn contains(&self, name: VarName) -> bool {
self.table.contains_key(&name)
}
}
11 changes: 8 additions & 3 deletions solvers/minion/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
pub mod bindings {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
mod raw_bindings;

mod run;
pub use run::*;

pub mod ast;

mod scoped_ptr;
22 changes: 0 additions & 22 deletions solvers/minion/src/main.rs

This file was deleted.

105 changes: 105 additions & 0 deletions solvers/minion/src/raw_bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#![allow(warnings)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

#[cfg(test)]
mod tests {

use super::*;
use std::ffi::CString;

// solutions
static mut X_VAL: i32 = 0;
static mut Y_VAL: i32 = 0;
static mut Z_VAL: i32 = 0;

#[no_mangle]
pub extern "C" fn hello_from_rust() -> bool {
unsafe {
X_VAL = printMatrix_getValue(0) as _;
Y_VAL = printMatrix_getValue(1) as _;
Z_VAL = printMatrix_getValue(2) as _;
return true;
}
}

#[test]
fn xyz_raw() {
// A simple constraints model, manually written using FFI functions.
// Testing to see if it does not segfault.
// Results can be manually inspected in the outputted minion logs.
unsafe {
// See https://rust-lang.github.io/rust-bindgen/cpp.html
let options = newSearchOptions();
let args = newSearchMethod();
let instance = newInstance();

let x_str = CString::new("x").expect("bad x");
let y_str = CString::new("y").expect("bad y");
let z_str = CString::new("z").expect("bad z");

newVar_ffi(instance, x_str.as_ptr() as _, VariableType_VAR_BOUND, 1, 3);
newVar_ffi(instance, y_str.as_ptr() as _, VariableType_VAR_BOUND, 2, 4);
newVar_ffi(instance, z_str.as_ptr() as _, VariableType_VAR_BOUND, 1, 5);

let x = getVarByName(instance, x_str.as_ptr() as _);
let y = getVarByName(instance, y_str.as_ptr() as _);
let z = getVarByName(instance, z_str.as_ptr() as _);

// PRINT
printMatrix_addVar(instance, x);
printMatrix_addVar(instance, y);
printMatrix_addVar(instance, z);

// VARORDER
let search_vars = vec_var_new();
vec_var_push_back(search_vars as _, x);
vec_var_push_back(search_vars as _, y);
vec_var_push_back(search_vars as _, z);
let search_order = newSearchOrder(search_vars as _, VarOrderEnum_ORDER_STATIC, false);
instance_addSearchOrder(instance, search_order);

// CONSTRAINTS
let leq = newConstraintBlob(ConstraintType_CT_LEQSUM);
let geq = newConstraintBlob(ConstraintType_CT_GEQSUM);
let ineq = newConstraintBlob(ConstraintType_CT_INEQ);

let rhs_vars = vec_var_new();
vec_var_push_back(rhs_vars, constantAsVar(4));

// leq / geq : [var] [var]
constraint_addVarList(leq, search_vars as _);
constraint_addVarList(leq, rhs_vars as _);

constraint_addVarList(geq, search_vars as _);
constraint_addVarList(geq, rhs_vars as _);

// ineq: [var] [var] [const]
let x_vec = vec_var_new();
vec_var_push_back(x_vec, x);

let y_vec = vec_var_new();
vec_var_push_back(y_vec, y);

let const_vec = vec_int_new();
vec_int_push_back(const_vec, -1);

constraint_addVarList(ineq, x_vec as _);
constraint_addVarList(ineq, y_vec as _);
constraint_addConstantList(ineq, const_vec as _);

instance_addConstraint(instance, leq);
instance_addConstraint(instance, geq);
instance_addConstraint(instance, ineq);

let res = runMinion(options, args, instance, Some(hello_from_rust));

// does it get this far?
assert_eq!(res, 0);

// test if solutions are correct
assert_eq!(X_VAL, 1);
assert_eq!(Y_VAL, 2);
assert_eq!(Z_VAL, 1);
}
}
}
Loading

0 comments on commit 515d869

Please sign in to comment.