.. _sec-jumpController:

Jump Controller in Pele++ control file
======================================

This page documents what is also known as the *jumping* or *spawning* functionality of Pele++.

The jump controller is the responsible to change the explorer
coordinates if some conditions are met in the task. Thus, the block is
defined at Task level. 

If you use the jumping functionality, you may want to enable the controller log file through the :ref:`PELE_Output controllerEventsLogPath <sec-peleOutput-controllerEventsLogPath>` option.

Example
-------

.. code-block:: json

   "jumpIf":
   [
        "diff_rmsd1 > 1.1",
        "diff_rmsd2 > 0.5"
   ],
       
   "jumpController":{
       "strategyToChooseBestExplorer":{
           "type": "bestHasMinimumValueForOneSensor",
           "sensorTag": "rmsd1"        
       },
       
       "jumpStrategy": {
           "type": "jumpToBestExplorerCoordinates"
       }
   }

jumpIf section
--------------

The jumpIf clause contains the jumping
:ref:`conditions <sec-conditionsByExample>`. If any
:ref:`condition <sec-conditionsByExample>` is met,
the system's state will change.

There are two kinds of conditions:

-  Absolute condition. We compare the value of the sensor to a certain
   value. The sensor is specified with a tag.
-  Relative condition. We compare the sensor's current value to the best
   one so far in the whole simulation. We write it: "diff\_" + tag.

.. note::

   For a relative condition, comparing to the best one so far in the whole 
   simulation implies that a given explorer (or the single explorer, in the
   serial version of PELE++) may compare its current value to a value
   already generated by that explorer in the past. It also means that an
   explorer does not necessarily compare its current value to the best
   value of the current state of any other explorer, but to the best value
   in the whole simulation.


.. note::

  In the current version it is not possible to mix absolute and
  relative conditions:

  .. code-block:: json

    "jumpIf":
    [
        "rmsdSelection > 1.1 and diff_rmsd1 > 0.5"
    ]

Example1
^^^^^^^^

.. code-block:: json

   "jumpIf":[ "rmsdSelection > 1.1" ]

Example2
^^^^^^^^

.. code-block:: json

   "jumpIf":[ "diff_rmsd1 > 0.5" ]

jumpController section
----------------------

The jumpController section is comprised of two configuration blocks:

-  strategyToChooseBestExplorer: the way we choose the best
   explorer/step in the simulation.
-  jumpStrategy: the way we jump after the jumpIf condition is met.

Example
^^^^^^^

.. code-block:: json

   "jumpController":{
       "strategyToChooseBestExplorer":{
           "type": "bestHasMinimumValueForOneSensor",
            "sensorTag": "rmsd1"
       },
       "jumpStrategy": {
           "type": "jumpToBestExplorerCoordinates"
       }
   }

Types of strategyToChooseBestExplorer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

-  bestHasMinimumValueForOneSensor: The best explorer will have the
   smaller value for one sensor. The sensor is specified with
   "sensorTag".

   .. code-block:: json

      "strategyToChooseBestExplorer":{
          "type": "bestHasMinimumValueForOneSensor",  
          "sensorTag": "rmsd1"  
      }

-  bestHasMinimumValueForWeightedSensors: The best explorer is
   calculated using more than one sensor, each one with a certain weight
   associated to it. It is specified with a block named "sensorWeights"
   that consists of the pairs `tag:weight`.

   .. code-block:: json

      "strategyToChooseBestExplorer":{
          "type":"bestHasMinimumValueForWeightedSensors",  
          "sensorWeights":{
              "rmsd1": 1,
              "rmsd2": 2,
              "rmsd3": 4  
          }  
      }

   We compare the current explorer with the best one by assigning a
   score. For all the sensors, we subtract "weight" to the score of the
   explorer with the minimum value for that sensor. The explorer with
   the smallest score is the best one. Ex: Using the weights of the
   previous example. Explorer1: rmsd1 = 1.12, rmsd2 = 0.024, rmsd3 =
   1.10. Explorer2: rmsd1 = 1.14, rmsd2 = 0.015, rmsd3 = 1.10. Explorer1
   score = -1 - 0 - 0 = -1. Explorer2 score = -0 - 2 - 0= -2. Hence,
   Explorer2 is better than Explorer1.

Types of jumpStrategy
^^^^^^^^^^^^^^^^^^^^^

-  jumpToBestExplorerCoordinates: In case of jump, it will jump to the
   coordinates of the best explorer.

   .. code-block:: json

      <pre>
      "jumpStrategy": {
          "type": "jumpToBestExplorerCoordinates"   
      }

MPI vs Serial execution
-----------------------

The "strategyToChooseBestExplorer" in MPI chooses the best result so far
of all the explorers. In Serial version, the best explorer is the best
step of the serial execution.

Who takes the decisions
-----------------------

A jump can be caused by two factors:

-  The explorer decides to jump: After evaluating its own sensors, an
   explorer decides that it has gone too far, so it asks the explorer
   for jumping.

-  The controller forces a jump: The controller compares the values of
   the sensors for a given explorer against the values of the best one.
   If the difference is too big, the controller will make the given
   explorer jump.

This can also be said as the following: although all the jump conditions
are listed in the "jumpIf" sections, the conditions which not involves a
comparison between the best one's values will be evaluated by the
explorer, who will be who takes the decision of either jumping or not.
Those are the conditions not prefixed by "diff\_". The conditions which
actually have a "diff\_" prefix will be the ones that the controller,
and only the controller, will evaluate in order to decide if an explorer
has to jump.

Jumping (Spawning)
------------------

With the previous tools we can make explorers spawn closer or further to
a certain point.

Example: Bringing the ligand close to a point.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

We need to define the distance of the ligand to the point of interest.
In the :ref:`metrics block <sec-metricTypes>` of the same task
we add:

.. code-block:: json

   "metrics": [
       {
           "type": "distanceToPoint",
           "point": [ 0.3, 0.1, 0.5 ],
           "atoms": {
           "links": {
               "ids": [
               "L:571"
               ]
           }
           },
           "tag": "distance"
       },
       ...
   ]

In the same task, we add the "jumpIf" block:

.. code-block:: json

   "jumpIf": [
       "diff_distance > 10"
   ],
   "jumpController": {
       "strategyToChooseBestExplorer": {
           "type": "bestHasMinimumValueForOneSensor",
           "sensorTag": "distance"
       },
       "jumpStrategy": {
           "type": "jumpToBestExplorerCoordinates"
       }
   }

This way, when a explorer is :math:`10 \AA{}` further to the point than the closest
explorer (i.e. :math:`| \Delta \text{distance}| > 10 \AA{}`, where :math:`\Delta \text{distance} = \text{CurrentExplorer_distance} - \text{BestExplorer_distance}`), it jumps to the
best explorer coordinates. It is worth emphazising that diff\_distance
is NOT the distance between the two explorers.

Example: Moving the ligand away from a point.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The example is essentialy the same as the previous example. The only
difference is that now the best explorer is the one that is the furthest
away.The jump controller is now:

.. code-block:: json

   "jumpIf": [
       "diff_distance > 10"
   ],
   "jumpController": {
       "strategyToChooseBestExplorer": {
           "type": "bestHasMaximumValueForOneSensor",
           "sensorTag": "distance"
       },
       "jumpStrategy": {
           "type": "jumpToBestExplorerCoordinates"
       }
   }