Typical workflow ------------------- The *spring* module can be used to perform reconstructions in a standard *sequential* way, as described in the example :doc:`examples/Reconstruction`. It is, however, possible (and suggested) to use asynchronous execution via the :meth:`spring.MPR.runasync` method to get full advantage from running in an interactive `Jupyter `_ notebook, as presented in the example :doc:`examples/Async`. In this section we enter more into the details of this mode, with some insights into the internal structure of the *spring* module. Module import ^^^^^^^^^^^^^^ In the following examples, it is assumed that the following objects, i.e. :class:`spring.Pattern`, :class:`spring.Settings` and :class:`spring.MPR`, are imported. .. code-block:: python3 from spring import Pattern, Settings, MPR The control of asynchronous execution is achieved by importing the :data:`spring.hypervisor`. .. code-block:: python3 from spring import hypervisor as hv It is important to note that :data:`spring.hypervisor` is **not a type/class**, but it is already an instance of :class:`spring._Hypervisor`. Preparing data ^^^^^^^^^^^^^^ The step of data loading aims at creating a :class:`spring.Pattern` object that contains the experimental diffraction pattern. The usage is rather simple, as reported in :doc:`examples/Pattern`. In a typical workflow, it is convenient to create a function that loads the data from the desired file, which can be in different data formats, and returns a :class:`spring.Pattern` object ready for use. In the folder ``examples/data`` some exemplary diffraction patterns are prepared with a very simple structure. A function to load this type of data can be implemented as it follows: .. code-block:: python3 import numpy as np def load_data(filename): data = np.load(filename) # load raw data # create an instance of the Pattern object pattern = Pattern(pattern=data['pattern'], # diffraction data mask=data['mask'], # mask matrix, with values 0 or 1. Coordinates where mask==1 are excluded from the analysis center=data['center'], # coordinates of the center pid=data['pid'], # pulse id cropsize=1024, # size to which the pattern is cropped size=256, # size to which the cropped pattern is rescaled satvalue=3e4) # pixel value above which pixels are considered to be saturated (especially relevant for pnCCD data) return pattern The implementation of a *data loader* function is specific of the data format. The ``data`` subfolder in the examples contains the following files: .. code-block:: python3 import os print(*sorted(os.listdir("data"))) .. code-block:: console example_A_1.npz example_A_2.npz example_A_3.npz example_A_4.npz example_B_1.npz example_B_2.npz example_B_3.npz example_B_4.npz The example patterns can be now quickly loaded via the custom ``load_data`` function and the pattern displayed with: .. code-block:: python3 pattern = load_data("data/example_B_2.npz") pattern.plot() The algorithm settings should be also defined. For more details see the :doc:`settings example ` and the reference documentation of the :class:`spring.Settings` class. .. code-block:: python3 settings = Settings() Live status of the process ^^^^^^^^^^^^^^^^^^^^^^^^^^^ The methods ``hypervisor.livelog`` (see :meth:`spring._Hypervisor.livelog`) and ``hypervisor.liveplot`` (see :meth:`spring._Hypervisor.liveplot`) allow one to get live information about the process. When run in a specific IPython cell, they update the output of that cell over time. For the ``liveplot`` usage, see :doc:`this example `. The ``livelog`` output before running any reconstruction looks like the following: .. code-block:: python3 hv.livelog() .. code-block:: console - - - - - - - - - - - IDLE When a new reconstruction process is launched with ``MPR(pattern=pattern, settings=settings).runasync()``, the output of the ``livelog`` is updated and the log of the MPR process is shown, along with a change of the status from ``IDLE`` to ``RUNNING`` accompained by the pulse-id (``pid``) of the pattern under analysis and a progressbar that informs about the proceeding of the reconstruction steps. .. code-block:: console Selected GPUs: - NVIDIA GeForce RTX 3090 (Id: 0, load: 0%) Initializing solver... Done. Initializing algorithms... Done. Initializing population... Done. ... Running main loop ... - - - - - - - - - - - - - - - - - - - - - RUNNING 19-7-14541946795 [###-------] 34% .. hint:: The ``livelog`` and ``liveplot`` functions updates their output from time to time in the cell they were called from. This may lead to some *jumps* in the visualization. Furthermore, the user typically wants to have their output always visible independently on the position in the ``.ipynb`` file. It is possible to create a dedicated view on a specific cell output on JupyterLab by right clicking with the mouse on the specific cell and selecting "Create New View for Cell Output". This will create a new page which only displays the specific cell output, which can be put on the side and always visible by tiling. See `this example `_. It this then possible to hide the output of the live cell in the main document view by `clickng on the blue bar at the side of the cell output `_. Handling multiple reconstructions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In a typical workflow, the user wants to perform more than a single reconstruction. Several reconstructions can be achieved by multiple calls to the :meth:`spring.MPR.runasync` method. For example, a reconstruction has already started by using data loaded from the ``examples/data/example_A_1.npy`` file: .. code-block:: python3 MPR(pattern=load_data("data/example_A_1.npz"), settings=sett).runasync() The cell where the ``hv.livelog()`` was started will show something similar to: .. code-block:: console Selected GPUs: - NVIDIA GeForce RTX 3090 (Id: 0, load: 0%) Initializing solver... Done. Initializing algorithms... Done. Initializing population... Done. ... Running main loop ... - - - - - - - - - - - - - - - - - - - - - RUNNING 197-10-14558540587 [#---------] 9% When a new reconstruction is submitted, for example on ``example_A_2`` with the command .. code-block:: python3 MPR(pattern=load_data("data/example_A_2.npz"), settings=sett).runasync() The ``hv.livelog()`` will still show the same output about the running reconstruction, but with the additional information that one reconstruction has been queued .. code-block:: console Selected GPUs: - NVIDIA GeForce RTX 3090 (Id: 0, load: 0%) Initializing solver... Done. Initializing algorithms... Done. Initializing population... Done. ... Running main loop ... - - - - - - - - - - - - - - - - - - - - - RUNNING 197-10-14558540587 [###-------] 26% (1 queued) The queued job will be executed as soon as the currently running one is completed. There is no restrinction on the number of reconstructions that can be submitted, i.e. the length of the *execution queue* (apart from the total installed memory in the system). For example, the execution of .. code-block:: python3 for examplename in ["A_1", "A_2", "A_3", "A_4"]: MPR(pattern=load_data("data/example_"+ examplename+".npz"), settings=sett).runasync() will make the ``livelog`` update to: .. code-block:: console Selected GPUs: - NVIDIA GeForce RTX 3090 (Id: 0, load: 0%) Initializing solver... Done. Initializing algorithms... Done. Initializing population... Done. ... Running main loop ... - - - - - - - - - - - - - - - - - - - - - RUNNING 197-10-14558540587 [#---------] 10% (3 queued) while the execution of ``hv.info()`` will provide the following output: .. code-block:: console Running: 197-10-14558540587 Execution queue (3 total): 0: 250-23-14565026066 1: 71-1-14546865407 2: 19-7-14541946795 The call to :meth:`spring.MPR.runasync` (which is a wrapper to :meth:`spring._Hypervisor.append`) will put the reconstruction at the back of the execution queue. A reconstruction process can be put at the front of the execution queue with the :meth:`spring._Hypervisor.prepend` method, i.e. .. code-block:: python3 hv.prepend(MPR(pattern=load_data("data/example_B_1.npz"), settings=sett)) This will modify the output of ``hv.info()`` to: .. code-block:: console Running: 197-10-14558540587 Execution queue (4 total): 0: 190-1769647347 1: 250-23-14565026066 2: 71-1-14546865407 3: 19-7-14541946795 The execution of the current reconstruction can be interrupted via ``hv.stop()``. In this case, the next reconstruction in the queue is automatically started. Tagging reconstructions """"""""""""""""""""""" By default, reconstruction processes are identified by concatenatig their pid numbers (e.g. ``190-1769647347`` or ``250-23-14565026066``). However, it is sometimes necessary to perform multiple reconstruction procedures on the same diffraction data, for example to test the outcome as function of different values for the :class:`spring.Settings`. This can be achieved via the ``tag`` argument when creating the instance of :class:`spring.MPR`. This `tag`, if different from an empty string, is added to the reconstruction name (and the filename of the :class:`mpr.Result`). In the following example, four different patterns are analyzed, tested using three different starting support sizes, for a total of 12 processes: .. code-block:: python3 for examplename in ["A_1", "A_2", "A_3", "A_4"]: for ss in [80,100,120]: sett = Settings().set('init','supportsize',ss) MPR(pattern=load_data("data/example_"+ examplename+".npz"), settings=sett, tag="suppsize:"+str(ss)).runasync() The queue is, thus, the following: .. code-block:: python3 hv.info() .. code-block:: console Running: 197-10-14558540587-suppsize:80 Execution queue (11 total): 0: 197-10-14558540587-suppsize:100 1: 197-10-14558540587-suppsize:120 2: 250-23-14565026066-suppsize:80 3: 250-23-14565026066-suppsize:100 4: 250-23-14565026066-suppsize:120 5: 71-1-14546865407-suppsize:80 6: 71-1-14546865407-suppsize:100 7: 71-1-14546865407-suppsize:120 8: 19-7-14541946795-suppsize:80 9: 19-7-14541946795-suppsize:100 10: 19-7-14541946795-suppsize:120 Removing jobs from the execution queue """""""""""""""""""""""""""""""""""""" The :data:`spring.hypervisor` object allows for further simple operations on the execution queue, and in particular it allows for the removal of submitted reconstructions via the method :meth:`spring._Hypervisor.remove`. The ``hv.remove(item)`` method takes two different types as value for ``item``. In case of an **integer** ``i``, the element in position ``i`` of the queue given by ``hv.info()`` is removed. Please note that the item position in the queue may change while inputting the command. In case of an ``item`` of type **string**, the full name of the job has to be given, e.g. ``item='71-1-14546865407-suppsize:120'``. This is more safe, as it is independent on the position in the queue. .. note:: The ``hv.remove()`` method only works on the process sitting in the execution queue. To cancel the running process, ``hv.stop()`` (see :meth:`spring._Hypervisor.stop()`) can be used. The **string** given as ``item`` parameter allows for the use of simple *wildcards* (the semantics follow the `fnmatch `_ module of the Python standard library). For example, it is possible to remove multiple items from the previous example (four patterns with three different configurations each) using the wildcards as it follows: .. code-block:: python3 # remove all jobs whose pid start with 71- # (as the first number in the pid list is the run number # this is like saying "remove all reconstructions from Run 71") hv.remove("71-*") .. code-block:: console Removing 71-1-14546865407-suppsize:80 from queue Removing 71-1-14546865407-suppsize:100 from queue Removing 71-1-14546865407-suppsize:120 from queue .. code-block:: python3 # Remove all jobs whose name ends with "suppsize:100" hv.remove("*suppsize:100") .. code-block:: console Removing 197-10-14558540587-suppsize:100 from queue Removing 250-23-14565026066-suppsize:100 from queue Removing 19-7-14541946795-suppsize:100 from queue After these operations, the output of ``hv.info()`` looks like: .. code-block:: console Running: 197-10-14558540587-suppsize:80 Execution queue (5 total): 0: 197-10-14558540587-suppsize:120 1: 250-23-14565026066-suppsize:80 2: 250-23-14565026066-suppsize:120 3: 19-7-14541946795-suppsize:80 4: 19-7-14541946795-suppsize:120 It is possible to kill all the queued jobs **and** the running one with: .. code-block:: python3 hv.kill() # Equivalent to: # hv.remove("*") # hv.stop() .. code-block:: console Removing 197-10-14558540587 from queue Removing 250-23-14565026066 from queue Removing 71-1-14546865407 from queue Removing 19-7-14541946795 from queue Terminating running MPR process... Done