Context Interface
Building a Context
CompetingClocks.SamplingContext — Method
SamplingContext(::Type{K}, ::Type{T}, rng::AbstractRNG; kwargs...)Convenience wrapper that allocates a SamplerBuilder then builds a SamplingContext.
CompetingClocks.SamplingContext — Method
SamplingContext(builder::SamplerBuilder, rng)Uses the SamplerBuilder to make a SamplingContext.
K is always the user key type (builder.clock_type). In a delayed context (builder.support_delayed == true) the public event identity is the tuple (clock, phase), with phase one of :regular, :initiate, or :complete. The sampler and middleware store keys as K_int, which is Tuple{K,Symbol} for delayed contexts and K for regular ones.
CompetingClocks.SamplerBuilder — Type
SamplerBuilder(::Type{K}, ::Type{T};
step_likelihood=false,
path_likelihood=false,
debug=false,
recording=false,
common_random=false,
method=nothing,
start_time::T,
likelihood_cnt::Int,
support_delayed=false,
)A SamplerBuilder is responsible for recording a user's requirements and building an initial sampler.
KandTare the clock type and time type.step_likelihood- whether you will callsteploglikelihoodbefore eachfire!path_likelihood- whether you will callpathloglikelihoodat the end of a simulation run.debug- Print log messages at the debug level.recording- Store every enable and disable for later examination.common_random- Use common random numbers during sampling.method- If you want a single, particular sampler, put itsSamplerSpechere. It will create a group called:allthat has this sampling method.start_time- Sometimes a simulation shouldn't start at zero.likelihood_cnt- The number of likelihoods to compute, corresponds to number of distributions inenable!calls. This turns onpath_likelihood.support_delayed- Iftrue, the internal sampler key type becomesTuple{K,Symbol}to distinguish regular, initiation, and completion phases.
CompetingClocks.add_group! — Function
The selector defines the group of clocks that go to this sampler using an inclusion rule, so it's a function from a clock key and distribution to a Bool.
CompetingClocks.available_samplers — Function
available_samplers()Returns a list of docstrings for all available samplers.
Distributions
CompetingClocks.Never — Type
Never{T}()This distribution represents a point mass at infinity.
Choosing Sampler Methods
CompetingClocks.NextReactionMethod — Type
NextReactionMethod()Uses Anderson's Modified Next Reaction method for distributions in an Exponential class (Exponential, Weibull, Erlang) and the Next Reaction method for other distributions. Yes, these two methods are mixed into one sampler because Julia's holy traits pattern makes it efficient to use the fastest sampler on a distribution-by-distribution basis. Because it reuses draws, this is the best choice if you want to do variance reduction with Common Random Numbers.
See CombinedNextReaction.
CompetingClocks.DirectMethod — Type
DirectMethod()
DirectMethod(memory::Symbol, search::Symbol)
DirectMethod(:keep, :array)
DirectMethod(:keep, :tree)
DirectMethod(:remove, :array)
DirectMethod(:remove, :tree)Use this to specify any Direct method for Exponential distributions. Defaults to memory=:remove so it limits memory growth over time but memory=:keep will be faster if the space of clock keys is limited. Defaults to search=:tree for best performance for many enabled clocks but search=:array is faster for small numbers of clocks. The different kinds of methods, like "Optimized Direct Methods" amount to using different computer science techniques for scanning sums of hazard rates, and that's what the search algorithm lets you choose.
See DirectCall.
CompetingClocks.FirstReactionMethod — Type
FirstReactionMethod()The classic sampler that draws every clock at every time step. Very fast for very small numbers of enabled clocks and returns a new next() every time it is called which helps when resampling paths. Other samplers return the same value every time you call next() unless you jitter! them, which is expensive.
See FirstReaction.
CompetingClocks.FirstToFireMethod — Type
FirstToFireMethod()The simplest and fastest sampler. When you enable!() a clock, this draws the firing time and saves it in a queue.
See FirstToFire.
CompetingClocks.PartialPropensityMethod — Type
PartialPropensityMethod()Exact, continuous-time sampler using composition-rejection over groups. Only for exponential distributions. This variant of partial-propensity composition-rejection implements the sampler but not the reaction network. The reaction-network can be implemented outside of the sampler.
See PSSACR.
CompetingClocks.PetriMethod — Type
PetriMethod()
PetriMethod(dt)Samples by picking at random ignoring distributions. Good for testing rare cases in simulations. Increments time dt=1.0 by default. It's called "Petri" because a Petri net model always chooses the next event at random.
See Petri.
CompetingClocks.RejectionMethod — Type
RejectionMethod(bound_factor=1.05)A rejection-based algorithm. Only for exponential distributions, this may be the fastest for large simulations. The bound_factor>= 1.0 controls the default upper bounds. Set to 1.0 for no rejections, which reduces this to the direct method.
See RSSA.
Using a Context
CompetingClocks.enable! — Function
enable!(sampler, clock, distribution, enablingtime, currenttime, RNG)Tell the sampler to start a clock.
sampler::SSA{KeyType,TimeType}- The sampler to tell.clock::KeyType- The ID of the clock. Can be a string, integer, tuple, etc.distribution::Distributions.UnivariateDistributionenablingtime::TimeType- The zero time for the clock's distribution, in absolute time. Usually equal towhen.when::TimeType- The current time of the simulation.rng::AbstractRNG- A random number generator.
These times are absolute since the start of the simulation. The current time should be when. If you want to shift the distribution so that this event cannot happen for a little while then choose enablingtime > when. If you want to modify the distribution by shifting it left, then choose enablingtime < when. Usually, enablingtime == when. The truncated() function from Distributions.jl can make the left-shift conditioning explicit, but pair it with the shifted enabling time — enable!(sampler, clock, truncated(dist; lower=age), when - age, when, rng) — because a truncated distribution with enablingtime == when measures the truncated sample from now, pushing every firing at least age into the future.
enable!(dc::DirectCall, clock::T, distribution::Exponential, when, rng)Tell the DirectCall sampler to enable this clock. The clock argument is an identifier for the clock. The distribution is a univariate distribution in time. In Julia, distributions are always relative to time t=0, but ours start at some absolute enabling time, $t_e$, so we provide that here. The when argument is the time at which this clock is enabled, which may be later than when it was first enabled. The rng is a random number generator.
If a particular clock had one rate before an event and it has another rate after the event, call enable! to update the rate.
enable!(ctx, clock, dist, relative_te)Enable a regular clock. In a delayed-support context the user key clock is mapped to the internal key (clock, :regular).
enable!(ctx, clock, dist)Enable with no shift in enabling time.
enable!(ctx, clock, dist::Vector, relative_te)Vectorized enable (importance sampling). The likelihood watcher receives the full vector of distributions while the sampler and other watchers receive the selected dist[sample_idx].
enable!(ctx, clock, dist::Vector)Vectorized enable with no shift in enabling time.
enable!(ctx, clock, delayed::Delayed, relative_te)Enable a delayed reaction. This:
- Stores the duration distribution
delayed.durationinctx.delayed.durations[clock]. - Enables an initiation event with internal key
(clock, :initiate)and distributiondelayed.initiation.
The relative_te shift applies only to the initiation, not the completion.
enable!(ctx, clock, p::Pair, relative_te)Enable a delayed reaction from a pair of distributions. Writing d1 => d2 builds an ordinary Pair, which this method converts to a Delayed and forwards to the Delayed method above. This is how the user-facing syntax enable!(ctx, clock, Exponential(1.0) => Gamma(2.0)) is supported without any type piracy on Base.:(=>).
enable!(ctx, clock, p::Pair, relative_te)A Pair of distributions specifies a delayed reaction, which this context was not built to support. Guard method so the mistake fails at the API boundary with a pointer to the fix, instead of deep inside a sampler.
CompetingClocks.disable! — Function
disable!(sampler, clock, when)Tell the sampler to forget a clock. We include the current simulation time because some Next Reaction methods use this to optimize sampling.
disable!(dc::DirectCall, clock::T, when)Tell the DirectCall sampler to disable this clock. The clock argument is an identifier for the clock. The when argument is the time at which this clock is enabled.
disable!(ctx, clock)Non-delayed context: disable a single clock.
disable!(ctx, clock)Delayed context: disable all phases associated with clock and clear any stored duration distribution.
CompetingClocks.next — Function
next(sampler, when, rng)Ask the sampler for what happens next, in the form of (when, which)::Tuple{TimeType,KeyType}. rng is a random number generator.
The returned (when, which) is a reservation, not a commitment. It is valid only until the next enable!, disable!, or fire! call changes the sampler's state. Calling next twice without an intervening state change returns valid reservations, but they are not guaranteed to be identical across samplers: FirstReaction redraws on each call, while CombinedNextReaction returns the same cached reservation. Act on the most recent call's result. The two supported responses are to fire it—call fire! with the returned clock and time—or to decline it and stop the simulation (the fixed-horizon pattern). There is deliberately no peek.
The when argument must not decrease from one call to the next, and must never advance past a pending firing time without that event being fired. The time of the most recently fired event is always a safe choice. Within this invariant, re-querying next at a later when is legal — this is how MultiSampler composes sub-samplers: the global minimum event time cannot exceed any sub-sampler's pending minimum, so losing sub-samplers are re-queried in-contract at the advanced global time. Outside the invariant, samplers legitimately disagree: FirstReaction re-conditions every clock on survival to when, while CombinedNextReaction returns putative times cached at enabling. Within it they agree, because every surviving clock's putative time is at least when.
next(dc::DirectCall, when::TimeType, rng::AbstractRNG)Ask the sampler what clock will be the next to fire and at what time. This does not change the sampler. You can call this multiple times and get multiple answers. Each answer is a tuple of (when, which clock). If there is no clock to fire, then the response will be (Inf, nothing). That's a good sign the simulation is done.
next(multiple_direct, when, rng)Selects the next transition to fire and when it fires.
There are two main algorithms for this selection. This implementation handles the case when there are a lot of clocks or when some clocks have much smaller hazards. It first draws a random number to choose which subset of hazards will be used, and then it asks that subset to draw a random number to choose which particular hazard is used. When there are many hazards, it is possible that a random number generator will never choose a particular value because there is no guarantee that a random number generator covers every combination of bits. Using more draws decreases the likelihood of this problem.
For the first reaction sampler, you can call next() multiple times and get different, valid, answers. For a CombinedNextReaction sampler, next() is non-mutating: it reads the minimum entry of the firing queue and returns it without changing any internal state and without consuming any random numbers. Repeated calls to next(), with no intervening enable!, disable!, or fire!, return the same cached reservation. Committing to a firing is done separately by calling fire!.
next(ctx::SamplingContext)Return (when, which) for the next event from the underlying sampler, where which::K is the user clock key.
The returned (when, which) is a reservation, not a commitment. It is valid only until the next enable!, disable!, or fire! call changes the sampler's state. Calling next twice without an intervening state change returns valid reservations, but they are not guaranteed to be identical across samplers: FirstReaction redraws on each call, while CombinedNextReaction returns the same cached reservation. Act on the most recent call's result. The two supported responses are to fire it—call fire! with the returned clock and time—or to decline it and stop the simulation (the fixed-horizon pattern). There is deliberately no peek.
The underlying sampler requires a time argument that never decreases and never advances past a pending event without firing it. The context satisfies this automatically: it passes its own current time, which fire! keeps equal to the last fired event's time.
On a delayed context this method errors; use next_delayed, which returns (when, clock, phase).
CompetingClocks.next_delayed — Function
next_delayed(ctx)For delayed-support contexts, return (when, which, phase) where which::K is the user key and phase::Symbol is :regular, :initiate, or :complete.
CompetingClocks.fire! — Function
fire!(sampler, clock, when)Tell the sampler that clock fired at time when.
Firing and disabling are different operations on a clock's draw. Disabling censors the draw: the simulation learns only that the clock had not yet fired, so a sampler may retain the draw's remaining randomness and reuse it if the clock is enabled again. Firing realizes the draw: the firing time is observed exactly, no residual randomness remains, and a re-enabled clock must draw fresh.
The two operations act identically on stored state whenever a sampler retains no residual draw randomness across state changes. That holds for every sampler in this package except CombinedNextReaction, whose per-clock survival is retained and reused across enabling episodes (the Anderson/Gibson-Bruck property). This fallback therefore forwards to disable!. Override fire! for any sampler that retains residual draw randomness, so that firing consumes the draw completely.
fire!(nr::CombinedNextReaction, clock, when)Commit to firing clock at time when. This removes the clock from the firing queue and consumes its draw completely by setting its stored survival to the zero of its sampling space (0.0 for linear space, -Inf for log space). As a result, re-enabling this clock later takes the fresh-redraw branch of enable!, which is the correct behavior for a draw that has been fully consumed by firing.
This differs from disable!, which preserves the clock's remaining survival so that a later re-enable can reuse the draw (the Anderson/Gibson-Bruck draw-reuse property).
fire!(ctx, clock, when)Non-delayed context: fire a regular event and advance time.
fire!(ctx, clock, when)Delayed context: treat this as a regular (non-delayed) event with phase :regular.
fire!(ctx, clock, phase, when)Delayed context: fire an event with phase information.
phase == :regular: regular eventphase == :initiate: initiation of a delayed reaction; schedules completionphase == :complete: completion of a delayed reaction; clears delayed state
CompetingClocks.reset! — Function
reset!(sampler)After a sampler is used for a simulation run, it has internal state. This function resets that internal state to the initial value in preparation for another sample run.
Doesn't reset the stored clocks, does reset miss count.
reset!(ds::DelayedState)Clear all stored duration distributions.
reset!(sampling)Clear all enabled clocks (and delayed state if present) and reset time to the fixed start time.
CompetingClocks.copy_clocks! — Function
copy_clocks!(destination_sampler, source_sampler)This copies the state of the source sampler to the destination sampler, replacing the current state of the destination sampler. This is useful for splitting techniques where you make copies of a simulation and restart it with different random number generators.
copy_clocks!(dst, src)Copy enabled clocks and delayed state from src into dst, jittering the destination sampler so it will generate different next() samples.
Base.Libc.time — Method
time(sampling)Current simulation time.
CompetingClocks.sample_from_distribution! — Function
sample_from_distribution!(sampling, index)Choose which element of a vector-of-distributions to sample from when using path likelihoods (importance sampling).
CompetingClocks.clone — Function
clone(sampler)Given an existing sampler, make a copy that has the same type and same constructor options but has no data in it. Use this to initialize an array of samplers.
clone(ds::DelayedState)Deep copy of a DelayedState.
clone(sampling, rng)Clone a SamplingContext as though constructed again, using a new RNG. All subcomponents (sampler, watchers, CRN, delayed state) are cloned.
CompetingClocks.enabled — Function
enabled(sampler)Returns a read-only set of currently-enabled clocks.
enabled(sampling)Return the set of enabled clock keys. In a delayed context each key is the public event identity (clock, phase), with phase one of :regular, :initiate, or :complete—the same identity that fire! takes and next_delayed returns.
CompetingClocks.enabled_history — Function
enabled_history(ctx::SamplingContext)Returns a Vector{EnablingEntry{K,T}} that has every time a clock was enabled.
CompetingClocks.disabled_history — Function
disabled_history(ctx::SamplingContext)Returns a Vector{DisablingEntry{K,T}} that has every time a clock was disabled.
CompetingClocks.EnablingEntry — Type
EnablingEntry{K,T}(clock::K, distribution, te::T, when::T)Records that clock is enabled at time when with an enabling time te (in absolute time) that sets the zero of the distribution.
Fields
clock::K: The key for the event/clock/transition.distribution::Distributions.UnivariateDistribution, a distribution of clock firing times.te::T- An absolute time to use as the zero-time for the distribution. Usually the same aswhen.when::T- The time this clock was enabled.
CompetingClocks.DisablingEntry — Type
DisablingEntry{K,T}(clock::K, when::T)Records that clock is disabled at time when.
Fields
clock::K: The key for the event/clock/transition.when::T- The time this clock was enabled.
Base.length — Method
Base.length(sampling)Total number of enabled clocks.
CompetingClocks.isenabled — Function
isenabled(sampling, clock)Whether this user clock key is enabled. In delayed contexts this returns true if any phase (:regular, :initiate, :complete) is enabled.
Base.keytype — Method
keytype(sampler)Return the type of clock keys.
keytype(sampling)User clock key type K.
CompetingClocks.timetype — Function
timetype(sampler)Return the type of clock times.
timetype(sampling)Time type T, usually Float64.
CompetingClocks.pathloglikelihood — Function
pathloglikelihood(ctx, endtime)Path log-likelihood up to endtime, including the probability that no event fires after the last event before endtime.