Choosing a Sampler
How to make a sampler for events in CompetingClocks.jl.
High-level Interface
Every event in a simulation has an identifying key which can be any immutable Julia type. A Tuple key type works fine, but it will be more performant to use a concrete type for the KeyType. The second argument is a type to use for time. Basic usage:
sampler = SamplingContext(KeyType, Float64, rng)The sampler takes a Random.AbstractRNG as its second argument.
Specify features for the sampler with keyword arguments. These features determine the type of the sampler.
method=nothing–-Setting this chooses a single sampling algorithm for all clocks.step_likelihood=false–-Setting this totruelet's you calculate the the likelihood of the next event and time before firing it.path_likelihood=false–-Set to true in order to calculate likelihood of a whole trajectory at any point in the simulation. This is more efficient than adding up steps in the likelihood along the way.likelihood_cnt=1–-Applies when likelihoods are enabled and supports importance sampling. Specifies how many event distributions will be used to calculate a vector of likelihoods.common_random=false–-For variance reduction, turn on recording of random number usage during sampling.start_time=0–-Sometimes you want a simulation to start at a different time.debug=false–-Whether to print debug messages using theLoggingpackage.recording=false–-This will create a vector of every enable and disable event for test and debug.
If you want to try a particular sampler instead of having the SamplingContext pick one for you, use the method parameter.
sampler = SamplingContext(KeyType, Float64, rng; method=DirectMethod(:keep, :tree))Quick Reference
These are all of the samplers.
| Name | Distribution support | Best use cases |
|---|---|---|
FirstToFireMethod | All | Fastest for simple simulation |
NextReactionMethod | All | Best for common random numbers |
FirstReactionMethod | All | Fastest for very few events |
DirectMethod | Exponential-only | Quicker likelihood calculation |
RejectionMethod | Exponential-only | RSSA for large systems |
PartialPropensityMethod | Exponential-only | For reaction networks. |
PetriMethod | All | Debug rare errors |
Every sampler in this table is validated by a statistical test matrix against a trusted reference; see Sampler Correctness for the methodology and current results.
Spec-to-Sampler Crosswalk
Each method spec on the left constructs the developer-level sampler type on the right. The right column is the developer-level type, reachable as CompetingClocks.<Name>; users normally pick the left column in a SamplerBuilder.
| Method spec | Underlying sampler type |
|---|---|
FirstToFireMethod | CompetingClocks.FirstToFire |
NextReactionMethod | CompetingClocks.CombinedNextReaction |
DirectMethod | CompetingClocks.DirectCall |
RejectionMethod | CompetingClocks.RSSA |
PartialPropensityMethod | CompetingClocks.PSSACR |
FirstReactionMethod | CompetingClocks.FirstReaction |
PetriMethod | CompetingClocks.Petri |
Samplers that continuously track a time-inhomogeneous hazard (tRSSA-style bounded rates) are deferred, not denied. In the meantime, re-enabling a clock with a shifted enabling time covers many cases where a rate changes at discrete events. See Re-enabling and Memory for the three idioms.
Hierarchical Samplers
For spatial simulations, chemical simulations that aren't well-mixed, or simulations with some fast and many slow clocks, it can help to split a sampler into multiple buckets so that each bucket can update its own list of what fires next. This package does this by adding sampler groups.
A sampler group is
- A Symbol name for the sampler.
- An inclusion function from
(ClockKey, Distribution)toBoolthat decides whether a given event belongs to this sampler. - An optional
methodto say what kind of sampler this group should use.
This example sends events with keys like (:infect, 3) tto the sampler called :forthright and sends keys like (:recover, 7) to the sampler called :sparky.
const KeyType = Tuple{Symbol,Integer}
builder = SamplerBuilder(KeyType, Float64)
add_group!(builder, :sparky => (x,d) -> x[1] == :recover, method=NextReaction())
add_group!(builder, :forthright=>(x,d) -> x[1] == :infect)
sampler = SamplingContext(builder, rng)The resulting sampler will be hierarchical. For every event, it will choose the soonest time among the soonest times from all sampler groups.