Compare commits

...

10 Commits

Author SHA1 Message Date
Hilmar Magnusson 1d83544eab dump updates
Signed-off-by: Hilmar Magnusson <hilmarmag@protonmail.com>
2024-11-14 08:18:17 +01:00
Lewis Woolfson 2c70326c7c
Merge pull request #1 from Lewisw3/add-license-1
Create LICENSE
2020-10-23 16:04:41 +01:00
Lewis Woolfson 9fa861ee1f
Create LICENSE 2020-10-23 16:04:27 +01:00
Lewis Woolfson 5163fd0370 updated case data 2020-07-12 15:10:50 +01:00
Lewis Woolfson 4d0e170684 adding initial model formulation 2020-05-31 14:42:08 +01:00
Lewis Woolfson 58d8ce1bcf removed initial formulation 2020-05-31 14:41:12 +01:00
Lewis Woolfson 22182ec3a9 removed median utilisation formulation 2020-05-31 14:40:28 +01:00
Lewis Woolfson 98951be34d updated yml for conda env 2020-05-31 14:39:24 +01:00
Lewis Woolfson 8013cf62a0 initial model formulation 2020-05-31 14:38:54 +01:00
Lewis Woolfson 7034bd7a28 removing remote case_data.csv 2020-05-31 14:33:53 +01:00
6 changed files with 182 additions and 227 deletions

29
LICENSE Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020, Lewis Woolfson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,26 +0,0 @@
CaseID,ConsultantID,Procedure,Speciality,Median Duration,TargetDeadline
1,11,Cataract Surgery,Ophthalmology,45,2020-06-07
2,11,Vitrectomy ,Ophthalmology,70,2020-07-17
3,11,Cataract Surgery,Ophthalmology,45,2020-06-05
4,11,Cataract Surgery,Ophthalmology,45,2020-06-28
5,11,Cataract Surgery,Ophthalmology,45,2020-07-22
6,11,Cataract Surgery,Ophthalmology,45,2020-07-03
7,11,Cataract Surgery,Ophthalmology,45,2020-07-03
8,11,Eyelid Lesion Excision,Ophthalmology,30,2020-06-11
9,11,Vitrectomy ,Ophthalmology,70,2020-07-04
10,11,Cataract Surgery,Ophthalmology,45,2020-07-14
11,11,Cataract Surgery,Ophthalmology,45,2020-07-06
12,11,Cataract Surgery,Ophthalmology,45,2020-07-16
13,11,Trabeculectomy,Ophthalmology,100,2020-07-11
14,11,Vitrectomy ,Ophthalmology,70,2020-06-15
15,11,Vitrectomy ,Ophthalmology,70,2020-06-20
16,11,Cataract Surgery,Ophthalmology,45,2020-06-18
17,11,Cataract Surgery,Ophthalmology,45,2020-07-20
18,11,Trabeculectomy,Ophthalmology,100,2020-07-04
19,11,Eyelid Skin Graft,Ophthalmology,120,2020-06-15
20,11,Cataract Surgery,Ophthalmology,45,2020-07-13
21,11,Cataract Surgery,Ophthalmology,45,2020-07-04
22,11,Cataract Surgery,Ophthalmology,45,2020-06-28
23,11,Cataract Surgery,Ophthalmology,45,2020-07-22
24,11,Cataract Surgery,Ophthalmology,45,2020-07-06
25,11,Vitrectomy ,Ophthalmology,70,2020-06-08
1 CaseID ConsultantID Procedure Speciality Median Duration TargetDeadline
2 1 11 Cataract Surgery Ophthalmology 45 2020-06-07
3 2 11 Vitrectomy  Ophthalmology 70 2020-07-17
4 3 11 Cataract Surgery Ophthalmology 45 2020-06-05
5 4 11 Cataract Surgery Ophthalmology 45 2020-06-28
6 5 11 Cataract Surgery Ophthalmology 45 2020-07-22
7 6 11 Cataract Surgery Ophthalmology 45 2020-07-03
8 7 11 Cataract Surgery Ophthalmology 45 2020-07-03
9 8 11 Eyelid Lesion Excision Ophthalmology 30 2020-06-11
10 9 11 Vitrectomy  Ophthalmology 70 2020-07-04
11 10 11 Cataract Surgery Ophthalmology 45 2020-07-14
12 11 11 Cataract Surgery Ophthalmology 45 2020-07-06
13 12 11 Cataract Surgery Ophthalmology 45 2020-07-16
14 13 11 Trabeculectomy Ophthalmology 100 2020-07-11
15 14 11 Vitrectomy  Ophthalmology 70 2020-06-15
16 15 11 Vitrectomy  Ophthalmology 70 2020-06-20
17 16 11 Cataract Surgery Ophthalmology 45 2020-06-18
18 17 11 Cataract Surgery Ophthalmology 45 2020-07-20
19 18 11 Trabeculectomy Ophthalmology 100 2020-07-04
20 19 11 Eyelid Skin Graft Ophthalmology 120 2020-06-15
21 20 11 Cataract Surgery Ophthalmology 45 2020-07-13
22 21 11 Cataract Surgery Ophthalmology 45 2020-07-04
23 22 11 Cataract Surgery Ophthalmology 45 2020-06-28
24 23 11 Cataract Surgery Ophthalmology 45 2020-07-22
25 24 11 Cataract Surgery Ophthalmology 45 2020-07-06
26 25 11 Vitrectomy  Ophthalmology 70 2020-06-08

View File

@ -1,31 +1,31 @@
CaseID,ConsultantID,Procedure,Speciality,Median Duration,TargetDeadline CaseID,ConsultantID,Procedure,Speciality,Median Duration,TargetDeadline
1,11,Cataract Surgery,Ophthalmology,45,07/07/2020 1,C011,Cataract Surgery,Ophthalmology,45,07/07/2020
2,11,Vitrectomy ,Ophthalmology,70,17/07/2020 2,C011,Vitrectomy ,Ophthalmology,70,17/07/2020
3,11,Cataract Surgery,Ophthalmology,45,05/06/2020 3,C011,Cataract Surgery,Ophthalmology,45,05/06/2020
4,11,Cataract Surgery,Ophthalmology,45,28/06/2020 4,C011,Cataract Surgery,Ophthalmology,45,28/06/2020
5,11,Cataract Surgery,Ophthalmology,45,22/07/2020 5,C011,Cataract Surgery,Ophthalmology,45,22/07/2020
6,11,Cataract Surgery,Ophthalmology,45,03/07/2020 6,C011,Cataract Surgery,Ophthalmology,45,03/07/2020
7,11,Cataract Surgery,Ophthalmology,45,03/07/2020 7,C011,Cataract Surgery,Ophthalmology,45,03/07/2020
8,11,Eyelid Lesion Excision,Ophthalmology,30,11/06/2020 8,C011,Eyelid Lesion Excision,Ophthalmology,30,11/06/2020
9,11,Vitrectomy,Ophthalmology,70,04/07/2020 9,C011,Vitrectomy,Ophthalmology,70,04/07/2020
10,11,Cataract Surgery,Ophthalmology,45,14/07/2020 10,C011,Cataract Surgery,Ophthalmology,45,14/07/2020
11,11,Cataract Surgery,Ophthalmology,45,06/07/2020 11,C011,Cataract Surgery,Ophthalmology,45,06/07/2020
12,11,Cataract Surgery,Ophthalmology,45,16/07/2020 12,C011,Cataract Surgery,Ophthalmology,45,16/07/2020
13,11,Trabeculectomy,Ophthalmology,100,01/08/2020 13,C011,Trabeculectomy,Ophthalmology,100,01/08/2020
14,11,Vitrectomy,Ophthalmology,70,15/06/2020 14,C011,Vitrectomy,Ophthalmology,70,15/06/2020
15,11,Vitrectomy,Ophthalmology,70,20/06/2020 15,C011,Vitrectomy,Ophthalmology,70,20/06/2020
16,11,Cataract Surgery,Ophthalmology,45,18/06/2020 16,C011,Cataract Surgery,Ophthalmology,45,18/06/2020
17,11,Cataract Surgery,Ophthalmology,45,20/07/2020 17,C011,Cataract Surgery,Ophthalmology,45,20/07/2020
18,11,Trabeculectomy,Ophthalmology,100,04/07/2020 18,C011,Trabeculectomy,Ophthalmology,100,04/07/2020
19,11,Eyelid Skin Graft,Ophthalmology,120,15/06/2020 19,C011,Eyelid Skin Graft,Ophthalmology,120,15/06/2020
20,11,Cataract Surgery,Ophthalmology,45,13/07/2020 20,C011,Cataract Surgery,Ophthalmology,45,13/07/2020
21,11,Cataract Surgery,Ophthalmology,45,04/07/2020 21,C011,Cataract Surgery,Ophthalmology,45,04/07/2020
22,11,Cataract Surgery,Ophthalmology,45,28/06/2020 22,C011,Cataract Surgery,Ophthalmology,45,28/06/2020
23,11,Cataract Surgery,Ophthalmology,45,22/07/2020 23,C011,Cataract Surgery,Ophthalmology,45,22/07/2020
24,11,Cataract Surgery,Ophthalmology,45,06/07/2020 24,C011,Cataract Surgery,Ophthalmology,45,06/07/2020
25,11,Vitrectomy,Ophthalmology,70,08/06/2020 25,C011,Vitrectomy,Ophthalmology,70,08/06/2020
26,11,Cataract Surgery,Ophthalmology,45,14/07/2020 26,C011,Cataract Surgery,Ophthalmology,45,14/07/2020
27,11,Cataract Surgery,Ophthalmology,45,06/07/2020 27,C011,Cataract Surgery,Ophthalmology,45,06/07/2020
28,11,Cataract Surgery,Ophthalmology,45,16/07/2020 28,C011,Cataract Surgery,Ophthalmology,45,16/07/2020
29,11,Trabeculectomy,Ophthalmology,100,11/07/2020 29,C011,Trabeculectomy,Ophthalmology,100,11/07/2020
30,11,Vitrectomy,Ophthalmology,70,27/06/2020 30,C011,Vitrectomy,Ophthalmology,70,27/06/2020

1 CaseID ConsultantID Procedure Speciality Median Duration TargetDeadline
2 1 11 C011 Cataract Surgery Ophthalmology 45 07/07/2020
3 2 11 C011 Vitrectomy  Ophthalmology 70 17/07/2020
4 3 11 C011 Cataract Surgery Ophthalmology 45 05/06/2020
5 4 11 C011 Cataract Surgery Ophthalmology 45 28/06/2020
6 5 11 C011 Cataract Surgery Ophthalmology 45 22/07/2020
7 6 11 C011 Cataract Surgery Ophthalmology 45 03/07/2020
8 7 11 C011 Cataract Surgery Ophthalmology 45 03/07/2020
9 8 11 C011 Eyelid Lesion Excision Ophthalmology 30 11/06/2020
10 9 11 C011 Vitrectomy Ophthalmology 70 04/07/2020
11 10 11 C011 Cataract Surgery Ophthalmology 45 14/07/2020
12 11 11 C011 Cataract Surgery Ophthalmology 45 06/07/2020
13 12 11 C011 Cataract Surgery Ophthalmology 45 16/07/2020
14 13 11 C011 Trabeculectomy Ophthalmology 100 01/08/2020
15 14 11 C011 Vitrectomy Ophthalmology 70 15/06/2020
16 15 11 C011 Vitrectomy Ophthalmology 70 20/06/2020
17 16 11 C011 Cataract Surgery Ophthalmology 45 18/06/2020
18 17 11 C011 Cataract Surgery Ophthalmology 45 20/07/2020
19 18 11 C011 Trabeculectomy Ophthalmology 100 04/07/2020
20 19 11 C011 Eyelid Skin Graft Ophthalmology 120 15/06/2020
21 20 11 C011 Cataract Surgery Ophthalmology 45 13/07/2020
22 21 11 C011 Cataract Surgery Ophthalmology 45 04/07/2020
23 22 11 C011 Cataract Surgery Ophthalmology 45 28/06/2020
24 23 11 C011 Cataract Surgery Ophthalmology 45 22/07/2020
25 24 11 C011 Cataract Surgery Ophthalmology 45 06/07/2020
26 25 11 C011 Vitrectomy Ophthalmology 70 08/06/2020
27 26 11 C011 Cataract Surgery Ophthalmology 45 14/07/2020
28 27 11 C011 Cataract Surgery Ophthalmology 45 06/07/2020
29 28 11 C011 Cataract Surgery Ophthalmology 45 16/07/2020
30 29 11 C011 Trabeculectomy Ophthalmology 100 11/07/2020
31 30 11 C011 Vitrectomy Ophthalmology 70 27/06/2020

View File

@ -1,5 +1,4 @@
import pandas as pd import pandas as pd
import numpy as np
import pyomo.environ as pe import pyomo.environ as pe
import pyomo.gdp as pyogdp import pyomo.gdp as pyogdp
import os import os
@ -8,7 +7,6 @@ import matplotlib.cm as cm
from itertools import product from itertools import product
# check with Taha if code is too similar to Alstom?
class TheatreScheduler: class TheatreScheduler:
def __init__(self, case_file_path, session_file_path): def __init__(self, case_file_path, session_file_path):
@ -53,10 +51,33 @@ class TheatreScheduler:
(dict): dictionary with SessionID as key and start time in minutes since midnight as value (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 # Convert session start time from HH:MM:SS format into seconds elapsed since midnight
print(self.df_sessions.loc[:, "Start"])
self.df_sessions.loc[:, "Start"] = pd.to_timedelta(self.df_sessions["Start"]) self.df_sessions.loc[:, "Start"] = pd.to_timedelta(self.df_sessions["Start"])
# pd.to_timedelta(s.dt.time.astype(str)).dt.total_seconds()
print(self.df_sessions.loc[:, "Start"])
self.df_sessions.loc[:, "Start"] = self.df_sessions["Start"].dt.total_seconds() / 60 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() 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 _generate_disjunctions(self): def _generate_disjunctions(self):
""" """
#TODO #TODO
@ -76,38 +97,57 @@ class TheatreScheduler:
model = pe.ConcreteModel() model = pe.ConcreteModel()
# Model Data # Model Data
print('test4')
# List of case IDs in surgical waiting list
model.CASES = pe.Set(initialize=self.df_cases["CaseID"].tolist()) model.CASES = pe.Set(initialize=self.df_cases["CaseID"].tolist())
print('test5')
print(model.CASES)
# List of sessions IDs
model.SESSIONS = pe.Set(initialize=self.df_sessions["SessionID"].tolist()) model.SESSIONS = pe.Set(initialize=self.df_sessions["SessionID"].tolist())
print('test6')
print(model)
# List of sessions IDs
# List of tasks - all possible (caseID, sessionID) combination
model.TASKS = pe.Set(initialize=model.CASES * model.SESSIONS, dimen=2) model.TASKS = pe.Set(initialize=model.CASES * model.SESSIONS, dimen=2)
print(model.TASKS)
print('test7')
# The duration (median case time) for each operation
model.CASE_DURATION = pe.Param(model.CASES, initialize=self._generate_case_durations()) model.CASE_DURATION = pe.Param(model.CASES, initialize=self._generate_case_durations())
print('test8')
# The duration of each theatre session
model.SESSION_DURATION = pe.Param(model.SESSIONS, initialize=self._generate_session_durations()) model.SESSION_DURATION = pe.Param(model.SESSIONS, initialize=self._generate_session_durations())
print('test9')
# The start time of each theatre session
model.SESSION_START_TIME = pe.Param(model.SESSIONS, initialize=self._generate_session_start_times()) model.SESSION_START_TIME = pe.Param(model.SESSIONS, initialize=self._generate_session_start_times())
print('te0')
# The deadline of each case
model.CASE_DEADLINES = pe.Param(model.CASES, initialize=self._get_ordinal_case_deadlines())
print('te1')
# 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) model.DISJUNCTIONS = pe.Set(initialize=self._generate_disjunctions(), dimen=3)
ub = 1440 # seconds in a day ub = 1440 # seconds in a day
model.M = pe.Param(initialize=1e3*ub) # big M model.M = pe.Param(initialize=1e3*ub) # big M
max_util = 0.85 max_util = 0.85
num_sessions = self.df_sessions.shape[0] num_cases = self.df_cases.shape[0]
# Decision Variables # Decision Variables
model.SESSION_ASSIGNED = pe.Var(model.TASKS, domain=pe.Binary) 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.CASE_START_TIME = pe.Var(model.TASKS, bounds=(0, ub), within=pe.PositiveReals)
model.UTILISATION = pe.Var(model.SESSIONS, bounds=(0, 1), within=pe.PositiveReals) model.CASES_IN_SESSION = pe.Var(model.SESSIONS, bounds=(0, num_cases), within=pe.PositiveReals)
model.MEDIAN_UTIL = pe.Var(bounds=(0, ub), within=pe.PositiveReals)
model.DUMMY_BINARY = pe.Var(model.SESSIONS, domain=pe.Binary)
model.CANCEL_SESSION = pe.Var(model.SESSIONS, domain=pe.Binary, within=pe.PositiveReals)
# Objective # Objective
def objective_function(model): def objective_function(model):
#return pe.summation(model.UTILISATION) return pe.summation(model.CASES_IN_SESSION)
return model.MEDIAN_UTIL #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) model.OBJECTIVE = pe.Objective(rule=objective_function, sense=pe.maximize)
# Constraints # Constraints
# TODO add constraint to complete before deadline if it is assigned
# TODO add constraint to make tasks follow each other without gaps?
# Case start time must be after start time of assigned theatre session # Case start time must be after start time of assigned theatre session
def case_start_time(model, case, session): def case_start_time(model, case, session):
return model.CASE_START_TIME[case, session] >= model.SESSION_START_TIME[session] - \ return model.CASE_START_TIME[case, session] >= model.SESSION_START_TIME[session] - \
@ -125,6 +165,10 @@ class TheatreScheduler:
return sum([model.SESSION_ASSIGNED[(case, session)] for session in model.SESSIONS]) <= 1 return sum([model.SESSION_ASSIGNED[(case, session)] for session in model.SESSIONS]) <= 1
model.SESSION_ASSIGNMENT = pe.Constraint(model.CASES, rule=session_assignment) 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): 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] + \ 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), ((2 - model.SESSION_ASSIGNED[case1, session] - model.SESSION_ASSIGNED[case2, session])*model.M),
@ -134,31 +178,16 @@ class TheatreScheduler:
model.DISJUNCTIONS_RULE = pyogdp.Disjunction(model.DISJUNCTIONS, rule=no_case_overlap) model.DISJUNCTIONS_RULE = pyogdp.Disjunction(model.DISJUNCTIONS, rule=no_case_overlap)
def theatre_util(model, session): def theatre_util(model, session):
return model.UTILISATION[session] == (1 / model.SESSION_DURATION[session]) * \ return model.CASES_IN_SESSION[session] == \
sum([model.SESSION_ASSIGNED[case, session]*model.CASE_DURATION[case] for case in model.CASES]) sum([model.SESSION_ASSIGNED[case, session] for case in model.CASES])
model.THEATRE_UTIL = pe.Constraint(model.SESSIONS, rule=theatre_util) model.THEATRE_UTIL = pe.Constraint(model.SESSIONS, rule=theatre_util)
def cancel_sessions(model, session): # TODO
return model.CANCEL_SESSION[session] <= 1 - model.M*sum([model.SESSION_ASSIGNED[case, session] for case in model.CASES])
model.SET_CANCEL_SESSIONS = pe.Constraint(model.SESSIONS, rule=cancel_sessions)
def force_cancel_sessions(model, session):
return sum([model.SESSION_ASSIGNED[case, session] for case in model.CASES]) <= 0 + model.M*(1-model.CANCEL_SESSION[session])
#model.FORCE_CANCEL_SESSIONS = pe.Constraint(model.SESSIONS, rule=force_cancel_sessions)
def set_dummy_variable(model):
return sum([model.DUMMY_BINARY[session] for session in model.SESSIONS]) == np.floor(num_sessions/2)
model.FLOOR = pe.Constraint(rule=set_dummy_variable)
def set_median_util(model, session):
return model.MEDIAN_UTIL <= model.UTILISATION[session] + model.DUMMY_BINARY[session]*model.M
model.SET_MEDIAN_UTIL = pe.Constraint(model.SESSIONS, rule=set_median_util)
pe.TransformationFactory("gdp.bigm").apply_to(model) pe.TransformationFactory("gdp.bigm").apply_to(model)
return model return model
def solve(self, solver_name, options=None, solver_path=None): def solve(self, solver_name, options=None, solver_path=None, local=True):
if solver_path is not None: if solver_path is not None:
solver = pe.SolverFactory(solver_name, executable=solver_path) solver = pe.SolverFactory(solver_name, executable=solver_path)
@ -170,10 +199,17 @@ class TheatreScheduler:
for key, value in options.items(): for key, value in options.items():
solver.options[key] = value solver.options[key] = value
if local:
solver_results = solver.solve(self.model, tee=True) solver_results = solver.solve(self.model, tee=True)
else:
solver_manager = pe.SolverManagerFactory("neos")
solver_results = solver_manager.solve(self.model, opt=solver)
results = [{"Case": case, results = [{"Case": case,
"Session": session, "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](), "Start": self.model.CASE_START_TIME[case, session](),
"Assignment": self.model.SESSION_ASSIGNED[case, session]()} "Assignment": self.model.SESSION_ASSIGNED[case, session]()}
for (case, session) in self.model.TASKS] for (case, session) in self.model.TASKS]
@ -182,7 +218,6 @@ class TheatreScheduler:
all_cases = self.model.CASES.value_list all_cases = self.model.CASES.value_list
cases_assigned = [] cases_assigned = []
cases_missed = []
for (case, session) in self.model.SESSION_ASSIGNED: for (case, session) in self.model.SESSION_ASSIGNED:
if self.model.SESSION_ASSIGNED[case, session] == 1: if self.model.SESSION_ASSIGNED[case, session] == 1:
cases_assigned.append(case) cases_assigned.append(case)
@ -192,11 +227,11 @@ class TheatreScheduler:
print("Cases assigned: ", cases_assigned) print("Cases assigned: ", cases_assigned)
print("Number of cases missed = {} out of {}:".format(len(cases_missed), len(all_cases))) print("Number of cases missed = {} out of {}:".format(len(cases_missed), len(all_cases)))
print("Cases missed: ", cases_missed) print("Cases missed: ", cases_missed)
self.model.UTILISATION.pprint() self.model.CASES_IN_SESSION.pprint()
print("Total Utilisation = {}".format(sum(self.model.UTILISATION.get_values().values()))) print("Total Objective = {}".format(sum(self.model.CASES_IN_SESSION.get_values().values())))
print("Number of constraints = {}".format(solver_results["Problem"].__getitem__(0)["Number of constraints"])) print("Number of constraints = {}".format(solver_results["Problem"].__getitem__(0)["Number of constraints"]))
#self.model.SESSION_ASSIGNED.pprint() #self.model.SESSION_ASSIGNED.pprint()
#print(self.df_times.to_string()) print(self.df_times[self.df_times["Assignment"] == 1].to_string())
self.draw_gantt() self.draw_gantt()
def draw_gantt(self): def draw_gantt(self):
@ -222,7 +257,7 @@ class TheatreScheduler:
ax.plot([xs, xf], [s] * 2, c=colors[c_ix % 7], **bar_style) ax.plot([xs, xf], [s] * 2, c=colors[c_ix % 7], **bar_style)
ax.text((xs + xf) / 2, s, c, **text_style) ax.text((xs + xf) / 2, s, c, **text_style)
ax.set_title('Session Schedule') ax.set_title('Assigning Ophthalmology Cases to Theatre Sessions')
ax.set_xlabel('Time') ax.set_xlabel('Time')
ax.set_ylabel('Sessions') ax.set_ylabel('Sessions')
ax.grid(True) ax.grid(True)
@ -232,10 +267,13 @@ class TheatreScheduler:
if __name__ == "__main__": if __name__ == "__main__":
case_path = os.path.join(os.path.dirname(os.getcwd()), "data", "case_data_long.csv") print('tet')
session_path = os.path.join(os.path.dirname(os.getcwd()), "data", "session_data.csv") case_path = os.path.join(os.path.dirname(os.getcwd()), "data", "cases.csv")
cbc_path = "C:\\Users\\LONLW15\\Documents\\Linear Programming\\Solvers\\cbc.exe" case_path = "/home/hmag/code/theatre-scheduling/data/cases.csv"
session_path = "/home/hmag/code/theatre-scheduling/data/sessions.csv"
# cbc_path = "C:\\Users\\LONLW15\\Documents\\Linear Programming\\Solvers\\cbc.exe"
options = {"seconds": 30} options = {"seconds": 300}
scheduler = TheatreScheduler(case_file_path=case_path, session_file_path=session_path) 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", solver_path=cbc_path, options=options)
#scheduler.solve(solver_name="cbc", local=False, options=None)

View File

@ -1,143 +1,54 @@
name: collider_env_v1 name: lp_pyomo
channels: channels:
- plotly
- anaconda
- conda-forge - conda-forge
- defaults - defaults
dependencies: dependencies:
- aniso8601=8.0.0=py_0
- appdirs=1.4.3=py_1 - appdirs=1.4.3=py_1
- attrs=19.3.0=py_0
- backcall=0.1.0=py37_0
- blas=1.0=mkl - blas=1.0=mkl
- bleach=3.1.0=py37_0 - ca-certificates=2020.4.5.1=hecc5488_0
- ca-certificates=2020.1.1=0 - certifi=2020.4.5.1=py38h32f6830_0
- click=7.0=py37_0 - cycler=0.10.0=py_2
- colorama=0.4.3=py_0 - freetype=2.10.2=hd328e21_0
- coverage=5.0.3=py37h8055547_1
- cycler=0.10.0=py37_0
- decorator=4.4.1=py_0
- defusedxml=0.6.0=py_0
- entrypoints=0.3=py37_0
- flask=1.1.1=py_0
- flask-restplus=0.13.0=py37_0
- freetype=2.9.1=ha9979f8_1
- glpk=4.65=h2fa13f4_1002 - glpk=4.65=h2fa13f4_1002
- icc_rt=2019.0.0=h0cc432a_1 - icc_rt=2019.0.0=h0cc432a_1
- icu=58.2=vc14hc45fdbb_0 - icu=64.2=he025d50_1
- importlib_metadata=1.5.0=py37_0 - intel-openmp=2020.1=216
- intel-openmp=2020.0=166 - jpeg=9c=hfa6e2cd_1001
- ipopt=3.11.1=2 - kiwisolver=1.2.0=py38heaebd3c_0
- ipykernel=5.1.4=py37h39e3cac_0 - libclang=9.0.1=default_hf44288c_0
- ipython=7.12.0=py37h5ca1d4c_0 - libpng=1.6.37=hfe6a214_1
- ipython_genutils=0.2.0=py37_0 - matplotlib=3.2.1=0
- ipywidgets=7.5.1=py_0 - matplotlib-base=3.2.1=py38h1626042_0
- itsdangerous=1.1.0=py37_0 - mkl=2020.1=216
- jedi=0.16.0=py37_0 - mkl-service=2.3.0=py38hb782905_0
- jinja2=2.11.1=py_0 - mkl_fft=1.0.15=py38h14836fe_0
- joblib=0.14.1=py_0 - mkl_random=1.1.0=py38hf9181ef_0
- jpeg=9b=vc14h4d7706e_1 - nose=1.3.7=py38h32f6830_1004
- json5=0.9.1=py_0 - numpy=1.18.1=py38h93ca92e_0
- jsonschema=3.2.0=py37_0 - numpy-base=1.18.1=py38hc3f5095_1
- jupyter=1.0.0=py37_7
- jupyter_client=5.3.4=py37_0
- jupyter_console=6.1.0=py_0
- jupyter_core=4.6.1=py37_0
- jupyterlab=1.2.6=pyhf63ae98_0
- jupyterlab_server=1.0.6=py_0
- kiwisolver=1.1.0=py37ha925a31_0
- libpng=1.6.37=h2a8f88b_0
- libsodium=1.0.16=h9d3ae62_0
- m2w64-gcc-libgfortran=5.3.0=6
- m2w64-gcc-libs=5.3.0=7
- m2w64-gcc-libs-core=5.3.0=7
- m2w64-gmp=6.1.0=2
- m2w64-libwinpthread-git=5.0.0.4634.697f757=2
- markupsafe=1.1.1=py37he774522_0
- matplotlib=3.1.3=py37_0
- matplotlib-base=3.1.3=py37h64f37c6_0
- mistune=0.8.4=py37he774522_0
- mkl=2019.5=281
- mkl-service=2.3.0=py37hb782905_0
- mkl_fft=1.0.15=py37h14836fe_0
- mkl_random=1.1.0=py37h675688f_0
- msys2-conda-epoch=20160418=1
- nb_conda=2.2.1=py37_0
- nb_conda_kernels=2.2.2=py37_0
- nbconvert=5.6.1=py37_0
- nbformat=5.0.4=py_0
- networkx=2.4=py_0
- nose=1.3.7=py37hc8dfbb8_1004
- nose2=0.9.2=py_0
- notebook=6.0.3=py37_0
- numpy=1.18.1=py37h93ca92e_0
- numpy-base=1.18.1=py37hc3f5095_1
- openssl=1.1.1g=he774522_0 - openssl=1.1.1g=he774522_0
- pandas=1.0.1=py37h47e9c7a_0 - pandas=1.0.3=py38h47e9c7a_0
- pandoc=2.2.3.2=0 - pip=20.0.2=py38_3
- pandocfilters=1.4.2=py37_1
- parso=0.6.1=py_0
- patsy=0.5.1=py37_0
- pickleshare=0.7.5=py37_0
- pip=20.0.2=py37_1
- plotly=4.5.4=py_0
- plotly_express=0.4.1=py_0
- ply=3.11=py_1 - ply=3.11=py_1
- prometheus_client=0.7.1=py_0 - pyomo=5.6.9=py38h32f6830_2
- prompt_toolkit=3.0.3=py_0 - pyparsing=2.4.7=pyh9f0ad1d_0
- pygments=2.5.2=py_0 - pyqt=5.12.3=py38h7ae7562_3
- pyodbc=4.0.30=py37ha925a31_0 - python=3.8.3=he1778fa_0
- pyomo=5.6.8=py37_0
- pyparsing=2.4.6=py_0
- pyqt=5.9.2=py37ha878b3d_0
- pyrsistent=0.15.7=py37he774522_0
- python=3.7.6=h60c2a47_2
- python-dateutil=2.8.1=py_0 - python-dateutil=2.8.1=py_0
- python_abi=3.7=1_cp37m - python_abi=3.8=1_cp38
- pytz=2019.3=py_0 - pytz=2020.1=py_0
- pyutilib=5.7.3=py37_4 - pyutilib=5.8.0=pyh9f0ad1d_1
- pywin32=227=py37he774522_1 - qt=5.12.5=h7ef1ec2_0
- pywinpty=0.5.7=py37_0 - setuptools=46.4.0=py38_0
- pyzmq=18.1.1=py37ha925a31_0 - six=1.14.0=py38_0
- qt=5.9.7=vc14h73c81de_0 - sqlite=3.31.1=h2a8f88b_1
- qtconsole=4.7.2=py_0 - tornado=6.0.4=py38hfa6e2cd_0
- qtpy=1.9.0=py_0
- retrying=1.3.3=py37_2
- scikit-learn=0.22.1=py37h6288b17_0
- scipy=1.4.1=py37h9439919_0
- seaborn=0.10.0=py_0
- send2trash=1.5.0=py37_0
- setuptools=46.0.0=py37_0
- sip=4.19.13=py37ha925a31_0
- sqlite=3.31.1=he774522_0
- statsmodels=0.11.0=py37he774522_0
- terminado=0.8.3=py37_0
- testpath=0.4.4=py_0
- tornado=6.0.3=py37he774522_3
- traitlets=4.3.3=py37_0
- vc=14.1=h0510ff6_4 - vc=14.1=h0510ff6_4
- vs2015_runtime=14.16.27012=hf0eaf9b_1 - vs2015_runtime=14.16.27012=hf0eaf9b_2
- waitress=1.4.3=py_0 - wheel=0.34.2=py38_0
- wcwidth=0.1.8=py_0 - wincertstore=0.2=py38_0
- webencodings=0.5.1=py37_1 - zlib=1.2.11=h62dcd97_4
- werkzeug=0.16.1=py_0
- wheel=0.34.2=py37_0
- widgetsnbextension=3.5.1=py37_0
- wincertstore=0.2=py37_0
- winpty=0.4.3=4
- xlrd=1.2.0=py37_0
- zeromq=4.3.1=h33f27b4_3
- zipp=2.2.0=py_0
- zlib=1.2.11=vc14h1cdd9ab_1
- pip: - pip:
- cbcpy==2.10.3a2 - pyqt5-sip==4.19.18
- certifi==2017.7.27.1 - pyqtchart==5.12
- cplex==12.9.0.0 - pyqtwebengine==5.12.1
- docloud==1.0.257
- docplex==2.9.133
- func-timeout==4.3.5
- requests==2.18.4
- six==1.10.0
- urllib3==1.22
prefix: C:\Users\200234360\AppData\Local\Continuum\anaconda3\envs\collider_env_v1

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
pandas
pyomo
matplotlib