-
Notifications
You must be signed in to change notification settings - Fork 94
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
Cant consistently create relation between two python planning entities #1282
Comments
Interestingly if you switch the order of the classes, you instead get the following error:
I'll also note this appears to be something more generally with reverse references (or declaration ordering more generally). Eg, this works: from typing import Annotated
from dataclasses import dataclass, field
from timefold.solver.domain import (
PlanningVariable, InverseRelationShadowVariable,
PlanningEntityCollectionProperty,
PlanningScore, ValueRangeProvider,
planning_entity, planning_solution
)
from timefold.solver.score import HardSoftDecimalScore, constraint_provider
from timefold.solver import SolverFactory
from timefold.solver.config import SolverConfig, ScoreDirectorFactoryConfig, TerminationConfig, Duration
@planning_entity
@dataclass
class Task:
'''
An individual unit of work that needs to be performed and its resource requirements
'''
shard: Annotated['ComputeShard', PlanningVariable] = field(default=None)
@planning_entity
@dataclass
class ResourceConfiguration:
'''
An amount of available resources to a task, as configured in a ComputeShard
'''
cpus: Annotated[int, PlanningVariable(value_range_provider_refs = ['cpu_range'])] = 1
def get_cpu_range(self) -> Annotated[list[int], ValueRangeProvider(id='cpu_range')]:
return list(range(1, 5))
@planning_entity
@dataclass
class ComputeShard:
resources: 'ResourceConfiguration'
tasks: Annotated[list['Task'], InverseRelationShadowVariable(source_variable_name='shard')] = field(default_factory=list)
@planning_solution
@dataclass
class Schedule:
tasks: Annotated[list[Task], PlanningEntityCollectionProperty, ValueRangeProvider]
compute_shards: Annotated[list[ComputeShard], PlanningEntityCollectionProperty, ValueRangeProvider]
resource_configurations: Annotated[list[ResourceConfiguration], PlanningEntityCollectionProperty]
score: Annotated[HardSoftDecimalScore, PlanningScore] = field(default=None)
@constraint_provider
def constraints(factory):
return [
factory.for_each(Task).penalize(HardSoftDecimalScore.ONE_HARD).as_constraint('static')
]
solver_factory = SolverFactory.create(
SolverConfig(
solution_class=Schedule,
entity_class_list=[Task, ResourceConfiguration, ComputeShard],
score_director_factory_config=ScoreDirectorFactoryConfig(
constraint_provider_function=constraints
),
termination_config=TerminationConfig(
spent_limit=Duration(seconds=1)
)
)
)
solver = solver_factory.build_solver()
resource_configurations = [ResourceConfiguration()]
compute_shards = [ComputeShard(resource_configurations[0])]
tasks = [Task(), Task()]
problem = Schedule(
tasks,
compute_shards,
resource_configurations
)
solver.solve(problem) But this, where ComputeShard is moved before Task, fails: from typing import Annotated
from dataclasses import dataclass, field
from timefold.solver.domain import (
PlanningVariable, InverseRelationShadowVariable,
PlanningEntityCollectionProperty,
PlanningScore, ValueRangeProvider,
planning_entity, planning_solution
)
from timefold.solver.score import HardSoftDecimalScore, constraint_provider
from timefold.solver import SolverFactory
from timefold.solver.config import SolverConfig, ScoreDirectorFactoryConfig, TerminationConfig, Duration
@planning_entity
@dataclass
class ComputeShard:
resources: 'ResourceConfiguration'
tasks: Annotated[list['Task'], InverseRelationShadowVariable(source_variable_name='shard')] = field(default_factory=list)
@planning_entity
@dataclass
class Task:
'''
An individual unit of work that needs to be performed and its resource requirements
'''
shard: Annotated['ComputeShard', PlanningVariable] = field(default=None)
@planning_entity
@dataclass
class ResourceConfiguration:
'''
An amount of available resources to a task, as configured in a ComputeShard
'''
cpus: Annotated[int, PlanningVariable(value_range_provider_refs = ['cpu_range'])] = 1
def get_cpu_range(self) -> Annotated[list[int], ValueRangeProvider(id='cpu_range')]:
return list(range(1, 5))
@planning_solution
@dataclass
class Schedule:
tasks: Annotated[list[Task], PlanningEntityCollectionProperty, ValueRangeProvider]
compute_shards: Annotated[list[ComputeShard], PlanningEntityCollectionProperty, ValueRangeProvider]
resource_configurations: Annotated[list[ResourceConfiguration], PlanningEntityCollectionProperty]
score: Annotated[HardSoftDecimalScore, PlanningScore] = field(default=None)
@constraint_provider
def constraints(factory):
return [
factory.for_each(Task).penalize(HardSoftDecimalScore.ONE_HARD).as_constraint('static')
]
solver_factory = SolverFactory.create(
SolverConfig(
solution_class=Schedule,
entity_class_list=[Task, ResourceConfiguration, ComputeShard],
score_director_factory_config=ScoreDirectorFactoryConfig(
constraint_provider_function=constraints
),
termination_config=TerminationConfig(
spent_limit=Duration(seconds=1)
)
)
)
solver = solver_factory.build_solver()
resource_configurations = [ResourceConfiguration()]
compute_shards = [ComputeShard(resource_configurations[0])]
tasks = [Task(), Task()]
problem = Schedule(
tasks,
compute_shards,
resource_configurations
)
solver.solve(problem)
|
Also seeing similar behavior when attempting to annotate the parameters of a VariableListener in my actual project. I don't have a minimal repro for that right now, but will include the error in case it's useful:
|
Describe the bug
Attempting to have two planning entities refer to each other leads to a runtime error
Expected behavior
It should work just as it already does if the inverse relation is not specified (even if you still set the property at runtime)
Actual behavior
To Reproduce
Environment
Timefold Solver Version or Git ref:
1.17.0
Output of
java -version
:Output of
uname -a
orver
:Additional information
While in this reduced example the error refers to
get_cpu_range
, in my actual project it instead complains about a lack of__eq__
, which suggests this is something more general with the binding generation rather than ValueRangeProviderThe text was updated successfully, but these errors were encountered: