scipopt / russcip Goto Github PK
View Code? Open in Web Editor NEWRust interface for SCIP
Home Page: https://crates.io/crates/russcip
License: Apache License 2.0
Rust interface for SCIP
Home Page: https://crates.io/crates/russcip
License: Apache License 2.0
A reference to the Model
class is needed for plugins to be able to query the model.
Can be fixed by changing all methods to take a -mutable- reference to self instead of requiring ownership.
This should be split into multiple pull requests for each plugin.
Using the new feature from-source, available since 0.3.3, I'm unable to run the built binary. Here's my MRE:
Dockerfile:
FROM rust:1.77
WORKDIR /work
RUN apt-get update && apt-get install -y cmake clang
RUN wget https://github.com/scipopt/russcip/archive/refs/tags/v0.3.3.tar.gz \
&& tar -xzvf v0.3.3.tar.gz \
&& mv russcip-0.3.3 /russcip
WORKDIR /russcip
RUN cargo build --release -F from-source --example create_and_solve
CMD ["./target/release/examples/create_and_solve"]
Build:
docker build .
Run / error:
docker run -it --rm russcip-test:latest
./target/release/examples/create_and_solve: error while loading shared libraries: libscip.so.9.0: cannot open shared object file: No such file or directory
Running this on my Macbook Pro M3 with russcip v0.3.3's new feature from-source
:
❯ cargo install russcip -F from-source --examples
Updating crates.io index
Installing russcip v0.3.3
Updating crates.io index
Compiling libc v0.2.153
[...]
Compiling russcip v0.3.3
Finished release [optimized] target(s) in 2m 30s
Installing /Users/jacob/.cargo/bin/create_and_solve
Installed package `russcip v0.3.3` (executable `create_and_solve`)
I get this error:
❯ create_and_solve
dyld[26197]: Library not loaded: libscip.9.0.dylib
Referenced from: <C3392B20-78D6-3592-8F6D-77169937FA93> /Users/jacob/.cargo/bin/create_and_solve
Reason: tried: 'libscip.9.0.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OSlibscip.9.0.dylib' (no such file), 'libscip.9.0.dylib' (no such file), '/Users/jacob/libscip.9.0.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/jacob/libscip.9.0.dylib' (no such file), '/Users/jacob/libscip.9.0.dylib' (no such file)
[1] 26197 abort create_and_solve
Note to self: brew install bison
was needed to get compilation to work
I was wondering how this library would interact if I were to use the exact-rational branch of the scip solver, or if it is even possible.
Currently there is no compile-time guarantee that, for instance, best_sol
is always None when the state is Created
and is never None when the state is solved.
Variables that are specific to a state should be stored inside the state to enforce such things at compile time. Something like
pub enum State {
Start,
ProblemCreated { vars: Variables },
Solved { vars: Variables, solution: Solution },
}
I couldn't find in the documentation if there's a safe way to add a constraint after the model was solved
This could be useful for solving a TSP with lazy constraint generation, for instance
I think the library should also document which version of scip it depends on. Currently it just tells the user to run
conda install --channel conda-forge scip
which works today, but will break when the interface of scip changes.
Originally posted by @lovasoa in #11 (comment)
Add a vendored feature to the crate, that would build SCIP from source and then generate bindings.
UPDATE: Since the SCIPOptSuite is quite big in size, it would be best if the source code is not included by default and only downloaded if the vendored feature is enabled.
Primal heuristic should return something that implement the Iterator
trait of solutions and russcip internally should handle adding it to scip. Would be great if this Iterator can be nonblocking (working on another thread).
None of the public methods of this library seems to be documented.
Additionally, the docs.rs builds are failing: https://docs.rs/crate/russcip/0.1.9
One thing you can do to make your life easier is to forbid some method calls at the type level.
See https://willcrichton.net/rust-api-type-patterns/state_machines.html
It looks like scip maintains a state machine internally. These states should probably be reflected at the type level in rust. Instead of a single Model
type, there should probably be at least an UnsolvedModel
and a SolvedModel
, and probably more.
This should also help you have less Result
in your return types. If you can guarantee at the type level that a particular C function call will not fail, then you can turn a faillible_call()?
into a faillible_call().expect("We are in state X, which meets all the preconditions of function faillible_call")
Originally posted by @lovasoa in #15 (comment)
When trying to compile russcip
on Windows MSVC, I get a couple of errors that are all similar to this:
error[E0308]: mismatched types
--> [...]\.cargo\registry\src\index.crates.io-6f17d22bba15001f\russcip-0.2.6\src\branchrule.rs:34:43
|
32 | fn from(val: BranchingResult) -> Self {
| ---- expected `u32` because of return type
33 | match val {
34 | BranchingResult::DidNotRun => ffi::SCIP_Result_SCIP_DIDNOTRUN,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found `i32`
|
help: you can convert an `i32` to a `u32` and panic if the converted value doesn't fit
|
34 | BranchingResult::DidNotRun => ffi::SCIP_Result_SCIP_DIDNOTRUN.try_into().unwrap(),
| ++++++++++++++++++++
The underlying reason is that the enum SCIP_Result
type is represented by unsigned int
on Linux, but by int
in Windows MSVC. Apparently this is allowed by the C++ spec:
Declares an unscoped enumeration type whose underlying type is not fixed (in this case, the underlying type is an implementation-defined integral type that can represent all enumerator values; this type is not larger than
int
unless the value of an enumerator cannot fit in anint
orunsigned int
.
bindgen
then just exposes the underlying type as u32
or i32
to Rust, see rust-lang/rust-bindgen#1966.
In turn, russcip
does not handle this type difference correctly, and fails to compile.
I see a couple of potential solutions:
bindgen
to generate an actual Rust enum instead. This might be problematic for the reasons described in rust-lang/rust-bindgen#758, but I don't know how this applies to SCIP specifically.russcip
, by changing all types to SCIP_Result
instead of u32
. This is annoying because it just pushes the problem onto users, who will probably all also end up with either broken Linux or Windows builds.u32
in all the right places in russcip
, at least for windows targets. Then everyone would be using u32
all the time, even though it's not what the underlying SCIP library is using.I think the last solution is the easiest to implement, and also the best one. I could submit a PR for any solution if that's helpful and a decision has been made.
This might also be the only thing stopping Windows CI from working, in which case it can be turned on again!
Hi,
Is it possible to do arithmetic operations for variables defined in the model?
Thanks,
Hang
The reasoning is that this can help users use SCIP functions that are not implemented in safe rust yet.
Build "Expressions" from variables that allow arithmetic operations, and add a range to bound them.
use russcip::prelude::*;
fn main() {
// Create model
let mut model = Model::new()
.hide_output()
.include_default_plugins()
.create_prob("test")
.set_obj_sense(ObjSense::Maximize);
// Add variables
let x1 = model.add_var(0., f64::INFINITY, 3., "x1", VarType::Integer);
let x2 = model.add_var(0., f64::INFINITY, 4., "x2", VarType::Integer);
// Add constraints
model.add_cons(
vec![x1.clone(), x2.clone()],
&[2., 1.],
-f64::INFINITY,
100.,
"c1",
);
model.add_cons(
vec![x1.clone(), x2.clone()],
&[1., 2.],
-f64::INFINITY,
80.,
"c2",
);
let sol = model.solve().best_sol().unwrap(); // Temporary value returned from `model.solve()` is dropped.
dbg!(sol); // <-- Segfault here
}
From looking over the russcip code the cause seems obvious.
ScipPtr
's drop impl is responsible for cleaning up and freeing, but Solution
also holds the same raw pointer to Scip
, causing a use after free.
My first thought for a fix would be to remove the pointer aliasing, only ScipPtr
should hold the pointer to Scip
. ScipPtr
could then be reference counted. Alternatively, change the API so that it more closely mimics that of scip and require passing the ScipPtr
separately.
Add an example for solving the TSP using a branch-and-cut approach by iteratively solving the model and calling free_transform()
and adding sub-tour elimination constraints.
Hi there,
I am currently using this solver for a scheduling problem, which I have modelled as a routing problem, where my objective is to maximise. As with routing problems, I have a set of nodes to visit and have created a "Virtual Node", for where the route start and ends. I have added constraints to ensure that at the start of the route, we must go from the virtual node to an uplink node, something specific to my problem. I have done this by introducing two sets of constraints.
for (k, s) in K.iter().enumerate() {
let (mut vars, mut coefs) = get_empty_vars_and_coefs();
for i in (0..N.len())
.into_iter()
.filter(|&n| uplink_indicies.contains(&n))
{
for p in 0..N[i].get_vtw(s).unwrap().len() {
vars.push(x[k][virtual_indicies[0]][0][i][p].clone());
coefs.push(1.0);
}
}
model.add_cons(vars, &coefs, 1.0, 1.0, "start_virtual_to_uplink");
}
for (k, s) in K.iter().enumerate() {
let (mut vars, mut coefs) = get_empty_vars_and_coefs();
for i in (0..N.len())
.into_iter()
.filter(|&n| !uplink_indicies.contains(&n))
{
for p in 0..N[i].get_vtw(s).unwrap().len() {
vars.push(x[k][virtual_indicies[0]][0][i][p].clone());
coefs.push(1.0);
}
}
model.add_cons(vars, &coefs, f64::MIN, 0.0, "start_virtual_to_uplink");
}
x is my variable where x[k][i][p][j][q] = 1 if vehicle k goes from node i, in window p to node j, in window q. In the first set of constraints, I define that each vehicle must travel from the virtual node to an uplink node, and in the second constraints, I say they cannot go from the virtual node to any other type of node. Along with these constraints I have the other necessary constraints for a routing problem.
However, I am finding that when I have both of these constraints, I am getting an objective value of 4, whereas when I drop the first set of constraints, the solver is returning the optimal objective as 0. This has been bugging me because how can removing a set of constraints make the objective worse?
I understand that without having all of the code this isn't much to go on, however I was wondering if anyone has come across this before or if there is something potentially wrong with my formulation?
Thanks
Karl
Follow up from rust-or/good_lp#38
Souldn't this be a Drop implementation ?
The library should not leak memory, or free memory that can still be accessed. Maybe you should add a test where you solve many large problems in a loop ?
Should return a result type wrapping the result of the underlying SCIP function SCIPgetDualSolVal
, and should return an error for any constraint that is not linear.
Now that SCIP is using the Apache license, it would be fitting to automatically fetch and compile the SCIP library when you cargo install this crate. This should make it way more accessible to people.
What do you think? Is this feasible?
First of all, thanks for making this great library.
On February 24, 2022 Russia invaded Ukraine in what can only be seen as a pure act of evil. The amount of civilian casualties and general misery that the Ukrainian people have to endure is chocking.
What does this have to do with this library? Nothing really. The name just immediately reminds me of Russia, and all the horribleness that they are inflicting on the Ukrainian people.
So I'm basically just throwing out the question - have you thought about changing the name to something that doesn't begin with Ru/Rus/Russ?
Something that comes to mind is scip-rs
(scip
is unfortunately taken on crates.io).
It looks like throughout the library, errors are handled with panics (which crash the user's program). This is probably not what the library user wants. Shouldn't scip errors be translated to rust Results instead, so that they can be handled with traditional rust error handling ?
Hi,
I was trying to use russcip example in here. I had a compile issue with the following information:
linking with `cc` failed: exit status: 1
ld: library not found for -lscip
clang: error: linker command failed with exit code 1 (use -v to see invocation)
should I install a library called -lscip somewhere? Could you provide some suggestions about this?
Thanks,
Hang
#[test]
fn create_sol() {
let mut model = Model::new()
.hide_output()
.include_default_plugins()
.create_prob("test")
.set_obj_sense(ObjSense::Minimize);
let x1 = model.add_var(0., 1., 3., "x1", VarType::Binary);
let x2 = model.add_var(0., 1., 4., "x2", VarType::Binary);
let cons1 = model.add_cons_set_part(vec![], "c");
model.add_cons_coef_setppc(cons1, x1.clone());
model.add_cons_set_pack(vec![x2.clone()], "c");
let sol = model.create_sol();
assert_eq!(sol.obj_val(), 0.);
sol.set_val(x1.clone(), 1.);
sol.set_val(x2.clone(), 1.);
assert_eq!(sol.obj_val(), 7.);
assert!(model.add_sol(sol).is_ok());
let mut model = model.solve();
assert_eq!(model.n_sols(), 2);
// I added following code. It lead to error: assertion failed: model.add_sol(sol).is_ok()
**let mut model = model.free_transform();
let sol = model.create_sol();
assert_eq!(sol.obj_val(), 0.);
sol.set_val(x1, 1.);
sol.set_val(x2, 1.);
assert_eq!(sol.obj_val(), 7.);
assert!(model.add_sol(sol).is_ok());**
}
Line 425 in 14dbea2
It can be that a solution is found but the status is not optimal; e.g. a timelimit is reached. We should instead check if a solution exists.
Hey, I want to use the function of "SCIPcreateExprPow". It is not available currently.
Therefore, I want to add it by my self.
pub(crate) fn create_pow_expr(
&mut self,
base: &Variable,
exponent: f64,
) -> Result<Expr, Retcode> {
let mut expr_ptr = MaybeUninit::uninit();
scip_call! { ffi::SCIPcreateExprPow(
self.raw,
expr_ptr.as_mut_ptr(),
base.raw as *mut _,
exponent,
None,
std::ptr::null_mut(),
) };
let expr_ptr = unsafe { expr_ptr.assume_init() };
Ok(Expr { raw: expr_ptr })
}
pub fn add_pow_expr(&mut self, base: &Variable, exponent: f64) -> Rc {
let exp = self
.scip
.create_pow_expr(base, exponent)
.expect("Failed to create express power in state ProblemCreated");
let exp_rc = Rc::new(exp);
return exp_rc;
}
Here is an example to utilize it,
// Create model
let mut model = Model::new()
.hide_output()
.include_default_plugins()
.create_prob("test")
.set_obj_sense(ObjSense::Maximize);
// Add variables
let x1 = model.add_var(0., f64::INFINITY, 3., "x1", VarType::Integer);
let xx = model.add_pow_expr(&x1, 2.0);
model.add_cons(
vec![x1.clone(), **xx.clone()**],
&[2., 1.],
-f64::INFINITY,
100.,
"c1",
);
I found that I cannot utilize it in model.add_cons .
Is any suggestion?
Thank you!
Young
Support all result types mentioned in the section titled "Further information for the three execution methods" in https://www.scipopt.org/doc/html/BRANCH.php
The SCIP solver supports concurrent solving, through the use of SCIPsolveConcurrent(). Note that SCIPsolveParallel
is mentioned in the docs about how to run concurrent solving for version 4.0, but is marked as deprecated in favor of SCIPsolveConcurrent in the code.
For example, it is possible to build SCIP with multi-threading support on macOS, by setting TPI=tny
.
However SCIPsolveConcurrent()
fails when SCIP is built without parallel support rather than calling the single-threaded code.
To correctly call the suitable method for the build, there is a SCIPtpiGetNumThreads()
method in tpi.h
, that returns 1 when no threading support is available. So we could check if the return value is 1 and call the single threaded solve, otherwise call SCIPsolveConcurrent()
.
I'll include a PR, however I have little experience with bindgen and it appears SCIPtpiGetNumThreads()
does not currently have ffi bindings.. Any idea how to add that?
cc @mmghannam if you might know.
Isn't this undefined behavior ?
https://github.com/mmghannam/russcip/blob/cdda8e7142d7bf62769eafc5cd58fd27eb305045/src/model.rs#L21
zero-initializing a variable of reference type causes instantaneous undefined behavior
https://doc.rust-lang.org/std/mem/union.MaybeUninit.html
In general, the usage of mem::zeroed
everywhere should probably be replaced with MaybeUninint.
Maybe you could add a CI check to test this library with miri ?
We currently use SOS1 with coin-cbc in good_lp, but would like to be able to try out SOS1 with SCIP instead. It seems that it's implemented by SCIP, and good_lp has a trait which must be implemented by russcip for support.
My C/C++ skills are lacking to say the least, but I could do the good_lp implementation if something lands in russcip.
I have locally made a change to enable indicator constraints with the following interface:
// Indicator constraint: `binvar == 1` implies `coefs * vars <= rhs`
fn add_cons_indicator(
&mut self,
binvar: Rc<Variable>,
vars: Vec<Rc<Variable>>,
coefs: &mut [f64],
rhs: f64,
name: &str,
) -> Rc<Constraint>;
which uses SCIP's SCIPcreateConsBasicIndicator
.
This is quite useful for manipulating all kinds of Boolean expressions such as OR constraints etc.
Would love to see this as part of the russcip
official published crate instead of publishing my own.
@mmghannam I am happy to put up a PR with my local changes to enable this. Are you okay with that?
Add a method in the Model<Unsolved>
struct that calls SCIPsetObjIntegral
from ffi
.
Maybe as a second iteration, we could improve the API a little bit. Having solve
always returning a Model<Solved>
and get_best_sol
return an Option
is a little bit confusing, I think.
Maybe we should have solve
return a Result<Model<SolvedOptimal>, SolveError>
or something like that ? That would make it easier to know what happened when an optimal solution is not available.
Originally posted by @lovasoa in #29 (review)
In other methods, you copy the `self.scip` pointer. Do you have a guarantee that you don't have any other references to it anywhere else when you get there ?
A quick look seems to indicate that one could drop the model but keep variables, or a solution, that still contain a pointer to the scip struct.
You should probably be more careful with copying raw pointers. At least, write a comment explaining why you think it's safe every time. At best, just never do it, make a single rust struct for each type of C pointer, and then manage that memory with traditional rust memory management.
Originally posted by @lovasoa in #13 (comment)
It's likely that many functions in russcip
will require a large numer of arguments to match the actual SCIP C-API. Instead of having to add #[allow(clippy::too_many_arguments)]
everywhere like I had to do in #121, it's probably better to simply add
[lints.clippy]
too_many_arguments = "allow"
in Cargo.toml
An alternative is to modify the limit on what "too many" means but that seems unnecessary.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.