Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add various options to the scip solver #84

Merged
merged 10 commits into from
Feb 27, 2025

Conversation

KnorpelSenf
Copy link
Contributor

  • setting the verbosity level
  • setting the heuristics
  • setting the time limit
  • setting the memory limit
  • setting int, longint, bool, str, and float options

- setting the verbosity level
- setting the heuristics
- setting the time limit
- setting the memory limit
- setting int, longint, bool, str, and float options
Co-authored-by: Mohammed Ghannam <mohammad.m.ghannam@gmail.com>
@KnorpelSenf KnorpelSenf requested a review from mmghannam January 23, 2025 17:39
Copy link
Collaborator

@lovasoa lovasoa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

Could we:

  • avoid panicking and return the error to the user instead?
  • make a single generic set_option method? (with a trait implemented on the required types)
  • add tests?

@lovasoa
Copy link
Collaborator

lovasoa commented Jan 23, 2025

By the way, can I add you to the rust-or GitHub org? You have been doing a lot of great work in the space recently.

@KnorpelSenf
Copy link
Contributor Author

Great!

Could we:

* avoid panicking and return the error to the user instead?

* make a single generic set_option method? (with a trait implemented on the required types)

* add tests?

Yep, I'll fix all that!

By the way, can I add you to the rust-or GitHub org? You have been doing a lot of great work in the space recently.

Absolutely, that would make things easier. Thanks!

@KnorpelSenf
Copy link
Contributor Author

@lovasoa is that how you expected the trait to look?

Also, I have no idea how to test this properly. There is no way to get the option, so we can't really test if the option was set without solving a model and asserting that it was solved a certain way. That seems excessively expensive. How to proceed?

@KnorpelSenf
Copy link
Contributor Author

Also, setting the time limit partially moves the model … that is not a very ergonomic API. I am not really sure how to improve this.

@lovasoa
Copy link
Collaborator

lovasoa commented Jan 23, 2025

Yes, you should either take the SCIPProblem by value and return a SCIPProblem, or take it by mutable reference and return nothing (using std::mem::take or replace). Try to be consistent with option setting in other solvers.

@lovasoa
Copy link
Collaborator

lovasoa commented Jan 23, 2025

for the trait, it looks good this way 👍

for testing, yes, you should make a problem, set an option, solve, and check that the option was respected by checking the solution

@KnorpelSenf
Copy link
Contributor Author

KnorpelSenf commented Jan 29, 2025

Yes, you should either take the SCIPProblem by value and return a SCIPProblem, or take it by mutable reference and return nothing (using std::mem::take or replace). Try to be consistent with option setting in other solvers.

So we would do something like

    pub fn set_memory_limit(&mut self, memory_limit: usize) {
        std::mem::take(self.as_inner_mut()).set_memory_limit(memory_limit);
    }

or maybe

    pub fn set_memory_limit(mut self, memory_limit: usize) -> Self {
        std::mem::take(self.as_inner_mut()).set_memory_limit(memory_limit);
        self
    }

etc?

The only other place I found that has this is the set_parameter of of coin_cbc. However, in using good_lp, I always found it to be a bit annoying to use. In the README example, I want to do:

let solution = vars.maximise(10 * (a - b / 5) - b)
        .using(default_solver) // multiple solvers available
        .with(constraint!(a + 2 <= b))
        .with(constraint!(1 + a >= 4 - b))
        .set_parameter("name", "value")
        .solve()?;

Unfortunately, this isn't possible and now I have to do

let mut model = vars.maximise(10 * (a - b / 5) - b)
        .using(default_solver) // multiple solvers available
        .with(constraint!(a + 2 <= b))
        .with(constraint!(1 + a >= 4 - b));
model.set_parameter("name", "value");
let solution = model.solve()?;

which looks odd to me.

Should I still go ahead with the the former suggestion for the sake of consistency?

@KnorpelSenf
Copy link
Contributor Author

for the trait, it looks good this way 👍

Nice! It inspired scipopt/russcip#196 btw

for testing, yes, you should make a problem, set an option, solve, and check that the option was respected by checking the solution

That will take a very long time, add a lot of complexity, and slow down the tests, too. I would have to

  • create a model, set a short timeout, solve it, measure the time, and assert that the solver completed fast enough
  • create a model, solve it, somehow capture the stdout of the process, and perform assertions based on what was logged
  • create a model with a complexity that the four SCIP heuristics matter for the solving process, solve this model with all heuristics, and somehow assert that the right heuristics were used by the solver (that would have to be investigated)
  • create a sufficiently complex model that it uses a lot of memory, set the memory limit option, measure the memory consumption of the current process, and assert that it did not exceed certain values
  • for each untested type of option in the model (bool, longint, etc), create a model that is affected by this option and then make sure it is set correctly

This honestly sounds overkill when factoring in the low complexity of my changes. We would effectively be performing the exact same tests as russcip already runs, so we just test the underlying crate. Instead, shouldn't we just read back the parameters in the tests and make sure that they were set correctly? Reading model parameters is available in latest russcip@0.6.1 which we could update to.

@lovasoa
Copy link
Collaborator

lovasoa commented Jan 29, 2025

Okay! let's go with option 2: read the parameters back.

@KnorpelSenf
Copy link
Contributor Author

Thanks, I'll perform these changes soon!

Do you have an opinion on which API to use in #84 (comment)?

@lovasoa
Copy link
Collaborator

lovasoa commented Jan 30, 2025

no opinion :)

@KnorpelSenf
Copy link
Contributor Author

I'm back!

And I have a number of questions …

Comment on lines 126 to 136
impl ScipOptionGetValue for String {
fn get_for(model: Model<ProblemCreated>, option: &str) -> Self {
model.str_param(option)
}
}
impl ScipOptionSetValue for &str {
fn set_for(self, model: Model<ProblemCreated>, option: &str) -> Result<(), Retcode> {
model.set_str_param(option, &self)?;
Ok(())
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I split this into two traits so that people can set a &str and get a String. Should we try to unify the two traits? If yes, how would we resolve this issue?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's okay to implement it for &str only, if we don't need a String

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't implement the getter for &str because we cannot return temporary values, we'll have to return String

@lovasoa lovasoa marked this pull request as draft February 18, 2025 16:54
@lovasoa
Copy link
Collaborator

lovasoa commented Feb 18, 2025

I converted this to a draft PR, feel free to set it back as ready for review when you want me to merge !

@KnorpelSenf KnorpelSenf marked this pull request as ready for review February 19, 2025 13:56
@KnorpelSenf KnorpelSenf requested a review from lovasoa February 19, 2025 13:56
@KnorpelSenf
Copy link
Contributor Author

KnorpelSenf commented Feb 19, 2025

@lovasoa I think I fixed everything now! From my side, this can be merged. Let's see if CI agrees with me (edit: it does!)

@KnorpelSenf KnorpelSenf requested a review from lovasoa February 27, 2025 12:00
@KnorpelSenf
Copy link
Contributor Author

@lovasoa please take another look, I completely got rid of as_inner_mut and thereby avoided the std::mem::take. Is there anything obvious that I'm missing as to why this approach won't work?

@lovasoa
Copy link
Collaborator

lovasoa commented Feb 27, 2025

Looks good to me !

@lovasoa lovasoa merged commit dfb38b1 into rust-or:main Feb 27, 2025
1 check passed
@KnorpelSenf KnorpelSenf deleted the scip-options branch February 27, 2025 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants