Code Monkey home page Code Monkey logo

simmer's Introduction

simmer

build Coverage Status CRAN_Status_Badge Downloads DOI

simmer is a process-oriented and trajectory-based Discrete-Event Simulation (DES) package for R. Designed to be a generic framework like SimPy or SimJulia, it leverages the power of Rcpp to boost the performance and turning DES in R feasible. As a noteworthy characteristic, simmer exploits the concept of trajectory: a common path in the simulation model for entities of the same type. It is pretty flexible and simple to use, and leverages the chaining/piping workflow introduced by the magrittr package.

Extensions

Package Description Status
simmer.plot Plotting Methods for simmer CRAN_Status_Badge
simmer.bricks Helper Methods for simmer Trajectories CRAN_Status_Badge
simmer.optim Parameter Optimization Functions for simmer Status_Badge
simmer.json Read / Load simmer Definitions in JSON Format Status_Badge
simmer.mon Monitoring Backends for simmer Status_Badge

Mailing list

For bugs and/or issues, create a new issue on GitHub. For other questions or comments, please subscribe to the simmer-devel mailing list. You must be a member to post messages, but anyone can read the archived discussions.

Documentation

Documentation is available at r-simmer.org/reference. To get started, please explore our vignettes online, or in R:

vignette(package = "simmer")

Installation

Install the release version from CRAN:

install.packages("simmer")

The installation from GitHub requires the remotes package.

remotes::install_github("r-simmer/simmer")

Please note that the package contains some C++ code and thus you need a development environment to build the package (e.g., Rtools for Windows).

Hexagon stickers!

You can purchase simmer hex stickers on Redbubble (sticker 1, sticker 2). Browse there for more stuff such as T-shirts and mugs!

design1design2

simmer's People

Contributors

bart6114 avatar enchufa2 avatar jonmcalder avatar nacnudus avatar tomjemmett avatar urswilke avatar yihui avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

simmer's Issues

unexpected output using scheduling functionality

Related to #21

library(simmer)

t0<-
  create_trajectory() %>%
  seize("t-rex") %>%
  timeout(5) %>%
  release("t-rex")

sim<-
  simmer() %>%
  add_resource("t-rex", capacity = schedule(timetable = c(5,10,15), 
                                            period=Inf, 
                                            values = c(1,0,1))) %>%
  add_generator("piggy", t0, at(0,0,0)) %>%
  run()

plot_evolution_arrival_times(sim)

get_mon_arrivals(sim)

Using the above example, we get the expected end time output with simmer version b1502ae (= version that closed this issue):

    name start_time end_time activity_time finished replication
1 piggy0          0       10             5     TRUE           1
2 piggy2          0       20             5     TRUE           1
3 piggy1          0       25             5     TRUE           1

However, the current version gives the wrong output, 12d04a7:

    name start_time end_time activity_time finished replication
1 piggy0          0       10             5     TRUE           1
2 piggy1          0       15             5     TRUE           1
3 piggy2          0       20             5     TRUE           1

Haven't looked into it yet.

move demos in vignettes to demo/ folder

If we move the in-vignette demo code to the demo/ folder they can be included in the vignette using knitr::read_demo and/or knitr::read_chunk; see e.g. 8acd843.

This allows users to easily browse the demo/ folder for examples. Non-vignette examples can be added to the demo/ folder later on.

@Enchufa2, ok with this?

Leave a trajectory with some probability

Now, it can be done with a branch (for instance), but it would be far more elegant to have an specific verb for this. Leave, stop, drop...?

So instead of

patient <- create_trajectory() %>%
  seize("bed", 1, priority=induction1) %>%
  timeout(induction1_timeout) %>%
  branch(induction1_prob_continue, c(T, F),
         create_trajectory() %>% release("bed"),
         create_trajectory() %>% release("bed")) %>%
  seize("bed", 1, priority=induction2) %>%
  ...

we could do this:

patient <- create_trajectory() %>%
  seize("bed", 1, priority=induction1) %>%
  timeout(induction1_timeout) %>%
  release("bed") %>%
  leave(induction1_prob_leave) %>%
  seize("bed", 1, priority=induction2) %>%
  ...

Travis building time too long

We should consider keeping a list of r_binary_packages in the Travis file to reduce the time required by this tool. For instance, the last build took about 10 minutes, while previous ones took about 2 minutes only. What do you think?

don't return `run` invisibly

Any specific reason why run() returns invisibly?
I personally think the below should automatically call show/print (i.e. not return invisibly).

simmer() %>%
  add_resource("server") %>%
  add_generator("test", t0, at(0)) %>%
  run()

add preemption functionality

Let's use this for further discussion on the preemption topic.

Maybe we should start with trying to settle on a definition, a suggestion:

If arrival A has seized 1 unit of resource X (with capacity 1) and arrival B - with a higher priority and a preempt attribute set to TRUE - tries to seize resource X:

  • processing of A is interrupted
  • resource X is released
  • resource X is immediately seized by arrival B (if no other arrivals with higher priority and preempt set to TRUE tries to seize it)
  • once arrival B releases resource X, arrival A re-seizes resource X (if no other arrivals with higher priority and preempt set to TRUE tries to seize it)
  • once arrival A has re-seized resource X, it continues processing (or restarts processing - debatable)

Implement error handling in plot_* functions

The packages dplyr, tidyr and ggplot2 are marked as suggested, because they are only used by plot_* functions, which are helper methods, just for exploratory purposes.

Instead of simply failing when these packages are missing, it would be better to print a nice error message explaining these things.

Try to use magrittr's `%>%` instead of `$` for chaining

The operator $ accesses R6 classes' methods and attributes. The only solution I can glimpse right now is to mask $ with %>%, but this makes magrittr's original %>% unusable, which is self-defeating.

Other ideas are welcome. Until then, R users must get accustomed to $. ยฏ_(ใƒ„)_/ยฏ

Implement select, seize_selected, release_selected

Suppose we have a pool of resources of the same kind, let's say 10 agents handling a stream of tickets, and we want to assign those tickets. Currently, it can be done with a branch, but this is slow and verbose. And since there are some typical interesting cases, we may implement them as activities. For instance,

agents <- c("agent1", "agent2", "agent3", ...)

t <- create_trajectory() %>%
  seize_rr(agents, ...)
  ...

would select agents following a Round-Robin policy. Or seize_shortest() for the shortest queue, and so on and so forth.

get_mon_arrivals doesn't show accurate values

โ€‹t0 <- create_trajectory() %>%
    timeout(3)

env0 <- simmer() %>%
    add_generator("entity", t0, at(0)) %>%
    run()

get_mon_arrivals(env0)

     name start_time end_time activity_time finished
1 entity0          0        0             0     TRUE

Here the end_time of entity0 should be 3

Test with at least commit a09b025

Fix compatibility with the upcoming version of testthat

1 error  | 0 warnings | 0 notes.

    checking tests ... ERROR
    Running the tests in โ€˜tests/testthat.Rโ€™ failed.
    Last 13 lines of output:
      > test_check("simmer")
      1. Failure: the trajectory stores the right number of activities
(@test-trajectory.R#76)
      `output` does not match ".*(13
activities.*Seize.*nurse.*1.*Timeout.*function.*Release.*nurse.*1.*Branch.*1.*6
activities.*Seize.*doctor.*function.*Timeout.*function.*Release.*doctor.*function.*Branch.*1.*3
activities.*Seize.*administration.*1.*Timeout.*1.*Release.*administration.*1.*Rollback.*1.*Branch.*1.*Rollback.*1.*Rollback.*function.*SetAttribute.*1.*SetAttribute.*function).*".
      Actual value: ""


      Trajectory: anonymous, 1 activities
      testthat results
================================================================
      OK: 192 SKIPPED: 0 FAILED: 1
      1. Failure: the trajectory stores the right number of activities
(@test-trajectory.R#76)

      Error: testthat unit tests failed
      Execution halted

There are two common classes of
new failures caused by this version of testthat:

  1. expect_output() no longer automatically prints the object. You'll need to
    explicitly print() if you want to check the output of a print method.
  2. expect_error() now only compares against the actual error message,
    not the prefix, e.g. "Error in foo(): ". You might need to change you
    match message to take this into account.

It seems the problem falls into category 1.

Change branch's 'merge' argument to 'continue'

I was thinking, @bart6114, that we chose a not-so-intuitive name for branch's merge argument. It would be much better something like continue, but I don't see an easy way to change this without breaking compatibility. Any ideas?

Travis says "missing config"

Check this, please. Something was broken by adding the r_binary_packages. But I don't know what's the problem, because I tried even to restore the Travis file to its previous state and the "missing config" message still appears.

Time-specific resource availability

Use case provided by @bart6114:

env <- simmer("SuperDuperSim") %>%
  add_resource("nurse", list(0=1, 30=2, 180=1)) %>%  # time schedule dependent capacity
  add_generator("patient", t0, function() rnorm(1, 10, 2))

This allows to simulate work schedules, in this case there would be 1 nurse available from time 0 to 30, 2 nurses from time 30 to 180 and 1 nurse from 180 onwards.

Need a function for multiple generations at a single time point, n_at

Please add an n_at function to the simmer library

If you need to generate multiple entities, all at the same time at the beginning of the simulation (or at any point), you can do that easily:

n_at <- function(n, t) {
  function() {
    if (n) {
      n <<- n - 1
      return(t)
    } else return(-1)
  }
}

env <- simmer() %>%
  add_generator("1000 at the beginning", t0, n_at(1000, 0))

Package redesign

I start this thread as an open channel to throw and discuss ideas. Some of them, down below.

I think it is important to maintain a Simulator class as simple as possible. Something like (brief Pythonic example)

def run():
  initialisation()
  while (now < end):
    now, event = pop_event(queue)
    new_events = event()
    schedule(new_events)

and that's it. No possible errors.

But despite this, it would be great to preserve the current trajectory definition in R. Or something equivalent if you plan to rewrite this with R6 classes, but anyway I find this concept of trajectory very flexible and powerful. However, maybe it would be desirable to substitute the current add_entities_with_interval with the concept of generator of entities (something that works on demand, instead of instantiating n entities at the begining). Something like

t0 <-
  create_trajectory("my trajectory") %>%
  add_seize_event("server", 1) %>%
  add_timeout_event("rexp(1, 2)") %>%
  add_release_event("server", 1)

sim<-
  create_simulator("SuperDuperSim", n = 100) %>%
  add_resource("server", 1, queue_size=Inf) %>%
  add_generator(trajectory=t0, name_prefix="customer", interval="rexp(1, 1)")

sim <-
  sim %>%
  simmer(until=1000)

Yes, it is essentially the same syntax, but, internally, a generator is quite different. Note also that I moved the condition until into the simmer() call. This is because, with generators, we will be able to rerun the simulation multiple times with different until values.

Finally, instead of add_skip_event, I'd suggest a more convenient and visual syntax. I don't know, for instance...

t1 <-
  create_trajectory("trajectory with a branch") %>%
  ## add a branch (50 - 50 chance)
  add_branch_if("runif(1) > 0.5") %>%
    add_timeout_event(15) %>%
  add_branch_else() %>%
    add_timeout_event(5)

Probably this is not the best solution either, but at least the trajectory flow is more evident.

1 % of coverage remaining

Again, the only lines without coverage are these: the catch section in the simmer_*__new_func constructors for Seize, Release, Timeout and SetAttribute. Any ideas on how to make this functions fail? I see only two possible tricks:

  • bypass is.function to pass a non-function argument.
  • bypass needs_attrs to return a string or something like that.

But I don't know if it's possible.

Monitoring: start/activity/end time per resource

This is a very useful feature. In fact, I've received another email asking for it, so I've started the implementation, but I need your feedback, @bart6114. Right now, we have this:

head( env %>% get_mon_arrivals() )
#>       name start_time end_time activity_time finished replication
#> 1 patient0   10.27215 53.74036      43.46821     TRUE           1
#> 2 patient1   16.27760 64.65632      38.13386     TRUE           1

In order to keep backward compatibility, we can add a parameter. For instance:

head( env %>% get_mon_arrivals(per_resource = TRUE) )
#>       name start_time end_time activity_time finished resource replication
#> 1 patient0   10.27215 12.74036       9.46821     TRUE    nurse           1
#> 2 patient0   12.74036 33.65632      20.91596     TRUE   doctor           1
#> ...

What do you think?

Should we reject a seize with an amount exceeding the limits of a given resource?

From #28, this arrival will be enqueued forever.

library(simmer)

t0<-create_trajectory() %>%
  seize("server", 2) %>%
  timeout(5) %>%
  release("server")

env<-
simmer() %>%
  add_resource("server", 1) %>%
  add_generator("test", t0, at(0)) %>%
  run()

In other words: is there any scenario where this would be desirable as a feature (blocking a resource with a too large seize)?

branch doesn't pass attrs to function

This might be a feature request rather than a bug. It would be handy to branch depending on attributes.

library(simmer)

t1 <- create_trajectory("trajectory with a branch") %>%
  # Decide whether to branch or not, store in attribute
  set_attribute("branch", function() sample(1:2, 1)) %>%
  seize("server", 1) %>%
  # Branch or not depending on the attribute
  branch(function(attrs) attrs[["branch"]], merge=c(T, F), 
    create_trajectory("branch1") %>%
      timeout(function() 1),
    create_trajectory("branch2") %>%
      timeout(function() rexp(1, 3)) %>%
      release("server", 1)
  ) %>%
  release("server", 1)

env<-
  simmer() %>%
  add_resource("server", 1) %>%
  add_generator("patient", t1, at(0), mon = 2) %>%
  run()

# Error in eval(expr, envir, enclos) : 
#   argument "attrs" is missing, with no default

Problem with dev tidyr

I see:

checking tests ... ERROR
Running the tests in โ€˜tests/testthat.Rโ€™ failed.
Last 13 lines of output:

  2. Failure (at test-simulation-1.R#37): a simple deterministic simulation with rejections behaves as expected 
  mean(resources$server) not equal to 2/3
  0.662 - 0.667 == -0.00442

  Trajectory: anonymous, 1 activities
  testthat results ================================================================
  OK: 178 SKIPPED: 0 FAILED: 2
  1. Failure (at test-simulation-1.R#35): a simple deterministic simulation with rejections behaves as expected 
  2. Failure (at test-simulation-1.R#37): a simple deterministic simulation with rejections behaves as expected 

  Error: testthat unit tests failed
  Execution halted

Could you please let me know if this is a new bug in tidyr?

Reneging customers

Linked with #49. Thanks to @nacnudus. From here,

Reneging (or, better, abandonment) occurs if an impatient customer gives up while still waiting and before being served.

priority == preemptible doesn't preempt

From ?seize:

preemptible: if the seize occurs in a preemptive resource, this parameter
establishes the minimum incoming priority that can preempt this
arrival (a seize with a priority equal to preemptible or more
gains the resource; by default, any seize may cause preemption
in a preemptive resource).

In which case I would expect the first block of code below to have the same result as the second. Have I misunderstood? I would prefer the documented behaviour, since it allows implementation of LIFO servers without needing to assign priorities.

  t0 <- create_trajectory() %>%
    seize("dummy", 1, priority = 0, preemptible = 0) %>%
    timeout(10) %>%
    release("dummy", 1)
  env <- simmer() %>%
    add_generator("p0p0:", t0, at(0, 1)) %>%
    add_resource("dummy", 1, preemptive=TRUE, preempt_order="lifo") %>%
    run()
  arrs <- env %>% get_mon_arrivals()
  arrs_ordered <- arrs[order(arrs$name),]
  arrs_ordered
#>     name start_time end_time activity_time finished replication
#> 1 p0p0:0          0       10            10     TRUE           1
#> 2 p0p0:1          1       20            10     TRUE           1
  t0 <- create_trajectory() %>%
    seize("dummy", 1, priority = 1, preemptible = 0) %>%
    timeout(10) %>%
    release("dummy", 1)
  env <- simmer() %>%
    add_generator("p1p0:", t0, at(0, 1)) %>%
    add_resource("dummy", 1, preemptive=TRUE, preempt_order="lifo") %>%
    run()
  arrs <- env %>% get_mon_arrivals()
  arrs_ordered <- arrs[order(arrs$name),]
  arrs_ordered 
#>     name start_time end_time activity_time finished replication
#> 2 p1p0:0          0       20            10     TRUE           1
#> 1 p1p0:1          1       11            10     TRUE           1

Just nitpicking really -- this is a marvellous package.

Generator functions are not reset

So this code produces no output:

t <- create_trajectory() %>% timeout(1)

simmer() %>% 
  add_generator("dummy", t, at(0, 1, 2)) %>%
  run() %>%
  reset() %>%
  run() %>%
  get_mon_arrivals()

Instead, 3 arrivals would be expected.

Fix ggplot2 compatibility

Hadley Wickham is trying to submit a new version of ggplot2 (2.0.0) to the CRAN and he is notifying the maintainers of the reverse dependencies because it breaks many packages. For our part, I'm fixing the warnings that simmer raises.

You probably noticed that I started using milestones. I propose that we move the current open issues to a new milestone v3.2.0 in order to submit to the CRAN the release v3.1.0 with this fix ASAP.

Implement batch/separate activities

Requested by several users, so I pull it out from the TODO list. In particular, they are interested in the following:

Let me explain the term Rollercoaster-process. The resource (in this case the rollercoaster) has a limited number of places (let's assume 10 here ) and a ride on the rollercoaster is lasting 5 minutes. So when the rollercoaster arrives for picking up new people and the queue is larger than 10, the queue is reduced with 10 people. If the rollercoaster arrives and the queue is less than 10 people (let's assume 8), the rollercoaster picks up these 8 persons and the queue is set to 0.

Implement post.seize and reject subtrajectories in seize

Many models require an arrival to do something if it gets rejected when trying to seize a resource. So we need something in between a seize and a branch: an ifseize activity would work similarly to the R function ifelse. For instance,

ifseize(traj, resource, amount = 1, retry = FALSE, merge = c(TRUE, TRUE),
           yes = NULL, 
           no = NULL,
           priority = 0, preemptible = 0, restart = FALSE)

Use case: a WiFi station trying to seize the wireless channel to transmit a frame:

frame <- create_trajectory() %>%
  timeout(backoff) %>%
  ifseize("channel", 1, retry=7, merge=c(T, T),
          yes = create_trajectory() %>%
            timeout(send_frame) %>%
            release("channel", 1),
          no = create_trajectory() %>%
            timeout(backoff))

@bart6114, review this proposal, please.

Seize activity shouldn't need to specify the amount

Linking with #25, seize("dummy") should seize the whole resource, not one unit. Let's suppose that our dummy resource has a capacity of 3. Then, seize("dummy") seizes 3. But if a previous arrival seized 1 and there is room for 2, then seize("dummy") stays in the queue until there is room for 3.

Wrapping up, I would maintain the syntax seize, release, but modifying the default behavior to be more natural:

  • seize("dummy") = seize the whole resource.
  • release("dummy") = totally leave the resource.

Rcpp question

I don't know if it is important, but it worries me this line (and equivalent ones). Do you know the implications of putting here a true/false? Because, from the documentation, it is not clear to me.

Resource monitoring: before or after seize/release

This is just a reminder for me to think about it, but also a discussion forum for throwing pros/cons and, eventually, deciding. Wrapping up:

  • Before: The time diffs are aligned when calculating statistics, but we lose the last observation.
  • After: The time diffs are misaligned and some reengineering is needed to get the right statistics, but we lose the first observation.

Rollback activity

An activity to go back a number of steps (allow looping...). Use case:

t0 <- create_trajectory("trauma patient") %>%
  seize("doctor", 1) %>%
  timeout(function() rnorm(1,15)) %>%
  release("doctor", 1) %>%
  timeout(function() rnorm(1,15)) %>%
  # the patient returns to see the doctor again two more times
  rollback(4, times=2)

Add attributes to arrivals

Probably, it is more efficient (and more easy to use) to integrate this attributes in the C++ core. Use case provided by @bart6114:

โ€‹t0 <- create_trajectory("trauma patient") %>%
  set_attribute(health_status=function() runif(1, .05, .3)) %>%
  seize("doctor", 1) %>%
  timeout(function() rnorm(1,15)) %>%
  release("doctor", 1) %>%
  set_attribute(health_status=
                       get_attribute("health_status") + 
                       runif(1, .05, .3))

Possibilities:

  • A list attached to the activities
  • Attributes for each arrival
  • ...?

Scheduling infinite capacity does not behave the same as a very large one

Reported by @nacnudus:

library(simmer)

arrival <- 
  create_trajectory("Arrival trajectory") %>%
  seize("server") %>%
  release("server")

schedule1 <- schedule(c(3, 100), c(999, 0))
schedule2 <- schedule(c(3, 100), c(Inf, 0))

## Model/Experiment ------------------------------

set.seed(393939)
env <- 
  simmer() %>%
  add_resource("server", capacity = schedule1) %>%
  add_generator("Arrival", arrival, at(1:9))
env %>% run(until = 12)
#> simmer environment: anonymous | now: 100 | next: 
#> { Resource: server | monitored: 1 | server status: 0(0) | queue status: 0(Inf) }
#> { Generator: Arrival | monitored: 1 | n_generated: 9 }
env %>% get_mon_resources
#>    time server queue system resource replication
#> 1     1      0     1      1   server           1
#> 2     2      0     2      2   server           1
#> 3     3      2     0      2   server           1
#> 4     3      1     0      1   server           1
#> 5     3      0     0      0   server           1
#> 6     3      1     0      1   server           1
#> 7     3      0     0      0   server           1
#> 8     4      1     0      1   server           1
#> 9     4      0     0      0   server           1
#> 10    5      1     0      1   server           1
#> 11    5      0     0      0   server           1
#> 12    6      1     0      1   server           1
#> 13    6      0     0      0   server           1
#> 14    7      1     0      1   server           1
#> 15    7      0     0      0   server           1
#> 16    8      1     0      1   server           1
#> 17    8      0     0      0   server           1
#> 18    9      1     0      1   server           1
#> 19    9      0     0      0   server           1
#> 20  100      0     0      0   server           1

set.seed(393939)
env <- 
  simmer() %>%
  add_resource("server", capacity = schedule2) %>%
  add_generator("Arrival", arrival, at(1:9))
env %>% run(until = 12)
#> simmer environment: anonymous | now: 100 | next: 
#> { Resource: server | monitored: 1 | server status: 0(0) | queue status: 0(Inf) }
#> { Generator: Arrival | monitored: 1 | n_generated: 9 }
env %>% get_mon_resources
#>    time server queue system resource replication
#> 1     1      0     1      1   server           1
#> 2     2      0     2      2   server           1
#> 3     3      0     2      2   server           1
#> 4     3      1     2      3   server           1
#> 5     3      2     0      2   server           1
#> 6     3      1     0      1   server           1
#> 7     3      0     0      0   server           1
#> 8     4      1     0      1   server           1
#> 9     4      0     0      0   server           1
#> 10    5      1     0      1   server           1
#> 11    5      0     0      0   server           1
#> 12    6      1     0      1   server           1
#> 13    6      0     0      0   server           1
#> 14    7      1     0      1   server           1
#> 15    7      0     0      0   server           1
#> 16    8      1     0      1   server           1
#> 17    8      0     0      0   server           1
#> 18    9      1     0      1   server           1
#> 19    9      0     0      0   server           1
#> 20  100      0     0      0   server           1

Incorrect branch backwards linking and count

The following rollback

t <- create_trajectory() %>%
  timeout(1) %>% 
  branch(function() 1, T, 
         create_trajectory() %>%
           rollback(1))
t
#> simmer trajectory: anonymous, 2 activities
#> { Activity: Timeout(-) | delay: 1 }
#> { Activity: Branch(-) | merge: 1 }
#>   simmer trajectory: anonymous, 1 activities
#>   { Activity: Rollback(-) | amount: 1 (Timeout), times: 1 }

should point to branch, not to timeout. And there are 3 activities, not 2.

Rollback activity: infinite loop

Some ponderings; maybe it should be possible for a rollback to be infinite, only to be interrupted by the simulation end or a (currently none-existing) skip(times) activity. If #16 would be realised this would allow for a huge amount of flexibility.

Concatenate trajectories

A new verb is needed (for instance, include) so the following:

t.common <- create_trajectory("common") %>%
  seize("receptionist", 1) %>% #Both dogs and cats go to the receptionist
  timeout(function() max(0,rnorm(1,5,2))) %>%
  release("receptionist", 1)

t.cat <- create_trajectory("cat") %>%
  include(t.common) %>%
  seize("catDoc", 1) %>%
  timeout(function() max(0,rnorm(1,30,10))) %>%
  release("catDoc", 1)

t.dog <- create_trajectory("dog") %>%
  include(t.common) %>%
  seize("dogDoc", 1) %>%
  timeout(function() max(0,rnorm(1,30,10))) %>%
  release("dogDoc", 1)

should be equivalent to this:

t.cat <- create_trajectory("cat") %>%
  seize("receptionist", 1) %>% #Both dogs and cats go to the receptionist
  timeout(function() max(0,rnorm(1,5,2))) %>%
  release("receptionist", 1) %>%
  seize("catDoc", 1) %>%
  timeout(function() max(0,rnorm(1,30,10))) %>%
  release("catDoc", 1)

t.dog <- create_trajectory("dog") %>%
  seize("receptionist", 1) %>% #Both dogs and cats go to the receptionist
  timeout(function() max(0,rnorm(1,5,2))) %>%
  release("receptionist", 1) %>%
  seize("dogDoc", 1) %>%
  timeout(function() max(0,rnorm(1,30,10))) %>%
  release("dogDoc", 1)

Review TODO list

The TODO list is a good long-term notepad. We should review it in order to determine which of those ideas make sense (and probably to add more). Then, we may decide which ideas we want implemented in the next release (along with the rollback activity and attributes) and, therefore, remove them from the TODO and open the corresponding issues.

For the time being, these are my two cents about the current TODO elements:

  • refine queue discipline (add priorities): Interesting feature implemented in all simulators. It seems easy to implement. +1 for the next release.
  • time-specific resource availability: I would need a use case to understand this better.
  • add depletable resources: The same than above.
  • stop the simulation mid-run, adjust some parameters and continue the simulation: To me, it does not make sense, because a simulation should be automatic. I would need a good use case to change my mind.
  • create release_all event: I think it isn't worth it. Too much complexity with no added value.
  • inspect the queue: I don't understand this.

Prevent preempted arrivals exceed queue size

In the following system with a zero-length queue, the first arrival is preempted, waits in the zero-length queue for service, and is eventually served.

library(simmer)
p0 <- 
  create_trajectory("Low-priority trajectory") %>%
  seize("Finite queue server", priority = 0, preemptible = 0) %>%
  timeout(10) %>%
  release("Finite queue server")
p1 <- 
  create_trajectory("High-priority trajectory") %>%
  seize("Finite queue server", priority = 1, preemptible = 1) %>%
  timeout(10) %>%
  release("Finite queue server")
env <- 
  simmer() %>%
  add_generator("Low-priority arrival ", p0, at(0, 2)) %>%
  add_generator("High-priority arrival ", p1, at(1)) %>%
  add_resource("Finite queue server", queue_size = 0, preemptive = TRUE) %>%
  run()
env %>% get_mon_arrivals
#>                      name start_time end_time activity_time finished
#> 1  Low-priority arrival 1          2        2             0    FALSE
#> 2  Low-priority arrival 0          0       20            10     TRUE
#> 3 High-priority arrival 0          1       11            10     TRUE
#>   replication
#> 1           1
#> 2           1
#> 3           1
#>                      name start_time end_time activity_time finished
#> 1  Low-priority arrival 1          2        2             0    FALSE
#> 2  Low-priority arrival 0          0       20            10     TRUE
#> 3 High-priority arrival 0          1       11            10     TRUE
#>   replication
#> 1           1
#> 2           1
#> 3           1
env %>% get_mon_resources
#>   time server queue system            resource replication
#> 1    0      1     0      1 Finite queue server           1
#> 2    1      1     1      2 Finite queue server           1
#> 3   11      1     0      1 Finite queue server           1
#> 4   20      0     0      0 Finite queue server           1
#>   time server queue system            resource replication
#> 1    0      1     0      1 Finite queue server           1
#> 2    1      1     1      2 Finite queue server           1
#> 3   11      1     0      1 Finite queue server           1
#> 4   20      0     0      0 Finite queue server           1

I guess any solution to this would imply some kind of FIFO/LIFO preemption policy on the queue, not necessarily the same as that on the server.

New vignettes

To keep track of this:

  • Introduction
  • JSS paper
  • Advanced trajectory usage
  • Advanced resource usage
    • Capacity & queue size scheduling
    • Priority arrivals without preemption
    • Priority arrivals with preemption
  • Advanced generator usage
    • General usage
    • Batched generation (#65)
    • Convenience functions
  • Inspecting results
  • The Bank Tutorial
  • Other SimPy Examples
  • Queueing Systems
  • Continuous-Time Markov Chains
  • Dining Philosophers Problem
  • IEEE Comm. Mag. paper

create sensible $print functions for the different R6 classes

low priority

consider:

> simmer()
<Simmer>
  Public:
    add_generator: function
    add_resource: function
    clone: function
    get_capacity: function
    get_generators: function
    get_mon_arrivals: function
    get_mon_attributes: function
    get_mon_resources: function
    get_n_generated: function
    get_queue_count: function
    get_queue_size: function
    get_resources: function
    get_server_count: function
    initialize: function
    name: anonymous
    now: function
    peek: function
    reset: function
    run: function
    step: function

  Private:
    mon_gen: NULL
    mon_res: NULL
    sim_obj: externalptr

a minimal example:

Simmer <- R6Class("Simmer",
  public = list(
    print = function() cat(paste0(
      "Simmer instance: ",self$name,"\n",
      "============================\n",
      "current sim time: ", self$now())),
    ....etc.....

which results in:

> simmer()
Simmer instance: anonymous
============================
current sim time: 0

Create dummy functions for instantiating R6 classes

For instantiating R6 classes a dummy function should be available. End-users shouldn't have to bother with the instantiation of the class (and thus the $new() aspect).

This can be done with minimal coding:

simmer<-function() Simmer$new()

Now we can simple call simmer() instead of Simmer$new()

Again no improvement in terms of functionality, but eases usability a bit.

Think I found a bug

Thanks so much for making this library; it really helped me out.

I created a resource with a maximum of 20 and the seize and release are both 1. I "simmer" with 100 iterations. When I do a get_resource_serve_mon_values and look at the resource, the value column should always go up or down by 1. Every time I run it, I get just a few dozen instances where the resources in use will be 20, and instead of making the next entity wait, when it goes to use the last resource, the value count jumps to 39.

Here is the output of iteration 2 from get_resource_serve_mon_values:
image

Here is a chart showing the jumps up to 39 and down:
image

If you would be willing to take a look at it for me, could I share my code with you privately? I will e-mail you my code.

Release the whole amount

We should be able to do this:

t0 <- create_trajectory() %>%
  seize("dummy", 3) %>%
  timeout(1) %>%
  release("dummy") # releases 3

And, of course, this:

t0 <- create_trajectory() %>%
  seize("dummy", 3) %>%
  timeout(1) %>%
  release("dummy", 1) %>% # releases 1
  timeout(1) %>% 
  release("dummy") # releases 2

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.