From 73628577ba78a690d6379e5ac0b42089fc6b27a8 Mon Sep 17 00:00:00 2001 From: Hilmar Magnusson Date: Thu, 28 Nov 2024 17:03:58 +0100 Subject: [PATCH] dump progresss Signed-off-by: Hilmar Magnusson --- data/cases.csv | 5 ++ data/sessions.csv | 16 +++-- opt.py | 152 ++++++++++++++++++++++++++++++++++++++-------- requirements.txt | 1 + 4 files changed, 143 insertions(+), 31 deletions(-) diff --git a/data/cases.csv b/data/cases.csv index cc53eac..a81d985 100644 --- a/data/cases.csv +++ b/data/cases.csv @@ -2,3 +2,8 @@ 1,21-239,1,15,0,no,28/11/2024 2,21-237,0,22,1,no,29/11/2024 3,21-238,3,12,1,no,30/11/2024 +4,21-248,2,12,1,no,30/11/2024 +5,21-243,3,12,1,no,30/11/2024 +6,21-233,3,12,1,no,30/11/2024 +7,21-218,3,12,1,no,30/11/2024 +8,21-208,3,12,1,no,30/11/2024 diff --git a/data/sessions.csv b/data/sessions.csv index d84a04e..8127527 100644 --- a/data/sessions.csv +++ b/data/sessions.csv @@ -1,5 +1,13 @@ SessionID,Date,Start,End,Duration,ConsultantID,Specialty -1001,03/06/2020,08:30:00,18:00:00,570,11,Ophthalmology -1002,10/06/2020,08:30:00,18:00:00,570,11,Ophthalmology -1003,17/06/2020,08:30:00,18:00:00,570,11,Ophthalmology -1004,25/06/2020,08:30:00,13:00:00,270,11,Ophthalmology +1001,29/11/2024,08:00:00,08:20:00,20,11,Ophthalmology +1002,29/11/2024,08:20:00,08:40:00,20,11,Ophthalmology +1003,29/11/2024,08:40:00,09:00:00,20,11,Ophthalmology +1004,29/11/2024,09:00:00,09:20:00,20,11,Ophthalmology +1005,29/11/2024,09:20:00,09:40:00,20,11,Ophthalmology +1006,29/11/2024,09:40:00,10:00:00,20,11,Ophthalmology +1007,29/11/2024,10:00:00,10:20:00,20,11,Ophthalmology +1008,29/11/2024,10:20:00,10:40:00,20,11,Ophthalmology +1009,29/11/2024,10:40:00,11:00:00,20,11,Ophthalmology +1010,29/11/2024,11:00:00,11:20:00,20,11,Ophthalmology +1011,29/11/2024,11:20:00,11:40:00,20,11,Ophthalmology +1012,29/11/2024,11:40:00,12:00:00,20,11,Ophthalmology diff --git a/opt.py b/opt.py index dc01f74..0db1345 100644 --- a/opt.py +++ b/opt.py @@ -1,30 +1,8 @@ -from pyomo.environ import SolverFactory -solver = SolverFactory('glpk') - import pandas as pd import pyomo.environ as pyo import datetime -# Create a model -model = pyo.ConcreteModel() - -## Define variables -#model.x = pyo.Var(within=pyo.NonNegativeReals) -#model.y = pyo.Var(within=pyo.NonNegativeReals) -## Define objective -#model.obj = pyo.Objective(expr=model.x + model.y, sense=pyo.minimize) -## Define constraints -#model.con1 = pyo.Constraint(expr=model.x + 2 * model.y >= 4) -#model.con2 = pyo.Constraint(expr=model.x - model.y <= 1) -## Select solver -#solver = pyo.SolverFactory('glpk') -## Solve the problem -#result = solver.solve(model) -## Display results -#print('Status:', result.solver.status) -#print('Termination Condition:', result.solver.termination_condition) -#print('Optimal x:', pyo.value(model.x)) -#print('Optimal y:', pyo.value(model.y)) -#print('Optimal Objective:', pyo.value(model.obj)) +import matplotlib.cm as cm +import matplotlib.pyplot as plt class Scheduler: def __init__(self, case_file_path, session_file_path, patient_file_path): @@ -37,7 +15,7 @@ class Scheduler: try: self.df_cases = pd.read_csv(case_file_path) except FileNotFoundError: - print("Case data not found.") + print(f"Case data not found. {case_file_path}") try: self.df_sessions = pd.read_csv(session_file_path) except FileNotFoundError: @@ -46,7 +24,119 @@ class Scheduler: self.df_patients = pd.read_csv(patient_file_path) except FileNotFoundError: print("Patient data not found") - #self.model = self.create_model() + 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() def date2int(date): d0 = pd.to_datetime("25/11/2024", dayfirst=True) @@ -61,7 +151,7 @@ def int2date(week, day): date = d0 + delta return date -path = '/home/hmag/code/pyomo/data' +path = '/home/hmag/git/octopusx/ekt/data' my = Scheduler(path+'/cases.csv', path+'/sessions.csv', path+'/patients.csv') print(my.df_cases) @@ -75,3 +165,11 @@ 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() diff --git a/requirements.txt b/requirements.txt index 0fec1bd..f2725ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ pyomo pandas +matplotlib