implements #60 EventHandler and ConsHandler - #62
patrickguenther wants to merge 9 commits into
Conversation
| @Override | ||
| protected SCIP_Retcode scipExec(Scip scip, SCIP_Event event) { | ||
| assert (event.getEventtype() & EventType.BESTSOLFOUND) != 0 : "Unexpected event caught"; | ||
| Solution solution = new Solution(SCIPJNI.getEventDataSolution(event)); |
There was a problem hiding this comment.
This is how I would envision a user to be implementing an event handler: He get's the raw SCIP_Event passed and has to call one of the SCIPJNI.getEventData*(SCIP_Event) methods to access the union member that corresponds to his event type. Would that be alright? Is it too complicated?
We could also implement custom scipExec* methods for each event type, but the documentation doesn't really say, which union member corresponds to which event type, so it would be a lot of untested code.
There was a problem hiding this comment.
This is not acceptable this way. The SCIPJNI class is not supposed to be user API. The event handler should be getting an object-oriented wrapper that supports getters such as event.getSolution().
There was a problem hiding this comment.
See, e.g., how my ExpressionHandler class works, e.g., how createObjExprhdlr implements scip_eval, constructing the object-oriented Expression and Solution wrappers, then calling ExpressionHandler.eval with the object-oriented abstractions.
There was a problem hiding this comment.
I see. The event data struct in SCIP however is a union (at least for SCIP 9, haven't looked at version 10 yet). I assume each member of the union corresponds to a specific event type. Would you then also have a different event class corresponding to each event type or would it suffice to have a single event class with all possible union member accessors on it? Potentially returning garbage or crashing if the wrong accessor is being called for the wrong event type.
I can do it either way, but I think if I go the route of having multiple Event subclasses each with their own data accessors, it would be tough to test all possible event types, since I'm not even sure under which conditions they get triggered. For that we should probably have a proper maven build with Junit tests.
There was a problem hiding this comment.
What you can do to make the union accessors safe is to have the object-oriented accessors first check the event type before calling the SCIPJNI C-style accessor functions, e.g.:
public Solution getSolution() {
if ((_eventptr.getEventtype() & EventType.SOLEVENT) == 0) {
throw new UnsupportedOperationException("getSolution() not available for this event");
}
return SCIPJNI.getEventDataSolution(_eventptr);
}(SOLEVENT is the event type mask for all events involving a solution, defined in the SCIP C code as follows:
/* event masks for primal solution events */
#define SCIP_EVENTTYPE_SOLFOUND (SCIP_EVENTTYPE_POORSOLFOUND | SCIP_EVENTTYPE_BESTSOLFOUND)
#define SCIP_EVENTTYPE_SOLEVENT (SCIP_EVENTTYPE_SOLFOUND)There are similar SCIP_EVENTTYPE_*EVENT masks for all the other union fields.)
|
|
||
| import java.util.Objects; | ||
|
|
||
| public class ResultHolder { |
There was a problem hiding this comment.
I picked this pattern up from the Fico Xpress java library. It corresponds to an output pointer that is passed to the user as a call back and the user is expected to set the result of the handler there.
One could also pass an instance of SCIP and make the corresponding JNI call when the user passes the result immediately, but this would not be safe, if the user somehow leaks the ResultHolder outside of the EventHandler and calls the set method later.
There was a problem hiding this comment.
The result should just be the return value of the callbacks in the end user API, rather than an output argument. The user API should not contain SCIP_Retcode at all, instead, the retcode should always be SCIP_OKAY unless an exception is thrown in the user code (then your Obj*Hdlr implementation should catch the exception and return SCIP_ERROR). This frees up the return value for the actual result (and callbacks without a result just return void). See, e.g., ExpressionHandler.scip_bwdiff.
There was a problem hiding this comment.
(Then you do not need this wrapper class at all.)
There was a problem hiding this comment.
What do I do with the methods that return a SCIP_Retcode and get passed 2 output pointers like scip_getnvars with output pointers nvars and success; and scip_getdivebdchgs with output pointers success and infeasible. I guess the object oriented way of doing it would be to return a value object with the respective pair of fields.
There was a problem hiding this comment.
The easiest way in those particular cases with success is to return a flag value for the failure case. One thing that you can always do is return the boxed type (e.g., Boolean instead of boolean for infeasible) and use null for failure. You can also use, say, a negative nvars as a failure value. For the boolean, you could also instead return an int that is 0 for false, >0 for true, <0 for failure, or an enum with the entries Feasible, Infeasible, and Failure. But I think the boxed Boolean is the cleanest solution in this case. (For nvars, though, I would probably just use negative numbers as failure.)
If you have truly multiple results, then returning a result object (a basic data object, like a C struct) is indeed the way. But for a result and a success flag, I would not do it that way.
There was a problem hiding this comment.
E.g., ExpressionHandler uses null flag values (though in that case, I did not have to box anything, because the return value was already an object) for the success flag in scip_estimate and scip_curvature and for the infeasible flag in scip_reverseprop.
|
@kkofler any chance this can make it into master? Do I need to make changes? 😁 |
|
So my first question would be: What version(s) of SCIP have you tested this with? Does it work with the latest (i.e., 10.0.x)? Do you know what the minimum version of SCIP it works with is? (That said, if I merge my |
Yes to the latter (unfortunately): as I commented above, I do not like at all the way this exposes some C/C++ API internals (raw |
|
Thank you for your comments. I wasn't aware of your exprhdlr branch. I'd be happy to take a look at it for inspiration.
Maybe we should just branch from the current master and keep it for SCIP 9.x compatibility and update master going forward to use SCIP 10.x only. |
| /** | ||
| * Illustrates the use of ConsHandler to solve the traveling salesman problem. | ||
| */ | ||
| public class ConsHandler { |
There was a problem hiding this comment.
Renamed the example classes to be in line with existing examples: naming them after the shown feature, rather than the problem type used.
| Solution solution = new Solution(sol); | ||
| GetDiveBdChgsResult result = getdivebdchgs(jScip, solution); | ||
| if (result == null) { | ||
| return SCIP_Retcode.SCIP_OKAY; |
There was a problem hiding this comment.
Right now using a return of null to indicate "not implemented". Should return OKAY here and not touch the pointers. Probably better to have a dedicated enum constant?
| if (result == null) { | ||
| return SCIP_Retcode.SCIP_OKAY; | ||
| } | ||
| switch(result) { |
There was a problem hiding this comment.
I rewrote this based on your comment, but I'm not sure I like it. Just looking at SCIP's API this method has 3 outputs: SCIP_RetCode, success and infeasable and all 3 seem to be independent of each other. Also because most methods here don't have the success output pointer.
By writing it like this, it is no longer possible for a user to return success = 0 and SCIP_Retcode.OKAY. Not sure what the usecase would be for that, but that's also the point: I wouldn't have to make that decision.
I'll have to double-check with SCIP's code/documentation what the API of these methods is supposed to be.
| @@ -0,0 +1,4 @@ | |||
| package jscip; | |||
|
|
|||
| public interface Event { | |||
There was a problem hiding this comment.
This is the SCIP_Event::data union type in scip. I chose to call it just Event instead of EventData
There was a problem hiding this comment.
Maybe I should put all Event related files in a new package jscip.events?
| return result; | ||
| } | ||
|
|
||
| private static class AllEventHandler extends jscip.EventHandler { |
There was a problem hiding this comment.
TODO: remove this class, just used for debugging.
I was curious if a triggered event by SCIP could have multiple Event types at the same time. Glad to see each event is only of a single type (at least for this problem).
| double[] returns, | ||
| double[][] covarianceMatrix | ||
| ) { | ||
| super("SolutionObserver", "Implements custom stopping criterion.", EventType.BESTSOLFOUND); |
There was a problem hiding this comment.
Should the user actually have to use this EventType? SCIP Api makes it possible to add the same event listener for multiple different event types. This however makes it necessary, that the user only gets a callback with the base type Event and has to inspect it's runtime class and cast it accordingly. I could circumvent this, by introducing dedicated subscription Methods like:
- scip.addSolEventHandler(...)
- scip.addBestSolEventHandler(...)
- scip.addPoorSolEventHandler(...)
- etc.
| @@ -0,0 +1,4 @@ | |||
| package jscip; | |||
|
|
|||
| public class EventRowAddedLp implements Event { | |||
There was a problem hiding this comment.
Some of these events, I have not bothered to implement properly. The row and node events look rather complicated and I would leave this up to a separate PR.
| } | ||
|
|
||
| static { | ||
| // Java 8 does not support switch statements over long. Using this map as a replacement. |
There was a problem hiding this comment.
I assume Java 8 is still the java version to use? Otherwise this could be replaced by a switch?
Sorry for the large PR, but it turns out EventHandler with the SCIP_Event data union has a lot of possible types.