ekt/opt.py

176 lines
5.9 KiB
Python
Raw Normal View History

2024-11-27 19:19:03 +00:00
import pandas as pd
import pyomo.environ as pyo
import datetime
import matplotlib.cm as cm
import matplotlib.pyplot as plt
2024-11-27 19:19:03 +00:00
class Scheduler:
def __init__(self, case_file_path, session_file_path, patient_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(f"Case data not found. {case_file_path}")
2024-11-27 19:19:03 +00:00
try:
self.df_sessions = pd.read_csv(session_file_path)
except FileNotFoundError:
print("Session data not found")
try:
self.df_patients = pd.read_csv(patient_file_path)
except FileNotFoundError:
print("Patient data not found")
self.solver = pyo.SolverFactory('glpk')
self.model = self.create_model()
self.build_model()
def solve_model(self):
self.solver_results = self.solver.solve(self.model, tee=True)
def extract_results(self):
results = [{"Case": case,
"Session": session,
#"Session Date": self.model.SESSION_DATES[session],
#"Case Deadline": self.model.CASE_DEADLINES[case],
#"Days before deadline": self.model.CASE_DEADLINES[case] - self.model.SESSION_DATES[session],
#"Start": self.model.CASE_START_TIME[case, session](),
"Assignment": self.model.SESSION_ASSIGNED[case, session]()}
for (case, session) in self.model.TASKS]
self.df_times = pd.DataFrame(results)
def create_model(self):
return pyo.ConcreteModel()
def set_constraint(self):
self.model.c = pyo.ConstraintList()
def solve(self):
result = self.solver.solve(self.model)
print(f'result was {result}')
def build_model(self):
self.add_cases()
#self.set_options()
self.add_sessions()
self.add_tasks()
self.set_decisions()
self.set_obj()
return
def add_cases(self):
# List of case IDs in surgical waiting list
self.model.CASES = pyo.Set(initialize=self.df_cases["CaseID"].tolist())
return
def set_options(self):
# Add solver parameters (time limit)
options = {"seconds": 6}
for key, value in options.items():
self.solver.options[key] = value
def add_sessions(self):
# List of sessions IDs
# TODO: Generate more sessions based on EKT Erhaltung
self.model.SESSIONS = pyo.Set(initialize=self.df_sessions["SessionID"].tolist())
return
# List of job shop tasks
# all possible combinations of cases and sessions
def add_tasks(self):
self.model.TASKS = pyo.Set(initialize=self.model.CASES * self.model.SESSIONS, dimen=2)
return
# Decision Variables
## Upper bound (minutes in a day)
#ub = 1440
## Upper bound of session utilisation set to 85%
#max_util = 0.85
def set_decisions(self):
# Binary flag, 1 if case is assigned to session, 0 otherwise
self.model.SESSION_ASSIGNED = pyo.Var(self.model.TASKS, domain=pyo.Binary)
# Start time of a case
#self.model.CASE_START_TIME = pe.Var(self.model.TASKS, bounds=(0, ub), within=pe.PositiveReals)
# Session utilisation
num_cases = self.df_cases.shape[0]
self.model.CASES_IN_SESSION = pyo.Var(self.model.SESSIONS, bounds=(0, num_cases), within=pyo.PositiveReals)
self.model.UTILISATION = pyo.Var(self.model.SESSIONS, bounds=(0, 1), within=pyo.PositiveReals)
def set_obj(self):
# Objective
def objective_function(model):
return pyo.summation(model.CASES_IN_SESSION)
self.model.OBJECTIVE = pyo.Objective(rule=objective_function, sense=pyo.maximize)
def draw_gantt(self):
df = self.df_times[self.df_times["Assignment"] == 1]
cases = sorted(list(df['Case'].unique()))
sessions = sorted(list(df['Session'].unique()))
bar_style = {'alpha': 1.0, 'lw': 25, 'solid_capstyle': 'butt'}
text_style = {'color': 'white', 'weight': 'bold', 'ha': 'center', 'va': 'center'}
colors = cm.Dark2.colors
df.sort_values(by=['Case', 'Session'])
df.set_index(['Case', 'Session'], inplace=True)
fig, ax = plt.subplots(1, 1)
for c_ix, c in enumerate(cases, 1):
for s_ix, s in enumerate(sessions, 1):
if (c, s) in df.index:
xs = df.loc[(c, s), 'Start']
xf = df.loc[(c, s), 'Start'] + \
self.df_cases[self.df_cases["CaseID"] == c]["Median Duration"]
ax.plot([xs, xf], [s] * 2, c=colors[c_ix % 7], **bar_style)
ax.text((xs + xf) / 2, s, c, **text_style)
ax.set_title('Assigning Ophthalmology Cases to Theatre Sessions')
ax.set_xlabel('Time')
ax.set_ylabel('Sessions')
ax.grid(True)
fig.tight_layout()
plt.show()
2024-11-27 19:19:03 +00:00
def date2int(date):
d0 = pd.to_datetime("25/11/2024", dayfirst=True)
delta = (pd.to_datetime(date, dayfirst=True) - d0).days
day = delta%7
week = int((delta - day)/7)
return week,day
def int2date(week, day):
d0 = pd.to_datetime("25/11/2024", dayfirst=True)
delta = datetime.timedelta(days = 7*week + day)
date = d0 + delta
return date
2024-11-27 19:19:03 +00:00
path = '/home/hmag/git/octopusx/ekt/data'
2024-11-27 19:19:03 +00:00
my = Scheduler(path+'/cases.csv', path+'/sessions.csv', path+'/patients.csv')
print(my.df_cases)
print(my.df_sessions)
#print(my.df_patients)
#for i in my.df_patients:
# print(i)
print(my.df_cases['Date'][0])
date = my.df_cases['Date'][0]
print(date2int(date))
print(int2date(1,0))
#my.set_constraint()
#my.set_obj()
my.solve_model()
my.extract_results()
print(my.df_times)
my.draw_gantt()