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)