Add Resources based on a list(s) of resources and schedules

36 views
Skip to first unread message

Ian Darbey

unread,
Jun 26, 2022, 4:30:05 PM6/26/22
to simmer-devel
Hi,

This I'm sure (if possible) is a relatively simply query.

I want to pass my model a list of resources with names, and capacity schedules stored in a list.

Is it as simple as passing the model through a for loop and looping over the list(s) with an add_generator being passed the parameters?

We've 52 resources to add and adding 52 separately is just asking for typos and problems.

If this is possible would anyone have an example?

Many thanks
Ian

Iñaki Ucar

unread,
Jun 26, 2022, 5:24:28 PM6/26/22
to simmer-devel
On Sun, 26 Jun 2022 at 22:30, Ian Darbey <iand...@gmail.com> wrote:
Hi,

This I'm sure (if possible) is a relatively simply query.

I want to pass my model a list of resources with names, and capacity schedules stored in a list.

Is it as simple as passing the model through a for loop and looping over the list(s) with an add_generator being passed the parameters?

Sure, something like this:

library(simmer)

resources <- list(
  resource1 = 1,
  resource2 = 2,
  resource3 = 3
)

env <- simmer()
for (i in seq_along(resources))
  add_resource(env, names(resources)[i], resources[[i]])
env
#> simmer environment: anonymous | now: 0 | next: 
#> { Monitor: in memory }
#> { Resource: resource1 | monitored: TRUE | server status: 0(1) | queue status: 0(Inf) }
#> { Resource: resource2 | monitored: TRUE | server status: 0(2) | queue status: 0(Inf) }
#> { Resource: resource3 | monitored: TRUE | server status: 0(3) | queue status: 0(Inf) }
Iñaki 

We've 52 resources to add and adding 52 separately is just asking for typos and problems.

If this is possible would anyone have an example?

Many thanks
Ian

--
You received this message because you are subscribed to the Google Groups "simmer-devel" group.
To unsubscribe from this group and stop receiving emails from it, send an email to simmer-devel...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/simmer-devel/834504aa-b034-4bb2-8d8c-aa0c02715e67n%40googlegroups.com.


--
Iñaki Úcar
Message has been deleted

Ian Darbey

unread,
Jun 27, 2022, 9:26:16 AM6/27/22
to simmer-devel
Thanks for the help on this.

By predefining the model up front like this I'm having difficulty getting replications to run properly (I end up with identical results across all runs).

#### Pull Model Format Together ####


ISM <-   simmer("ISM") %>%
  add_resource(paste0("emergency_bed_", c(1:52)), Inf) %>%
  add_resource(paste0("elective_bed_", c(1:52))) %>%
  add_resource(paste0("elective_emerg_bed_", c(1:52))) %>%
  add_resource("home", home) %>%
  add_dataframe("ED_Patient_", ED_Patient,
                data = arrivals,
                mon = 0,
                col_time = "Day",
                time = "absolute",
                col_priority = "Priority") %>%
  add_dataframe("Elec_Patient_", Elective_Patient,
                data = elec_arrivals,
                mon = 0,
                col_time = "Day",
                time = "absolute") %>%
  add_dataframe("Elec_Emerg_Patient_", Elective_Emerg_Patient,
                data = elec__emerg_arrivals,
                mon = 0,
                col_time = "Day",
                time = "absolute",
                col_priority = "Priority") %>%
  add_dataframe("Surge_Emerge_Patient", Surge_Patient,
                data = surge_arrivals,
                mon = 0,
                col_time = "Day",
                time = "absolute",
                col_priority = "Priority") %>%
  add_generator(name_prefix = "Capacity_Monitor_", trajectory = daily_capacity_monitor,
                distribution = function() c(rep(1, DES_Length)), mon = 1) 


for (i in seq_along(sort(unique(RAW_00_Data_dictionary$`Model ID`)))) {
  ISM <- ISM %>%
    add_global(key = paste0("days_over_cap_bed_", i), 0) %>%
    add_global(key = paste0("days_under_cap_bed_", i), 0)
}

for (i in seq_along(sort(unique(RAW_00_Data_dictionary$`Model ID`)))) {
  ISM <- ISM %>%
    add_resource(paste0("bed_",i), INPUT_inpatient_capacity_Schedules[[i]])
}

for (i in seq_along(sort(unique(RAW_00_Data_dictionary$`Region ID`)))) {
  ISM <- ISM %>%
    add_resource(paste0("ss_bed_",i), INPUT_BEDS_Short_Stay_capacity_Schedules[[i]])
}





#### Run the Simulation ####
envs <- lapply(1:5, function(x) {

  ISM %>%
    run(DES_Length, progress = progress::progress_bar$new()$update)
 
})

If I have this like above I get it run once and then copied.
If I reset(ISM) before running I get it to run 5 times but with identical results.

I'm guessing there's some way of setting the seed for the environment???

Iñaki Ucar

unread,
Jun 27, 2022, 9:54:53 AM6/27/22
to simmer-devel
On Mon, 27 Jun 2022 at 15:26, Ian Darbey <iand...@gmail.com> wrote:
Thanks for the help on this.

By predefining the model up front like this I'm having difficulty getting replications to run properly (I end up with identical results across all runs).

TL;DR, you need to wrap() the results of your simulation.

Long explanation: there's a C++ simulation core that is encapsulated in an R environment. As such, the simmer environment obeys reference semantics. Therefore, you are simulating multiple times, but you end up referencing the last simulation in all your replicas. The wrap() method solves this by extracting the simulation results and getting rid of the simulation core.

Therefore, there are a couple of ways to do this. You can easily modify your code to make it work by resetting the environment and wrapping the results after run for each replica. Simple example:

t <- trajectory() %>%
  timeout(function() rexp(1))

env <- simmer() %>%
  add_generator("dummy", t, at(0))


envs <- lapply(1:5, function(x) {
  env %>%
    reset() %>%
    run() %>%
    wrap()
})

get_mon_arrivals(envs)
#>     name start_time  end_time activity_time finished replication
#> 1 dummy0          0 0.7248537     0.7248537     TRUE           1
#> 2 dummy0          0 0.6964843     0.6964843     TRUE           2
#> 3 dummy0          0 0.8985124     0.8985124     TRUE           3
#> 4 dummy0          0 1.0515001     1.0515001     TRUE           4
#> 5 dummy0          0 0.6277753     0.6277753     TRUE           5
But (and this is a big "but"), if you are planning to parallelize the execution of replicas, this won't work anymore, because the C++ simulation core is not exportable to the workers. Instead, you need to recreate the simulation environment for each worker. Therefore, the easiest and recommended way to proceed is to wrap the whole simulation into a function. Then replicate it. Same example with this approach:

simulate <- function(i) {
  t <- trajectory() %>%
    timeout(function() rexp(1))
  
  env <- simmer() %>%
    add_generator("dummy", t, at(0)) %>%
    run() %>%
    wrap()
}

envs <- future.apply::future_lapply(1:5, simulate, future.seed=TRUE)

get_mon_arrivals(envs)
#>     name start_time   end_time activity_time finished replication
#> 1 dummy0          0 0.01209485    0.01209485     TRUE           1
#> 2 dummy0          0 1.99459200    1.99459200     TRUE           2
#> 3 dummy0          0 0.21366555    0.21366555     TRUE           3
#> 4 dummy0          0 1.34564897    1.34564897     TRUE           4
#> 5 dummy0          0 0.09768665    0.09768665     TRUE           5
Iñaki
 

Ian Darbey

unread,
Jun 27, 2022, 10:26:31 AM6/27/22
to simmer-devel
Hi,

The first solution works great (I had been using both reset and wrap but obviously not both together!) but I do prefer future proofing once the model is finalised.

Does every single trajectory object needs to be wrapped in too (they can't just be referenced?)? I've a lot of objects with functions etc. pulling in variables etc. I'm still building out some so wrapping it all in one function I can do once complete.

Many thanks
Ian

Iñaki Ucar

unread,
Jun 27, 2022, 12:23:47 PM6/27/22
to simmer-devel
On Mon, 27 Jun 2022 at 16:26, Ian Darbey <iand...@gmail.com> wrote:
Hi,

The first solution works great (I had been using both reset and wrap but obviously not both together!) but I do prefer future proofing once the model is finalised.

Does every single trajectory object needs to be wrapped in too (they can't just be referenced?)? I've a lot of objects with functions etc. pulling in variables etc. I'm still building out some so wrapping it all in one function I can do once complete.

Yes, they do. Trajectories are also C++ objects under the hood, and cannot be exported to workers either. Pure R objects that are global (e.g., constants, data.frames, auxiliary functions...) can be kept outside the main function, because they are exportable.

See this vignette for examples, including parametrized creation of trajectories, resources, generators and scenarios.

Iñaki
 
Reply all
Reply to author
Forward
0 new messages