Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 docs/source/actor_visualization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Available Settings

* Optional

- ***port***: (int) The port on which the visualization server will run. Defaults to `5006`.
- ***port***: (int) The port on which the visualization server will run. Defaults to `0`, indicating a random available port.
- ***throttle_interval***: (float) The minimum time in seconds between plot updates.
This can be used to prevent the visualization from slowing down if data arrives very quickly. Defaults to `0.1`.
- ***keep_alive***: (bool) If `True`, the visualization server will remain active after
Expand Down
120 changes: 113 additions & 7 deletions docs/source/training.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ Exercise 1a: Setting Up Your First Visualization
We will start by running the visualization actor for a simple example configuration.
First, create a yMMSL configuration file that sets up a simple visualization pipeline with:

1. A source actor that sends the equilibrium IDS
2. A visualization actor that receives and plots the data.
1. A :ref:`source actor <actor_sink_source>` that sends the equilibrium IDS
2. A :ref:`visualization actor <actor_visualization>` that receives and plots the data.

Use the following settings in the yMMSL:

Expand All @@ -60,7 +60,8 @@ Exercise 1a: Setting Up Your First Visualization
What do you see in your browser?

.. hint::
Take a look at the example yMMSL file in ``imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.ymmsl``. If you want detailed information about the visualization actor, take a look
There are premade examples available that you use can use available, located in this
location: ``imas_muscle3/visualization/examples``. For this specific exercise, take a look at the example yMMSL file in ``imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.ymmsl``. If you want detailed information about the visualization actor, take a look
at the :ref:`documentation <actor_visualization>`.

.. md-tab-item:: Solution
Expand Down Expand Up @@ -99,7 +100,7 @@ Exercise 1a: Setting Up Your First Visualization
visualization_component:
threads: 1

When you launch the muscle_manger, the browser should open, and you will see the
When you launch the muscle_manager, the browser should open, and you will see the
plasma current plotted over time, updating in real-time as the new time slices are
received by the visualization actor.

Expand Down Expand Up @@ -137,14 +138,15 @@ Exercise 1b: Understanding the Basic Structure
The ``State`` class **must** implement the ``extract(self, ids)`` method.
The ``extract`` method for this example case:

- Checks if the incoming IDS is an equilibrium IDS.
- handles every IDS that is received on the S port, one at a time. So first it checks if the incoming IDS is an equilibrium IDS.
- Extracts the plasma current of the time slice (``ids.time_slice[0].global_quantities.ip``) and
its corresponding time value (``ids.time[0]``), and stores it in an Xarray dataset.
- Either stores the first Xarray dataset entry in ``self.data`` or appends it to the existing Xarray dataset.

The ``Plotter`` class **must** implement the ``get_dashboard(self)`` method.
The ``get_dashboard`` method for this example case:

- Gets called once when the visualization actor is initialized.
- Uses `HoloViews <https://holoviews.org/>`_ as its cornerstone to enable interactive visualizations.
- Returns a `HoloViews DynamicMap <https://holoviews.org/reference/containers/bokeh/DynamicMap.html>`_ object,
which allows you to dynamically update a plot whenever its argument function is called, here ``self.plot_ip_vs_time``.
Expand Down Expand Up @@ -210,13 +212,13 @@ Exercise 1c: Creating a custom visualization
the profile points in addition to time.

Also implement the ``plot_f_df_dpsi_profile`` method in the ``Plotter`` class that
displays the ff' profile as a function of psi for the current time step.
displays the ff' profile stored in the state object as a function of psi for the current time step.

Your ``plot_f_df_dpsi_profile`` should do the following:

- Load the state data from the current ``self.active_state``.
- Extract the arrays for ff' and psi from the state data (use ``state.sel(time=self.time)``).
- Display psi on the x-axis and f_df_dpsi on the y-axis.
- Display psi on the x-axis and f_df_dpsi on the y-axis, using a `HoloViews Curve <https://holoviews.org/reference/elements/bokeh/Curve.html>`_.
- Give an appropriate title, xlabel, and ylabel.
- Properly handle the case when no data is available yet (Return an empty ``hv.Curve``).

Expand Down Expand Up @@ -293,11 +295,13 @@ Exercise 1c: Creating a custom visualization

.. figure:: ../source/images/ff_prime.gif


.. tip:: More complex examples of visualizations are available in the
``imas_muscle3/visualization/examples/`` directory. For example, the PDS example
combines data from multiple IDSs, handles machine description data, and
handles different types of plots.


Exercise 2: Using Automatic Mode
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand All @@ -320,6 +324,7 @@ Exercise 2: Using Automatic Mode

- No fine grain control over the plots
- Unable to combine data
- Slower performance and increased memory usage

Repeat exercise 1a, however this time add the following settings to the yMMSL:

Expand Down Expand Up @@ -370,3 +375,104 @@ Exercise 3: Using the CLI
"imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/" \
imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py


Exercise 4: Loading Machine Descriptions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. md-tab-set::
.. md-tab-item:: Exercise

In this exercise you will create a 2D plot that combines **static machine description data** with **time-evolving equilibrium data**. Specifically,

- The plasma boundary outline (from the `equilibrium` IDS) as it changes over time.
- The tokamak first wall and divertor (from the `wall` machine description IDS).

You will need to update your yMMSL from exercise 1a, with the following changes:

- Add a new source actor which will send the ``wall`` and ``pf_active``
machine description IDSs to the visualization actor.
- The new source actor should use the following URI:
.. code-block:: bash

imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/
- The visulization actor should receive the machine description IDSs on the S port, with the names ``wall_md_in`` and ``pf_active_md_in``.


You will need to implement the ``extract_equilibrium``, ``_plot_boundary_outline``, and ``_plot_wall`` methods, in the template below.

Implement the ``extract_equilibrium`` method which does the following:

- Loads R and Z coordinates of the the boundary outline from the equilibrium IDS:
``ids.time_slice[0].boundary.ouline.r``, ``ids.time_slice[0].boundary.ouline.z``
- Stores both in an Xarray Dataset.
- Either saves the first entry in ``self.data`` or concatenates it to an existing Dataset.

Implement the ``_plot_boundary_outline`` method which does the following:

- Load the state data from the current ``self.active_state``.
- Extract the r and z arrays from the state data (use ``state.sel(time=self.time)``).
- Display r and z, using a `HoloViews Curve <https://holoviews.org/reference/elements/bokeh/Curve.html>`_.

Implement the ``_plot_wall`` method which does the following:

- Load the wall machine description IDS using ``self.active_state.md.get("wall")``.
- Loads the first wall and divertor from the wall IDS:
``ids.description_2d[0].limiter.unit[i].outline.r`` and ``ids.description_2d[0].limiter.unit[i].outline.z`` for ``i = 0`` and ``i = 1``.
- Display r and z, using a `HoloViews Path <https://holoviews.org/reference/elements/bokeh/Path.html>`_.

.. code-block:: python


import holoviews as hv
import numpy as np
import panel as pn
import xarray as xr

from imas_muscle3.visualization.base_plotter import BasePlotter
from imas_muscle3.visualization.base_state import BaseState


class State(BaseState):
def extract(self, ids):
if ids.metadata.name == "equilibrium":
self.extract_equilibrium(ids)

def extract_equilibrium(self, ids):
# Implement this method!

class Plotter(BasePlotter):
DEFAULT_OPTS = hv.opts.Overlay(
xlim=(0, 13),
ylim=(-10, 10),
title="Wall and equilibrium boundary outline",
xlabel="r [m]",
ylabel="z [m]",
)

def get_dashboard(self):
elements = [
hv.DynamicMap(self._plot_boundary_outline),
hv.DynamicMap(self._plot_wall),
]
overlay = hv.Overlay(elements).collate().opts(self.DEFAULT_OPTS)
return pn.pane.HoloViews(overlay, width=800, height=1000)

@pn.depends("time")
def _plot_boundary_outline(self):
# Implement this method!

def _plot_wall(self):
# Implement this method!


.. md-tab-item:: Solution

Example yMMSL file:

.. literalinclude:: ../../imas_muscle3/visualization/examples/machine_description/machine_description.ymmsl

Example plotting script:

.. literalinclude:: ../../imas_muscle3/visualization/examples/machine_description/machine_description.py
:language: python

3 changes: 2 additions & 1 deletion imas_muscle3/actors/visualization_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ def main() -> None:
while instance.reuse_instance():
if first_run:
plot_file_path = instance.get_setting("plot_file_path", "str")
port = get_setting_optional(instance, "port", 5006)
# If port is not specified, use a random available port
port = get_setting_optional(instance, "port", 0)
# FIXME: there is an issue when the plotting takes much longer
# than it takes for data to arrive from the MUSCLE actor. As a
# remedy, set a plotting throttle interval.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""
Example that plots the following:
- First wall and divertor from machine description IDS.
- Boundary outline from an equilibrium IDS
"""

import holoviews as hv
import numpy as np
import panel as pn
import xarray as xr

from imas_muscle3.visualization.base_plotter import BasePlotter
from imas_muscle3.visualization.base_state import BaseState


class State(BaseState):
def extract(self, ids):
if ids.metadata.name == "equilibrium":
self.extract_equilibrium(ids)

def extract_equilibrium(self, ids):
ts = ids.time_slice[0]
outline = ts.boundary.outline

boundary_data = xr.Dataset(
{
"r": (("time", "point"), [outline.r]),
"z": (("time", "point"), [outline.z]),
},
coords={
"time": [ids.time[0]],
"point": np.arange(len(outline.r)),
},
)

current_data = self.data.get("equilibrium")
if current_data is None:
self.data["equilibrium"] = boundary_data
else:
self.data["equilibrium"] = xr.concat(
[current_data, boundary_data], dim="time", join="outer"
)


class Plotter(BasePlotter):
DEFAULT_OPTS = hv.opts.Overlay(
xlim=(0, 13),
ylim=(-10, 10),
title="Wall and equilibrium boundary outline",
xlabel="r [m]",
ylabel="z [m]",
)

def get_dashboard(self):
elements = [
hv.DynamicMap(self._plot_boundary_outline),
hv.DynamicMap(self._plot_wall),
]
overlay = hv.Overlay(elements).collate().opts(self.DEFAULT_OPTS)
return pn.pane.HoloViews(overlay, width=800, height=1000)

@pn.depends("time")
def _plot_boundary_outline(self):
state = self.active_state.data.get("equilibrium")

if state is not None and "r" in state and "z" in state:
selected_data = state.sel(time=self.time)
r = selected_data.r.values
z = selected_data.z.values
else:
r, z = [], [], "Waiting for data..."

return hv.Curve((r, z)).opts(self.DEFAULT_OPTS)

def _plot_wall(self):
"""Generates path for limiter and divertor."""
paths = []
wall = self.active_state.md.get("wall")
if wall is not None:
for unit in wall.description_2d[0].limiter.unit:
name = str(unit.name)
r_vals = unit.outline.r
z_vals = unit.outline.z
paths.append((r_vals, z_vals, name))
return hv.Path(paths, vdims=["name"]).opts(
color="black",
line_width=2,
hover_tooltips=[("", "@name")],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
ymmsl_version: v0.1
model:
name: test_model
components:
source_component:
implementation: source_component
ports:
o_i: [equilibrium_out]
source_component_md:
implementation: source_component
ports:
o_i: [wall_out, pf_active_out]
visualization_component:
implementation: visualization_component
ports:
s: [equilibrium_in, pf_active_md_in, wall_md_in]
conduits:
source_component.equilibrium_out: visualization_component.equilibrium_in
source_component_md.wall_out: visualization_component.wall_md_in
source_component_md.pf_active_out: visualization_component.pf_active_md_in
settings:
source_component.source_uri: imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/
source_component_md.source_uri: imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/
visualization_component.plot_file_path: /home/ITER/blokhus/projects/IMAS-MUSCLE3/imas_muscle3/visualization/examples/machine_description/machine_description.py
visualization_component.keep_alive: true
implementations:
visualization_component:
executable: python
args: -u -m imas_muscle3.actors.visualization_component
source_component:
executable: python
args: -u -m imas_muscle3.actors.source_component
resources:
source_component:
threads: 1
source_component_md:
threads: 1
visualization_component:
threads: 1
1 change: 0 additions & 1 deletion imas_muscle3/visualization/examples/pds/pds.ymmsl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ settings:
source_component.source_uri: imas:hdf5?path=/home/sebbe/projects/iter_python/IMAS-MUSCLE3/data/pds_example
source_component_md.source_uri: imas:hdf5?path=/home/sebbe/projects/iter_python/Waveform-Editor/data/nice-input-dd4
visualization_component.plot_file_path: /home/sebbe/projects/iter_python/IMAS-MUSCLE3/imas_muscle3/visualization/examples/pds/pds.py
visualization_component.port: 5007
visualization_component.keep_alive: true
implementations:
visualization_component:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ model:
conduits:
source_component.equilibrium_out: visualization_component.equilibrium_in
settings:
source_component.source_uri: imas:hdf5?path=/home/sebbe/projects/iter_python/IMAS-MUSCLE3/data/striped_eq
visualization_component.plot_file_path: /home/sebbe/projects/iter_python/IMAS-MUSCLE3/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py
visualization_component.port: 5007
source_component.source_uri: imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/
visualization_component.plot_file_path: /home/ITER/blokhus/projects/IMAS-MUSCLE3/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py
visualization_component.throttle_interval: 0
visualization_component.keep_alive: true
implementations:
Expand Down
14 changes: 1 addition & 13 deletions imas_muscle3/visualization/visualization_actor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
import runpy
import webbrowser
from typing import Dict

import panel as pn
Expand Down Expand Up @@ -92,18 +91,7 @@ def _start_server(self) -> None:
self.server = pn.serve( # type: ignore[no-untyped-call]
self.dynamic_panel,
port=self.port,
show=False,
show=self.open_browser_on_start,
threaded=True,
start=True,
)
if self.open_browser_on_start:
self._open_browser()

def _open_browser(self) -> None:
"""Open the dashboard in the system web browser."""
url = f"http://localhost:{self.port}"
try:
webbrowser.open(url)
except Exception as e:
logger.warning(f"Could not open browser automatically: {e}")
logger.info(f"Dashboard is available at {url}")