Running pydidas applications#

Pydidas applications are designed to be runnable both in serial and parallel modes. This requires a different syntax from the user which will be presented below.

Note

For a detailed description of the internal workings of applications please refer to the Developers guide to pydidas applications section.

Configuring an application#

pydidas applications are standard pydidas objects and their configuration is handled using pydidas Parameters. A short usage guide has already been given in Accessing Parameters.

Some applications might have additional properties or methods to further interact with the user, but these will be covered in the specific manuals.

As an example, let us use a DummyApp which creates a list with random strings and takes two parameters for the length of each string and the total number of strings.

Warning

The DummyApp is not a real object in pydidas. This example cannot be run in a real python console.

>>> import pydidas
>>> app = pydidas.apps.DummyApp()
>>> app.get_param_keys()
['number_of_strings', 'length_of_string']
>>> app.set_param_value('number_of_strings', 10)
>>> app.set_param_value('length_of_string', 10)

Running an application serially#

To run an app serially, simply call the run method of the app. Note that this will be a blocking call until the application is finished with its processing.

>>> import pydidas
>>> app = pydidas.apps.DummyApp()
>>> app.set_param_value('number_of_strings', 10)
>>> app.set_param_value('length_of_string', 10)
>>> app.run()
>>> app.get_results()
['yeOhsEodCq',
 'gnqZKRxHcK',
 'SbEbVlyjgx',
 'gcrNYzGUQY',
 'NnfLeTcXbS',
 'xoweEcFgqs',
 'ujHXTPsWyh',
 'XoOkaRqvIv',
 'ewrMXWBpdG',
 'TurPkkywwJ']

Running an application using parallelization#

Note

For a detailed description of how the pydidas multiprocessing works, please refer to the The multiprocessing sub-package or to the Developers guide to pydidas multiprocessing.

To run an application using parallel processing, an additional object is needed to control the parallelization. This is the AppRunner. Usage is straightforward and happens through only a few commands. The AppRunner starts in a separate thread and is non-blocking. It launches multiple independent processes and can run in the background.

To run an application, first configure the application as usually. Then, create an AppRunner instance with the configured application as calling argument. The app instance in the AppRunner is not directly accessible but the user can use the runner’s call_app_method method to call a method of the app or the set_app_param method to modify one of the application’s parameters.

Warning

Starting the AppRunner will create a new instance of the application and any changes made to the local instance will not be mirrored in the AppRunner’s app instance.

Running an application with the :py:class`AppRunner <pydidas.multiprocessing.AppRunner>` requires to call the start method. This will start the thread and create the worker processes.

Warning

Running apps which rely on Qt’s Signals and Slots (like the ExecuteWorkflowApp) will require a running QApplication event loop. Also do not forget to stop the event loop when finished.

An example with a custom application is given below:

import random
import string

import numpy as np
from qtpy import QtWidgets

import pydidas


#define CHARS to create random strings
CHARS = string.ascii_letters + string.digits


class DummyApp(pydidas.core.BaseApp):
    default_params = pydidas.core.ParameterCollection(
        pydidas.core.Parameter('number_of_strings', int, 10),
        pydidas.core.Parameter('length_of_strings', int, 10),
    )

    def multiprocessing_pre_run(self):
        self.results = {}
        pydidas.core.BaseApp.multiprocessing_pre_run(self)

    def multiprocessing_get_tasks(self):
        """Get the dummy tasks."""
        return np.arange(self.get_param_value('number_of_strings'))

    def multiprocessing_func(self, index):
        """Create a random string."""
        length = self.get_param_value('length_of_strings', 10)
        return ''.join(random.choice(CHARS) for _ in range(length))

    def multiprocessing_store_results(self, index, result):
        self.results[index] = result


def main():
    qtapp = QtWidgets.QApplication.instance()
    app = DummyApp()
    runner = pydidas.multiprocessing.AppRunner(app)

    # Now connect the AppRunner's emitted results to our local app:
    runner.sig_results.connect(app.multiprocessing_store_results)

    # Also make sure to terminate the QApplication event loop after
    # finishing the calculations:
    runner.finished.connect(qtapp.quit)

    # Start the runner and the QApplication event loop:
    runner.start()
    qtapp.exec_()

    print('Resulting random strings:')
    for _key, _val in app.results.items():
        print(f'  {_key:02d}: {_val}')


if __name__ == '__main__':
    main()

Running this script will create an output like:

Resulting random strings:
  00: EgLDoQCBto
  01: tbE07t910T
  02: JpmZZJ6QjL
  03: YTgKKtLWqP
  04: vvFAuJ8W9d
  05: HFLVkWt1Gm
  06: kPjP2pWA6Z
  07: AHiPGRADWa
  08: 1vFmmMM2mZ
  09: BSrpoTxzOg

For status updates, one could use the AppRunner.sig_process Signal. An example with a new printing slot and an updated main function is given below:

def print_progress(progress):
    """Print the current progress repeatedly in the same line."""
    _n_chars = int(60 * progress)
    _txt = (
        "\u2588" * _n_chars
        + "-" * (60 - _n_chars)
        + "|"
        + f" {100*progress:05.2f}%: "
    )
    print(_txt, end='\r', flush=True)

def main():
    qtapp = QtWidgets.QApplication.instance()
    app = DummyApp()
    runner = pydidas.multiprocessing.AppRunner(app)

    # Now connect the AppRunner's emitted results to our local app:
    runner.sig_results.connect(app.multiprocessing_store_results)

    # Also make sure to terminate the QApplication event loop after
    # finishing the calculations:
    runner.finished.connect(qtapp.quit)

    # Connect also the AppRunner.sig_progress to the print_progress
    # function:
    runner.sig_progress.connect(print_progress)

    # Start the runner and the QApplication event loop:
    runner.start()
    qtapp.exec_()

    print('\nResulting random strings:')
    for _key, _val in app.results.items():
        print(f'  {_key:02d}: {_val}')