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: Flexible configuration mechanism #1383

Open
tomAgnicio opened this issue Feb 12, 2025 · 2 comments
Open

Feat: Flexible configuration mechanism #1383

tomAgnicio opened this issue Feb 12, 2025 · 2 comments
Labels
enhancement New feature or request process/needs triage Requires initial assessment of validity, priority etc.

Comments

@tomAgnicio
Copy link

Is your feature request related to a problem? Please describe.
The config mechanism is rather rigid, for this topic I'm focussing on ConstraintProvider. You can only pass in Class<? extends ConstraintProvider>

Describe the solution you'd like
An option to pass in an instance of a ConstrainProvider , so I can take full power.

I want to be able to load my constraints from a database or any other storage and define scores based on some runtime context.

Describe alternatives you've considered
There are some override mechanisms but its all seems limited and clumsy.

I took a checkout of the solver core to implement an alternative mechanism but the class is being used all over the place and instantiation happens late. I hoped to easily provide an alternative solution by implementing some interfaces.

So before even attempting this I want to get other people's opinion on this.

One other option is to make a really weird setup where I make a different class for each possible use case and have that delegate to everything else. But this feels weird and ugly.

@tomAgnicio tomAgnicio added enhancement New feature or request process/needs triage Requires initial assessment of validity, priority etc. labels Feb 12, 2025
@triceo
Copy link
Contributor

triceo commented Feb 12, 2025

Hello @tomAgnicio, and thanks for the feedback. To properly consider the best solution to your problem, would you mind explaining what exactly you're trying to accomplish?

  • Why exactly is it important that constraints be loaded from a database?
  • Why is the idea of having code in a database preferable to editing that same code in an IDE that is actually designed to do that?
  • Why is it important for a constraint provider to be mutable, and therefore for you to be able to provide your own instances?

About constraint weights: To set constraint weights at runtime, we do have the constraint weight override feature, as you mentioned. Those constraint weights belong together with your planning solution, because only together do they define the problem you're solving. With different weights, you will get substantially different solutions even to the same data set - and therefore the dataset and the weights have to be considered as a single unit.

About the constraint provider class: the solver config needs to be serializable to a file, and therefore we can not accept arguments to it which do not serialize. For that reason, we only accept FQNs of classes, and not instances of those classes. Also, constraint providers are designed to be immutable and stateless, and therefore it should not matter that we don't let you create their instances.

We are aware of limitations of the constraint provider API though - it doesn't allow for easy extensibility and composability. Those are things we want to change, and we may even have some changes already in the works. But we are unlikely to ever make it easy to load in constraints from the database, that is simply not the development model we want to encourage. Constraints are code, and should be treated as such - including test coverage, CI etc.

@tomAgnicio
Copy link
Author

tomAgnicio commented Feb 20, 2025

Hi @triceo
Sorry for the delay; I was on vacation last week.

Regarding my request, the database aspect was the first thing that came to mind. In general, it would be nice to build the configuration myself using, for example, the builder pattern.

What I'm trying to accomplish:
1. Why exactly is it important that constraints be loaded from a database?

We are building a SAAS application. I envision providing a variety of constraints, like a ConstraintCatalog.
Not every tenant might want or even allow the same constraints. This is especially true for soft constraints. So, there is the concept of a ConstraintSet, which represents enabled constraints together with weights.

I also want to delegate testing and evaluation of constraints away from development. Users (analysts) can configure different ConstraintSets by adjusting weights. They can then run data scenarios against those different ConstraintSets to see how they perform, compare them, and optimize them.

2. Why is the idea of having code in a database preferable to editing that same code in an IDE that is actually designed to do that?
We are not putting the code in the database. Every constraint in the ConstraintCatalog is backed by a piece of code. It is the configuration of that constraint (the ConstraintSet) that is stored in the database.

3. Why is it important for a constraint provider to be mutable, and therefore for you to be able to provide your own instances?
Not sure if the word "mutable" is correct here; I'd call it "configurable." Let me give an example.

Note that the serializable requirement is new to me, I don't think this is stated in the documentation and it's not reflected in any of the quickstart examples.

Is this for the enterprise version? I'm doing experiments in the open-source version and since the constraintProvider is created through reflection I could technically pass in any instance by changing some code. It's just not easy to do, that's how I ended up here.

package com.agnicio.solver.constraints;

import ai.timefold.solver.core.api.score.stream.Constraint;
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
import org.jspecify.annotations.NonNull;

import java.io.Serializable;

public class MyCustomConstraintProvider  implements ConstraintProvider, Serializable {
    private final boolean isTodayANiceDay;

    public MyCustomConstraintProvider(boolean isTodayANiceDay) {
        this.isTodayANiceDay = isTodayANiceDay;
    }

    @Override
    public Constraint @NonNull [] defineConstraints(@NonNull ConstraintFactory constraintFactory) {
        if (isTodayANiceDay) {
            return new Constraint[0];
        } else {
            return new Constraint[0];
        }
    }
}

Yes, I could create two constraintProviders and put the if statement on the outside, but that's beside the question. In the ConstraintSet example I gave above, I have essentially n constraints, n if-statements, and 2^n constraint providers.

This class is still serializable. But I have more control over how to return those constraints.

As I said, I didn't know about the serializable requirement. So as far as I was concerned I would pass in spring beans, doing whatever I want, depending on whatever I want, and just adhere to the ConstraintProvider interface.

Pretty much any configuration mechanism would come in handy. The interface could for example accept an extra serializable argument.

    @Override
    public Constraint @NonNull [] defineConstraints(@NonNull ConstraintFactory constraintFactory, String constraintFactoryContext) {
        if (constraintFactoryContext.contains("....")) {
            return new Constraint[0];
        } else {
            return new Constraint[0];
        }
    }

At this moment the only way I could think of to do anything configurable would be through static fields, but besides the fact that you essentially have a singleton, those fields are not serializable anyway.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request process/needs triage Requires initial assessment of validity, priority etc.
Projects
None yet
Development

No branches or pull requests

2 participants