This notebook contains material from CBE40455-2020; content is available on Github.
(to be submitted Thursday, Sept. 3rd).
The facility has expanded to an average of 100 hours of machine cleaning time are required during the 16 hour overnight shift. The company would like to settle on a single cleaning model rather than servicing five different models.
a. Modify the above model to determine the the model (A, B, C, D, or E) and number of devices required to meet the service requirement.
b. Modify the above model to include a second process that writes the number of charging stations in use at every minute to a second data log. Prepare a plot and histogram of charging station usage.
# necessary installations
!pip install simpy
# import section
import simpy
import pandas as pd
import numpy as np
# data section
roomba_data = [
["A", 1.0, 2.5],
["B", 0.5, 1.5],
["C", 0.8, 2.0],
["D", 1.4, 3.5],
["E", 0.5, 1.2],
]
roomba_df = pd.DataFrame(roomba_data, columns=["id", "charge_time", "clean_time"])
display(roomba_df)
def kpi(df):
df["time"] = df["end"] - df["begin"]
return pd.pivot_table(df, index=["event"], values="time", aggfunc={"time":np.sum} )
%matplotlib inline
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
def gantt(df, lw=10):
# create sorted lists of the unique ids and events appearing in the data log
ids = sorted(list(set(df["id"])))
events = sorted(list(set(df["event"])))
# create list of unique colors for each event
colors = [f"C{i}" for i in range(len(events))]
# create plot window
fig, ax = plt.subplots(1, 1, figsize=(12, 3))
# for each event and id, find entries in the data log and plot the begin and end points
for i, event in enumerate(events):
for j, id in enumerate(ids):
for k in df[(df["id"]==id) & (df["event"]==event)].index:
ax.plot([df["begin"][k], df["end"][k]], [j,j],
colors[i], solid_capstyle="butt", lw=lw)
# create legend
lines = [Line2D([0], [0], lw=lw, color=colors[i]) for i in range(len(events))]
ax.legend(lines, events, bbox_to_anchor=(0.0, 1.1), loc="lower left")
# annotate the axes
ax.set_yticks(range(len(ids)))
ax.set_yticklabels(ids)
ax.grid(True)
ax.set_xlabel("Time")
ax.set_title("Gannt Chart")
for sp in ['top', 'bottom', 'right', 'left']:
ax.spines[sp].set_visible(False)
import random
five_min = 5.0/60.0
ten_min = 10.0/60.0
twenty_min = 20.0/60.0
thirty_min = 30.0/60.0
def roomba_model(id, charge_time, clean_time, reserve=0.1):
soc = 1.0 # state of charge
sow = 0.0 # state of waste
while True:
if soc > reserve and sow < 1.0:
tic = env.now
yield simpy.AnyOf(env, [env.timeout((soc - reserve) * clean_time),
env.timeout((1.0 - sow)*random.uniform(ten_min, thirty_min))])
toc = env.now
soc = max(0.0, soc - (toc - tic)/clean_time)
sow = min(1.0, sow + (toc - tic)/twenty_min)
data_log.append([id, "cleaning", tic, toc])
if sow >= 1.0 - 0.001:
with waste_stations.request() as request:
yield request
tic = env.now
yield env.timeout(five_min)
toc = env.now
sow = 0.0
data_log.append([id, "waste disposal", tic, toc])
if soc <= reserve+ 0.001:
with chargers.request() as request:
yield request
tic = env.now
yield env.timeout((1 - soc)*charge_time)
toc = env.now
soc = min(1.0, soc + (toc - tic)/charge_time)
data_log.append([id, "charging", tic, toc])
Our strategy will be to determine, first, which model can provide 100 hours of cleaning with the fewest units. For this determination we will use an equal number of charging and waste stations.
N = 10
for r in roomba_df.index:
data_log = []
env = simpy.Environment()
chargers = simpy.Resource(env, capacity=N)
waste_stations = simpy.Resource(env, capacity=N)
for k in range(N):
env.process(roomba_model(roomba_df["id"][r] + str(k), roomba_df["charge_time"][r], roomba_df["clean_time"][r]))
env.run(until=16)
df = pd.DataFrame(data_log, columns=["id", "event", "begin", "end"])
print("Model ", roomba_df["id"][r])
display(kpi(df))
#gantt(df)
Both models B and C can meet the 100 specification with 10 units, but model B has a clear advantage. So will continue the analysis with model B. Next determine the minimum number of charging and waste stations while still meeting the 100 hour requirement.
import random
five_min = 5.0/60.0
ten_min = 10.0/60.0
twenty_min = 20.0/60.0
thirty_min = 30.0/60.0
def roomba_model(id, charge_time, clean_time, reserve=0.1):
soc = 1.0 # state of charge
sow = 0.0 # state of waste
while True:
if soc > reserve and sow < 1.0:
tic = env.now
yield simpy.AnyOf(env, [env.timeout((soc - reserve) * clean_time),
env.timeout((1.0 - sow)*random.uniform(ten_min, thirty_min))])
toc = env.now
soc = max(0.0, soc - (toc - tic)/clean_time)
sow = min(1.0, sow + (toc - tic)/twenty_min)
data_log.append([id, "cleaning", tic, toc])
if sow >= 1.0 - 0.001:
with waste_stations.request() as request:
yield request
tic = env.now
yield env.timeout(five_min)
toc = env.now
sow = 0.0
data_log.append([id, "waste disposal", tic, toc])
if soc <= reserve+ 0.001:
with chargers.request() as request:
yield request
tic = env.now
charger_log.append([chargers.count, tic])
yield env.timeout((1 - soc)*charge_time)
toc = env.now
soc = min(1.0, soc + (toc - tic)/charge_time)
data_log.append([id, "charging", tic, toc])
charger_log.append([chargers.count, toc])
data_log = []
charger_log = [[0, 0]]
env = simpy.Environment()
chargers = simpy.Resource(env, capacity=5)
waste_stations = simpy.Resource(env, capacity=2)
for r in roomba_df.index:
env.process(roomba_model(roomba_df["id"][r], roomba_df["charge_time"][r], roomba_df["clean_time"][r]))
env.run(until=16)
df = pd.DataFrame(data_log, columns=["id", "event", "begin", "end"])
display(kpi(df))
gantt(df)
charger_df = pd.DataFrame(charger_log, columns=["count", "time"])
plt.step(charger_df["time"], charger_df["count"], where='post')
plt.hist(charger_df["count"])