In a previous blog post I documented simmer in R for discrete-event simulation. In this post I want to provide a more hands-on practical example. It is a very simple example, to be honest. Regardless of this, and regardless of this example in general, I recommend learning simmer in R to anyone interested in discrete-event simulation. Understanding the simmer R-package will mean that you had a good look under the hood of what a discrete-event simulation sofware looks like, what it must consider in terms of architecture and governance.
A simple conceptual model of a receival inspection process
I provide a very simple conceptual model of a receival inspection process in the figure below.
The process is a follows: Pallets received are unloaded box by box and placed onto a first conveyor line. This conveyor line leads to the inspection station. In this simple model the assumption is that every box is inspected. After inspection, the box is forwarded to the central storage area via another conveyor line.
Model implementation with simmer R-package
Having drafted a conceptual model I can now implement it in code. This is where the simmer R-package comes into play. The R code is displayed below. As you can see I also make use of the R-packages magrittr and dplyr.
# importing simmer and other packages
library(magrittr)
library(simmer)
library(simmer.plot)
library(dplyr)
# set seed for random number generation
set.seed(42)
# declarate / create simulation environment
env = simmer("Inspection model")
# setup a receival inspection trajectory workflow
inspection = trajectory("receival inspection") %>%
seize("conveyor1", 1) %>%
timeout(3) %>%
release("conveyor1", 1) %>%
seize("inspection", 1) %>%
timeout(15) %>%
release("inspection",1) %>%
seize("conveyor2",1) %>%
timeout(3) %>%
release("conveyor2",1)
# adding a ressource to the simulation environment
env %>%
# there is one conveyor belt going towards inspection
add_resource("conveyor1",
capacity=1,
queue_size=10) %>%
# there is one inspection station
add_resource("inspection",
capacity = 1,
queue_size=0) %>%
# there is another conveyor line goig from inspection towards central storage area
add_resource("conveyor2",
capacity=1,
queue_size=10)
# adding generator to the simulation environment and assigning production trajectory to generator
env %>%
add_generator(name_prefix = "boxes",
trajectory = inspection,
distribution = function() rnorm(n=1,
mean=6,
sd=1))
Having implemented the code I can now execute a simulation run.
Simulation run with simmer R-package
In the line of R code displayed below I execute the simulation. I let the simulation run until simulation time 1000. In this case this represents a simulation time 1000 sec.
env %>% run(1000)
## simmer environment: Inspection model | now: 1000 | next: 1001.26780219819
## { Monitor: in memory }
## { Resource: conveyor1 | monitored: TRUE | server status: 0(1) | queue status: 0(10) }
## { Resource: inspection | monitored: TRUE | server status: 1(1) | queue status: 0(0) }
## { Resource: conveyor2 | monitored: TRUE | server status: 0(1) | queue status: 0(10) }
## { Source: boxes | monitored: 1 | n_generated: 168 }
The information displayed above does not tell us much. We can see that the source triggered 168 arrivals, which matches the box unloading time of 6 sec (1000 sec/168 boxes = 5.95 sec). But this does NOT mean that 168 boxes were inspected. If we look at the conceptual model this would clearly not be possible since inspection time is 15 sec per box. Thus within 1000 sec max. 66 boxes can be inspected. In other words, just because 168 arrivals where generated by the source does not mean that all of these were inspected/processed. Many of these arrivals were rejected. When an arrival cannot be served by a resource, and when the queue is not big enough to contain it, the resource is rejected and dropped from the trajectory.
As I will demonstrate in another blog post simmer allows for sub-trajectories. Rejected arrivals can be assigned to these sub-trajectories. However, for now: If I want to analyze the simulation results I have to use monitoring functions as provided by the simmer R-package.
Getter-functions for monitored data in simmer
As introduced by me in my simmer documentation (see my earlier blog post) the simmer R-package provides monitoring functions for simulation analysis.
In the code below I make use of get_mon_resources() getter-function:
dfResults_resources = env %>% get_mon_resources()
dfResults_resources %>% head()
resource<chr> | time<dbl> | server<int> | queue<int> | capacity<dbl> | queue_size<dbl> | system<int> | limit<dbl> | replication<int> | |
---|---|---|---|---|---|---|---|---|---|
1 | conveyor1 | 7.370958 | 1 | 0 | 1 | 10 | 1 | 11 | 1 |
2 | conveyor1 | 10.370958 | 0 | 0 | 1 | 10 | 0 | 11 | 1 |
3 | inspection | 10.370958 | 1 | 0 | 1 | 0 | 1 | 1 | 1 |
4 | conveyor1 | 12.806260 | 1 | 0 | 1 | 10 | 1 | 11 | 1 |
5 | conveyor1 | 15.806260 | 0 | 0 | 1 | 10 | 0 | 11 | 1 |
6 | conveyor1 | 19.169389 | 1 | 0 | 1 | 10 | 1 | 11 | 1 |
get_mon_resources() is a getter-function for obtaining data that is being monitored by simmer. The simmer R-package provides such getter-functions for arrivals, attributes and resources. As becomes clear from the dataframe excerpt displayed above, state changes related to resources are documented in the data returned by get_mon_resources(). For example, conveyor 1 is serving a box at t = 7.37 sec and is done serving that box 3 sec later, at t = 10.37 sec.
Three getter-functions come along with the simmer R-package. All of them return dataframes.
The getter-functions are the following:
- get_mon_arrivals(): Returns data for each arrival, including name, start time, end time, active time, finished boolean flag.
- get_mon_resources(): Returns data that documents state changes in resources, including resource name, event time (trigger time), server and queue count, capacity, queue size, system count (server + queue) and system limit (capacity + capacity_size).
- get_mon_attributes(): Returns data on state changes in attributes, including name, time of attribute change event, and attribute key.
Calculating simulated system throughput based on monitored simmer data
Using data obtained by getter-functions I can develop custom statistics to assess the performance of a simulated system. In this case I will use the get_mon_arrivals() getter-function to implement a throughput statistic. I do so in the code below:
throughput = function(env){
throughput_count = env %>% get_mon_arrivals() %>% filter(finished==TRUE) %>% nrow()
return(throughput_count)
}
throughput(env)
## [1] 54
After 1000 sec simulation time, 54 boxes have been inspected and put away in the central storage area.
With the simmer R-package I can visualize monitored data, e.g. for resources
Using the monitored resource data retrieved as a dataframe with the get_mon_resources() getter-function provided by the simmer R-package I can produce helpful plots. In the example below I implement a resource utilization plot.
plot(get_mon_resources(env),
metric="utilization",
c("conveyor1","inspection","conveyor2"))
Above visualization indicates that the inspection station is timed out more than 80% of its time. From the model parameters we know that the inspection process itself takes 15 sec. If this represents roughly 83.33% of the inspection stations total available time then that means the available time per box is somewhere around 18.00 sec (rough calculation). That also means that the inspection station is spending 3.00 sec on something else. This “something else” is the convey-in time from conveyor1 onto the inspection station.
Increasing utilization of the inspection station by increasing its queue size
I will increase the queue size of the inspection station to 1, up from 0. This should increase utilization of the inspection station.
env2 = simmer("Inspection model 2") %>%
add_resource("conveyor1",
capacity=1,
queue_size=10) %>%
add_resource("inspection",
capacity = 1,
queue_size=1) %>%
add_resource("conveyor2",
capacity=1,
queue_size=10) %>%
add_generator(name_prefix = "boxes",
trajectory = inspection,
distribution = function() rnorm(n=1,
mean=6,
sd=1)) %>%
run(1000)
env2 %>%
get_mon_resources() %>%
plot(metric="utilization",
c("conveyor1","inspection","conveyor2"))
There you go. Since convey-in can now be executed onto a buffer position in parallel to the inspection station the utilization of the inspection process itself increases. No time is lost on conveying in.
I complete this analysis by evaluating the throughput of this adjusted system configuration.
throughput(env2)
## [1] 65
The throughput is now 65 boxes after 1000 sec simulation time, which verifies the simulation model.
Concluding remarks on this simple receival inspection process simulation with simmer
In this article I introduced the simmer R-package and provided a very simple practical example. In future blog posts I will provide additional examples. I thereby aim at providing a better understanding about simmer. I am confident to thereby convince simulation engineers about the fact that simmer is a valid alternative when compared with commercial simulation software such as e.g. AnyLogic, Plant Simulation, FlexSim etc.
Data scientist focusing on simulation, optimization and modeling in R, SQL, VBA and Python
Leave a Reply