Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Developing guide

magnific0 edited this page Feb 24, 2014 · 9 revisions

Intro

Developing PaGMO can freak you out sometimes, but remember: don't panic! There is an answer to life, the universe and everything - even to PaGMO! This developing guide is a tiny contribution to help you solve all the "how do I do xyz?" and "where is alpha-beta-gamma?" questions. It also gives you checklists for implementing common features. So have a look here if you are stuck or want to know more. If you feel something is really missing in this guide and would help your co-developers avoid severe frustration, please remember that this is a wiki! the main idea of a wiki is that people put their stuff in it, right? So don't be shy and have some fun.

HOWTO: Program new problems into PaGMO (C++)

First: consider creating a new git branch for developing the problem. It makes it easier to keep track on your changes and no one will interfere with your coding.

Next, search the already implemented problems in src/problem to find one that might be similar to the one you want to program. Clone the .cpp and the .h of that problem. Lets assume that is rastrigin for the remainder as this problem has the minimum interface you need. Do now some basic refactoring (changing occurences of 'rastrigin' to your problem name).

Lets discuss the header-file. The following headers should always be included, so leave them.

#include "../config.h"
#include "../serialization.h"
#include "../types.h"
#include "base.h"

Above them you can include your boost-stuff, if you need it. below, usually the namespace starts with:

namespace pagmo { namespace problem { 

Next lines should be documentation for the problem, describing where you find its definition and what its all about. We use these comments do create our documentation with doxygen, so it is a must have for every problem.

Next, the class definition starts with something like:

class __PAGMO_VISIBLE yourclassname: public base

Here we declare all the functions and stuff. Think about, what needs to be public, protected and private. There is something very important hidden here which is the serialization. PaGMO is designed to perform distributed computing so it needs mechanisms to serialize all its objects in a proper way. For this, it uses the Boost-library and it looks like this in the end:

friend class boost::serialization::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int)
{
    ar & boost::serialization::base_object<base>(*this);
}

IMPORTANT: You definitely want this to stay there and you definitely want to add all member variables that you might use in your problem to ;r. If you don't, you introduce the most weirdest bugs you can imagine. Everything will still compile, but as soon as PaGMO needs to serialize things, it will die or behave weirdly and you will be super confused. Most of the time we produce bugs, its because we forget about the serialization, so you should really check this out. So, lets assume you need some membervariables to save problem-dependent stuff. By naming convention, member variables should precede with m_ and remain private. So, lets add them:

void serialize(Archive &ar, const unsigned int)
{
    ar & boost::serialization::base_object<base>(*this);
    ar & const_cast<int &>(m_spam);
    ar & m_eggs;
}
const int m_spam;
mutable std::vector<double> m_eggs;

Please note that we need to cast the const member variable and have to declare m_eggs as mutable if we intend on changing it during evolution. Your problem will usually not change during evolution, but if you code an algorithm, it may maintain some information in m_eggs that are updated on each evolutionary step. Its just like this...

Next for some refactoring in the .cpp: implement all the functions and methods you like, but what really counts are two methods that you necessarily need to implement: clone and objfun_impl. clone is the polymorphic copy-constructor and is usually easy to adapt. objfun_impl is the heart of your problem: it computes the fitness vector of the corresponding decision vector. It is usually called all over from the optimizers, so you definitely want to make this method as efficient, clean and correct as possible.

After you finished implementing the objective function, you can add more nice things like get_name or human_readable_extra to enhance the user experience. Depending on your problem, you also might want to override the base-implementations of several other methods, i.e. like compute_constraints_impl, which you need for constrained optimization problems. For deeper enlightment you should check out the documentation of the base problem class, from which every problem is derived in PaGMO.

Finished? Then Lets add your new problem to problems.h under /src! Next we need to add your code to the cmake-file so that it will be compiled. Open /src/CMakeLists.txt and add you problemfile to the list. Should like like

${CMAKE_CURRENT_SOURCE_DIR}/problem/verydifficultproblem.cpp

now move to /build and

ccmake ../.

configure and generate the makefiles and start the compiler

make

Done! If everything worked fine, you will be able to use your problem within C++. You can try out in the main.cpp of PaGMO:

pagmo::problem::verydifficultproblem prob(42);

should do. Note that you will not see your problem within PyGMO! If you want your problem to be accessible via the Python interface, you have to expose your problem to PyGMO! There is another entry in this guide for doing the Python-bindings, so keep on reading.

Oh, but one last thing: we are not big in automated testing, but we do have some, so please add your problem to tests/serialization_problems.cpp. It basically tests if the cumbersome serialization part I was talking about works as supposed. Add your problem to the long list - should look like:

probs_new.push_back(problem::verydifficultproblem(1,2,3).clone());
probs.push_back(problem::verydifficultproblem(4,5,6).clone());

Note that we should use different parameters here! In general you should avoid the default parameters while dealing with serialization and serialization tests since some processes in PaGMO assume default parameter if serialization fails, which might hide the problems and bugs from you.

You can run these tests from the /build directory with

make test

Check and celebrate, if you have not forgotten something. If you have, don't worry - happens to all of us.

HOWTO: Program new algorithms into PaGMO (C++)

You can almost blindly apply everything written under the previous HOWTO to the programming of algorithms as well. You will find them in src/algorithm and you can also clone one as a starting point. Recommended headers in the .h-file are

#include "../config.h"
#include "base.h"
#include "../population.h"
#include "../types.h"
#include "../serialization.h"

Usually, your algorithm will have some parameters to tune or you might read out some internal state variables. For this you should provide getter and setter methods like void set_xyz(const int foo); and int get_xyz() const;. Internally, the membervariable saving xyz should be named something like m_foo and should be declared as private!

Also keep in mind that we do have to do the serialization here in the same fashion as with the problems. Do not forget to add your membervariables to the archive, or the superheroic parallelization mechanisms of PaGMO will fail.

Now for the .cpp: it is pretty much the usual refactoring. The main-part of your work will be changing the evolve-method:

void superfancyalgorithm::evolve(population &pop) const

This method operates on a population and usually changes the chromosomes of the individuals. If you do not know what to do with a population object, you can always check our documentation or look at already written code. For example, a common task is to extract the chromosomes and corresponding fitnessvalues. This can be done like this:

std::vector<decision_vector> chromosomes;
std::vector<fitness_vector> fitness;
for (std::vector<double>::size_type i = 0; i < pop.size(); ++i) {
    chromosomes.push_back(pop.get_individual(i).cur_x);
    fitness.push_back(pop.get_individual(i).cur_f);
}

You will figure out, how this works and if not don't be afraid to ask someone. Remember that at the end of the evolve-method, you should have changed the pop-parameter that was given to the method.

When you are finished, add the algorithm the /src/CMakeLists.txt and build it like in the previous HOWTO.

Of course, we also have serialization tests for our algorithms, so don't forget to add yours to tests/serialization_algorithms.cpp!

HOWTO: Do the Python bindings

PyGMO is the good soul of PaGMO which gives you all the nice interface you can use in your interactive python session. Would be a pity if we would hide all the glorious functionality of PaGMO without exposing it to Python, right? Problems are exposed in

src/PyGMO/problem/problem.cpp

algorithms in

src/PyGMO/algorithm/algorithm.cpp

topologies, migrations, etc. - well you will guess that. Core features like populations, archipelagos, islands etc. are in

src/PyGMO/core/core.cpp

Please touch only if you know what you are doing.

Lets expose a simple problem in this example, named myprob. We use the following problem_wrapper:

problem_wrapper<problem::myprob>("myprob","My Problem")

Note that "My Problem" is part of the docstring of the problem later on. Lets say, our problem needs a special constructor that needs an integer and optionally a double. We add this constructor by:

    .def(init<int, optional<const double &> >());

HOWTO: Do the documentation

Clone this wiki locally