commit message

This commit is contained in:
Hilmar Magnusson 2024-11-27 20:19:03 +01:00
commit d608f3fc6f
4 changed files with 285 additions and 0 deletions

8
README Normal file
View File

@ -0,0 +1,8 @@
# Create EKG Schedule
based on https://python.plainenglish.io/solving-the-resource-constrained-project-scheduling-problem-rcpsp-with-python-and-pyomo-001cffd5344a
$ python -m venv ekg
$ source ekg/bin/activate
$ pip install -r requirements.txt

69
opt.py Normal file
View File

@ -0,0 +1,69 @@
from pyomo.environ import SolverFactory
solver = SolverFactory('glpk')
import pandas as pd
import pyomo.environ as pyo
# 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))
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("Case data not found.")
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.model = self.create_model()
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
path = '/home/hmag/code/pyomo/data'
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))

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
pyomo
pandas

206
scheduler.py Normal file
View File

@ -0,0 +1,206 @@
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)