Skip to content

Commit 4b8b614

Browse files
committed
modified the debugging and context to make documentation more complete so it's a good outcome
1 parent 100b96d commit 4b8b614

File tree

15 files changed

+192
-78
lines changed

15 files changed

+192
-78
lines changed

docs/make.jl

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,14 @@ makedocs(;
111111
"commonrandom.md",
112112
"importance_skills.md",
113113
"hamiltonianmontecarlo.md",
114-
"gen/overview.md",
115-
"gen/distribution.md",
116-
"gen/generative_function.md",
117-
"gen/observation_likelihood.md",
118-
"gen/importance_mixture.md",
119-
"gen/hmc_paths.md",
114+
"Gen.jl Integration" => [
115+
"gen/overview.md",
116+
"gen/distribution.md",
117+
"gen/generative_function.md",
118+
"gen/observation_likelihood.md",
119+
"gen/importance_mixture.md",
120+
"gen/hmc_paths.md",
121+
],
120122
"gen/turing_dist.md",
121123
"gen/survival_snippet.md",
122124
],

docs/src/choosing_sampler.md

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,6 @@
22

33
How to make a sampler for events in CompetingClocks.jl.
44

5-
## Quick Reference
6-
7-
| Name | Distribution support | Best use cases |
8-
|----------------|----------------------|--------------------------------|
9-
| First-to-fire | All | Fastest for simple simulation |
10-
| Next Reaction | All | Best for common random numbers |
11-
| First Reaction | All | Fastest for very few events |
12-
| Direct | Exponential-only | Quicker likelihood calculation |
13-
| Petri | All | Debug rare errors |
14-
155
## High-level Interface
166

177
Every event in a simulation has an identifying key which can be any immutable Julia
@@ -26,6 +16,7 @@ The sampler takes a `Random.AbstractRNG` as its second argument.
2616
Specify features for the sampler with keyword arguments.
2717
These features determine the type of the sampler.
2818

19+
* `method=nothing`---Setting this chooses a single sampling algorithm for all clocks.
2920
* `step_likelihood=false`---Setting this to `true` let's you calculate the
3021
the likelihood of the next event and time before firing it.
3122
* `path_likelihood=false`---Set to true in order to calculate likelihood of a
@@ -41,9 +32,32 @@ These features determine the type of the sampler.
4132
* `recording=false`---This will create a vector of every enable and disable
4233
event for test and debug.
4334

35+
If you want to try a particular sampler instead of having the `SamplingContext`
36+
pick one for you, use the `method` parameter.
37+
38+
```julia
39+
sampler = SamplingContext(KeyType, Float64, rng; method=DirectMethod(:keep, :tree))
40+
```
41+
42+
## Quick Reference
43+
44+
These are all of the samplers.
45+
46+
| Name | Distribution support | Best use cases |
47+
|----------------|----------------------|--------------------------------|
48+
| [`FirstToFireMethod`](@ref) | All | Fastest for simple simulation |
49+
| [`NextReactionMethod`](@ref) | All | Best for common random numbers |
50+
| [`FirstReactionMethod`](@ref) | All | Fastest for very few events |
51+
| [`DirectMethod`](@ref) | Exponential-only | Quicker likelihood calculation |
52+
| [`RejectionMethod`](@ref) | Exponential-only | RSSA for large systems |
53+
| [`PartialPropensityMethod`](@ref) | Exponential-only | For reaction networks. |
54+
| [`PetriMethod`](@ref) | All | Debug rare errors |
55+
56+
4457
## Hierarchical Samplers
4558

46-
For spatial simulations or for chemical simulations that aren't well-mixed,
59+
For spatial simulations, chemical simulations that aren't well-mixed, or
60+
simulations with some fast and many slow clocks,
4761
it can help to split a sampler into multiple buckets so that each bucket can
4862
update its own list of what fires next. This package does this by adding
4963
sampler groups.
@@ -55,7 +69,11 @@ A sampler group is
5569
whether a given event belongs to this sampler.
5670
* An optional `method` to say what kind of sampler this group should use.
5771

72+
This example sends events with keys like `(:infect, 3)` tto the sampler called
73+
`:forthright` and sends keys like `(:recover, 7)` to the sampler called
74+
`:sparky`.
5875
```julia
76+
const KeyType = Tuple{Symbol,Integer}
5977
builder = SamplerBuilder(KeyType, Float64)
6078
add_group!(builder, :sparky => (x,d) -> x[1] == :recover, method=NextReaction())
6179
add_group!(builder, :forthright=>(x,d) -> x[1] == :infect)

docs/src/contextinterface.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ CompetingClocks.clone(::SamplingContext{K,T,Sampler,RNG,Like,CRN,Dbg}, rng::RNG)
4141
CompetingClocks.freeze_crn!(::SamplingContext)
4242
CompetingClocks.reset_crn!(::SamplingContext)
4343
CompetingClocks.enabled(::SamplingContext)
44+
CompetingClocks.enabled_history(::SamplingContext)
45+
CompetingClocks.disabled_history(::SamplingContext)
46+
CompetingClocks.EnablingEntry
47+
CompetingClocks.DisablingEntry
4448
Base.length(::SamplingContext)
4549
CompetingClocks.isenabled(::SamplingContext{K}, ::K) where {K}
4650
Base.keytype(::SamplingContext{K}) where {K}

docs/src/debugging.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,14 @@ The `DebugWatcher` records every time an event is enabled or disabled.
2727
You wouldn't normally want to use all of this time and memory (and memory churn),
2828
but it can be helpful to see the trace of all events enabled and disabled,
2929
in addition to those that fired.
30+
31+
```julia
32+
for enabling_event in enabled_history(sampler)
33+
println("$(enabling_event.clock), $(enabling_event.when)")
34+
end
35+
for disabling_event in disabled_history(sampler)
36+
println("$(disabling_event.clock), $(disabling_event.when)")
37+
end
38+
```
39+
40+
Look at [`CompetingClocks.enabled_history`](@ref) and [`CompetingClocks.disabled_history`](@ref).

docs/src/index.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,21 @@ or to calculate the likelihood of a sample path for statistical estimation.
3737

3838
## Usage
3939

40-
The library provides you with samplers. Each sampler has the same interface. Here, a distribution is a [Distributions.ContinuousUnivariateDistribution](https://juliastats.org/Distributions.jl/stable/univariate/#Continuous-Distributions), `RNG` is a [random number generator](https://docs.julialang.org/en/v1/stdlib/Random/#Generators-(creation-and-seeding)), the `key` is some identifier (maybe an integer) for the event, and an enabling time is a zero-time for the given distribution.
40+
The library provides you with samplers. Each sampler has the same interface. Here, a distribution is a [Distributions.ContinuousUnivariateDistribution](https://juliastats.org/Distributions.jl/stable/univariate/#Continuous-Distributions), `RNG` is a [random number generator](https://docs.julialang.org/en/v1/stdlib/Random/#Generators-(creation-and-seeding)), the `key` is some identifier (maybe an integer, tuple, or pair) for the event, and an enabling time is a zero-time for the given distribution.
4141

4242
* `enable!(sampler, key, distribution, enabling_time)` - to start the clock on when an event will fire next.
4343

4444
* `disable!(sampler, key)` - to turn off an event so it can't fire.
4545

46-
* `next(sampler)` - to ask this library who could fire next.
46+
* `next(sampler)` - to ask this library who could fire next, and it return a (time, key).
4747

4848
* `fire!(sampler, key, time)` - choose which event happens at what time.
4949

50-
Different samplers are specialized for sampling more quickly and accurately for different applications. For instance, some applications have very few events enabled at once, while some have many. Some applications use only exponentially-distributed events, while some have a mix of distribution types. Because continuous-time discrete event systems can fire many events, the literature has focused on reducing the number of CPU instructions required to sample each event, and this library reflects that focus.
51-
5250
## When NOT to use Competing Clocks
5351

54-
* **Pure exponential distributions?** JumpProcesses.jl is a complete framework for this.
55-
* **Need ODE coupling?** Again, it's easier to stay within SciML and JumpProcesses.jl.
56-
* **Want high-level frameworks?** Try Agents.jl.
52+
* **Pure exponential distributions and chemical systems?** JumpProcesses.jl is a complete framework for this.
53+
* **Need ODE coupling?** Again, it's easier to stay within SciML and JumpProcesses.jl. CompetingClocks.jl supports ODEs as Dirac distributions.
54+
* **Want high-level frameworks?** Try [Agents.jl](https://juliadynamics.github.io/Agents.jl/stable/) or [ConcurrentSim.jl](https://juliadynamics.github.io/ConcurrentSim.jl/stable/).
5755

5856
CompetingClocks.jl is for:
5957

docs/src/integration-guide.md

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ performant for sampling.
4949
This package doesn't use Tasks or thread guards because it is so common to
5050
run multiple copies of a simulation, each on its own thread. If you run
5151
multiple simulations, it can be more correct to use random number generators
52-
that work in parallel, such as those in the `Random123` package.
52+
that work in parallel, such as those in the `Random123` package or the `jump!()`
53+
method for the `Xoshiro` sampler in the `Random` package.
5354

5455
### State and Dependencies
5556

@@ -58,14 +59,14 @@ events. It can be very helpful for performance if you use information about
5859
relationships among events to limit updates to the samplers. There are a few
5960
kinds of dependency graphs that may help.
6061

61-
* When one event fires, it changes state. Which other events could be, or are,
62-
enabled because of the changed state?
63-
* When one event fires, it changes state. Which other events have transition
64-
rates that depend on that state? The event may remain enabled, but its
62+
* Dependency Graph---When one event fires, it changes state. Which other events could be
63+
enabled or disabled because of the changed state?
64+
* Rate Dependency Graph---When one event fires, it changes state. Which other events have transition
65+
*rates* that depend on that state? The event may remain enabled, but its
6566
distribution of firing times may have a different parameter value.
66-
* When one event fires, you might know directly which other events have
67+
* Event-to-event Graph---When one event fires, you might know directly which other events have
6768
enabling rules or transition rates that depend on that first event, without
68-
needing to ask what state changed.
69+
needing to ask what state changed and what events depend on the state.
6970

7071
How you represent a dependency graph depends on the problem.
7172

@@ -94,16 +95,16 @@ Split the sampler into groups so that each region is faster.
9495
```julia
9596
builder = SamplerBuilder(EventKey, Float64)
9697
for region in spatial_grid
97-
add_group!(builder, regin.name => (key, dist) -> key.location == region.id)
98+
add_group!(builder, region.name => (key, dist) -> key.location == region.id)
9899
end
99100
```
100101

101102
### Chemical Reaction Networks
102103
Map reactions to events.
103104
```julia
104105
function enable_reaction!(sampler, reaction, state, params)
105-
rate = calculate_propensity(reaction, state, params)
106-
enable!(sampler, reaction.id, Exponential(1/rate))
106+
propensity = calculate_propensity(reaction, state, params)
107+
enable!(sampler, reaction.id, Exponential(1/propensity))
107108
end
108109
```
109110

@@ -123,17 +124,22 @@ sampler = SamplingContext(builder, rng)
123124
# After simulation, the log_prob is a Float64.
124125
log_prob = pathloglikelihood(sampler, end_time)
125126
```
127+
The resulting value is the likelihood of a single trajectory of the system.
128+
The `pathloglikelihood` is a regular function and can be differentiated.
126129

127130
## Variance reduction
128131
Construct the sampler with the `common_random` option.
129132
```julia
130133
builder = SamplerBuilder(KeyType, Float64; common_random=true)
131134
sampler = SamplingContext(builder, rng)
132135
```
133-
Run it a bunch of times. Each run will pin more random draws. Then use
136+
Run the simulation a bunch of times, calling `reset!()` between runs to
137+
clear the sampler's memory of the previous run but save the common random
138+
numbers. Each run will pin more random draws. Then use
134139
`freeze_crn!` to stop collecting random draws.
135140
```julia
136141
for i in 1:warm_up
142+
reset!(sampler)
137143
results1 = run_simulation(model, sampler)
138144
end
139145
freeze_crn!(sampler) # Lock random draws
@@ -173,7 +179,7 @@ of each copy by one over the number of copies.
173179

174180
CompetingClocks offers a `split!` function that copies the state to a vector
175181
of clocks and keeps a `split_weight` as a record of how many times splitting
176-
was done. Take a look at the implementation of `split!()`.
182+
was done. Take a look at the implementation of [`CompetingClocks.split!()`](@ref).
177183

178184
Your code might keep the sampler in the main simulation framework, in which case
179185
you would include the sampler in the cloning and copying process.
@@ -187,22 +193,21 @@ sampler = SamplingContext(builder, rng)
187193
```
188194
Then run with logging on.
189195
```julia
190-
with_logger(ConsoleLogger(stdout, Logging.Info)) do
196+
with_logger(ConsoleLogger(stdout, Logging.Debug)) do
191197
observed, importance = run_epochs(100)
192198
end
193199
```
194200

195-
There is also a chaos-monkey option called a `Petri` sampler.
201+
There is also a [chaos-monkey](https://en.wikipedia.org/wiki/Chaos_engineering) option called a `Petri` sampler.
196202
This sampler always advances time by the same time step `dt=1.0` by default,
197203
and it ignores the clock distributions. It chooses any enabled sampler at
198204
each step, with the goal of finding unusual sampling paths. This is surprisingly
199205
effective at finding logic errors in code to change state.
200206

201207
## Performance Considerations
202208

203-
**Type Stability**---The ClockKey is a place where it can be very helpful to
204-
use different types to describe different kinds of events, but it's also a place
205-
where type stability makes the calculations faster.
209+
**Type Stability**---Use a concrete type for the
210+
ClockKey for more speed.
206211

207212
**Sampler Choice**---If there are a lot of clocks enabled at the same time,
208213
the the sampler choice can be important. Experts in traffic simulation use
@@ -212,5 +217,3 @@ samplers about whether clock keys are reused.
212217

213218
**Memory Allocation**---Instead of making a new sampler for each run, use
214219
`reset!(sampler)` between runs to clear out the clocks.
215-
216-
In short, make a sampler choice and benchmark.

docs/src/low_level_interface.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
# Low-level Sampler Interface
33

44
The main interface to the package is the [`SamplingContext`](@ref).
5-
Within that class are the samplers themselves. This describes their interface.
5+
The `SamplingContext` contains sampling algorithms and other helper classes. This describes the interface to underlying sampling algorithms, which differ slightly:
6+
7+
* They don't hold a random number generator internally so it is passed as an argument.
8+
* All input times are absolute, so the enabling time of a distribution is given in absolute time.
69

710
## Common Interface to Low-level Samplers
811

@@ -45,8 +48,8 @@ CompetingClocks.DirectCall{K,T,P}
4548
CompetingClocks.FirstReaction{K,T}
4649
CompetingClocks.FirstToFire{K,T}
4750
CompetingClocks.PSSACR{K,T}
48-
CompetingClocks.Petri{K,T}
4951
CompetingClocks.RSSA{K,T}
52+
CompetingClocks.Petri{K,T}
5053
```
5154

5255
## Hierarchical Sampling

docs/src/quickstart.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# CompetingClocks.jl Quickstart
1+
# Quickstart
22

33
Learn CompetingClocks.jl basics by building a minimal simulation.
44

@@ -20,8 +20,7 @@ population = Set(1:person_cnt)
2020
# Events will be identified by a tuple of type and individual.
2121
const ClockKey = Tuple{Symbol,Int}
2222
# Create a sampler.
23-
builder = SamplerBuilder(ClockKey, Float64)
24-
sampler = SamplingContext(builder, rng)
23+
sampler = SamplingContext(ClockKey, Float64, rng)
2524
enable!(sampler, (:birth, 0), Exponential(inv(length(population) * 2.0)))
2625
for mort in population
2726
enable!(sampler, (:death, mort), Gamma(inv(1.5)))
@@ -51,7 +50,7 @@ end
5150
3. Create a sampler.
5251
4. Enable initial events.
5352
5. Ask what happens next and
54-
- `fire!` that event.
53+
- `fire!` that event, which also disables the event.
5554
- Handle changes to state and enabled events.
5655

5756
You tell it what *could* happen with `enable!()`, ask what happens `next()`,
@@ -62,20 +61,23 @@ So that you can stop a simulation at a fixed time rather than after an event.
6261

6362
Learn More Techniques
6463

65-
- State management: your-first-sim.md - Better state handling.
66-
- Choosing samplers: choosign-samplers.md - When to use which algorithm.
64+
- State management: [Sample Main Loop](@ref "Sample Main Loop") - Better state handling.
65+
- Choosing samplers: [Choosing a Sampler](@ref "Choosing a Sampler") - When to use which algorithm.
6766
- Real examples:
68-
- SIR
69-
- reliability
67+
- [SIR Model](sir.md)
68+
- [Reliability](reliability.md)
69+
- [Birth-Death Process](constant_birth.md)
70+
- [Gene Expression](gene_expression.md)
7071

7172
Advanced Features
7273

73-
- Get likelihoods: likelihood-tutorial
74-
- Variance reduction: variance-reduction.md
75-
- Build a framework: integration-guide.md
74+
- [Get likelihoods](@ref "Importance Sampling for Simulation")
75+
- [Variance reduction](@ref "Common Random Numbers")
76+
- [Build a framework](@ref "Integration Guide")
7677

7778
API Reference
7879

79-
- api.md#enable - Full parameter documentation
80-
- api.md#next - Return value details
81-
- api.md#samplerbuilder - Configuration options
80+
- [Build a Context](@ref "Building a Context")
81+
- [`CompetingClocks.enable!`](@ref) - Parameter documenation.
82+
- [`CompetingClocks.next`](@ref) - Return value details.
83+
- [`CompetingClocks.SamplerBuilder`](@ref) - Configuration options.

src/context.jl

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export SamplingContext, enable!, fire!, isenabled, freeze_crn!
2-
export sample_from_distribution!
2+
export sample_from_distribution!, enabled_history, disabled_history
33

44

55
mutable struct SamplingContext{K,T,Sampler<:SSA{K,T},RNG,Like,CRN,Dbg}
@@ -78,7 +78,8 @@ We keep the internal logic simple. If something is present, call it.
7878
* `step_likelihood` - whether you will call `steploglikelihood` before each `fire!`
7979
* `path_likelihood` - whether you will call `pathloglikelihood`
8080
at the end of a simulation run.
81-
* `debug` - Print log messages at the debug level.
81+
* `debug` - Print log messages at the debug level. Enabling this
82+
stores every enabling and disabling event so don't leave it on.
8283
* `recording` - Store every enable and disable for later examination.
8384
* `common_random` - Use common random numbers during sampling.
8485
* `method` - If you want a single, particular sampler, put its `SamplerSpec` here.
@@ -412,6 +413,36 @@ function enabled(ctx::SamplingContext)
412413
end
413414

414415

416+
"""
417+
enabled_history(ctx::SamplingContext)
418+
419+
Returns a `Vector{EnablingEntry{K,T}}` that has every time a clock was enabled.
420+
421+
See [`CompetingClocks.EnablingEntry`](@ref).
422+
"""
423+
function enabled_history(ctx::SamplingContext)
424+
if !isnothing(ctx.debug)
425+
return enabled_history(ctx.debug)
426+
else
427+
error("In order to get history of enabling create context with `recording=true`")
428+
end
429+
end
430+
431+
"""
432+
disabled_history(ctx::SamplingContext)
433+
434+
Returns a `Vector{DisablingEntry{K,T}}` that has every time a clock was enabled.
435+
436+
See [`CompetingClocks.DisablingEntry`](@ref).
437+
"""
438+
function disabled_history(ctx::SamplingContext)
439+
if !isnothing(ctx.debug)
440+
return disabled_history(ctx.debug)
441+
else
442+
error("In order to get history of disabling create context with `recording=true`")
443+
end
444+
end
445+
415446
"""
416447
length(sampling)
417448

0 commit comments

Comments
 (0)