Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CoevolveSynced and Synapse benchmarks WIP #597

Merged
merged 17 commits into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmarks/Jumps/Diffusion_CTRW.jmd
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ N = 256
h = 1 / N
u0 = 10 * ones(Int64, N)
tf = .01
methods = (Direct(), FRM(), SortingDirect(), NRM(), DirectCR(), RSSA(), RSSACR(), Coevolve())
methods = (Direct(), FRM(), SortingDirect(), NRM(), DirectCR(), RSSA(), RSSACR(), Coevolve(), CoevolveSynced())
shortlabels = [string(leg)[15:end-2] for leg in methods]
jprob = JumpProblemLibrary.prob_jump_diffnetwork
rn = jprob.network(N)
Expand Down
641 changes: 470 additions & 171 deletions benchmarks/Jumps/Manifest.toml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions benchmarks/Jumps/Mendes_multistate_example.jmd
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ reactions(rn)

# Plot solutions by each method
```julia
methods = (Direct(), FRM(), SortingDirect(), NRM(), DirectCR(), RSSA(), RSSACR(), Coevolve())
methods = (Direct(), FRM(), SortingDirect(), NRM(), DirectCR(), RSSA(), RSSACR(), Coevolve(), CoevolveSynced())
shortlabels = [string(leg)[15:end-2] for leg in methods]
tf = 10.0 * jprob.tstop
prob = DiscreteProblem(rn, jprob.u0, (0.0, tf), jprob.rates)
Expand Down Expand Up @@ -55,7 +55,7 @@ for (i,method) in enumerate(methods)
end
end
push!(p, plot((1:4)', framestyle = :none, legend=:inside, labels=varlegs))
plot(p..., layout=(5,2), format=fmt)
plot(p..., layout=(6,2), format=fmt)
```

# Benchmarking performance of the methods
Expand Down
64 changes: 35 additions & 29 deletions benchmarks/Jumps/MultivariateHawkes.jmd
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,27 @@ width_px, height_px = default(:size);

# Model and example solutions

Let a graph with $V$ nodes, then the multivariate Hawkes process is characterized by $V$ point processes such that the conditional intensity rate of node $i$ connected to a set of nodes $E_i$ in the graph is given by:
$$
Let a graph with ``V`` nodes, then the multivariate Hawkes process is characterized by ``V`` point processes such that the conditional intensity rate of node ``i`` connected to a set of nodes ``E_i`` in the graph is given by:
```math
\lambda_i^\ast (t) = \lambda + \sum_{j \in E_i} \sum_{t_{n_j} < t} \alpha \exp \left[-\beta (t - t_{n_j}) \right]
$$
This process is known as self-exciting, because the occurence of an event $ j $ at $t_{n_j}$ will increase the conditional intensity of all the processes connected to it by $\alpha$. The excited intensity then decreases at a rate proportional to $\beta$.
```
This process is known as self-exciting, because the occurence of an event ``j `` at ``t_{n_j}`` will increase the conditional intensity of all the processes connected to it by ``\alpha``. The excited intensity then decreases at a rate proportional to ``\beta``.

The conditional intensity of this process has a recursive formulation which can significantly speed the simulation. The recursive formulation for the univariate case is derived in Laub et al. [2]. We derive the compound case here. Let $t_{N_i} = \max \{ t_{n_j} < t \mid j \in E_i \}$ and
$$
The conditional intensity of this process has a recursive formulation which can significantly speed the simulation. The recursive formulation for the univariate case is derived in Laub et al. [2]. We derive the compound case here. Let ``t_{N_i} = \max \{ t_{n_j} < t \mid j \in E_i \}`` and
```math
\begin{split}
\phi_i^\ast (t)
&= \sum_{j \in E_i} \sum_{t_{n_j} < t} \alpha \exp \left[-\beta (t - t_{N_i} + t_{N_i} - t_{n_j}) \right] \\
&= \exp \left[ -\beta (t - t_{N_i}) \right] \sum_{j \in E_i} \sum_{t_{n_j} \leq t_{N_i}} \alpha \exp \left[-\beta (t_{N_i} - t_{n_j}) \right] \\
&= \exp \left[ -\beta (t - t_{N_i}) \right] \left( \alpha + \phi^\ast (t_{N_i}) \right)
\end{split}
$$
Then the conditional intensity can be re-written in terms of $\phi_i^\ast (t_{N_i})$
$$
```
Then the conditional intensity can be re-written in terms of ``\phi_i^\ast (t_{N_i})``
```math
\lambda_i^\ast (t) = \lambda + \phi_i^\ast (t) = \lambda + \exp \left[ -\beta (t - t_{N_i}) \right] \left( \alpha + \phi_i^\ast (t_{N_i}) \right)
$$
```

In Julia, we define a factory for the conditional intensity $\lambda_i$ which returns the brute-force or recursive versions of the intensity given node $i$ and network $g$.
In Julia, we define a factory for the conditional intensity ``\lambda_i`` which returns the brute-force or recursive versions of the intensity given node ``i`` and network ``g``.

```julia
function hawkes_rate(i::Int, g; use_recursion = false)
Expand Down Expand Up @@ -130,12 +130,12 @@ function hawkes_problem(
end
```

The `Coevolve()` aggregator knows how to handle the `SSAStepper`, so it accepts a `DiscreteProblem`.
The `Coevolve()` and `CoevolveSynced()` aggregator knows how to handle the `SSAStepper`, so it accepts a `DiscreteProblem`.

```julia
function hawkes_problem(
p,
agg::Coevolve;
agg::Union{Coevolve, CoevolveSynced};
u = [0.0],
tspan = (0.0, 50.0),
save_positions = (false, true),
Expand All @@ -150,15 +150,15 @@ function hawkes_problem(
end
```

Lets solve the problems defined so far. We sample a random graph sampled from the Erdős-Rényi model. This model assumes that the probability of an edge between two nodes is independent of other edges, which we fix at $0.2$. For illustration purposes, we fix $V = 10$.
Lets solve the problems defined so far. We sample a random graph sampled from the Erdős-Rényi model. This model assumes that the probability of an edge between two nodes is independent of other edges, which we fix at ``0.2``. For illustration purposes, we fix ``V = 10``.

```julia
V = 10
G = erdos_renyi(V, 0.2, seed = 9103)
g = [neighbors(G, i) for i = 1:nv(G)]
```

We fix the Hawkes parameters at $\lambda = 0.5 , \alpha = 0.1 , \beta = 2.0$ which ensures the process does not explode.
We fix the Hawkes parameters at ``\lambda = 0.5 , \alpha = 0.1 , \beta = 2.0`` which ensures the process does not explode.

```julia
tspan = (0.0, 50.0)
Expand All @@ -173,12 +173,15 @@ Now, we instantiate the problems, find their solutions and plot the results.
algorithms = Tuple{Any, Any, Bool, String}[
(Direct(), Tsit5(), false, "Direct (brute-force)"),
(Coevolve(), SSAStepper(), false, "Coevolve (brute-force)"),
(CoevolveSynced(), SSAStepper(), false, "CoevolveSynced (brute-force)"),
(Direct(), Tsit5(), true, "Direct (recursive)"),
(Coevolve(), SSAStepper(), true, "Coevolve (recursive)"),
(CoevolveSynced(), SSAStepper(), true, "CoevolveSynced (recursive)"),
]

let fig = []
for (i, (algo, stepper, use_recursion, label)) in enumerate(algorithms)
@info label
if use_recursion
h = zeros(eltype(tspan), nv(G))
urate = zeros(eltype(tspan), nv(G))
Expand All @@ -193,7 +196,7 @@ let fig = []
sol = solve(jump_prob, stepper)
push!(fig, plot(sol.t, sol[1:V, :]', title=label, legend=false, format=fmt))
end
fig = plot(fig..., layout=(2,2), format=fmt)
fig = plot(fig..., layout=(3,2), format=fmt, size=(width_px, 3*height_px/2))
end
```

Expand All @@ -202,13 +205,13 @@ end
We benchmark `JumpProcesses.jl` against `PiecewiseDeterministicMarkovProcesses.jl` and Python `Tick` library.

In order to compare with the `PiecewiseDeterministicMarkovProcesses.jl`, we need to reformulate our jump problem as a Piecewise Deterministic Markov Process (PDMP). In this setting, we need to describe how the conditional intensity changes with time which we derive below:
$$
```math
\begin{split}
\frac{d \lambda_i^\ast (t)}{d t}
&= -\beta \sum_{j \in E_i} \sum_{t_{n_j} < t} \alpha \exp \left[-\beta (t - t_{n_j}) \right] \\
&= -\beta \left( \lambda_i^\ast (t) - \lambda \right)
\end{split}
$$
```

```julia
function hawkes_drate(dxc, xc, xd, p, t)
Expand Down Expand Up @@ -270,8 +273,8 @@ push!(algorithms, (PDMPCHV(), CHV(Tsit5()), true, "PDMPCHV"));
The Python `Tick` library can be accessed with the `PyCall.jl`. We install the required Python dependencies with `Conda.jl` and define a factory for the Multivariate Hawkes `PyTick` problem.

```julia
const BENCHMARK_PYTHON = false
const REBUILD_PYCALL = false
const BENCHMARK_PYTHON::Bool = tryparse(Bool, get(ENV, "SCIMLBENCHMARK_PYTHON", "false"))
const REBUILD_PYCALL::Bool = tryparse(Bool, get(ENV, "SCIMLBENCHMARK_REBUILD_PYCALL", "false"))

struct PyTick end

Expand Down Expand Up @@ -323,7 +326,8 @@ Now, we instantiate the problems, find their solutions and plot the results.

```julia
let fig = []
for (i, (algo, stepper, use_recursion, label)) in enumerate(algorithms[5:end])
for (i, (algo, stepper, use_recursion, label)) in enumerate(algorithms[7:end])
@info label
if typeof(algo) <: PyTick
_p = (p[1], p[2], p[3])
jump_prob = hawkes_problem(_p, algo; u, tspan, g, use_recursion)
Expand All @@ -346,10 +350,10 @@ end

We check that the algorithms produce correct simulation by inspecting their QQ-plots. Point process theory says that transforming the simulated points using the compensator should produce points whose inter-arrival duration is distributed according to the exponential distribution (see Section 7.4 [1]).

The compensator of any point process is the integral of the conditional intensity $\Lambda_i^\ast(t) = \int_0^t \lambda_i^\ast(u) du$. The compensator for the Multivariate Hawkes process is defined below.
$$
The compensator of any point process is the integral of the conditional intensity ``\Lambda_i^\ast(t) = \int_0^t \lambda_i^\ast(u) du``. The compensator for the Multivariate Hawkes process is defined below.
```math
\Lambda_i^\ast(t) = \lambda t + \frac{\alpha}{\beta} \sum_{j \in E_i} \sum_{t_{n_j} < t} ( 1 - \exp \left[-\beta (t - t_{n_j}) \right])
$$
```

```julia
function hawkes_Λ(i::Int, g, p)
Expand Down Expand Up @@ -535,11 +539,12 @@ We can construct QQ-plots with a Plot recipe as following.
end
```

Now, we simulate all of the algorithms we defined in the previous Section $250$ times to produce their QQ-plots.
Now, we simulate all of the algorithms we defined in the previous Section ``250`` times to produce their QQ-plots.

```julia
let fig = []
for (i, (algo, stepper, use_recursion, label)) in enumerate(algorithms)
@info label
if typeof(algo) <: PyTick
_p = (p[1], p[2], p[3])
elseif typeof(algo) <: PDMPCHV
Expand Down Expand Up @@ -579,17 +584,17 @@ let fig = []
qqs = qq(runs, Λ)
push!(fig, qqplot(qqs..., legend = false, aspect_ratio = :equal, title=label, fmt=fmt))
end
fig = plot(fig..., layout = (3, 2), fmt=fmt, size=(width_px, 3*height_px/2))
fig = plot(fig..., layout = (4, 2), fmt=fmt, size=(width_px, 4*height_px/2))
end
```

# Benchmarking performance

In this Section we benchmark all the algorithms introduced in the first Section.

We generate networks in the range from $1$ to $95$ nodes and simulate the Multivariate Hawkes process $25$ units of time.
We generate networks in the range from ``1`` to ``95`` nodes and simulate the Multivariate Hawkes process ``25`` units of time.

and simulate models in the range from $1$ to $95$ nodes for $25$ units of time. We fix the Hawkes parameters at $\lambda = 0.5 , \alpha = 0.1 , \beta = 5.0$ which ensures the process does not explode. We simulate $50$ trajectories with a limit of ten seconds to complete execution for each configuration.
and simulate models in the range from ``1`` to ``95`` nodes for ``25`` units of time. We fix the Hawkes parameters at ``\lambda = 0.5 , \alpha = 0.1 , \beta = 5.0`` which ensures the process does not explode. We simulate ``50`` trajectories with a limit of ten seconds to complete execution for each configuration.

```julia
tspan = (0.0, 25.0)
Expand All @@ -600,6 +605,7 @@ Gs = [erdos_renyi(V, 0.2, seed = 6221) for V in Vs]
bs = Vector{Vector{BenchmarkTools.Trial}}()

for (algo, stepper, use_recursion, label) in algorithms
@info label
global _stepper = stepper
push!(bs, Vector{BenchmarkTools.Trial}())
_bs = bs[end]
Expand Down Expand Up @@ -671,7 +677,7 @@ for (algo, stepper, use_recursion, label) in algorithms
median_time =
length(trial) > 0 ? "$(BenchmarkTools.prettytime(median(trial.times)))" :
"nan"
println("algo=$(label), V = $(nv(G)), length = $(length(trial.times)), median time = $median_time")
println("algo=``(label), V = ``(nv(G)), length = ``(length(trial.times)), median time = ``median_time")
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/Jumps/NegFeedback_GeneExpr.jmd
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ reactions(rn)
# Plot solutions by each method

```julia
methods = (Direct(), FRM(), SortingDirect(), NRM(), DirectCR(), RSSA(), RSSACR(), Coevolve())
methods = (Direct(), FRM(), SortingDirect(), NRM(), DirectCR(), RSSA(), RSSACR(), Coevolve(), CoevolveSynced())
shortlabels = [string(leg)[15:end-2] for leg in methods]
prob = prob_jump_dnarepressor.discrete_prob
tf = prob_jump_dnarepressor.tstop
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/Jumps/NegFeedback_GeneExpr_Marchetti.jmd
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ plot(solution, format=fmt)

```julia
tf = 4000.
methods = (Direct(), FRM(), SortingDirect(), NRM(), DirectCR(), RSSA(), RSSACR(), Coevolve())
methods = (Direct(), FRM(), SortingDirect(), NRM(), DirectCR(), RSSA(), RSSACR(), Coevolve(), CoevolveSynced())
shortlabels = [string(leg)[15:end-2] for leg in methods]
prob = prob = DiscreteProblem(rn, u0, (0.0, tf), rnpar)
ploth = plot(reuse=false)
Expand Down
1 change: 1 addition & 0 deletions benchmarks/Jumps/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SciMLBenchmarks = "31c91b34-3c75-11e9-0341-95557aab0344"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Synapse = "7cc9ea39-daa9-4846-be95-d8a08c9e3c85"

[compat]
BenchmarkTools = "1.3.1"
Expand Down
Loading