Definition of an algorithm for the OptimizerLoop

The definition of the optimization algorithm is done by way of plugin. The plugin can be a C++ plugin implemented in a dynamic library (.so file) or a Python plugin implemented in a Python module (.py). It is possible to implement two kinds of algorithm : synchronous or asynchronous.

Synchronous algorithm

In synchronous mode, the OptimizerLoop calls the algorithm to know what are the types of the input port (sample sent to the internal node), and of the output port (data returned by the internal node). Then it calls the algorithm to initialize it. At each iteration, it calls the algorithm to produce new sample or to stop the iteration. Finally, it calls the algorithm to finalize the optimization process.

A synchronous algorithm is implemented in a class derived from the base class OptimizerAlgSync with several methods that must be implemented and some optional methods (in C++ and in Python):

  • getTCForIn, this method must return the YACS type of the input port of the internal node
  • getTCForOut, this method must return the YACS type of the output port of the internal node
  • initialize (optional), this method is called during the algorithm initialization
  • start, this method is called at the beginning of iterations
  • takeDecision, this method is called at each iteration
  • finish (optional), this method is called to finish the algorithm at the end of the iteration process

In Python you need to implement another method:

  • setPool, this method is used to set the data pool that is used to exchange data

C++ plugin example

Here is a small example of a C++ synchronous algorithm:

#include <cmath>

#include "OptimizerAlg.hxx"

using namespace YACS::ENGINE;

extern "C"
{
  OptimizerAlgBase * createOptimizerAlgSyncExample(Pool * pool);
}

class OptimizerAlgSyncExample : public OptimizerAlgSync
  {
  private:
    int _idTest;
    TypeCode *_tcIn;
    TypeCode *_tcOut;
  public:
    OptimizerAlgSyncExample(Pool *pool);
    virtual ~OptimizerAlgSyncExample();
    TypeCode *getTCForIn() const;
    TypeCode *getTCForOut() const;
    void start();
    void takeDecision();
    void initialize(const Any *input) throw(YACS::Exception);
    void finish();
  };

OptimizerAlgSyncExample::OptimizerAlgSyncExample(Pool *pool)
  : OptimizerAlgSync(pool), _tcIn(0), _tcOut(0), _idTest(0)
{
  _tcIn=new TypeCode(Double);
  _tcOut=new TypeCode(Int);
}

OptimizerAlgSyncExample::~OptimizerAlgSyncExample()
{
  _tcIn->decrRef();
  _tcOut->decrRef();
}

//! Return the typecode of the expected input type
TypeCode * OptimizerAlgSyncExample::getTCForIn() const
{
  return _tcIn;
}

//! Return the typecode of the expected output type
TypeCode * OptimizerAlgSyncExample::getTCForOut() const
{
  return _tcOut;
}

//! Start to fill the pool with samples to evaluate
void OptimizerAlgSyncExample::start()
{
  _idTest=0;
  Any *val=AtomAny::New(1.2);
  _pool->pushInSample(4,val);
  val=AtomAny::New(3.4);
  _pool->pushInSample(9,val);
}

//! This method is called each time a sample has been evaluated.
/*!
 *  It can either add new samples to evaluate in the pool, do nothing (wait
 *  for more samples), or empty the pool to finish the evaluation.
 */
void OptimizerAlgSyncExample::takeDecision()
{
  if(_idTest==1)
    {
      Any *val=AtomAny::New(5.6);
      _pool->pushInSample(16,val);
      val=AtomAny::New(7.8);
      _pool->pushInSample(25,val);
      val=AtomAny::New(9. );
      _pool->pushInSample(36,val);
      val=AtomAny::New(12.3);
      _pool->pushInSample(49,val);
    }
  else if(_idTest==4)
    {
      Any *val=AtomAny::New(45.6);
      _pool->pushInSample(64,val);
      val=AtomAny::New(78.9);
      _pool->pushInSample(81,val);
    }
  else
    {
      Any *tmp= _pool->getCurrentInSample();
      if(fabs(tmp->getDoubleValue()-45.6)<1.e-12)
        _pool->destroyAll();
    }
  _idTest++;
}

//! Optional method to initialize the algorithm.
/*!
 *  For now, the parameter input is always NULL. It might be used in the
 *  future to initialize an algorithm with custom data.
 */
void OptimizerAlgSyncExample::initialize(const Any *input)
  throw (YACS::Exception)
{
}

/*!
 *  Optional method called when the algorithm has finished, successfully or
 *  not, to perform any necessary clean up.
 */
void OptimizerAlgSyncExample::finish()
{
}

//! Factory method to create the algorithm.
OptimizerAlgBase * createOptimizerAlgSyncExample(Pool *pool)
{
  return new OptimizerAlgSyncExample(pool);
}

Here, the entry point in the dynamic library is the name of the factory function : createOptimizerAlgSyncExample that returns an instance of the OptimizerAlgSyncExample class that implements the algorithm.

Python plugin example

Here, the same example of a synchronous algorithm in Python:

import SALOMERuntime

class myalgosync(SALOMERuntime.OptimizerAlgSync):
  def __init__(self):
    SALOMERuntime.OptimizerAlgSync.__init__(self, None)
    r=SALOMERuntime.getSALOMERuntime()
    self.tin=r.getTypeCode("double")
    self.tout=r.getTypeCode("int")

  def setPool(self,pool):
    """Must be implemented to set the pool"""
    self.pool=pool

  def getTCForIn(self):
    """returns typecode of type expected as Input"""
    return self.tin

  def getTCForOut(self):
    """returns typecode of type expected as Output"""
    return self.tout

  def initialize(self,input):
    """Optional method called on initialization. Do nothing here"""

  def start(self):
    """Start to fill the pool with samples to evaluate."""
    self.iter=0
    self.pool.pushInSample(4,1.2)
    self.pool.pushInSample(9,3.4)

  def takeDecision(self):
    """ This method is called each time a sample has been evaluated. It can
        either add new samples to evaluate in the pool, do nothing (wait for
        more samples), or empty the pool to finish the evaluation.
    """
    currentId=self.pool.getCurrentId()

    if self.iter==1:
      self.pool.pushInSample(16,5.6)
      self.pool.pushInSample(25,7.8)
      self.pool.pushInSample(36,9.)
      self.pool.pushInSample(49,12.3)
    elif self.iter==4:
      self.pool.pushInSample(64,45.6)
      self.pool.pushInSample(81,78.9)
    else:
      val=self.pool.getCurrentInSample()
      if abs(val.getDoubleValue()-45.6) < 1.e-12:
        self.pool.destroyAll()
    self.iter=self.iter+1

  def finish(self):
    """Optional method called when the algorithm has finished, successfully
       or not, to perform any necessary clean up. Do nothing here"""

Here, the entry point in the Python module is directly the name of the class that implements the algorithm : myalgosync.

Asynchronous algorithm

In asynchronous mode, all is the same except that after the initialization phase, the OptimizerLoop calls the algorithm only one time to start it in a separate thread.

An asynchronous algorithm is implemented in a class derived from the base class OptimizerAlgASync with several methods that must be implemented and some optional methods (in C++ and in Python):

  • getTCForIn, this method must return the YACS type of the input port of the internal node
  • getTCForOut, this method must return the YACS type of the output port of the internal node
  • initialize (optional), this method is called during the algorithm initialization
  • startToTakeDecision, this method is called to start the iteration process in a separate thread. It is the body of the algorithm.
  • finish (optional), this method is called to finish the algorithm at the end of the iteration process

In Python you need to implement another method:

  • setPool, this method is used to set the data pool that is used to exchange data

C++ plugin example

Here is a small example of a C++ asynchronous algorithm:

#include "OptimizerAlg.hxx"

using namespace YACS::ENGINE;

extern "C"
{
  OptimizerAlgBase * createOptimizerAlgASyncExample(Pool * pool);
}

class OptimizerAlgASyncExample : public OptimizerAlgASync
  {
  private:
    TypeCode * _tcIn;
    TypeCode * _tcOut;
  public:
    OptimizerAlgASyncExample(Pool * pool);
    virtual ~OptimizerAlgASyncExample();
    TypeCode * getTCForIn() const;
    TypeCode * getTCForOut() const;
    void startToTakeDecision();
  };

OptimizerAlgASyncExample::OptimizerAlgASyncExample(Pool * pool)
  : OptimizerAlgASync(pool), _tcIn(0), _tcOut(0)
{
  _tcIn = new TypeCode(Double);
  _tcOut = new TypeCode(Int);
}

OptimizerAlgASyncExample::~OptimizerAlgASyncExample()
{
  _tcIn->decrRef();
  _tcOut->decrRef();
}

//! Return the typecode of the expected input type
TypeCode *OptimizerAlgASyncExample::getTCForIn() const
{
  return _tcIn;
}

//! Return the typecode of the expected output type
TypeCode *OptimizerAlgASyncExample::getTCForOut() const
{
  return _tcOut;
}

//! This method is called only once to launch the algorithm.
/*!
 *  It must first fill the pool with samples to evaluate and call
 *  signalMasterAndWait() to block until a sample has been evaluated. When
 *  returning from this method, it MUST check for an eventual termination
 *  request (with the method isTerminationRequested()). If the termination
 *  is requested, the method must perform any necessary cleanup and return
 *  as soon as possible. Otherwise it can either add new samples to evaluate
 *  in the pool, do nothing (wait for more samples), or empty the pool and
 *  return to finish the evaluation.
 */
void OptimizerAlgASyncExample::startToTakeDecision()
{
  double val = 1.2;
  for (int i=0 ; i<5 ; i++) {
    // push a sample in the input of the slave node
    _pool->pushInSample(i, AtomAny::New(val));
    // wait until next sample is ready
    signalMasterAndWait();
    // check error notification
    if (isTerminationRequested()) {
      _pool->destroyAll();
      return;
    }

    // get a sample from the output of the slave node
    Any * v = _pool->getCurrentOutSample();
    val += v->getIntValue();
  }

  // in the end destroy the pool content
  _pool->destroyAll();
}

//! Factory method to create the algorithm.
OptimizerAlgBase * createOptimizerAlgASyncExample(Pool * pool)
{
  return new OptimizerAlgASyncExample(pool);
}

Here, the entry point in the dynamic library is the name of the factory function : createOptimizerAlgASyncExample that returns an instance of the OptimizerAlgASyncExample class that implements the algorithm.

Python plugin example

Here is an example of an asynchronous algorithm implemented in Python:

import SALOMERuntime

class myalgoasync(SALOMERuntime.OptimizerAlgASync):
  def __init__(self):
    SALOMERuntime.OptimizerAlgASync.__init__(self, None)
    r=SALOMERuntime.getSALOMERuntime()
    self.tin=r.getTypeCode("double")
    self.tout=r.getTypeCode("int")

  def setPool(self,pool):
    """Must be implemented to set the pool"""
    self.pool=pool

  def getTCForIn(self):
    """returns typecode of type expected as Input"""
    return self.tin

  def getTCForOut(self):
    """returns typecode of type expected as Output"""
    return self.tout

  def startToTakeDecision(self):
    """This method is called only once to launch the algorithm. It must
       first fill the pool with samples to evaluate and call
       self.signalMasterAndWait() to block until a sample has been
       evaluated. When returning from this method, it MUST check for an
       eventual termination request (with the method
       self.isTerminationRequested()). If the termination is requested, the
       method must perform any necessary cleanup and return as soon as
       possible. Otherwise it can either add new samples to evaluate in the
       pool, do nothing (wait for more samples), or empty the pool and
       return to finish the evaluation.
    """
    val=1.2
    for iter in xrange(5):
      #push a sample in the input of the slave node
      self.pool.pushInSample(iter,val)
      #wait until next sample is ready
      self.signalMasterAndWait()
      #check error notification
      if self.isTerminationRequested():
        self.pool.destroyAll()
        return

      #get a sample from the output of the slave node
      currentId=self.pool.getCurrentId()
      v=self.pool.getCurrentOutSample()
      val=val+v.getIntValue()

    #in the end destroy the pool content
    self.pool.destroyAll()

Here, the entry point in the Python module is directly the name of the class that implements the algorithm : myalgoasync.

C++ algorithm calling Python code

In some cases, it can be necessary to implement the algorithm as a C++ class but nevertheless to call some Python code from this class. This is also possible with the OptimizerLoop and even quite simple. To achieve this, your C++ class should inherit from PyOptimizerAlgSync for a synchronous algorithm or from PyOptimizerAlgASync for an asynchronous algorithm. The guidelines for developing the algorithm are the same as in the C++ case, but you can also call any method from the Python C API. You don’t need to take care of the Python global interpreter lock or of thread states because this is already done in the PyOptimizerAlg classes. An example of this kind of algorithm is the class OpenTURNSScriptLauncher that can be found in the module OPENTURNS_SRC.