Inject dependencies in ConstraintProvider

254 views
Skip to first unread message

Dario Arena

unread,
Aug 8, 2022, 11:19:15 AM8/8/22
to OptaPlanner development
Hi everyone,
I'm starting to learn how to use OptaPlanner and i built a "toy" application with spring boot integration that uses the constraint stream score calculation. I got stuck when it came the time to calculate something based on a value of every planning variable couple that becomes larger as long as the solution is worse and penalize the score solution accordingly.
To better organize the code, i tried to put the calculation logic (that can be arbitrarily complex) in a "service" class that I'm trying to inject in the class that implements the ConstraintProvider interface.
I just realize that in spring boot and quarkus integration the constraint provider is not instantiated as a managed bean so it is impossible to do something like this:

public class MyConstraintProvider implements ConstraintProvider {

    @Autowired
     private final ComplexComputationService computationService;

     @Override
     public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
          return constraintFactory.forEach(PlanningEntityClass.class)
               .join(PlanningEntityClass.class)
               .penalizeBigDecimal(
                   "Complex calculus contraint",
                    HardSoftBigDecimalScore.ONE_SOFT,
                    (a, b) -> computationService.doComplexCalculation(a, b)
               );

}

Living out optimizations (assuming for example that the values can all be precomputated and cached), am i doing something very wrong with this approach? I would like to avoid to put the logic in the @PlanningEntity annotated class because i would like to try adding more constraints and even maybe trying to build them "on the fly" parsing data from an external source.
I hope that maybe this could be helpful even for other people (i've not found anything useful online)

Best regards,
Dario Arena

Lukáš Petrovický

unread,
Aug 8, 2022, 11:55:28 AM8/8/22
to optapla...@googlegroups.com
Dario,

On Mon, Aug 8, 2022 at 5:19 PM Dario Arena <dario....@gmail.com> wrote:
I just realize that in spring boot and quarkus integration the constraint provider is not instantiated as a managed bean so it is impossible to do something like this:

you are right. At the moment, this is not possible. I know of no way to inject anything into the ConstraintProvider. However, this is not the first time we are seeing people trying to do that, and we may consider this feature request for a future release.
 
--

Lukáš Petrovický

Principal Software Engineer

lukas.pe...@redhat.com

My work week is Monday to Thursday.
No need to respond outside of your working hours.

Dario Arena

unread,
Aug 9, 2022, 6:11:35 AM8/9/22
to OptaPlanner development
Hi Lukas,
thanks for the quick reply. I'm very sad to hear that it is not possible to inject dependencies in constraint providers. From a user point of view i think this could be very useful: It can contribute to make applications more SOLID and maybe it can help in integrating Optaplanner in legacy systems more quickly (suppose for example that part of the score need to be calculated calling a quite complex stored procedure that is expensive to rewrite in pure java or something like this). I am quite experienced in writing Spring Boot and Quarkus applications but unfortunately i never worked "inside" of theese framework to fix a bug or extend them with a plugin or i would have been very happy to help.
If there is something that i can do to "speed up" the implementation of this feature feel free to let me know

Best regards,
Dario Arena

Lukáš Petrovický

unread,
Aug 9, 2022, 10:33:02 AM8/9/22
to optapla...@googlegroups.com
On Tue, Aug 9, 2022 at 12:11 PM Dario Arena <dario....@gmail.com> wrote:
It can contribute to make applications more SOLID and maybe it can help in integrating Optaplanner in legacy systems more quickly (suppose for example that part of the score need to be calculated calling a quite complex stored procedure that is expensive to rewrite in pure java or something like this). 

I am glad you brought this up, because this was originally the reason why we decided not to allow any sort of injection into ConstraintProviders.

In order for the solver to be efficient and effective, score calculation needs to be as fast as possible. Constraints need to be pared to the bone, microsecond-level performance optimizations can have significant impact as the scores are calculated over and over and over again. That's why Constraint Streams are incremental in nature, that's why there's all sorts of indexing, node sharing and filtering.

Putting any remote service call into your scoring function goes directly against constraint performance. That, in turn, decreases your solution quality, therefore increasing your costs. Our decision to isolate ConstraintProviders was made years ago in order to not encourage these anti-patterns.

However, by now we have seen very clearly that people will find a way, through all sorts of "evil" means, such as ThreadLocals, static fields etc. So, going forward, there is no use in fighting - if people want to do bad things, we can hopefully figure out the least bad way for people to do them.

Hopefully this explains why things are the way things are, and that we will eventually do something about this.

Remigius Stalder

unread,
Jan 15, 2024, 5:55:41 AMJan 15
to OptaPlanner development
Hi Lukáš,

Our use case is simple: we want to implement a cost function that is parametrized, namely it has some weights for different terms (represented each as a Constraint object) and each term only contributes to the overall cost if the weight is non-zero. On one hand we need to inject the parameters to the place where the constraints are created (namely the defineConstraints method) and on the other hand, we want to completely omit creating constraint objects for zero weights. As defineConstraints is supposedly only invoked once, this approach would imo not result in any performance penalty, rather the opposite. In our case the weights should be ultimately user defined, hence we cannot hard code them. Following your (anti-)hint, I used the "evil" means of a ThreadLocal variable (the solver is created inside a synchronized block), but obviously I would prefer not having to fall to the dark side of the force just to solve a simple problem.

Best regards, Remi.

Rob Johansen

unread,
Jan 30, 2024, 2:11:31 PMJan 30
to OptaPlanner development
Hi Remi,

This sounds like you are describing a lot of the out-of-the-box functionality. Typically you use a ConstraintConfiguration to parameterise the weights of constraints and these values need not be constants. You can think of a Configuration as a PlanningFact naturally injected under the hood when using the ...Configurable methods to impact the score, e.g. impactConfigurable(...), instead of impact(...). Also, conveniently, no constraint with a weighting of 0 is needlessly calculated, which means that setting the value to 0 dynamically is the same as disabling the constraint. Lastly, the ConstraintConfiguration can also be considered a special PlanningFact but you can also create your own Config facts which can be matched in constraints, or you can annotate Entities / Facts with a "weight" field if you want to weight constraints per Entity / Fact. This then leaves how you map those dynamic weights to the configuration and that is simply done during creation of the problem and, in the case of real-time solving, this can be done by a ProblemFactChange.

Hope this helps. Let me know if you need some clarification on any of the above.

Best,
Rob

Reply all
Reply to author
Forward
0 new messages