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: Dynamic Constraint Weight in constraint factory #1172

Open
noguespi opened this issue Oct 30, 2024 · 5 comments
Open

Feat: Dynamic Constraint Weight in constraint factory #1172

noguespi opened this issue Oct 30, 2024 · 5 comments
Labels
enhancement New feature or request process/needs triage Requires initial assessment of validity, priority etc.

Comments

@noguespi
Copy link

noguespi commented Oct 30, 2024

Constraint Weight can be configured at runtime/dynamically using ConstraintWeightOverrides but it's a bit tedious to configure.

Could it be possible to configure them dynamically/lazily using the constraintFactory ? Example:

constraintFactory.forEach(MyValue::class.java)
   .join(ConstraintDefinition::class, // ...
   .penalize( { constraintDef, _ -> constraintDef.level } ) { constraintDef, value -> constraintDef.score(value) }
@noguespi noguespi added enhancement New feature or request process/needs triage Requires initial assessment of validity, priority etc. labels Oct 30, 2024
@triceo
Copy link
Contributor

triceo commented Oct 30, 2024

The approach you propose here is effectively turning a constraint weight into a match weight. A constraint weight is a configurable yet static number - a match weight depends on the specific impact you want the particular constraint match to make.

Since you already have a fully dynamic match weight, I really do not see the point of a dynamic constraint weight as well. Set your constraint weight to 1 (which is the default), and use match weight to penalize by an arbitrary amount - that is how the system is designed to function.

What do you intend to accomplish by making both the constraint and the match weights dynamic?

@noguespi
Copy link
Author

noguespi commented Oct 30, 2024

The level (HARD/SOFT/...) can only be set on the constraint weight.

The penalize/reward method only offer penalizeInt/Long/BigDecimal, you can't "penalizeScore".

EDIT:
I mean, I can't set the level (HARD/SOFT) dynamically (or I have to use the ConstraintWeightOverrides). The purpose of this feature request is to allow to set the level from the constraintFactory, which is less tedious than the ConstraintWeightOverrides.

@triceo
Copy link
Contributor

triceo commented Oct 30, 2024

I see your point now, thanks for clarifying. Not the first time we're seeing such request.

The goal for ConstraintWeightOverrides is to allow this configuration to happen at solution-level - this makes sense, because if you need to override weights (especially if you want to change what's hard and what's soft), you're really solving two mutually incompatible problems.

Conceptually, allowing to do this on the match level IMO doesn't make sense - I don't see which single constraint can be both hard and soft, based on what it matched. Would overtime hours suddenly be less serious, because it's Bob and not Ann? Can Ed be in two places at once, but nobody else can? Would fuel costs suddenly break the solution, if the driver is Peter and not Paul?

Instead, this requirement seems to me like a side effect of some other problem you're attempting to solve. Perhaps having two constraints would bring performance costs, and therefore you're trying to get it done with just one constraint?

@noguespi
Copy link
Author

Here is an example:

I have a planning with configurable tasks.

I have a task limit rule (constraint). Sometime a task is mandatory (hard) sometime it is not (soft). But in both cases they are a limit constraint.

The user create the tasks and choose what tasks are mandatory (hard or soft level) and the weight of the constraint.

I could probably use the constraint override but I don't like to have to use "constraint name" to associate a dynamic weight to a constraint.

Especially when the name of the constraint itself is dynamic. ( .asConstraint("Task Limit TASK_ID=XYZ")).

I think it would be easier to just set the weight from the constraintFactory by keeping the weight in some planningvalue.

Yes having two constraints could be a solution, but they would be the same, they would have the same flow (forEach/join/map/groupBy/penalize etc.) the only difference is one penalizing hard and the other is penalizing soft.

@triceo
Copy link
Contributor

triceo commented Oct 30, 2024

Thanks for the explanation.

I would indeed go with two constraints here. A constraint called "Mandatory tasks must be assigned" is different in my mind to a constraint called "Reward optional task assignments" - even though their implementation may be virtually the same.

Consider score analysis, another feature of the solver - if you see matches from the same constraint being hard once, and soft another time, you have to start drilling down into justifications to figure out what exactly happened. Having two constraints for this saves you from that work, it makes score analysis self-explanatory.

The fact that two constraints take more performance is often not really a problem, because many constraints are cheap. For expensive constraints, the Enterprise Edition of Timefold Solver has a "node sharing" feature which can (under the hood) merge those executions is they are indeed the same.

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