Planning Solutions
A planning solution is the container class that holds all problem data, planning entities, and the solution score.
The @planning_solution Decorator
from dataclasses import dataclass, field
from typing import Annotated
from solverforge_legacy.solver.domain import (
planning_solution,
ProblemFactCollectionProperty,
ProblemFactProperty,
PlanningEntityCollectionProperty,
ValueRangeProvider,
PlanningScore,
)
from solverforge_legacy.solver.score import HardSoftScore
@planning_solution
@dataclass
class Timetable:
id: str
timeslots: Annotated[list[Timeslot], ProblemFactCollectionProperty, ValueRangeProvider]
rooms: Annotated[list[Room], ProblemFactCollectionProperty, ValueRangeProvider]
lessons: Annotated[list[Lesson], PlanningEntityCollectionProperty]
score: Annotated[HardSoftScore, PlanningScore] = field(default=None)
Solution Components
A planning solution contains:
- Problem Facts - Immutable input data
- Planning Entities - Mutable entities with planning variables
- Score - Quality measure of the solution
Problem Facts
Problem facts are immutable data that define the problem:
Collection Property
For lists of facts:
timeslots: Annotated[list[Timeslot], ProblemFactCollectionProperty]
rooms: Annotated[list[Room], ProblemFactCollectionProperty]
employees: Annotated[list[Employee], ProblemFactCollectionProperty]
Single Property
For single facts:
config: Annotated[ScheduleConfig, ProblemFactProperty]
start_date: Annotated[date, ProblemFactProperty]
Value Range Providers
Value ranges provide possible values for planning variables. Combine with problem fact annotations:
# This list provides values for Timeslot planning variables
timeslots: Annotated[list[Timeslot], ProblemFactCollectionProperty, ValueRangeProvider]
# This list provides values for Room planning variables
rooms: Annotated[list[Room], ProblemFactCollectionProperty, ValueRangeProvider]
The solver automatically matches variables to value ranges by type:
PlanningVariableof typeTimeslotuseslist[Timeslot]PlanningVariableof typeRoomuseslist[Room]
Multiple Ranges for Same Type
If you have multiple value ranges of the same type, use explicit references:
@planning_solution
@dataclass
class Schedule:
preferred_timeslots: Annotated[list[Timeslot], ProblemFactCollectionProperty, ValueRangeProvider]
backup_timeslots: Annotated[list[Timeslot], ProblemFactCollectionProperty, ValueRangeProvider]
Planning Entities
Collect planning entities in the solution:
lessons: Annotated[list[Lesson], PlanningEntityCollectionProperty]
For a single entity:
main_vehicle: Annotated[Vehicle, PlanningEntityProperty]
Score
Every solution needs a score field:
score: Annotated[HardSoftScore, PlanningScore] = field(default=None)
Common score types:
SimpleScore- Single levelHardSoftScore- Feasibility + optimizationHardMediumSoftScore- Three levelsBendableScore- Custom levels
The solver calculates and updates this field automatically.
Solution Identity
Include an identifier for tracking:
@planning_solution
@dataclass
class Timetable:
id: str # For tracking in SolverManager
...
Complete Example
from dataclasses import dataclass, field
from typing import Annotated
from datetime import date
from solverforge_legacy.solver.domain import (
planning_solution,
ProblemFactCollectionProperty,
ProblemFactProperty,
PlanningEntityCollectionProperty,
ValueRangeProvider,
PlanningScore,
)
from solverforge_legacy.solver.score import HardSoftScore
@planning_solution
@dataclass
class EmployeeSchedule:
# Identity
id: str
# Problem facts
schedule_start: Annotated[date, ProblemFactProperty]
employees: Annotated[list[Employee], ProblemFactCollectionProperty, ValueRangeProvider]
skills: Annotated[list[Skill], ProblemFactCollectionProperty]
# Planning entities
shifts: Annotated[list[Shift], PlanningEntityCollectionProperty]
# Score
score: Annotated[HardSoftScore, PlanningScore] = field(default=None)
Creating Problem Instances
Load or generate problem data:
def load_problem() -> Timetable:
timeslots = [
Timeslot("MONDAY", time(8, 30), time(9, 30)),
Timeslot("MONDAY", time(9, 30), time(10, 30)),
# ...
]
rooms = [
Room("Room A"),
Room("Room B"),
]
lessons = [
Lesson("1", "Math", "A. Turing", "9th grade"),
Lesson("2", "Physics", "M. Curie", "9th grade"),
# ...
]
return Timetable(
id="problem-001",
timeslots=timeslots,
rooms=rooms,
lessons=lessons,
score=None, # Solver will calculate this
)
Accessing the Solved Solution
After solving, the solution contains assigned variables and score:
solution = solver.solve(problem)
print(f"Score: {solution.score}")
print(f"Is feasible: {solution.score.is_feasible}")
for lesson in solution.lessons:
print(f"{lesson.subject}: {lesson.timeslot} in {lesson.room}")
Solution Cloning
The solver internally clones solutions to track the best solution. This happens automatically with @dataclass entities.
For custom classes, ensure proper cloning behavior or use @deep_planning_clone:
from solverforge_legacy.solver.domain import deep_planning_clone
@deep_planning_clone
class CustomClass:
# This class will be deeply cloned during solving
pass
Best Practices
Do
- Use
@dataclassfor solutions - Initialize score to
None - Include all data needed for constraint evaluation
- Use descriptive field names
Don’t
- Include data not used in constraints (performance impact)
- Modify problem facts during solving
- Forget value range providers for planning variables
Next Steps
- Shadow Variables - Calculated variables
- Constraints - Define scoring rules
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.