|
| 1 | +Adding Custom Widgets |
| 2 | +===================== |
| 3 | + |
| 4 | +Custom Widgets share the same underlying framework used by :doc:`built_in_widgets`. |
| 5 | + |
| 6 | +A Custom Widget has to derive from the :class:`~mdadash.backend.widgets.base.WidgetBase` |
| 7 | +base class and implement certain handlers as described here. |
| 8 | + |
| 9 | +Widget Registration |
| 10 | +------------------- |
| 11 | + |
| 12 | +A Widget class must have a unique ``name`` class attibute to be registered. |
| 13 | + |
| 14 | +.. code-block:: python |
| 15 | +
|
| 16 | + class CustomWidget(WidgetBase) |
| 17 | + name = "Custom Widget" |
| 18 | +
|
| 19 | +
|
| 20 | +An error is raised when the ``name`` class attribute is missing or if it exists but |
| 21 | +is the same as an already registered widget. The uniqueness of the ``name`` exists to |
| 22 | +prevent accidental overwrite of existing Widget classes. During testing or for use in |
| 23 | +Notebooks, an option is provided to force re-registraion of a Widget class if a |
| 24 | +``_override_name`` class attribute set to ``True`` exists in the class defintion. |
| 25 | +
|
| 26 | +In the example below, the ``CustomWidget`` class overrides the built-in |
| 27 | +:class:`~mdadash.backend.analyses.energies.AbsoluteTemperature` widget because it uses |
| 28 | +the same name "Absolute Temperature". |
| 29 | + |
| 30 | +.. code-block:: python |
| 31 | +
|
| 32 | + class CustomWidget(WidgetBase) |
| 33 | + name = "Absolute Temperature" |
| 34 | + _override_name = True |
| 35 | +
|
| 36 | +This also enables customization of :doc:`built_in_widgets` by cloning them in Notebooks |
| 37 | +and modifying them as needed. |
| 38 | + |
| 39 | +An optional ``description`` class attribute can be used to specify more details about the |
| 40 | +Widget and this gets displayed along with the name in the list of available Widgets in |
| 41 | +the dashboard UI. |
| 42 | + |
| 43 | +Run frequency and Run mode |
| 44 | +-------------------------- |
| 45 | + |
| 46 | +The :class:`~mdadash.backend.widgets.base.WidgetBase` base class specifies two attributes |
| 47 | +for all Widgets (defaults shown below): |
| 48 | + |
| 49 | +.. code-block:: python |
| 50 | +
|
| 51 | + _run_frequency = "every-frame" |
| 52 | + _run_mode = "serial" |
| 53 | +
|
| 54 | +``_run_frequency`` specifies how often the widget is run. It takes one of two values: |
| 55 | +``every-frame`` or ``batch``. |
| 56 | + |
| 57 | +``_run_mode`` specifies how the widget code is run. It takes one of two values: |
| 58 | +``serial`` or ``parallel``. |
| 59 | + |
| 60 | +By default, all Widgets run every frame serially (due to defaults above) unless the above |
| 61 | +attributes are customized. |
| 62 | + |
| 63 | +Both these attributes can be configured independent of each other. Which method(s) in |
| 64 | +the Widget class gets invoked depend on both these attributes as described below. |
| 65 | + |
| 66 | +.. note:: |
| 67 | + |
| 68 | + A Widget can make these attributes dynamically changeable at runtime as well by making |
| 69 | + them as `Inputs`_, which then show corresponding options in the UI. |
| 70 | + |
| 71 | +_run_frequency |
| 72 | +~~~~~~~~~~~~~~ |
| 73 | + |
| 74 | +This attribute specifies how often the widget is run. |
| 75 | + |
| 76 | +When ``_run_frequency`` is ``every-frame``, a method is invoked for every frame of the |
| 77 | +trajectory iteration. |
| 78 | + |
| 79 | +When ``_run_frequency`` is ``batch``, a method is invoked when a new batch of timesteps |
| 80 | +is full. A global "Buffer / batch size" under "Settings > Universe Configuration" in the |
| 81 | +dashboard controls the size of this timesteps buffer. |
| 82 | + |
| 83 | +The method that is invoked depends on the ``_run_mode``. |
| 84 | + |
| 85 | +If the ``_run_mode`` is ``parallel``, see the next section to see what gets invoked. |
| 86 | + |
| 87 | +If the ``_run_mode`` is ``serial``: |
| 88 | + |
| 89 | +* When ``_run_frequency`` is ``every-frame``, |
| 90 | + :meth:`~mdadash.backend.widgets.base.WidgetBase.run_every_frame` method is invoked. |
| 91 | + |
| 92 | +* When ``_run_frequency`` is ``batch``, |
| 93 | + :meth:`~mdadash.backend.widgets.base.WidgetBase.run_batch` method is invoked. |
| 94 | + |
| 95 | +_run_mode |
| 96 | +~~~~~~~~~ |
| 97 | + |
| 98 | +This attribute specifies how the widget analysis code is run. |
| 99 | + |
| 100 | +If the ``_run_mode`` is ``parallel`` for a given widget instance, a |
| 101 | +:meth:`~mdadash.backend.widgets.base.WidgetBase.get_parallel_job` method is invoked to |
| 102 | +retrieve the parallel job (a ``joblib.delayed`` tuple). A global "Parallel Jobs" under |
| 103 | +"Settings > Dashboard Configuration" in the dasboard controls the total number of jobs |
| 104 | +run in parallel during each iteration (``n_jobs`` param for ``joblib.Parallel`` call). |
| 105 | + |
| 106 | +If a widget has ``_run_mode`` as ``parallel``, after the parallel job is completed, a |
| 107 | +:meth:`~mdadash.backend.widgets.base.WidgetBase.apply_parallel_results` method is invoked |
| 108 | +where the results from the parallel job are passed back to the instance. The instance can |
| 109 | +apply the results back to its data structures (like updating it's values ``deque`` etc). |
| 110 | + |
| 111 | +If a widget has ``_run_mode`` as ``serial``, one of the methods described in the previous |
| 112 | +section are invoked. |
| 113 | + |
| 114 | +Lifecycle methods |
| 115 | +----------------- |
| 116 | + |
| 117 | +There are several lifecycle methods that Widgets can implement (handlers) and these get |
| 118 | +invoked by the dashboard framework at those stages. |
| 119 | + |
| 120 | +* :meth:`~mdadash.backend.widgets.base.WidgetBase.on_post_create` |
| 121 | +* :meth:`~mdadash.backend.widgets.base.WidgetBase.on_post_connect` |
| 122 | +* :meth:`~mdadash.backend.widgets.base.WidgetBase.on_post_disconnect` |
| 123 | +* :meth:`~mdadash.backend.widgets.base.WidgetBase.on_post_pause` |
| 124 | +* :meth:`~mdadash.backend.widgets.base.WidgetBase.on_pre_resume` |
| 125 | +* :meth:`~mdadash.backend.widgets.base.WidgetBase.on_input_change` |
| 126 | + |
| 127 | +Inputs |
| 128 | +------ |
| 129 | + |
| 130 | +Widgets can specify certain instance variables as inputs. These inputs show up in the |
| 131 | +dashboard UI allowing users to configure and modify them at runtime. |
| 132 | + |
| 133 | +An array of inputs is specified using the ``_inputs`` class attribute. Each item of this |
| 134 | +array is a dict that has at minimum the following keys: |
| 135 | + |
| 136 | +* ``attribute`` |
| 137 | + |
| 138 | + * The attribute that will be get / set |
| 139 | + |
| 140 | +* ``name`` |
| 141 | + |
| 142 | + * The name to display in the UI for this input |
| 143 | + |
| 144 | +* ``description`` |
| 145 | + |
| 146 | + * An optional description to display as hint for the input in the UI |
| 147 | + |
| 148 | +* ``type`` |
| 149 | + |
| 150 | + * The type of the input. The following types are supported: |
| 151 | + |
| 152 | + * ``str`` - A text input |
| 153 | + * ``int`` - An integer number input |
| 154 | + * ``float`` - A decimal number input |
| 155 | + * ``bool`` - A switch input |
| 156 | + * ``select`` - A select dropdown with options |
| 157 | + * ``toggle`` - A binary toggle between two options |
| 158 | + * ``cell`` - A Notebook cell |
| 159 | + |
| 160 | +Here is an example that creates a string input for the ``selection`` attribute: |
| 161 | + |
| 162 | +.. code-block:: python |
| 163 | +
|
| 164 | + { |
| 165 | + "attribute": "selection", |
| 166 | + "name": "Selection", |
| 167 | + "description": "MDAnalysis selection phrase", |
| 168 | + "type": "str", |
| 169 | + }, |
| 170 | +
|
| 171 | +Some of the input types take additonal keys as shown in the examples below: |
| 172 | + |
| 173 | +A select dropdown with options: |
| 174 | + |
| 175 | +.. code-block:: python |
| 176 | +
|
| 177 | + { |
| 178 | + "attribute": "physical_property", |
| 179 | + "name": "Physical property", |
| 180 | + "description": "Physical property to analyze", |
| 181 | + "type": "select", |
| 182 | + "items": [ |
| 183 | + "velocity", |
| 184 | + "position", |
| 185 | + "force", |
| 186 | + ], |
| 187 | + }, |
| 188 | +
|
| 189 | +A toggle option: |
| 190 | + |
| 191 | +.. code-block:: python |
| 192 | +
|
| 193 | + { |
| 194 | + "attribute": "x_type", |
| 195 | + "name": "X-axis", |
| 196 | + "type": "toggle", |
| 197 | + "options": [ |
| 198 | + {"name": "Time", "value": "time"}, |
| 199 | + {"name": "Step", "value": "step"}, |
| 200 | + ], |
| 201 | + }, |
| 202 | +
|
| 203 | +The :mod:`~mdadash.backend.analyses.custom_code` Widget uses the ``cell`` input type as |
| 204 | +shown below: |
| 205 | + |
| 206 | +.. code-block:: python |
| 207 | +
|
| 208 | + { |
| 209 | + "attribute": "setup_code", |
| 210 | + "name": "Setup code", |
| 211 | + "description": "This code will run once during widget creation", |
| 212 | + "type": "cell", |
| 213 | + }, |
| 214 | +
|
| 215 | +The :meth:`~mdadash.backend.widgets.base.WidgetBase.on_input_change` handler gets invoked |
| 216 | +for any input change made from the dasboard UI. Any validation errors raised by the |
| 217 | +handler will show up as errors in the UI as well. |
| 218 | + |
| 219 | +.. caution:: |
| 220 | + |
| 221 | + Widgets will not be run as long as there are input errors as shown in the dasboard UI. |
| 222 | + Users will need to fix the inputs after which they will automatically run as configured. |
| 223 | + |
| 224 | +Utils |
| 225 | +----- |
| 226 | + |
| 227 | +The following utils are available for Widgets to create alerts and pause the simulation |
| 228 | +if required when any custom conditions are met in their code. |
| 229 | + |
| 230 | +* :meth:`~mdadash.backend.widgets.base.WidgetBase.alert` |
| 231 | +* :meth:`~mdadash.backend.widgets.base.WidgetBase.pause_simulation` |
| 232 | + |
| 233 | + |
| 234 | +Automatic refresh |
| 235 | +----------------- |
| 236 | + |
| 237 | +All existing instances of a given Widget are automatically refreshed (re-created) when that |
| 238 | +Widget class gets updated (typically through a Notebook cell execution in the dashboard). |
| 239 | +All existing inputs are retained as is. This allows updates to the Widget class code reflect |
| 240 | +immediately in existing Widget outputs. |
| 241 | + |
| 242 | + |
| 243 | +---- |
| 244 | + |
| 245 | +.. tip:: |
| 246 | + |
| 247 | + :mod:`Custom Code <mdadash.backend.analyses.custom_code>` built-in Widget provides a |
| 248 | + quick way to run simpler custom code. |
| 249 | + |
| 250 | + :doc:`built_in_widgets` can also be cloned into new Notebooks in the dasboard UI and |
| 251 | + customized as described in this document. |
| 252 | + |
| 253 | + |
| 254 | +If you are adding a custom Widget that could be useful for others in the community, you can |
| 255 | +create a `pull request <https://github.com/MDAnalysis/mdadash/pulls>`_ to make it part of the |
| 256 | +:doc:`built_in_widgets`. |
0 commit comments