Adding Custom Widgets

Custom Widgets share the same underlying framework used by Built-in Analysis Widgets.

A Custom Widget has to derive from the WidgetBase base class and implement certain handlers as described here.

Widget Registration

A Widget class must have a unique name class attibute to be registered.

class CustomWidget(WidgetBase)
    name = "Custom Widget"

An error is raised when the name class attribute is missing or if it exists but is the same as an already registered widget. The uniqueness of the name exists to prevent accidental overwrite of existing Widget classes. During testing or for use in Notebooks, an option is provided to force re-registraion of a Widget class if a _override_name class attribute set to True exists in the class defintion.

In the example below, the CustomWidget class overrides the built-in AbsoluteTemperature widget because it uses the same name “Absolute Temperature”.

class CustomWidget(WidgetBase)
    name = "Absolute Temperature"
    _override_name = True

This also enables customization of Built-in Analysis Widgets by cloning them in Notebooks and modifying them as needed.

An optional description class attribute can be used to specify more details about the Widget and this gets displayed along with the name in the list of available Widgets in the dashboard UI.

Run frequency and Run mode

The WidgetBase base class specifies two attributes for all Widgets (defaults shown below):

_run_frequency = "every-frame"
_run_mode = "serial"
  • _run_frequency specifies how often the widget is run. It takes one of two values:

    • every-frame

    • batch.

  • _run_mode specifies how the widget code is run. It takes one of two values:

    • serial

    • parallel.

By default, all Widgets run every frame serially (due to defaults above) unless the above attributes are customized.

Both these attributes can be configured independent of each other. Which run method(s) in the Widget class gets invoked depend on both these attributes as described in the following sections.

These available run methods are:

Note

One of run_every_frame() or run_batch() must be implemented by the Widget class.

A Widget can make the _run_frequency and _run_mode attributes dynamically changeable at runtime as well by making them as Inputs.

_run_frequency

This attribute specifies how often the widget is run.

When _run_frequency is every-frame, a method is invoked for every frame of the trajectory iteration.

When _run_frequency is batch, a method is invoked when a new batch of timesteps is full. A global “Buffer / batch size” under “Settings > Universe Configuration” in the dashboard controls the size of this timesteps buffer.

The method that is invoked depends on the _run_mode.

If the _run_mode is parallel, see the next section to see what gets invoked.

If the _run_mode is serial:

_run_mode

This attribute specifies how the widget analysis code is run.

If the _run_mode is parallel for a given widget instance, a get_parallel_job() method is invoked to retrieve the parallel job (a joblib.delayed tuple). A global “Parallel Jobs” under “Settings > Dashboard Configuration” in the dasboard controls the total number of jobs run in parallel during each iteration (n_jobs param for joblib.Parallel call).

If a widget has _run_mode as parallel, after the parallel job is completed, a apply_parallel_results() method is invoked where the results from the parallel job are passed back to the instance. The instance can apply the results back to its data structures (like updating it’s values deque etc).

If a widget has _run_mode as serial, one of the methods described in the previous section are invoked.

Lifecycle methods

There are several lifecycle methods that Widgets can implement (handlers) and these get invoked by the dashboard framework at those stages.

All the lifecycle methods are optional and the Widget class can choose to implement them as they see fit.

Inputs

Widgets can specify certain instance variables as inputs. These inputs show up in the dashboard UI allowing users to configure and modify them at runtime.

An array of inputs is specified using the _inputs class attribute. Each item of this array is a dict that has at minimum the following keys:

  • attribute

    • The attribute that will be get / set

  • name

    • The name to display in the UI for this input

  • description

    • An optional description to display as hint for the input in the UI

  • type

    • The type of the input. The following types are supported:

      • str - A text input

      • int - An integer number input

      • float - A decimal number input

      • bool - A switch input

      • select - A select dropdown with options

      • toggle - A binary toggle between two options

      • cell - A Notebook cell

Here is an example that creates a string input for the selection attribute:

{
    "attribute": "selection",
    "name": "Selection",
    "description": "MDAnalysis selection phrase",
    "type": "str",
},

Some of the input types take additonal keys as shown in the examples below:

A select dropdown with options:

{
    "attribute": "physical_property",
    "name": "Physical property",
    "description": "Physical property to analyze",
    "type": "select",
    "items": [
        "velocity",
        "position",
        "force",
    ],
},

A toggle option:

{
    "attribute": "x_type",
    "name": "X-axis",
    "type": "toggle",
    "options": [
        {"name": "Time", "value": "time"},
        {"name": "Step", "value": "step"},
    ],
},

The custom_code Widget uses the cell input type as shown below:

{
    "attribute": "setup_code",
    "name": "Setup code",
    "description": "This code will run once during widget creation",
    "type": "cell",
},

Here is an example of how the different inputs show up in the UI based on their type:

Custom Widget Inputs

The on_input_change() handler gets invoked for any input change made from the dasboard UI. Any validation errors raised by the handler will show up as errors in the UI as well.

Having inputs for the Widget is optional and the Widget class can choose to add them as they see fit.

Caution

Widgets will not be run as long as there are input errors as shown in the dasboard UI. Users will need to fix the inputs after which they will automatically run as configured.

Utils

The following utils are available for Widgets to create alerts and pause the simulation if required when any custom conditions are met in their code.

Automatic refresh

All existing instances of a given Widget are automatically refreshed (re-created) when that Widget class gets updated (typically through a Notebook cell execution in the dashboard). All existing inputs are retained as is. This allows updates to the Widget class code reflect immediately in existing Widget outputs.

Examples

Here is a simple Widget that has a single input made available in the UI to customize the MDAnalysis selection phrase and displays the center-of-mass of that selection every frame:

from mdadash.backend.widgets.base import WidgetBase

class CustomWidget(WidgetBase):
    name = "Custom Widget"
    _override_name = True

    _inputs = [
        {
            "attribute": "selection",
            "name": "Selection",
            "description": "MDAnalysis selection phrase",
            "type": "str",
        },
    ]

    def __init__(self):
        super().__init__()
        self.selection = "protein"

    def run_every_frame(self):
        com = self.u.select_atoms(self.selection).center_of_mass()
        print(f"COM of {self.selection} is ", com)

The _override_name attribute set to True is added in the class above to make any code changes to the above class update in real-time.

Here is an example of how this Widget shows up in the UI along with its output:

Custom Widget Output

All the Built-in Analysis Widgets use the exact same framework described here and the sources for these are examples of more complex use cases.


Tip

Custom Code built-in Widget provides a quick way to run simpler custom code.

Built-in Analysis Widgets can also be cloned into new Notebooks in the dasboard UI and customized as described in this document.

If you are adding a custom Widget that could be useful for others in the community, you can create a pull request to make it part of the Built-in Analysis Widgets.