diff --git a/docs/source/actor_visualization.rst b/docs/source/actor_visualization.rst
index 3f2a4f7..fe56b28 100644
--- a/docs/source/actor_visualization.rst
+++ b/docs/source/actor_visualization.rst
@@ -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
diff --git a/docs/source/images/automatic.png b/docs/source/images/automatic.png
new file mode 100644
index 0000000..510d7e1
Binary files /dev/null and b/docs/source/images/automatic.png differ
diff --git a/docs/source/images/ff_prime.gif b/docs/source/images/ff_prime.gif
new file mode 100644
index 0000000..ccd9c0c
Binary files /dev/null and b/docs/source/images/ff_prime.gif differ
diff --git a/docs/source/images/ip_curve.gif b/docs/source/images/ip_curve.gif
new file mode 100644
index 0000000..9132982
Binary files /dev/null and b/docs/source/images/ip_curve.gif differ
diff --git a/docs/source/images/outline.gif b/docs/source/images/outline.gif
new file mode 100644
index 0000000..c298082
Binary files /dev/null and b/docs/source/images/outline.gif differ
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 53c3e92..3da8ee5 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -26,6 +26,7 @@ Manual
installing
usage
tips_and_tricks
+ training
.. toctree::
:caption: API docs
diff --git a/docs/source/training.rst b/docs/source/training.rst
new file mode 100644
index 0000000..2311dad
--- /dev/null
+++ b/docs/source/training.rst
@@ -0,0 +1,480 @@
+
+.. _`training`:
+
+********
+Training
+********
+
+Visualization Actor Training
+============================
+
+In this training you will learn the following:
+
+- Working with live data visualization
+- Creating custom plotting scripts for the visualization actor
+- Setting up visualization components in yMMSL files
+- Using both MUSCLE3 and standalone modes
+
+This training assumes you are familiar with the with MUSCLE3 workflows. If you are not,
+please take a look at the `MUSCLE3 documentation `_.
+
+There is also training material available for the ITER Pulse Design Simulation, which can also serve as
+an introduction to IMAS-MUSCLE3 workflows, which is available
+`here `_. Note that this requires an ITER account to view.
+
+All examples require that you have an environment with IMAS-MUSCLE3 up and running.
+If you do not have this yet, please have a look at the :ref:`installation instructions `.
+
+.. important::
+ For this training you will need access to a graphical environment to visualize
+ the simulation results. If you are on SDCC, it is recommended to follow this training
+ through the NoMachine client, and using chrome as your default browser (there have been
+ issues when using firefox through NoMachine).
+
+Exercise 1a: Setting Up Your First Visualization
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. md-tab-set::
+ .. md-tab-item:: Exercise
+
+ 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 :ref:`source actor ` that sends the equilibrium IDS
+ 2. A :ref:`visualization actor ` that receives and plots the data.
+
+ Use the following settings in the yMMSL:
+
+ - Source URI: ``imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/``
+ - Plotting script: ``imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py``
+
+ This plotting script is a simple configuration that will only plot the plasma current
+ of an equilibrium IDS over time.
+ Run the MUSCLE pipeline, supplying the yMMSL file you made:
+
+ .. code-block:: bash
+
+ muscle_manager --start-all
+
+ The visualization actor will automatically open your default browser after it is initiated.
+ What do you see in your browser?
+
+ .. hint::
+ 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 `.
+
+ .. md-tab-item:: Solution
+
+ Create a file called ``my_visualization.ymmsl`` with the following content:
+
+ .. code-block:: yaml
+
+ ymmsl_version: v0.1
+ model:
+ name: my_visualization
+ components:
+ source_component:
+ implementation: source_component
+ ports:
+ o_i: [equilibrium_out]
+ visualization_component:
+ implementation: visualization_component
+ ports:
+ s: [equilibrium_in]
+ conduits:
+ source_component.equilibrium_out: visualization_component.equilibrium_in
+ settings:
+ source_component.source_uri: imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/
+ visualization_component.plot_file_path: /imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py
+ 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
+ visualization_component:
+ threads: 1
+
+ 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.
+
+ .. figure:: ../source/images/ip_curve.gif
+
+
+Exercise 1b: Understanding the Basic Structure
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. md-tab-set::
+ .. md-tab-item:: Exercise
+
+ Now that you were able to run the visualization actor in the previous exercise, let's
+ take a look under the hood to see what the plotting script actually does.
+
+ Every plotting script for the visualization actor must include the following two classes:
+
+ 1. ``State(BaseState)``: This class handles extracting and storing data from incoming IDSs.
+ 2. ``Plotter(BasePlotter)``: This class handles how to plot the extracted data in the ``State`` class.
+
+ Take a look at the simple example plotting script below that you used in previous exercise
+ to visualize the plasma current (Ip) from an equilibrium IDS over time.
+
+ **File:** `imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py`
+
+ .. literalinclude:: ../../imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py
+ :language: python
+
+ What does the ``extract`` method do in the State class?
+
+ What does the ``get_dashboard`` method do in the Plotter class?
+
+ .. md-tab-item:: Solution
+
+ The ``State`` class **must** implement the ``extract(self, ids)`` method.
+ The ``extract`` method for this example case:
+
+ - 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 `_ as its cornerstone to enable interactive visualizations.
+ - Returns a `HoloViews DynamicMap `_ object,
+ which allows you to dynamically update a plot whenever its argument function is called, here ``self.plot_ip_vs_time``.
+ - Implements ``self.plot_ip_vs_time`` which automatically runs whenever the ``self.time`` parameter is updated.
+ This happens when the Visualization actor receives new data, or when the user changes the time slider in the UI.
+ ``self.plot_ip_vs_time`` loads the state defined in the ``State`` class above, using ``self.active_data.data.get("equilibrium")``.
+ - Extracts the Ip and time arrays from the state object, based on the selected time parameter.
+ - It plots the plasma current versus time using a `HoloViews Curve `_,
+ which it returns to the DynamicMap, which will automatically update the plot.
+
+
+Exercise 1c: Creating a custom visualization
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. md-tab-set::
+ .. md-tab-item:: Exercise
+
+ Now that you understand how the ``State`` and ``Plotter`` classes work, let's
+ try to create your own plotting script for the visualization actor. In this
+ exercise you will learn how to visualize a 1D ff' profile, as a function of the
+ poloidal flux, over time.
+
+ For this exercise you can use the template below, in which you only have to implement
+ the ``extract_equilibrium`` and ``plot_f_df_dpsi_profile`` methods.
+
+ .. code-block:: python
+
+ import holoviews as hv
+ import numpy as np
+ import param
+ 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):
+ def get_dashboard(self):
+ profile_plot = hv.DynamicMap(self.plot_f_df_dpsi_profile)
+ return profile_plot
+
+ @param.depends("time")
+ def plot_f_df_dpsi_profile(self):
+ # Implement this method!
+
+ Implement the ``extract_equilibrium`` method which does the following:
+
+ - Loads the ff' profile from the IDS: ``ids.time_slice[0].profiles_1d.f_df_dpsi``
+ - Loads the corresponding psi coordinates: ``ids.time_slice[0].profiles_1d.psi``
+ - Stores both in an Xarray Dataset.
+ - Either saves the first entry in ``self.data`` or concatenates it to an existing Dataset.
+
+ .. hint::
+ Profile data is a 1D array for each time slice, so you'll need a dimension for
+ 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 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, using a `HoloViews Curve `_.
+ - Give an appropriate title, xlabel, and ylabel.
+ - Properly handle the case when no data is available yet (Return an empty ``hv.Curve``).
+
+ .. md-tab-item:: Solution
+
+ .. code-block:: python
+
+ import holoviews as hv
+ import numpy as np
+ import param
+ 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]
+
+ profiles_data = xr.Dataset(
+ {
+ "f_df_dpsi": (("time", "profile"), [ts.profiles_1d.f_df_dpsi]),
+ "psi_profile": (("time", "profile"), [ts.profiles_1d.psi]),
+ },
+ coords={
+ "time": [ids.time[0]],
+ "profile": np.arange(len(ts.profiles_1d.f_df_dpsi)),
+ },
+ )
+
+ current_data = self.data.get("equilibrium")
+ if current_data is None:
+ self.data["equilibrium"] = profiles_data
+ else:
+ self.data["equilibrium"] = xr.concat(
+ [current_data, profiles_data], dim="time", join="outer"
+ )
+
+
+ class Plotter(BasePlotter):
+ def get_dashboard(self):
+ profile_plot = hv.DynamicMap(self.plot_f_df_dpsi_profile)
+ return profile_plot
+
+ @param.depends("time")
+ def plot_f_df_dpsi_profile(self):
+ xlabel = "Psi [Wb]"
+ ylabel = "ff'"
+ state = self.active_state.data.get("equilibrium")
+
+ if state:
+ selected_data = state.sel(time=self.time)
+ psi = selected_data.psi_profile.values
+ f_df_dpsi = selected_data.f_df_dpsi.values
+ title = f"ff' profile (t={self.time:.3f}s)"
+ else:
+ psi, f_df_dpsi, title = [], [], "Waiting for data..."
+
+ return hv.Curve((psi, f_df_dpsi), kdims=[xlabel], vdims=[ylabel]).opts(
+ framewise=True,
+ height=400,
+ width=600,
+ title=title,
+ xlabel=xlabel,
+ ylabel=ylabel,
+ )
+
+ This generates the following ff' plot over time:
+
+ .. 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
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. md-tab-set::
+ .. md-tab-item:: Exercise
+
+ In this exercise you will your yMMSL configuration to enable automatic mode. This mode allows
+ the visualization actor to automatically discover and plot time-dependent
+ quantities without needing a custom plotting script.
+
+ Advantages of automatic mode:
+
+ - Useful for exploring unfamiliar datasets
+ - Automatically discovers all time-dependent quantities in the IDS
+ - Provides a dropdown menu to select quantities to visualize
+ - Chooses appropriate plot types automatically
+ - No need to manually extract quantities
+
+ Disadvantages:
+
+ - 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:
+
+ .. code-block:: yaml
+
+ settings:
+ visualization_component.automatic_mode: true
+ visualization_component.automatic_extract_all: true
+
+ Run the MUSCLE pipeline, supplying the yMMSL file you made. Use the dropdown menu to
+ visualize the following parameters:
+
+ - ``equilibrium/time_slice[0]/profiles_1d[0]/dpressure_dpsi``
+ - ``equilibrium/time_slice[0]/global_quantities/energy_mhd``
+
+ .. md-tab-item:: Solution
+
+ Besides the plasma current curve, which was defined in the plotter class, you
+ should also see the p' and the MHD energy curves in separate panels:
+
+ .. figure:: ../source/images/automatic.png
+
+Exercise 3: Using the CLI
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. md-tab-set::
+ .. md-tab-item:: Exercise
+
+ It is also possible to run the visualization actor from the command line instead,
+ without setting up a MUSCLE3 workflow. Try running the simple_1d_plot example
+ through the CLI.
+
+ Run the visualization with:
+
+ - URI: ``imas:hdf5?path=/home/ITER/blokhus/public/imasdb/ITER/4/666666/3/``
+ - Plotting script: ``imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.py``
+
+ .. hint::
+ Use ``python -m imas_muscle3.visualization.cli --help`` to see available options.
+
+ .. md-tab-item:: Solution
+
+ Run the following command:
+
+ .. code-block:: bash
+
+ python -m imas_muscle3.visualization.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 visualization 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.outline.r``, ``ids.time_slice[0].boundary.outline.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 `_.
+
+ 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 `_.
+
+ .. 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
+
+ .. figure:: ../source/images/outline.gif
+
+ 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
+
diff --git a/imas_muscle3/actors/visualization_component.py b/imas_muscle3/actors/visualization_component.py
index 36816ae..c5c6209 100644
--- a/imas_muscle3/actors/visualization_component.py
+++ b/imas_muscle3/actors/visualization_component.py
@@ -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.
diff --git a/imas_muscle3/visualization/examples/machine_description/machine_description.py b/imas_muscle3/visualization/examples/machine_description/machine_description.py
new file mode 100644
index 0000000..eb449e7
--- /dev/null
+++ b/imas_muscle3/visualization/examples/machine_description/machine_description.py
@@ -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")],
+ )
diff --git a/imas_muscle3/visualization/examples/machine_description/machine_description.ymmsl b/imas_muscle3/visualization/examples/machine_description/machine_description.ymmsl
new file mode 100644
index 0000000..1a10e2d
--- /dev/null
+++ b/imas_muscle3/visualization/examples/machine_description/machine_description.ymmsl
@@ -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
diff --git a/imas_muscle3/visualization/examples/pds/pds.ymmsl b/imas_muscle3/visualization/examples/pds/pds.ymmsl
index d514634..f02475b 100644
--- a/imas_muscle3/visualization/examples/pds/pds.ymmsl
+++ b/imas_muscle3/visualization/examples/pds/pds.ymmsl
@@ -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:
diff --git a/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.ymmsl b/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.ymmsl
index 9c804f2..79d58a0 100644
--- a/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.ymmsl
+++ b/imas_muscle3/visualization/examples/simple_1d_plot/simple_1d_plot.ymmsl
@@ -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:
diff --git a/imas_muscle3/visualization/visualization_actor.py b/imas_muscle3/visualization/visualization_actor.py
index 20a5573..4f49e8e 100644
--- a/imas_muscle3/visualization/visualization_actor.py
+++ b/imas_muscle3/visualization/visualization_actor.py
@@ -1,6 +1,5 @@
import logging
import runpy
-import webbrowser
from typing import Dict
import panel as pn
@@ -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}")