commit message
This commit is contained in:
commit
d608f3fc6f
|
@ -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
|
||||
|
|
@ -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))
|
|
@ -0,0 +1,2 @@
|
|||
pyomo
|
||||
pandas
|
|
@ -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)
|
Loading…
Reference in New Issue