Planning Entities
A planning entity is a class whose instances the solver can change during optimization. Planning entities contain planning variables that get assigned values.
The @planning_entity Decorator
Mark a class as a planning entity with @planning_entity:
from dataclasses import dataclass, field
from typing import Annotated
from solverforge_legacy.solver.domain import planning_entity, PlanningId, PlanningVariable
@planning_entity
@dataclass
class Lesson:
id: Annotated[str, PlanningId]
subject: str
teacher: str
student_group: str
timeslot: Annotated[Timeslot | None, PlanningVariable] = field(default=None)
room: Annotated[Room | None, PlanningVariable] = field(default=None)
Planning ID
Every planning entity must have a unique identifier marked with PlanningId:
id: Annotated[str, PlanningId]
The ID is used for:
- Tracking entities during solving
- Cloning solutions
- Score explanation
The ID type can be str, int, or any hashable type.
Genuine vs Shadow Entities
There are two types of planning entities:
Genuine Entities
A genuine planning entity has at least one genuine planning variable that the solver directly assigns:
@planning_entity
@dataclass
class Lesson:
id: Annotated[str, PlanningId]
# Genuine variable - solver assigns this
timeslot: Annotated[Timeslot | None, PlanningVariable] = field(default=None)
Shadow-Only Entities
A shadow-only entity has only shadow variables (calculated from other entities):
@planning_entity
@dataclass
class Visit:
id: Annotated[str, PlanningId]
location: Location
# Shadow variable - calculated from vehicle's visit list
vehicle: Annotated[Vehicle | None, InverseRelationShadowVariable(...)] = field(default=None)
Entity Properties
Immutable Properties
Properties without PlanningVariable annotations are immutable during solving:
@planning_entity
@dataclass
class Lesson:
id: Annotated[str, PlanningId]
subject: str # Immutable
teacher: str # Immutable
student_group: str # Immutable
timeslot: Annotated[Timeslot | None, PlanningVariable] = field(default=None) # Mutable
The solver never changes subject, teacher, or student_group.
Default Values
Planning variables should have default values (typically None) for uninitialized state:
timeslot: Annotated[Timeslot | None, PlanningVariable] = field(default=None)
Multiple Planning Variables
An entity can have multiple planning variables:
@planning_entity
@dataclass
class Lesson:
id: Annotated[str, PlanningId]
subject: str
teacher: str
timeslot: Annotated[Timeslot | None, PlanningVariable] = field(default=None)
room: Annotated[Room | None, PlanningVariable] = field(default=None)
Each variable is assigned independently by the solver.
Entity Collections in Solution
Planning entities are collected in the planning solution:
@planning_solution
@dataclass
class Timetable:
lessons: Annotated[list[Lesson], PlanningEntityCollectionProperty]
The solver iterates over this collection to find entities to optimize.
Nullable Variables
By default, planning variables must be assigned. For nullable variables (when some entities might be unassigned), see Planning Variables.
Best Practices
Do
- Use
@dataclassfor clean, simple entity definitions - Give each entity a unique, stable ID
- Initialize planning variables to
None - Keep entities focused on a single concept
Don’t
- Put business logic in entities (use constraints instead)
- Make planning variables required in
__init__ - Use mutable default arguments (use
field(default_factory=...)instead)
Example: Shift Assignment
@planning_entity
@dataclass
class Shift:
id: Annotated[str, PlanningId]
start_time: datetime
end_time: datetime
required_skill: str
# Assigned by solver
employee: Annotated[Employee | None, PlanningVariable] = field(default=None)
Example: Vehicle Routing
@planning_entity
@dataclass
class Vehicle:
id: Annotated[str, PlanningId]
capacity: int
home_location: Location
# List of visits assigned to this vehicle
visits: Annotated[list[Visit], PlanningListVariable] = field(default_factory=list)
Next Steps
- Planning Variables - Learn about variable types
- Planning Solutions - Container for entities and facts
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.