Skip to content

scip interface (first version)#412

Draft
tias wants to merge 19 commits intomasterfrom
scip2
Draft

scip interface (first version)#412
tias wants to merge 19 commits intomasterfrom
scip2

Conversation

@tias
Copy link
Collaborator

@tias tias commented Sep 17, 2023

with help of Mark Turner from SCIP.

Now... turns out that @IgnaceBleukx already worked on a SCIP interface too, about a year ago (e.g. SCIP branch

So maybe Ignace can now make a best of both worlds? : )
(current branch is up to date with current template, but the add part is minimal, e.g. reified linear is not properly tested/supported at the least).

@IgnaceBleukx
Copy link
Collaborator

Yes, I would be happy to merge both versions of the interface!

@IgnaceBleukx
Copy link
Collaborator

Updated the interface somewhat, it seems SCIP does not have the fancy Min/Max/Abs constraints built-in in its API : /

Added the SOS1 and cardinality constraints to the interface too, these constraints are created when decomposing alldiff for example so definately usefull to have them imho. We should also add them to the Gurobi interface

SolveAll is in a weird state for PySCIPOpt: SCIP does allow for retrieving all feasible solutions, but the Python interface does not seem to allow for collecting the solutions which are found... Added the issues to track in the code so we can add the specialized implementation when these are resolved.

All tests are passing so ready for review I think.

@Wout4
Copy link
Collaborator

Wout4 commented Sep 19, 2023

I get an error for optimization problems:

\cpmpy\solvers\scip.py", line 154, in solve
    self.objective_value_ = scip_objective.getObjVal()
AttributeError: 'pyscipopt.scip.Expr' object has no attribute 'getObjVal'

if self.has_objective():
self.objective_value_ = scip_objective.getObjVal()

self.scip_model.freeTransform() # Mark Turner from SCIP told me you need to do this if you want to support adding additional vars/constraints later...

Choose a reason for hiding this comment

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

So a tiny summary for this: SCIP transforms the problem that you are actual solving into something it believes is easier to solve (presolve techniques etc). All solutions when found are then transformed back to the original space, because that is obviously what the user modelled and can interpret. Allowing the user to change the model during solving gets messy when balancing these things however, so it's forbidden. What self.scip_model.freeTransform() does is to remove all information from the transformed space, and just leave the original model. All solutions are kept, and the user can now change the model as they will. The downside is that potentially useful information for speeding up the next optimisation call is thrown out.

sciplhs = self._make_numexpr(lhs)
self.scip_model.addCons(sciplhs <= sciprhs)

elif cpm_expr.name == '>=':
Copy link

@Opt-Mucca Opt-Mucca Sep 26, 2023

Choose a reason for hiding this comment

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

Just a general interest: Due to how the constraints are transformed are you certain that these constraints can't be multiplied by -1 to become cardinality constraints?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Aha no, actually we are not... I will do a check for it here

lhs = Operator("wsum", [[-w for w in lhs.args[0]], lhs.args[1]])
else:
lhs = -lhs
self += cond.implies(lhs <= -rhs)

Choose a reason for hiding this comment

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

Does this constraint actually end up being added to the SCIP model?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, this line sends the new constraint cond => lhs <= -rhs through the entire __add__ pipeline again.
But coming to think of it, we probably pay quite a penalty by doing so as it also sends the constraint through the transformation pipeline...
I'll refactor this somewhat so we avoid unnecessary transformations...

raise Exception(f"Unknown linear expression {sub_expr} name")

# True or False
elif isinstance(cpm_expr, BoolVal):

Choose a reason for hiding this comment

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

Don't really understand what this constraint means, always struggle a bit with the CP language. If you can explain then I can see if there's a more natural MIP approach.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a very uncommon case where a user posts the constraint "False" to the solver, in essence making the solver status trivialy infeasible.
We run into this case sometimes when our expressions are simplified to "False"

Returns: number of solutions found
"""

warnings.warn("Solution enumeration is not implemented in PyScipOPT, defaulting to CPMpy's naive implementation")

Choose a reason for hiding this comment

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

So I mentioned this is an email. I think it's just that very few MIP practitioners actually need such functionality. To mimic most of this:

  • Call optimize for the self.scip_model.
  • After solving is completed. Call self.scip_model.getSols().
    The potential problems with this is that SCIP automatically deletes the "worst" solutions in storage after it has found x many. This is by default 10. See limits/maxsol and limits/maxorigsol. So to save memory SCIP will at times remove older solutions that it knows are suboptimal because it has clearly better ones. The number of solutions found in total can be retrieved with scip_model.getNSolsFound(), but unless you increase the limits you will not be able to retrieve all of them.

@Opt-Mucca
Copy link

I get an error for optimization problems:

\cpmpy\solvers\scip.py", line 154, in solve
    self.objective_value_ = scip_objective.getObjVal()
AttributeError: 'pyscipopt.scip.Expr' object has no attribute 'getObjVal'

This should now be addressed in one of my points above

@Opt-Mucca
Copy link

@IgnaceBleukx @tias I haven't run anything, and truthfully haven't tried to understand the larger code base, but I've gone through and given comments that I think should make the interface work.
For a final point: While min / max expressions probably won't work, SCIP can handle the absolute value expressions fairly easily. I'm just not sure where to add them in the current code without some guidance.

@tias tias marked this pull request as draft December 6, 2024 14:51
@tias tias mentioned this pull request Feb 2, 2026
@tias tias added the new solver label Feb 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants