Optimizer Return Type
*********************

class MinimizeResult(success=False, x=None, fx=inf, stats=None, origin=None)

   The return value of "BaseOptimizer" classes. The results of an
   optimization can be accessed by:

   Attributes:
   success : bool
      Whether the optimization was successful or not.

   x : Sequence[float]
      The optimized parameters.

   fx : float
      The corresponding function value of "x".

   stats : Dict[str, Any]
      Dictionary of various statistics related to the optimization.

   origin : Dict[str, Any]
      Dictionary with configurations details of the optimizer which
      produced the result.


Abstract Base Optimizer
***********************

class BaseOptimizer(_opt_id=None, _signal_pipe=None, _results_queue=None, _pause_flag=None, _is_log_detailed=False, _workers=1, _backend='threads', **kwargs)

   Abstract base class for optimizers used within the GloMPO
   framework. Cannot be used directly, must be superclassed by child
   classes which implement a specific optimization algorithm.

   Attention:

     To Ensure GloMPO Functionality:

     1. Messages to the GloMPO manager must be sent via
        "message_manager()".

     2. Messages from the manager must be read by "check_messages()"
        which executes "BaseOptimizer" methods corresponding to the
        signals. The defaults provided in the "BaseOptimizer" class
        are generally suitable and should not need to be overwritten!
        The only methods which must implemented by the user are:

        1. "minimize()" which is the algorithm specific optimization
           loop;

        2. "callstop()" which interrupts the optimization loop.

     3. The statement "self._pause_signal.wait()" must appear
        somewhere in the body of the iterative loop to allow the
        optimizer to be paused by the manager as needed.

     4. Optional: the class should be able to handle resuming an
        optimization from any point using "checkpoint_save()" and
        "checkpoint_load()".

   Tip:

     The "TestSubclassGlompoCompatible" test in "test_optimizers.py"
     can be used to test that an optimizer meets these criteria and is
     GloMPO compatible. Simply add your optimizer to
     "AVAILABLE_CLASSES" there.

   Parameters:
   _opt_id
      Unique optimizer identifier.

   _signal_pipe
      Bidirectional pipe used to message management behaviour between
      the manager and optimizer.

   _results_queue
      Threading queue into which optimizer iteration results are
      centralised across all optimizers and sent to the manager.

   _pause_flag
      Event flag which can be used to pause the optimizer between
      iterations.

   _is_log_detailed
      See "is_log_detailed".

   _workers
      The number of concurrent calculations used by the optimizer.
      Defaults to one. The manager will only start the optimizer if
      there are sufficient slots available for it.

   _backend
      The type of concurrency used by the optimizers (processes or
      threads). This is not necessarily applicable to all optimizers.
      This will default to "'threads'" unless forced to use
      "'processes'" (see "GloMPOManager.setup()" and Parallelism).

   "**kwargs"
      Optimizer specific initialization arguments.

   Notes:
   The user need not concern themselves with the particulars of the
   "_opt_id", "_signal_pipe", "_results_queue", "_pause_flag",
   "_workers" and "_backend" parameters. These are automatically
   generated by the manager.

   Important:

     Make sure to call the superclass initialization method *as the
     first statement* when creating your own optimizers:

        super().__init__(_opt_id,
                         _signal_pipe,
                         _results_queue,
                         _pause_flag,
                         _is_log_detailed
                         _workers,
                         _backend,
                         **kwargs)

   Attributes:
   incumbent : Dict[str, Any]
      Dictionary with keys "'x'" and "'fx'" which contain the lowest
      function value and associated parameter vector seen thus far by
      the optimizer.

   is_log_detailed : bool
      If "True":

      1. When the task's "__call__()" method is called, its
         "detailed_call()" method will actually be evaluated.

      2. All the return values from "detailed_call()" will be added to
         the log history of the optimizer.

      3. The function itself will only return the function value (as
         if the "__call__()" method had been used).

      Note:

        This will *not* result in a doubling of the computational time
        as the original call will be intercepted. This setting is
        useful for cases where optimizers do not need/cannot handle
        the extra information generated by a detailed call but one
        would still like the iteration details logged for analysis.

   logger : "logging.Logger"
      Logger instance into which status messages may be added.

   workers : int
      Maximum number of threads/processes the optimizer may use for
      evaluating the objective function.

   _scaler : str
      Optional class attribute flagging that the optimizer is only
      compatible with a specific scaling:

      * "'linear'": All parameters scaled [0, 1];

      * "'std'": All parameters scaled [-1, 1];

      * "'none'": No scaling must be used.

      * If not set, then any scaling may be used.

   property is_restart

      "True" if the optimizer is loaded from a checkpoint.

   property opt_id

      The unique GloMPO generated identification number of the
      optimizer.

   classmethod checkpoint_load(path, **kwargs)

      Recreates an optimizer from a saved snapshot.

      Parameters:
      path
         Path to checkpoint file from which to build from. It must be
         a file produced by the corresponding "checkpoint_save()"
         method.

      "**kwargs"
         See "__init__".

      Notes:
      This is a basic implementation which should suit most
      optimizers; may need to be overwritten.

   abstract minimize(function, x0, bounds)

      Run the optimization algorithm to minimize a function.

      Parameters:
      function
         Function to be minimized. See "BaseFunction" for an API
         guide. In the context of ParAMS, "function" is automatically
         produced by the
         *scm.params.core.parameteroptimization.Optimization*
         instance, and behaves like a regular callable such that "fx =
         function(x)", returning the loss function value "fx" to the
         parameter set "x".

      x0
         The initial optimizer starting point. In the context of
         GloMPO, it is provided by "BaseGenerator" objects.

      bounds
         Min/max boundary limit pairs for each element of the input
         vector to the minimisation function.

         Note:

           Even though your optimizer might not support lower and
           upper parameter bounds, ParAMS internally enforces the
           constraints as defined through the *Parameter Interface* by
           returning "float('inf')" whenever a candidate is outside of
           the bounded space.

   check_messages()

      Processes and executes manager signals from the manager.

      Warning:

        This implementation has been very carefully structured to
        operate as expected by the manager. Should be suitable for all
        optimizers. *Should not* be overwritten.

      Returns:
      List[int]
         Signal keys received by the manager during the call.

   message_manager(key, message=None)

      Sends arguments to the manager.

      Caution:

        Should not be overwritten.

      Parameters:
      key
         Indicates the type of signal sent. The manager recognises the
         following keys:

         0: The optimizer has terminated normally according to its own
         internal convergence conditions.

         1: Confirm that a pause signal has been received from the
         manager and the optimizer has complied with the request.

         9: General message to be appended to the optimizer's log.

      message
         Message to be appended when sending signal 9.

   abstract callstop(reason)

      Breaks out of the "minimize()" minimization loop.

   checkpoint_save(path, force=None, block=None)

      Save current state, suitable for restarting.

      Parameters:
      path
         Path to file into which the object will be dumped. Typically
         supplied by the manager.

      force
         Set of variable names which will be forced into the dumped
         file. Convenient shortcut for overwriting if fails for a
         particular optimizer because a certain variable is filtered
         out of the data dump.

      block
         Set of variable names which are typically caught in the
         construction of the checkpoint but should be excluded. Useful
         for excluding some properties.

      Notes:
      1. Only the absolutely critical aspects of the state of the
         optimizer need to be saved. The manager will resupply
         multiprocessing parameters when the optimizer is
         reconstructed.

      2. This method will almost never be called directly by the user.
         Rather it will be called (via signals) by the manager.

      3. This is a basic implementation which should suit most
         optimizers; may need to be overwritten. Typically it is
         sufficient to call the super method and use the "force" and
         "block" parameters to get a working implementation.

   inject(x, fx)

      Updates the "incumbent" with a better solution from the manager.
