Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
21 changes: 21 additions & 0 deletions docs/src/visualizations.md
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,24 @@ j = 2 # the dimension of the plane

interactive_poincaresos_scan(trs, j; linekw = (transparency = true,))
```

## Interactive projected dynamical system
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, to be clear: this works for any 2D system, right? This should be 2D maps, projected 2D systems, poincare maps, whatever. Right? If so, do we even need the interactive poincare map function anymore?

Additionally please rename this section to reflect the generality of the function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the docs.

If so, do we even need the interactive poincare map function anymore?

I don't know if we need it, but I think it's pretty useful (and one of the most common use cases, I believe). It would help from a backwards compatibility perspective to preserve the existing interactive_poincaresos API (even though now that function is now a wrapper for this one).


```@docs
interactive_clicker
```

The following code show how to interactively explore a [`ProjectedDynamicalSystem`](@ref):

```julia
using GLMakie, DynamicalSystems

# This is the 3D Lorenz model
lorenz = Systems.lorenz()

projection = [1, 2]
complete_state = [0.0]
projected_ds = ProjectedDynamicalSystem(lorenz, projection, complete_state)

interactive_clicker(projected_ds; tfinal = (10.0, 150.0))
```
3 changes: 2 additions & 1 deletion ext/DynamicalSystemsVisualizations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ include("src/cobweb.jl")
include("src/orbitdiagram.jl")
include("src/poincareclick.jl")
include("src/brainscan.jl")
include("src/clicker.jl")

subscript = DynamicalSystemsVisualizations.subscript

end
end
75 changes: 75 additions & 0 deletions ext/src/clicker.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
function DynamicalSystems.interactive_clicker(dds;
# DynamicalSystems kwargs:
tfinal = (1000.0, 10.0^4),
complete = (x, y) -> [x, y],
project = identity,
Comment on lines +4 to +5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these keywords provided here? Shouldn't we expect a ProjectedDynamicalSystem directly instead of allowing the projection to happen within the function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, but then I don't think we could use it directly with PoincareMap?

# Makie kwargs:
color = randomcolor,
scatterkwargs = (),
labels = ("x", "y")
)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you check here that dimension(dds) == 2?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because this function can be used with higher-dimensional dynamical systems, as long as the complete and project parameters are properly defined. Adding such a check would break the use of this in interactive_poincaresos.

u0 = DynamicalSystems.get_state(dds)

figure = Figure(size = (1000, 800), backgroundcolor = :white)

T_slider, m_slider = _add_clicker_controls!(figure, tfinal)
ax = figure[0, :] = Axis(figure)

# Compute the initial section
tr, = trajectory(dds, T_slider[]; t0 = 0)
length(tr) == 0 && error("Initial computed trajectory is empty!")

data = project(tr)
length(data[1]) != 2 && error("(Projected) trajectory is not 2D")

positions_node = Observable(data)
colors = (c = color(u0); [c for _ in 1:length(data)])
colors_node = Observable(colors)
scatter!(
ax, positions_node, color = colors_node,
markersize = lift(o -> o*px, m_slider), marker = :circle, scatterkwargs...
)

ax.xlabel, ax.ylabel = labels
laststate = Observable(u0)

# Interactive clicking on the phase space:
Makie.deactivate_interaction!(ax, :rectanglezoom)
spoint = select_point(ax.scene)
on(spoint) do pos
x, y = pos;
newstate = try
complete(x, y)
catch err
@error "Could not complete state, got error: " exception=err
return
end

tr, = trajectory(dds, T_slider[], newstate; t0 = 0)
data = project(tr)

positions = positions_node[]; colors = colors_node[]
append!(positions, data)
c = color(newstate)
append!(colors, fill(c, length(data)))
# Update all the observables with Array as value:
positions_node[], colors_node[], laststate[] = positions, colors, newstate
end

display(figure)

return figure, laststate
end

function _add_clicker_controls!(figure, tfinal)
sg1 = SliderGrid(figure[1, :][1, 1],
(label = "T", range = range(tfinal[1], tfinal[2], length = 1000),
format = x -> string(round(x)), )
)
sg2 = SliderGrid(figure[1, :][1, 2],
(label = "ms", range = 10.0 .^ range(0, 2, length = 100),
format = x -> string(round(x)), startvalue = 10)
)
return sg1.sliders[1].value, sg2.sliders[1].value
end
62 changes: 8 additions & 54 deletions ext/src/poincareclick.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,64 +20,18 @@ function DynamicalSystems.interactive_poincaresos(ds, plane, idxs, complete;

i = DynamicalSystems.SVector{2, Int}(idxs)

figure = Figure(size = (1000, 800), backgroundcolor = :white)

T_slider, m_slider = _add_psos_controls!(figure, tfinal)
ax = figure[0, :] = Axis(figure)

# Construct a new `PoincareMap` structure with the given parameters
pmap = DynamicalSystems.DynamicalSystemsBase.PoincareMap(ds, plane;
direction, u0, rootkw, Tmax = tfinal[2])

# Compute the initial section
psos, = trajectory(pmap, T_slider[]; t0 = 0)
data = psos[:, i]
length(data) == 0 && error(ChaosTools.PSOS_ERROR)

positions_node = Observable(data)
colors = (c = color(u0); [c for i in 1:length(data)])
colors_node = Observable(colors)
scatter!(
ax, positions_node, color = colors_node,
markersize = lift(o -> o*px, m_slider), marker = :circle, scatterkwargs...
z = plane[2] # third variable comes from plane
return interactive_clicker(pmap;
tfinal = tfinal,
complete = (x, y) -> complete(x, y, z),
project = tr -> tr[:, i],
color = color,
scatterkwargs = scatterkwargs,
labels = labels
)

ax.xlabel, ax.ylabel = labels
laststate = Observable(u0)

# Interactive clicking on the psos:
Makie.deactivate_interaction!(ax, :rectanglezoom)
spoint = select_point(ax.scene)
on(spoint) do pos
x, y = pos; z = plane[2] # third variable comes from plane
newstate = try
complete(x, y, z)
catch err
@error "Could not get state, got error: " exception=err
return
end

psos, = trajectory(pmap, T_slider[], newstate; t0 = 0)
data = psos[:, i]
positions = positions_node[]; colors = colors_node[]
append!(positions, data)
c = color(newstate)
append!(colors, fill(c, length(data)))
# Update all the observables with Array as value:
positions_node[], colors_node[], laststate[] = positions, colors, newstate
end
display(figure)
return figure, laststate
end

function _add_psos_controls!(figure, tfinal)
sg1 = SliderGrid(figure[1, :][1,1],
(label = "T", range = range(tfinal[1], tfinal[2], length = 1000),
format = x -> string(round(x)), )
)
sg2 = SliderGrid(figure[1, :][1,2],
(label = "ms", range = 10.0 .^ range(0, 2, length = 100),
format = x -> string(round(x)), startvalue = 10)
)
return sg1.sliders[1].value, sg2.sliders[1].value
end
35 changes: 33 additions & 2 deletions src/visualizations.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interactive_trajectory, interactive_cobweb, interactive_orbitdiagram, scaleod,
interactive_poincaresos_scan, interactive_poincaresos, interactive_trajectory_timeseries
interactive_poincaresos_scan, interactive_poincaresos, interactive_trajectory_timeseries,
interactive_clicker

"""
interactive_trajectory_timeseries(ds::DynamicalSystem, fs, [, u0s]; kwargs...) → fig, dsobs
Expand Down Expand Up @@ -313,4 +314,34 @@ This will be properly handled instead of breaking the application.
This `newstate` is also given to the function `color` that
gets a new color for the new points.
"""
function interactive_poincaresos end
function interactive_poincaresos end


"""
interactive_clicker(dds; kwargs...)
Launch an interactive application for exploring the state space of a
2D discrete dynamical system `dds`, usually derived from a continuous dynamical system.
Requires `DynamicalSystems`.

The function returns: `figure, laststate` with the latter being
an observable containing the latest initial `state`.

## Keyword Arguments
* `tfinal = (1000.0, 10.0^4)` : A 2-element tuple for the range of values
for the total integration time (chosen interactively).
* `color` : A **function** of the system's initial condition, that returns a color to
plot the new points with. The color must be `RGBf/RGBAf`.
A random color is chosen by default.
* `labels = ("x" , "y")` : Scatter plot labels.
* `scatterkwargs = ()`: Named tuple of keywords passed to `scatter`.
* `diffeq = NamedTuple()` : Any extra keyword arguments are passed into `init` of DiffEq.

## Interaction
The application is a standard scatterplot, which shows the state space of the dynamical system,
initially using the system's `u0`. Two sliders control the total evolution time
and the size of the marker points (which is always in pixels).

Upon clicking within the bounds of the scatter plot your click is transformed into
a new initial condition, which is further evolved and then plotted into the scatter plot.
"""
function interactive_clicker end
Loading