ekt/scheduler.py

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)