Skip to content

Commit 5524f39

Browse files
authored
Revert "Add EverySecondWeekendFreeConstraint and remove objective implementation" (#247)
Revert "Add EverySecondWeekendFreeConstraint and remove objective implementat…" This reverts commit ed802d9.
1 parent 7749392 commit 5524f39

6 files changed

Lines changed: 35 additions & 25 deletions

File tree

src/cp/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
from .constraints import (
22
Constraint as Constraint,
33
)
4-
from .constraints import (
5-
EverySecondWeekendFreeConstraint as EverySecondWeekendFreeConstraint,
6-
)
74
from .constraints import (
85
FreeDayAfterNightShiftPhaseConstraint as FreeDayAfterNightShiftPhaseConstraint,
96
)
@@ -32,6 +29,9 @@
3229
VacationDaysAndShiftsConstraint as VacationDaysAndShiftsConstraint,
3330
)
3431
from .model import Model as Model
32+
from .objectives import (
33+
EverySecondWeekendFreeObjective as EverySecondWeekendFreeObjective,
34+
)
3535
from .objectives import (
3636
FreeDaysAfterNightShiftPhaseObjective as FreeDaysAfterNightShiftPhaseObjective,
3737
)

src/cp/constraints/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
from .constraint import Constraint as Constraint
2-
from .every_second_weekend_free import (
3-
EverySecondWeekendFreeConstraint as EverySecondWeekendFreeConstraint,
4-
)
52
from .free_day_after_night_shift_phase import (
63
FreeDayAfterNightShiftPhaseConstraint as FreeDayAfterNightShiftPhaseConstraint,
74
)

src/cp/objectives/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from .every_second_weekend_free import (
2+
EverySecondWeekendFreeObjective as EverySecondWeekendFreeObjective,
3+
)
14
from .free_days_after_night_shift_phase import (
25
FreeDaysAfterNightShiftPhaseObjective as FreeDaysAfterNightShiftPhaseObjective,
36
)

src/cp/constraints/every_second_weekend_free.py renamed to src/cp/objectives/every_second_weekend_free.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,41 @@
11
import logging
22
from datetime import timedelta
3+
from typing import cast
34

4-
from ortools.sat.python.cp_model import CpModel
5+
from ortools.sat.python.cp_model import CpModel, IntVar, LinearExpr
56

67
from src.day import Day
78
from src.employee import Employee
8-
from src.shift import Shift
99

1010
from ..variables import EmployeeWorksOnDayVariables, ShiftAssignmentVariables
11-
from .constraint import Constraint
11+
from .objective import Objective
1212

1313

14-
class EverySecondWeekendFreeConstraint(Constraint):
14+
class EverySecondWeekendFreeObjective(Objective):
1515
@property
1616
def KEY(self) -> str:
1717
return "every-second-weekend-free"
1818

1919
def __init__(
2020
self,
21+
weight: float,
2122
employees: list[Employee],
2223
days: list[Day],
23-
shifts: list[Shift],
2424
):
2525
"""
26-
Initializes the constraint that enforces alternating free weekends.
26+
Initializes the objective that encourages alternating free weekends.
2727
A weekend is defined as Saturday and Sunday, both days must be free.
2828
"""
29-
super().__init__(employees, days, shifts)
29+
super().__init__(weight, employees, days, [])
3030

3131
def create(
3232
self,
3333
model: CpModel,
3434
shift_assignment_variables: ShiftAssignmentVariables,
3535
employee_works_on_day_variables: EmployeeWorksOnDayVariables,
36-
) -> None:
36+
) -> LinearExpr:
37+
penalties: list[IntVar] = []
38+
3739
# Collect all complete weekends (Saturday-Sunday pairs) in the planning period
3840
weekends: list[tuple[Day, Day]] = []
3941

@@ -57,21 +59,17 @@ def create(
5759
logging.info(f"Found {len(weekends)} complete weekends in the planning period")
5860

5961
for employee in self._employees:
60-
if employee.hidden:
61-
continue
62-
63-
# For each pair of consecutive weekends, enforce alternating pattern
62+
# For each pair of consecutive weekends, penalize if both are free or both have work
6463
for i in range(len(weekends) - 1):
6564
# Get two consecutive weekends
6665
weekend1_sat, weekend1_sun = weekends[i]
6766
weekend2_sat, weekend2_sun = weekends[i + 1]
68-
6967
w1_sat_var = employee_works_on_day_variables[employee][weekend1_sat]
7068
w1_sun_var = employee_works_on_day_variables[employee][weekend1_sun]
7169
w2_sat_var = employee_works_on_day_variables[employee][weekend2_sat]
7270
w2_sun_var = employee_works_on_day_variables[employee][weekend2_sun]
7371

74-
# Create boolean variables for weekend status
72+
# Check if weekends are free (both days must be free)
7573
w1_free = model.new_bool_var(f"w1_free_e:{employee.get_key()}_i:{i}")
7674
w2_free = model.new_bool_var(f"w2_free_e:{employee.get_key()}_i:{i}")
7775

@@ -81,7 +79,16 @@ def create(
8179

8280
model.add(w2_sat_var + w2_sun_var == 0).only_enforce_if(w2_free)
8381
model.add(w2_sat_var + w2_sun_var >= 1).only_enforce_if(w2_free.Not())
82+
same_status_penalty = model.new_bool_var(f"same_status_penalty_e:{employee.get_key()}_i:{i}")
83+
84+
# Penalty = 1 if (w1_free AND w2_free) OR (NOT w1_free AND NOT w2_free)
85+
model.add(same_status_penalty == 1).only_enforce_if([w1_free, w2_free])
86+
model.add(same_status_penalty == 1).only_enforce_if([w1_free.Not(), w2_free.Not()])
87+
88+
# these two penalties seem useless
89+
model.add(same_status_penalty == 0).only_enforce_if([w1_free, w2_free.Not()])
90+
model.add(same_status_penalty == 0).only_enforce_if([w1_free.Not(), w2_free])
91+
92+
penalties.append(same_status_penalty)
8493

85-
# Hard constraint: two consecutive weekends may not both be worked
86-
# At least one of the two consecutive weekends must be free
87-
model.add_bool_or([w1_free, w2_free])
94+
return cast(LinearExpr, sum(penalties) * self.weight)

src/solve.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from datetime import date
44

55
from src.cp import (
6-
EverySecondWeekendFreeConstraint,
6+
EverySecondWeekendFreeObjective,
77
FreeDayAfterNightShiftPhaseConstraint,
88
FreeDaysAfterNightShiftPhaseObjective,
99
FreeDaysNearWeekendObjective,
@@ -53,6 +53,7 @@ def main(
5353
"rotate": 1,
5454
"wishes": 3,
5555
"after_night": 3,
56+
"second_weekend": 1,
5657
}
5758

5859
logging.info("General information:")
@@ -75,7 +76,6 @@ def main(
7576
VacationDaysAndShiftsConstraint(employees, days, shifts),
7677
HierarchyOfIntermediateShiftsConstraint(employees, days, shifts),
7778
PlannedShiftsConstraint(employees, days, shifts),
78-
EverySecondWeekendFreeConstraint(employees, days, shifts),
7979
]
8080
objectives = [
8181
FreeDaysNearWeekendObjective(weights["free_weekend"], employees, days),
@@ -86,6 +86,7 @@ def main(
8686
RotateShiftsForwardObjective(weights["rotate"], employees, days, shifts),
8787
MaximizeEmployeeWishesObjective(weights["wishes"], employees, days, shifts),
8888
FreeDaysAfterNightShiftPhaseObjective(weights["after_night"], employees, days, shifts),
89+
EverySecondWeekendFreeObjective(weights["second_weekend"], employees, days),
8990
]
9091

9192
model = Model(employees, days, shifts)

tests/cp/constraints/test_all_constraints.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727
from src.cp.model import Model
2828
from src.cp.objectives import (
29+
EverySecondWeekendFreeObjective,
2930
FreeDaysAfterNightShiftPhaseObjective,
3031
FreeDaysNearWeekendObjective,
3132
MaximizeEmployeeWishesObjective,
@@ -114,6 +115,7 @@ def test_all_constraints_mass_case(
114115
model.add_objective(RotateShiftsForwardObjective(1.0, employees, days, shifts))
115116
model.add_objective(MaximizeEmployeeWishesObjective(3.0, employees, days, shifts))
116117
model.add_objective(FreeDaysAfterNightShiftPhaseObjective(3.0, employees, days, shifts))
118+
model.add_objective(EverySecondWeekendFreeObjective(1.0, employees, days))
117119

118120
model.add_constraint(FreeDayAfterNightShiftPhaseConstraint(employees, days, shifts))
119121
model.add_constraint(HierarchyOfIntermediateShiftsConstraint(employees, days, shifts))

0 commit comments

Comments
 (0)