207 lines
9.4 KiB
Python
207 lines
9.4 KiB
Python
|
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)
|