-
-
Notifications
You must be signed in to change notification settings - Fork 78
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
Added docs and assets for interactive simulation plotting tutorial #1053
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
36d25ff
testing my interactive visualizer docs
jonathanfischer97 bf4150e
added interactive brusselator doc
jonathanfischer97 22f253f
Added mp4 demo
jonathanfischer97 54b81c3
removed initial setup note, changed saveat tipe to note, added Makie …
jonathanfischer97 33b60e6
Update docs/src/model_simulation/examples/interactive_brusselator_sim…
jonathanfischer97 5557e55
removed saveat comment
jonathanfischer97 df78148
Merge branch 'testdocs' of https://github.com/jonathanfischer97/Catal…
jonathanfischer97 1fc79df
changed code blocks to run dynamically
jonathanfischer97 0f19713
removed `display`, think it was messing up the inline plots
jonathanfischer97 a7a3bfa
set GLMakie to not render in window for the docs
jonathanfischer97 9ec3177
hide GLMakie setup code
jonathanfischer97 9010c78
fixed plotting grid typo
jonathanfischer97 66333be
removed old plot asses
jonathanfischer97 5b25e8f
stopped code blocks from executing and returning prior to plotting
jonathanfischer97 c7ce86d
Merge branch 'SciML:master' into testdocs
jonathanfischer97 a86c550
Merge branch 'SciML:master' into testdocs
jonathanfischer97 dbad69b
Merge branch 'SciML:master' into testdocs
jonathanfischer97 8015ec7
added GLMakie dependency to docs env
jonathanfischer97 31eae9f
added xvfb to Documentation workflow to allow headless GLMakie doctes…
jonathanfischer97 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
263 changes: 263 additions & 0 deletions
263
docs/src/model_simulation/examples/interactive_brusselator_simulation.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
# [Interactive Simulation and Plotting](@id interactive_brusselator) | ||
|
||
Catalyst can utilize the [GLMakie.jl](https://github.com/JuliaPlots/GLMakie.jl) package for creating interactive visualizations of your reaction network dynamics. This tutorial provides a step-by-step guide to creating an interactive visualization of the Brusselator model, building upon the basic [Brusselator](@ref basic_CRN_library_brusselator) example. | ||
|
||
|
||
## [Setting up the Brusselator model](@id setup_brusselator) | ||
|
||
Let's again use the oscillating Brusselator model, extending the basic simulation [plotting](@ref simulation_plotting) workflow we saw earlier. | ||
|
||
```@example interactive_brusselator; continued = true | ||
using Catalyst | ||
using OrdinaryDiffEq | ||
using GLMakie | ||
GLMakie.activate!(inline = true, visible = false) # hide | ||
|
||
# Define the Brusselator model | ||
brusselator = @reaction_network begin | ||
A, ∅ → X | ||
1, 2X + Y → 3X | ||
B, X → Y | ||
1, X → ∅ | ||
end | ||
|
||
# Initial parameter values and conditions | ||
p = [:A => 1.0, :B => 4.0] | ||
u0 = [:X => 1.0, :Y => 0.0] | ||
tspan = (0.0, 50.0) | ||
|
||
oprob = ODEProblem(brusselator, u0, tspan, p) | ||
|
||
# Function to solve the ODE | ||
function solve_brusselator(A, B, X0, Y0, prob = oprob) | ||
p = [:A => A, :B => B] | ||
u0 = [:X => X0, :Y => Y0] | ||
newprob = remake(prob, p=p, u0=u0) | ||
solve(newprob, Tsit5(), saveat = 0.1) | ||
end | ||
``` | ||
This code sets up our Brusselator model using Catalyst.jl's `@reaction_network` macro. We also define initial parameters, initial conditions, create an `ODEProblem`, and define a function to solve the ODE with given parameters. Setting `saveat = 0.1` in the call to `solve` ensures the solution is saved with the desired temporal frequency we want for our later plots. | ||
|
||
!!! note | ||
Be sure to set `saveat` to a value that is appropriate for your system; otherwise, the size of the solution can change during interactivity, which will cause dimension mismatch errors once we add our interactive elements. | ||
jonathanfischer97 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## [Basic static plotting](@id basic_static_plotting) | ||
|
||
Let's start by creating a basic plot of our Brusselator model: | ||
|
||
```@example interactive_brusselator | ||
# Create the main figure | ||
fig = Figure(size = (800, 600), fontsize = 18); | ||
|
||
# Create an axis for the plot | ||
ax = Axis(fig[1, 1], | ||
title = "Brusselator Model", | ||
xlabel = "Time", | ||
ylabel = "Concentration") | ||
|
||
# Solve the ODE | ||
sol = solve_brusselator(1.0, 4.0, 1.0, 0.0) | ||
|
||
# Plot the solution | ||
lines!(ax, sol.t, sol[:X], label = "X", color = :blue, linewidth = 3) | ||
lines!(ax, sol.t, sol[:Y], label = "Y", color = :red, linewidth = 3) | ||
|
||
# Add a legend | ||
axislegend(ax, position = :rt) | ||
|
||
# Display the figure | ||
fig | ||
``` | ||
|
||
The plot shows the concentrations of species X and Y over time. Notice the oscillatory behavior characteristic of the Brusselator model. | ||
|
||
## [Adding interactivity](@id adding_interactivity) | ||
|
||
Now, let's add interactivity to our plot using Observables and sliders. We'll build this up step by step. | ||
|
||
### [Creating Observables](@id creating_observables) | ||
|
||
Observables are a key concept in reactive programming and are central to how Makie.jl creates interactive visualizations. You can read more about them [here](https://docs.makie.org/stable/explanations/observables). | ||
|
||
```@example interactive_brusselator; continued = true | ||
# Create observables for parameters and initial conditions | ||
A = Observable(1.0) | ||
B = Observable(4.0) | ||
X0 = Observable(1.0) | ||
Y0 = Observable(0.0) | ||
``` | ||
|
||
An Observable is a container for a value that can change over time. When the value changes, any dependent computations are automatically updated. | ||
|
||
### [Adding sliders and connecting to Observables](@id adding_sliders) | ||
|
||
Let's add [sliders](https://docs.makie.org/stable/reference/blocks/slider) that will control our Observables: | ||
|
||
```@example interactive_brusselator; continued = true | ||
# Create the main figure | ||
fig = Figure(size = (800, 600), fontsize = 18); | ||
|
||
# Create layout for plot and sliders | ||
plot_layout = fig[1, 1] = GridLayout() | ||
slider_layout = fig[2, 1] = GridLayout() | ||
|
||
# Create sliders | ||
slider_A = Slider(slider_layout[1, 1], range = 0.0:0.01:5.0, startvalue = to_value(A)) # to_value(A) unwraps the Observable to a value | ||
slider_B = Slider(slider_layout[2, 1], range = 0.0:0.01:5.0, startvalue = to_value(B)) | ||
slider_X0 = Slider(slider_layout[3, 1], range = 0.0:0.01:5.0, startvalue = to_value(X0)) | ||
slider_Y0 = Slider(slider_layout[4, 1], range = 0.0:0.01:5.0, startvalue = to_value(Y0)) | ||
|
||
# Add labels for sliders | ||
Label(slider_layout[1, 1, Left()], "A") | ||
Label(slider_layout[2, 1, Left()], "B") | ||
Label(slider_layout[3, 1, Left()], "X₀") | ||
Label(slider_layout[4, 1, Left()], "Y₀") | ||
|
||
# Connect the values of the sliders to the observables | ||
connect!(A, slider_A.value) | ||
connect!(B, slider_B.value) | ||
connect!(X0, slider_X0.value) | ||
connect!(Y0, slider_Y0.value) | ||
``` | ||
|
||
These sliders allow us to interactively change the parameters A and B, as well as the initial conditions X₀ and Y₀. | ||
|
||
### [Creating a reactive plot](@id reactive_plot) | ||
|
||
Now, let's create a plot that reacts to changes in our sliders: | ||
|
||
```@example interactive_brusselator | ||
# Create an axis for the plot | ||
ax = Axis(plot_layout[1, 1], | ||
title = "Brusselator Model", | ||
xlabel = "Time", | ||
ylabel = "Concentration") | ||
|
||
# Create an observable for the solution | ||
# The `@lift` macro is used to create an Observable that depends on the observables `A`, `B`, `X0`, and `Y0`, and automatically updates when any of these observables change | ||
solution = @lift(solve_brusselator($A, $B, $X0, $Y0)) | ||
|
||
# Plot the solution | ||
# We don't use the ODESolution plot recipe here, as you've seen in the previous examples where only the solution and an `idxs` argument was passed to the plot method, because we are passing in an Observable wrapping the solution | ||
lines!(ax, lift(sol -> sol.t, solution), lift(sol -> sol[:X], solution), label = "X", color = :blue, linewidth = 3) # `lift` can either be used as a function or a macro | ||
lines!(ax, lift(sol -> sol.t, solution), lift(sol -> sol[:Y], solution), label = "Y", color = :red, linewidth = 3) | ||
|
||
# Add a legend | ||
axislegend(ax, position = :rt) | ||
|
||
# Display the figure | ||
fig | ||
``` | ||
|
||
This plot will now update in real-time as you move the sliders, allowing for interactive exploration of the Brusselator's behavior under different conditions. | ||
|
||
## [Adding a phase plot](@id adding_phase_plot) | ||
|
||
To gain more insight into the system's behavior, let's enhance our visualization by adding a phase plot, along with some other improvements: | ||
|
||
```@example interactive_brusselator | ||
# Create the main figure | ||
fig = Figure(size = (1200, 800), fontsize = 18); | ||
|
||
# Create main layout: plots on top, sliders at bottom | ||
plot_grid = fig[1, 1] = GridLayout() | ||
slider_grid = fig[2, 1] = GridLayout() | ||
|
||
# Create sub-grids for plots | ||
time_plot = plot_grid[1, 1] = GridLayout() | ||
phase_plot = plot_grid[1, 2] = GridLayout() | ||
|
||
# Create axes for the time series plot and phase plot | ||
ax_time = Axis(time_plot[1, 1], | ||
title = "Brusselator Model - Time Series", | ||
xlabel = "Time", | ||
ylabel = "Concentration") | ||
|
||
ax_phase = Axis(phase_plot[1, 1], | ||
title = "Brusselator Model - Phase Plot", | ||
xlabel = "X", | ||
ylabel = "Y") | ||
|
||
# Create sub-grids for sliders | ||
param_grid = slider_grid[1, 1] = GridLayout() | ||
ic_grid = slider_grid[1, 2] = GridLayout() | ||
|
||
# Create observables for parameters and initial conditions | ||
A = Observable{Float64}(1.0) # We can specify the type of the Observable value, which can help with type stability and performance | ||
B = Observable{Float64}(4.0) | ||
X0 = Observable{Float64}(1.0) | ||
Y0 = Observable{Float64}(0.0) | ||
|
||
# Create sliders with labels and group titles | ||
Label(param_grid[1, 1:2], "Parameters", fontsize = 22) | ||
slider_A = Slider(param_grid[2, 2], range = 0.0:0.01:5.0, startvalue = to_value(A)) | ||
slider_B = Slider(param_grid[3, 2], range = 0.0:0.01:5.0, startvalue = to_value(B)) | ||
Label(param_grid[2, 1], "A") | ||
Label(param_grid[3, 1], "B") | ||
|
||
Label(ic_grid[1, 1:2], "Initial Conditions", fontsize = 22) | ||
slider_X0 = Slider(ic_grid[2, 2], range = 0.0:0.01:5.0, startvalue = to_value(X0)) | ||
slider_Y0 = Slider(ic_grid[3, 2], range = 0.0:0.01:5.0, startvalue = to_value(Y0)) | ||
Label(ic_grid[2, 1], "X₀") | ||
Label(ic_grid[3, 1], "Y₀") | ||
|
||
# Connect sliders to observables | ||
connect!(A, slider_A.value) | ||
connect!(B, slider_B.value) | ||
connect!(X0, slider_X0.value) | ||
connect!(Y0, slider_Y0.value) | ||
|
||
# Create an observable for the solution. | ||
solution = @lift(solve_brusselator($A, $B, $X0, $Y0)) | ||
|
||
# Plot the time series | ||
lines!(ax_time, lift(sol -> sol.t, solution), lift(sol -> sol[:X], solution), label = "X", color = :blue, linewidth = 3) | ||
lines!(ax_time, lift(sol -> sol.t, solution), lift(sol -> sol[:Y], solution), label = "Y", color = :red, linewidth = 3) | ||
|
||
# Plot the phase plot | ||
phase_plot_obj = lines!(ax_phase, lift(sol -> sol[:X], solution), lift(sol -> sol[:Y], solution), | ||
color = lift(sol -> sol.t, solution), colormap = :viridis) | ||
|
||
# Add a colorbar for the phase plot | ||
Colorbar(phase_plot[1, 2], phase_plot_obj, label = "Time") | ||
|
||
# Add legends | ||
axislegend(ax_time, position = :rt) | ||
|
||
# Adjust layout to your liking | ||
colgap!(plot_grid, 20) | ||
rowgap!(fig.layout, 20) | ||
colgap!(param_grid, 10) | ||
colgap!(ic_grid, 10) | ||
|
||
# Display the figure | ||
#fig | ||
``` | ||
|
||
This will create a visualization with both time series and phase plots: | ||
|
||
![Interactive Brusselator Plot with Time Series and Phase Plot](../../assets/interactive_brusselator.mp4) | ||
|
||
## [Common plotting options](@id common_makie_plotting_options) | ||
|
||
Various plotting options can be provided as optional arguments to the `lines!` command. Common options include: | ||
- `linewidth` or `lw`: Determine plot line widths. | ||
- `linestyle`: Determines plot line style. | ||
- `color`: Determines the line colors. | ||
- `label`: Determines label texts displayed in the legend. | ||
|
||
For example: | ||
|
||
```julia | ||
lines!(ax_time, lift(sol -> sol.t, solution), lift(sol -> sol[:X], solution), | ||
label = "X", color = :green, linewidth = 2, linestyle = :dash) | ||
``` | ||
|
||
## [Extending the interactive visualization](@id extending_interactive_visualization) | ||
|
||
You can further extend this visualization by: | ||
- Adding other interactive elements, such as [buttons](https://docs.makie.org/stable/reference/blocks/button) or [dropdown menus](https://docs.makie.org/stable/reference/blocks/menu) to control different aspects of the simulation or visualization. | ||
- Adding additonal axes to the plot, such as plotting the derivatives of the species. | ||
- Color coding the slider and slider labels to match the plot colors. | ||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What practical impact does this have? Does it mean all our docs are now being built under the assumption they live in a virtual 1024x768 framebuffer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the use of
xvfb
with-screen 0 1024x768x24
does not mean all the plots rendered during doc build will be constrained to that resolution, if that's what you mean. This setup only applies during the CI build to provide a virtual display forGLMakie
plots, which requireOpenGL
, but also doesn't dictate the final resolution of theGLMakie
plots, which instead are determined by the plot settings themselves.Plots rendered by
Plots.jl
(using GR) orCairoMakie.jl
aren't affected, cause these libraries use software-based rendering that doesn't depend on anOpenGL
display. Their output is generated independently of thexvfb
virtual framebuffer, so that those final doc artifacts are unaffected by this temporary display configuration.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, if you'd rather not implement this change in the the docs CI, another option is I can just use
CairoMakie
as the backend for all the inline rendered plots, but make the presented code blocks look like they useGLMakie
.But this would obviously break some of the spirit of making the plots dynamically to ensure
GLMakie
functionality in the tutorial.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No that's fine. I just wanted to understand the code.
If tests pass is this ready to merge on your end?