Git Product home page Git Product logo

russcip's People

Contributors

codingtil avatar karelpeeters avatar lovasoa avatar mmghannam avatar mohammadfawaz avatar rinde avatar veladus avatar yonch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

russcip's Issues

from-source gives: `libscip.so.9.0: cannot open shared object file: No such file or directory`

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

from-source feature on macOS: Assertion failed: (var == vars[j]), function presolRoundCardinality, file cons_cardinality.c, line 932.

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

Store state-specific variables inside the state object

https://github.com/mmghannam/russcip/blob/ac0bd3c23c04d1a33b4593c8ab7fe6957df624b0/src/model.rs#L14-L28

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 }, 
 } 

Add constraint to Model<Solved>

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

Document compatible SCIP version(s)

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)

Build SCIP from source

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 Plugin

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).

Make illegal states irrepresentable

          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)

russcip fails to compile on Windows MSVC: u32 != i32

Problem

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(),
   |                                                                          ++++++++++++++++++++

Cause

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 an int or unsigned 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.

Solution

I see a couple of potential solutions:

  • Tell 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.
  • Expose this type difference to users 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.
  • Add casts to 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!

# TODO: fix windows workflow

segfault (use after free) when accessing solution after model is dropped

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.

TSP example on iterated solving

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.

Adding constraint improves objective.

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

dual() method for any constraint

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.

Automatically fetch and compile SCIP

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?

Library name?

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).

unable to compile

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

Maybe a bug about adding solutions

#[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());**
    
}

Any plan to add SCIPcreateExprPow ?

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

Call concurrent solver if available, to leverage multiple CPU cores

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.

Feature request: SOS1 constraint support

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.

Add indicator constraints

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?

Expand Solved into multiple states depending on the solution status

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)

Possible dangling raw pointers

          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)

Consider allowing `clippy::too_many_arguments` directly in `Cargo.toml`

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.