This commit is contained in:
Ross
2022-07-14 14:51:11 +01:00
parent 8f8e5a2a28
commit fecddb2db1
5 changed files with 925 additions and 153 deletions
+55 -22
View File
@@ -1,5 +1,6 @@
from calendar import week
import datetime
from distutils.log import debug
import itertools
from typing import Dict, Iterable, List, Sequence, Tuple, Set, Any, Type
@@ -13,6 +14,7 @@ from pyomo.environ import *
from pyomo.opt import SolverFactory
from rota.workers import Worker
from rota.console import console
import json
@@ -25,6 +27,15 @@ import sys
from rich.pretty import pprint
import logging
logging.basicConfig(
filename="rota.log",
filemode="w",
format="%(name)s - %(levelname)s - %(message)s",
level=logging.DEBUG,
)
ShiftName = str
DayStr = str
WeekInt = int
@@ -38,9 +49,12 @@ from govuk_bank_holidays.bank_holidays import BankHolidays
bank_holidays = BankHolidays()
bank_holiday_map = {}
logging.debug("Creating bank holiday map")
for bank_holiday in bank_holidays.get_holidays(division="england-and-wales"):
bank_holiday_map[bank_holiday["date"]] = bank_holiday["title"]
logging.debug(bank_holiday_map)
SHIFT_BOUNDS = {
"bank_holiday": (0, 9),
"shift_count": (0, 400),
@@ -253,12 +267,16 @@ class RotaBuilder(object):
self.ignore_valid_shifts = False
def solve_model(
self, solver: str = "cbc", use_neos: bool = False, options: dict = {}
self,
solver: str = "cbc",
use_neos: bool = False,
options: dict = {},
debug_if_fail: bool = False,
):
print("Setting up solver")
console.print("Setting up solver")
self.opt = SolverFactory(solver)
print("Solving")
console.print("Solving")
if use_neos:
solver_manager = SolverManagerFactory("neos") # Solve using neos server
# results = solver_manager.solve(Rota.model, opt=opt, logfile="test.log")
@@ -278,39 +296,47 @@ class RotaBuilder(object):
self.results = results
print(results)
results.solver.status
console.print(f"Complete - outcome: {results.solver.status}")
if results.solver.status != "ok" and debug_if_fail:
console.print(f"Attempting each shift individually")
self.solve_shifts_individually(options)
if not results.solver.status:
sys.exit(0)
def solve_shifts_individually(self, options, folder="individual_shifts"):
shifts = self.shifts
outcomes = {}
for shift in shifts:
console.print(f"Testing shifts: {shift.name}")
self.ignore_valid_shifts = True
self.clear_shifts()
self.add_shift(shift)
self.build_and_solve(options)
self.export_rota_to_html(filename=shift.name, folder=folder)
outcomes[shift.name] = (self.results.solver.status, self.results.solver.termination_condition)
pprint(outcomes)
pprint(outcomes)
outcomes[shift.name] = (
self.results.solver.status,
self.results.solver.termination_condition,
)
console.print(outcomes)
def build_and_solve(
self, options={"ratio": 0.1, "seconds": 1000, "threads": 10}, solve=True
self,
options={"ratio": 0.1, "seconds": 1000, "threads": 10},
solve=True,
debug_if_fail: bool = False,
):
self.build_shifts()
self.build_workers()
self.build_model()
if solve:
self.solve_model(options=options)
self.solve_model(options=options, debug_if_fail=debug_if_fail)
def build_model(self):
# Initialize model
@@ -1078,7 +1104,7 @@ class RotaBuilder(object):
set(self.get_shift_names())
):
if self.ignore_valid_shifts:
continue # Skip if we don't want to raise an error
continue # Skip if we don't want to raise an error
else:
raise InvalidShift(
f"avoid shifts by grade constraint contains a non existent shift: {invalid_shifts}"
@@ -1478,7 +1504,9 @@ class RotaBuilder(object):
shift.name, week, worker.id
]
for week in week_blocks
for shift in self.get_shifts_with_constraint("night")
for shift in self.get_shifts_with_constraint(
"night"
)
)
)
@@ -1747,7 +1775,7 @@ class RotaBuilder(object):
#
#def get_pre_may(week_days, max_pre)
# def get_pre_may(week_days, max_pre)
for n in range(len(weeks_days)):
@@ -1955,7 +1983,7 @@ class RotaBuilder(object):
)
for constraint_shift in self.get_shifts_with_constraints(
"pre",
"pre",
):
for n in range(0, constraint_shift.constraint_options["pre"]):
if day in constraint_shift.days:
@@ -1965,7 +1993,10 @@ class RotaBuilder(object):
worker.id, week, day, constraint_shift.name
]
+ sum(
pre_map[n][0] * self.model.works[w.id, pre_map[n][1], pre_map[n][2], shiftname]
pre_map[n][0]
* self.model.works[
w.id, pre_map[n][1], pre_map[n][2], shiftname
]
for shiftname in self.get_shift_names_by_week_day(
pre_map[n][1], pre_map[n][2]
)
@@ -1973,7 +2004,7 @@ class RotaBuilder(object):
for w in workers
)
)
#for constraint_shift in self.get_shifts_with_constraint( "pre"):
# for constraint_shift in self.get_shifts_with_constraint( "pre"):
# to_sum = []
# for n in range(
# 0, constraint_shift.constraint_options["pre"]
@@ -1988,7 +2019,6 @@ class RotaBuilder(object):
# w.id, wk, dy, shiftname
# ])
# if day in constraint_shift.days:
# self.model.constraints.add(
# 1
@@ -2403,7 +2433,10 @@ class RotaBuilder(object):
set: Set of ShiftName
"""
return set((week, day, shift_name) for shift_name in self.week_day_shifts_dict[(week, day)])
return set(
(week, day, shift_name)
for shift_name in self.week_day_shifts_dict[(week, day)]
)
def get_shift_by_name(self, name: str) -> SingleShift:
"""
@@ -2738,7 +2771,7 @@ class RotaBuilder(object):
return self.start_date + datetime.timedelta(weeks=week)
# RESULTS
def export_rota_to_html(self, filename: str = "rota", folder = None):
def export_rota_to_html(self, filename: str = "rota", folder=None):
if folder is not None:
output_file = Path("output", folder, f"{filename}.html")
else: