How best to trigger code after each move in optaplanner?

302 views Asked by At

I need to run a calculation into memory at the end of each move and then update the score based on this. The outcome of the calculation needs to be stateful as its referenced elsewhere (stored on the solution).

The documentation suggests this should be a PlanningEntity updated as a ShadowVariable with a VariableListener on the main entity but of course this naturally triggers per entity change, re-calculating unnecessarily.

  1. How can this be triggered at the end of the move, instead of per variable? From Drools? Is that not a hack?
  2. If not using a VariableListener, it would no longer be a PlanningEntity. Isn't making it a ProblemFact contrary to the documentation? What's the downside?

Further info:

  • The "calculation" is a forecast of values of a flow network over the planning period, where the flow levels are dependent on the current position of tasks.
  • We are using real-time planning, so the planning period window adjusts to task set size, and shifts through time.
1

There are 1 answers

15
Geoffrey De Smet On BEST ANSWER

1) Sounds like you should use a shadow variable. Do note you still need a normal constraint too update the score according to that shadow variable's state.

Do note that you can have a shadow entity class that is a singleton (per problem solution) which has a single shadow variable that is updated if any planning entity changes. Use @PlanningEntityProperty (without Collection) on a @PlanningSolution class's field.

At the end of a move, each Move calls triggerShadowVariableListeners(), not in the middle. So if you're worried about performance, you shouldn't be. If you're worried about correctness - because the number of calls affects your calculation - well don't design/implement it that way...

2) ProblemFacts can't change during solving (not counting real-time planning). Not a single field of them. Otherwise it's a PlanningEntity.

0) MoveListener doesn't exist, intentionally (for good reasons), not even internally (even the very internal PhaseLifecycleListener only listens to steps, not moves).