ekt/scheduler.py

207 lines
9.4 KiB
Python
Raw Normal View History

2024-11-27 19:19:03 +00:00
import pandas as pd
import pyomo.environ as pe
import pyomo.gdp as pyogdp
import os
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from itertools import product
#import numpy as np
class TheatreScheduler:
def __init__(self, case_file_path, session_file_path):
"""
Read case and session data into Pandas DataFrames
Args:
case_file_path (str): path to case data in CSV format
session_file_path (str): path to theatre session data in CSV format
"""
try:
self.df_cases = pd.read_csv(case_file_path)
except FileNotFoundError:
print("Case data not found.")
try:
self.df_sessions = pd.read_csv(session_file_path)
except FileNotFoundError:
print("Session data not found")
self.model = self.create_model()
def _generate_case_durations(self):
"""
Generate mapping of cases IDs to median case time for the procedure
Returns:
(dict): dictionary with CaseID as key and median case time (mins) for procedure as value
"""
#return pd.Series(self.df_cases["Median Duration"].values, index=self.df_cases["CaseID"]).to_dict()
return
def _generate_session_durations(self):
"""
Generate mapping of all theatre sessions IDs to session duration in minutes
Returns:
(dict): dictionary with SessionID as key and session duration as value
"""
#return pd.Series(self.df_sessions["Duration"].values, index=self.df_sessions["SessionID"]).to_dict()
return
def _generate_session_start_times(self):
"""
Generate mapping from SessionID to session start time
Returns:
(dict): dictionary with SessionID as key and start time in minutes since midnight as value
"""
# Convert session start time from HH:MM:SS format into seconds elapsed since midnight
print("print df sessions type")
print(self.df_sessions.loc[:, "Start"])
print('done')
#self.df_sessions.loc[:, "Start"] = pd.to_datetime(self.df_sessions["Start"])
self.df_sessions.loc[:, "Start"] = pd.to_timedelta(self.df_sessions["Start"])
print("print AFTER")
print(self.df_sessions.loc[:, "Start"])
print('done')
for i in range(0,len(self.df_sessions.loc[:, "Start"])):
print(self.df_sessions.loc[:, "Start"][i])
print(type(self.df_sessions.loc[:, "Start"][i]))
print(f'total sceconds = {self.df_sessions.loc[:, "Start"][i].total_seconds()}')
self.df_sessions.loc[i, "Start"] = self.df_sessions.loc[:, "Start"][i].total_seconds()/60
# for session in self.df_sessions.loc[:, "Start"]:
# print(session)
# print(session.total_seconds())
# session = session.total_seconds()
print('test values;')
for session in self.df_sessions.loc[:, "Start"]:
print(session)
#self.df_sessions.loc[:, "Start"] = self.df_sessions["Start"].dt.total_seconds() / 60
return pd.Series(self.df_sessions["Start"].values, index=self.df_sessions["SessionID"]).to_dict()
def _get_ordinal_case_deadlines(self):
"""
#TODO
Returns:
"""
self.df_cases.loc[:, "TargetDeadline"] = pd.to_datetime(self.df_cases["TargetDeadline"], format="%d/%m/%Y")
self.df_cases.loc[:, "TargetDeadline"] = self.df_cases["TargetDeadline"].apply(lambda date: date.toordinal())
return pd.Series(self.df_cases["TargetDeadline"].values, index=self.df_cases["CaseID"]).to_dict()
def _get_ordinal_session_dates(self):
"""
#TODO
Returns:
"""
self.df_sessions.loc[:, "Date"] = pd.to_datetime(self.df_sessions["Date"], format="%d/%m/%Y")
self.df_sessions.loc[:, "Date"] = self.df_sessions["Date"].apply(lambda date: date.toordinal())
return pd.Series(self.df_sessions["Date"].values, index=self.df_sessions["SessionID"]).to_dict()
def create_model(self):
model = pe.ConcreteModel()
return model
# # Model Data
# # List of case IDs in surgical waiting list
# model.CASES = pe.Set(initialize=self.df_cases["CaseID"].tolist())
# # List of sessions IDs
# model.SESSIONS = pe.Set(initialize=self.df_sessions["SessionID"].tolist())
# # List of sessions IDs
# # List of tasks - all possible (caseID, sessionID) combination
# model.TASKS = pe.Set(initialize=model.CASES * model.SESSIONS, dimen=2)
# # The duration (median case time) for each operation
# model.CASE_DURATION = pe.Param(model.CASES, initialize=self._generate_case_durations())
# # The duration of each theatre session
# model.SESSION_DURATION = pe.Param(model.SESSIONS, initialize=self._generate_session_durations())
# # The start time of each theatre session
# model.SESSION_START_TIME = pe.Param(model.SESSIONS, initialize=self._generate_session_start_times())
# # The deadline of each case
# model.CASE_DEADLINES = pe.Param(model.CASES, initialize=self._get_ordinal_case_deadlines())
# # The date of each theatre session
# model.SESSION_DATES = pe.Param(model.SESSIONS, initialize=self._get_ordinal_session_dates())
# model.DISJUNCTIONS = pe.Set(initialize=self._generate_disjunctions(), dimen=3)
# ub = 1440 # seconds in a day
# model.M = pe.Param(initialize=1e3*ub) # big M
# max_util = 0.85
# num_cases = self.df_cases.shape[0]
# # Decision Variables
# model.SESSION_ASSIGNED = pe.Var(model.TASKS, domain=pe.Binary)
# model.CASE_START_TIME = pe.Var(model.TASKS, bounds=(0, ub), within=pe.PositiveReals)
# model.CASES_IN_SESSION = pe.Var(model.SESSIONS, bounds=(0, num_cases), within=pe.PositiveReals)
# # Objective
# def objective_function(model):
# return pe.summation(model.CASES_IN_SESSION)
# #return sum([model.SESSION_ASSIGNED[case, session] for case in model.CASES for session in model.SESSIONS])
# model.OBJECTIVE = pe.Objective(rule=objective_function, sense=pe.maximize)
# # Constraints
# # Case start time must be after start time of assigned theatre session
# def case_start_time(model, case, session):
# return model.CASE_START_TIME[case, session] >= model.SESSION_START_TIME[session] - \
# ((1 - model.SESSION_ASSIGNED[(case, session)])*model.M)
# model.CASE_START = pe.Constraint(model.TASKS, rule=case_start_time)
# # Case end time must be before end time of assigned theatre session
# def case_end_time(model, case, session):
# return model.CASE_START_TIME[case, session] + model.CASE_DURATION[case] <= model.SESSION_START_TIME[session] + \
# model.SESSION_DURATION[session]*max_util + ((1 - model.SESSION_ASSIGNED[(case, session)]) * model.M)
# model.CASE_END_TIME = pe.Constraint(model.TASKS, rule=case_end_time)
# # Cases can be assigned to a maximum of one session
# def session_assignment(model, case):
# return sum([model.SESSION_ASSIGNED[(case, session)] for session in model.SESSIONS]) <= 1
# model.SESSION_ASSIGNMENT = pe.Constraint(model.CASES, rule=session_assignment)
# def set_deadline_condition(model, case, session):
# return model.SESSION_DATES[session] <= model.CASE_DEADLINES[case] + ((1 - model.SESSION_ASSIGNED[case, session])*model.M)
# model.APPLY_DEADLINE = pe.Constraint(model.TASKS, rule=set_deadline_condition)
# def no_case_overlap(model, case1, case2, session):
# return [model.CASE_START_TIME[case1, session] + model.CASE_DURATION[case1] <= model.CASE_START_TIME[case2, session] + \
# ((2 - model.SESSION_ASSIGNED[case1, session] - model.SESSION_ASSIGNED[case2, session])*model.M),
# model.CASE_START_TIME[case2, session] + model.CASE_DURATION[case2] <= model.CASE_START_TIME[case1, session] + \
# ((2 - model.SESSION_ASSIGNED[case1, session] - model.SESSION_ASSIGNED[case2, session])*model.M)]
# model.DISJUNCTIONS_RULE = pyogdp.Disjunction(model.DISJUNCTIONS, rule=no_case_overlap)
# def theatre_util(model, session):
# return model.CASES_IN_SESSION[session] == \
# sum([model.SESSION_ASSIGNED[case, session] for case in model.CASES])
# model.THEATRE_UTIL = pe.Constraint(model.SESSIONS, rule=theatre_util)
# pe.TransformationFactory("gdp.bigm").apply_to(model)
# return model
def solve(self, solver_name, options=None, solver_path=None, local=True):
return
def draw_gantt(self):
return
if __name__ == "__main__":
print('tet')
case_path = os.path.join(os.path.dirname(os.getcwd()), "data", "cases.csv")
#case_path = "/home/hmag/code/theatre-scheduling/data/cases.csv"
patient_path = os.path.join(os.path.dirname(os.getcwd()), "data", "patients.csv")
session_path = os.path.join(os.path.dirname(os.getcwd()), "data", "sessions.csv")
schedule_path = os.path.join(os.path.dirname(os.getcwd()), "data", "EKT-Plan.docx")
cbc_path = "/usr/bin/cbc"
options = {"seconds": 10}
scheduler = TheatreScheduler(case_file_path=case_path, session_file_path=session_path)
scheduler.solve(solver_name="cbc", solver_path=cbc_path, options=options)
#scheduler.solve(solver_name="cbc", local=False, options=None)