Skip to content

Commit 244e1d9

Browse files
feat: Add support for ConstraintConfiguration (#37)
- Remove penalize, reward, impact methods docs as they are out of date, will be re-added with correct info later.
1 parent c304215 commit 244e1d9

File tree

3 files changed

+208
-247
lines changed

3 files changed

+208
-247
lines changed

tests/test_solver_configuration.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from timefold.solver.api import *
2+
from timefold.solver.annotation import *
3+
from timefold.solver.config import *
4+
from timefold.solver.constraint import *
5+
from timefold.solver.score import *
6+
7+
from dataclasses import dataclass, field
8+
from typing import Annotated, List
9+
10+
11+
def test_solver_configuration():
12+
@planning_entity
13+
@dataclass
14+
class Entity:
15+
code: str
16+
value: Annotated[int, PlanningVariable] = field(default=None)
17+
18+
@constraint_provider
19+
def my_constraints(constraint_factory: ConstraintFactory):
20+
return [
21+
constraint_factory.for_each(Entity)
22+
.reward_configurable(lambda entity: entity.value)
23+
.as_constraint('Maximize value'),
24+
]
25+
26+
@constraint_configuration
27+
@dataclass
28+
class ConstraintConfiguration:
29+
maximize_value: Annotated[SimpleScore, ConstraintWeight('Maximize value')] = field(default=SimpleScore.ONE)
30+
31+
@planning_solution
32+
@dataclass
33+
class Solution:
34+
entities: Annotated[List[Entity], PlanningEntityCollectionProperty]
35+
value_range: Annotated[List[int], ValueRangeProvider]
36+
score: Annotated[SimpleScore, PlanningScore] = field(default=None)
37+
configuration: Annotated[ConstraintConfiguration, ConstraintConfigurationProvider] = (
38+
field(default_factory=ConstraintConfiguration))
39+
40+
solver_config = SolverConfig(
41+
solution_class=Solution,
42+
entity_class_list=[Entity],
43+
score_director_factory_config=ScoreDirectorFactoryConfig(
44+
constraint_provider_function=my_constraints,
45+
),
46+
termination_config=TerminationConfig(
47+
best_score_limit='6'
48+
)
49+
)
50+
51+
problem: Solution = Solution([Entity('A')], [1, 2, 3],
52+
configuration=ConstraintConfiguration(maximize_value=SimpleScore.of(2)))
53+
54+
solver = SolverFactory.create(solver_config).build_solver()
55+
solution = solver.solve(problem)
56+
57+
assert solution.score.score() == 6
58+
assert solution.entities[0].value == 3

timefold-solver-python-core/src/main/python/annotation/_annotations.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,24 @@ def __init__(self):
194194
super().__init__(JavaDeepPlanningClone, {})
195195

196196

197+
class ConstraintConfigurationProvider(JavaAnnotation):
198+
def __init__(self):
199+
ensure_init()
200+
from ai.timefold.solver.core.api.domain.constraintweight import (
201+
ConstraintConfigurationProvider as JavaConstraintConfigurationProvider)
202+
super().__init__(JavaConstraintConfigurationProvider, {})
203+
204+
205+
class ConstraintWeight(JavaAnnotation):
206+
def __init__(self, constraint_name: str, *, constraint_package: str = None):
207+
ensure_init()
208+
from ai.timefold.solver.core.api.domain.constraintweight import ConstraintWeight as JavaConstraintWeight
209+
super().__init__(JavaConstraintWeight, {
210+
'value': constraint_name,
211+
'constraintPackage': constraint_package
212+
})
213+
214+
197215
@JImplements('ai.timefold.solver.core.api.domain.entity.PinningFilter', deferred=True)
198216
class _PythonPinningFilter:
199217
def __init__(self, delegate):
@@ -301,6 +319,15 @@ def __init__(self, a_list):
301319
return out
302320

303321

322+
def constraint_configuration(constraint_configuration_class: Type[Solution_]) -> Type[Solution_]:
323+
ensure_init()
324+
from jpyinterpreter import add_class_annotation
325+
from ai.timefold.solver.core.api.domain.constraintweight import (
326+
ConstraintConfiguration as JavaConstraintConfiguration)
327+
out = add_class_annotation(JavaConstraintConfiguration)(constraint_configuration_class)
328+
return out
329+
330+
304331
def nearby_distance_meter(distance_function: Callable[[Origin_, Destination_], float], /) \
305332
-> Callable[[Origin_, Destination_], float]:
306333
ensure_init()
@@ -575,8 +602,9 @@ def wrapper_doChange(self, solution, problem_change_director):
575602
'IndexShadowVariable', 'AnchorShadowVariable', 'InverseRelationShadowVariable',
576603
'ProblemFactProperty', 'ProblemFactCollectionProperty',
577604
'PlanningEntityProperty', 'PlanningEntityCollectionProperty',
578-
'ValueRangeProvider', 'DeepPlanningClone',
579-
'planning_entity', 'planning_solution',
605+
'ValueRangeProvider', 'DeepPlanningClone', 'ConstraintConfigurationProvider',
606+
'ConstraintWeight',
607+
'planning_entity', 'planning_solution', 'constraint_configuration',
580608
'nearby_distance_meter',
581609
'constraint_provider', 'easy_score_calculator', 'incremental_score_calculator',
582610
'variable_listener', 'problem_change']

0 commit comments

Comments
 (0)