simmer.bricks 0.1.0: new add-on for simmer

The new package simmer.bricks has found its way to CRAN. The simmer package provides a rich and flexible API to build discrete-event simulations. However, there are certain recurring patterns that are typed over and over again, higher-level tasks which can be conceptualised in concrete activity sequences. This new package is intended to capture this kind of patterns into usable bricks, i.e., methods that can be used as simmer activities, but return an arrangement of activities implementing higher-level tasks.

For instance, consider an entity visiting a resource:

library(simmer)

trajectory("customer") %>%
  seize("clerk") %>%
  timeout(10) %>%
  release("clerk")
## trajectory: customer, 3 activities
## { Activity: Seize        | resource: clerk, amount: 1 }
## { Activity: Timeout      | delay: 10 }
## { Activity: Release      | resource: clerk, amount: 1 }

The simmer.bricks package wraps this pattern into the visit() brick:

library(simmer.bricks)

trajectory("customer") %>%
  visit("clerk", 10)
## trajectory: customer, 3 activities
## { Activity: Seize        | resource: clerk, amount: 1 }
## { Activity: Timeout      | delay: 10 }
## { Activity: Release      | resource: clerk, amount: 1 }

This is a very naive example though. As a more compelling use case, consider a resource that becomes inoperative for some time after each release; i.e., the clerk above needs to do some paperwork after each customer leaves. There are several ways of programming this with simmer. The most compact implementation requires a clone() activity to let a clone hold the resource for some more time while the original entity continues its way. This package encapsulates all this logic in a very easy-to-use brick called delayed_release():

env <- simmer()

customer <- trajectory("customer") %>%
  log_("waiting") %>%
  seize("clerk") %>%
  log_("being attended") %>%
  timeout(10) %>%
  # inoperative for 5 units of time
  delayed_release(env, "clerk", 5) %>%
  log_("leaving")

env %>%
  add_resource("clerk") %>%
  add_generator("customer", customer, at(0, 1)) %>%
  run() %>% invisible
## 0: customer0: waiting
## 0: customer0: being attended
## 1: customer1: waiting
## 10: customer0: leaving
## 15: customer1: being attended
## 25: customer1: leaving

The reference index lists all the available bricks included in this inital release. The examples included in the help page for each method show the equivalence in plain activities. This is very important if you want to mix bricks with rollbacks to produce loops, since the rollback() activity works in terms of the number of activities. For instance, this is what a delayed_release() does behind the scenes:

customer
## trajectory: customer, 11 activities
## { Activity: Log          | message }
## { Activity: Seize        | resource: clerk, amount: 1 }
## { Activity: Log          | message }
## { Activity: Timeout      | delay: 10 }
## { Activity: Clone        | n: 2 }
##   Fork 1, continue,  trajectory: anonymous, 2 activities
##   { Activity: SetCapacity  | resource: clerk, value: 0x55a7c5b524c0 }
##   { Activity: Release      | resource: clerk, amount: 1 }
##   Fork 2, continue,  trajectory: anonymous, 2 activities
##   { Activity: Timeout      | delay: 5 }
##   { Activity: SetCapacity  | resource: clerk, value: 0x55a7c59ddc18 }
## { Activity: Synchronize  | wait: 0 }
## { Activity: Log          | message }

As always, we are more than happy to receive feedback and suggestions, either via the mailing list or via GitHub issues and PRs. If you identify any pattern that you frequently use in your simulations and you think it could become a useful simmer brick, please don’t hesitate to share it!

simmer 3.6.5

The fifth update of the 3.6.x release of simmer, the Discrete-Event Simulator for R, is on CRAN. This release extends the attributes API by allowing users to set/get multiple attributes at once (a pretty straightforward change as well as useful; I don’t know why it didn’t occurred to me before…). Vectors as attributes and other data types are not supported yet, but they are on the roadmap.

This version also fixes some minor bugs (many thanks to the users of the simmer-devel mailing list for taking their simulations to edge cases, where these bugs arise), deprecates the onestep() function and provides the new stepn() instead. Since onestep() serves primarily for debugging purposes, the transition to the new one may go unnoticed. Finally, there is a new vignette about the Dining Philosophers Problem.

New features:

  • set_attribute() (and set_global() by extension) can set multiple attributes at once by providing vectors of keys and values (or functions returning such keys and/or values). get_attribute() (and get_global() by extension) can retrieve multiple keys (#122).
  • New stepn() method deprecates onestep() (e452975).

Minor changes and fixes:

  • Restore ostream after formatting (9ff11f8).
  • Fix arrival cloning to copy attributes over to the clone (#118).
  • Fix self-induced preemption through set_capacity() (#125).
  • Update “Queueing Systems” vignette (a0409a0, 8f03f4f).
  • Update “Advanced Trajectory Usage” vignette (4501927).
  • Fix print methods to return the object invisibly (#128).
  • New “Dining Philosophers Problem” vignette (ff6137e).

Visualising SSH attacks with R

If you have any machine with an SSH server open to the world and you take a look at your logs, you may be alarmed to see so many login attempts from so many unknown IP addresses. DenyHosts is a pretty neat service for Unix-based systems which works in the background reviewing such logs and appending the offending addresses into the hosts.deny file, thus avoiding brute-force attacks.

The following R snippet may be useful to quickly visualise a hosts.deny file with logs from DenyHosts. Such file may have comments (lines starting with #), and actual records are stored in the form <service>: <IP>. Therefore, read.table is more than enough to load it into R. The rgeolocate package is used to geolocate the IPs, and the counts per country are represented in a world map using rworldmap:

library(dplyr)
library(rgeolocate)
library(rworldmap)
hosts.deny <- "/etc/hosts.deny"
db <- system.file("extdata", "GeoLite2-Country.mmdb", package="rgeolocate")
read.table(hosts.deny, col.names=c("service", "IP")) %>%
  pull(IP) %>%
  maxmind(db, fields="country_code") %>%
  count(country_code) %>%
  as.data.frame() %>%
  joinCountryData2Map(joinCode="ISO2", nameJoinColumn="country_code") %>%
  mapCountryData(nameColumnToPlot="n", catMethod="pretty", mapTitle="Attacks per country")
## 74 codes from your data successfully matched countries in the map
## 2 codes from your data failed to match with a country code in the map
## 168 codes from the map weren't represented in your data

Then, you may consider more specific access restrictions based on IP prefixes…

simmer 3.6.4

The fourth update of the 3.6.x release of simmer, the Discrete-Event Simulator for R, is on CRAN. This release patches several bugs regarding resource priority and preemption management when seized amounts greater than one were involved. Check the examples available in the corresponding issues on GitHub (#114#115#116) to know if you are affected.

It can be noted that we already broke the intended bi-monthly release cycle, but it is for a good reason, since we are preparing a journal publication. Further details to come.

Minor changes and fixes:

  • Fix preemption in non-saturated multi-server resources when seizing amounts > 1 (#114).
  • Fix queue priority in non-saturated finite-queue resources when seizing amounts > 1 (#115).
  • Fix resource seizing: avoid jumping the queue when there is room in the server but other arrivals are waiting (#116).

simmer 3.6.3

The third update of the 3.6.x release of simmer, the Discrete-Event Simulator for R, is on CRAN. First of all and once again, I must thank Duncan Garmonsway (@nacnudus) for writing a new vignette: “The Bank Tutorial: Part II”.

Among various fixes and performance improvements, this release provides a way of knowing the progress of a simulation. This feature is embed into the run() function as a parameter called progress, which accepts a function with a single argument and will be called for each one of the steps (10 by default) with the current progress ratio (a number between 0 and 1). A very naive example using a simple print():

library(simmer)

wait_and_go <- trajectory() %>%
  timeout(1)

simmer() %>%
  add_generator("dummy", wait_and_go, function() 1) %>%
  run(until=10, progress=print, steps=5)
## [1] 0
## [1] 0.2
## [1] 0.4
## [1] 0.6
## [1] 0.8
## [1] 1
## simmer environment: anonymous | now: 10 | next: 10
## { Generator: dummy | monitored: 1 | n_generated: 10 }

Or we can get a nice progress bar with the assistance of the progress package:

simmer() %>%
  add_generator("dummy", wait_and_go, function() 1) %>%
  run(until=1e5, progress=progress::progress_bar$new()$update)
#> [==============---------------------------------------------------------]  20%

But more importantly, this release implements a new way of retrieving attributes (thus deprecating the old approach, which will be still available throughout the 3.6.x series and will be removed in version 3.7). Since v3.1.x, arrival attributes were retrieved by providing a function with one argument. A very simple example:

trajectory() %>%
  set_attribute("delay", 3) %>%
  timeout(function(attr) attr["delay"])
## Warning: Attribute retrieval through function arguments is deprecated.
## Use 'get_attribute' instead.
## trajectory: anonymous, 2 activities
## { Activity: SetAttribute | key: delay, value: 3, global: 0 }
## { Activity: Timeout      | delay: 0x5569098b9228 }

Later on, v3.5.1 added support for global attributes, making it necessary to add a second argument to retrieve this new set of attributes:

trajectory() %>%
  set_attribute("delay", 3, global=TRUE) %>%
  timeout(function(attr, glb) glb["delay"])
## Warning: Attribute retrieval through function arguments is deprecated.
## Use 'get_attribute' instead.
## trajectory: anonymous, 2 activities
## { Activity: SetAttribute | key: delay, value: 3, global: 1 }
## { Activity: Timeout      | delay: 0x556908730320 }

This method is a kind of rarity in simmer. It’s clunky, as it is not easy to document (and therefore to discover and learn), and non-scalable, because new features would require more and more additional arguments. Thus, it is now deprecated, and the get_attribute() function becomes the new method for retrieving attributes. It works in the same way as now() for the simulation time:

env <- simmer()

trajectory() %>%
  set_attribute("delay_1", 3) %>%
  # shortcut equivalent to set_attribute(..., global=TRUE)
  set_global("delay_2", 2) %>% 
  timeout(function() get_attribute(env, "delay_1")) %>%
  # shortcut equivalent to get_attribute(..., global=TRUE)
  timeout(function() get_global(env, "delay_2"))
## trajectory: anonymous, 4 activities
## { Activity: SetAttribute | key: delay_1, value: 3, global: 0 }
## { Activity: SetAttribute | key: delay_2, value: 2, global: 1 }
## { Activity: Timeout      | delay: 0x55690829f550 }
## { Activity: Timeout      | delay: 0x55690830c310 }

This is a little bit more verbose, but I believe it is more consistent and intuitive. Moreover, it allows us to easily implement new features for extracting arrival information. In fact, get_attribute() will come hand in hand with two more verbs: get_name() and get_prioritization(), to retrieve the arrival name and prioritization values respectively.

New features:

  • Show simulation progress via an optional progress callback in run() (#103).
  • New “The Bank Tutorial: Part II” vignette, by Duncan Garmonsway @nacnudus (#106).
  • New getters for running arrivals (#109), meant to be used inside trajectories:
    • get_name() retrieves the arrival name.
    • get_attribute() retrieves an attribute by name. The old method of retrieving them by providing a function with one argument is deprecated in favour of get_attribute(), and will be removed in version 3.7.x.
    • get_prioritization() retrieves the three prioritization values (prioritypreemptiblerestart) of the active arrival.
  • New shortcuts for global attributes (#110): set_global() and get_global(), equivalent to set_attribute(global=TRUE) and get_attribute(global=TRUE) respectively.

Minor changes and fixes:

  • Some code refactoring and performance improvements (2f4b484, ffafe1e, f16912a, fb7941b, 2783cd8).
  • Use Rcpp::DataFrame instead of Rcpp::List (#104).
  • Improve argument parsing and error messages (#107).
  • Improve internal function make_resetable() (c596f73).