Generally speaking migrating code from Vita isn't too hard: Ultra has a simpler interface but the backbone structure is similar in many respects.

Many of the examples have been ported to Ultra and are a good starting point for understanding the differences.

General aspects

The majority of class specializations have their own namespace. So instead of vita::de_problem, vita::de_search, vita::de_individual, ultra::src_search you should use ultra:de::problem, ultra:de::search, ultra::de::individual, ultra::src::search.


You usually haven't to specify template arguments since they're automatically deduced:

vita::de_search<decltype(neg_rastrigin)> search(prob, neg_rastrigin);

is now the simpler:

de::search search(prob, neg_rastrigin);

environment class has been renamed to parameters and parameters have been grouped by category. E.g.

environment env;
env.p_mutation = 0.6;
env.tournament_size = 3;

becomes

parameters params;
params.evolution.p_mutation = 0.6;
params.evolution.tournament_size = 3;

Return results of a search are contained in a different structure (search_stats defined in kernel/search.h). So, for example, the usual access to the best individual / fitness changes from:

const auto res(search.run());

const auto solution(res.best.solution);
const auto value(res.best.score.fitness);

to

const auto res(search.run());

const auto solution(res.best_individual);
const auto value(*res.best_measurements.fitness);

Note that scores contained in the search_stats struct are optional values hence the * to get the fitness.


The threshold for identifying successful runs isn't specified anymore in the environment / parameters structure but is passed to the search::run member functions:

environment env;
env.threshold.fitness = -0.5;
// ...
src_search s(prob);
const auto result(s.run(10));

should be replaced with

model_measurements<double> threshold;
mm.fitness = -0.5;
// ...
src::search s(prob);
const auto result(s.run(10, threshold));

Differences deriving from design choices

All the search strategies can be used enabling multiple layers / subgroups (prob.params.population.init_subgroups > 1). Evolution happens in parallel for every subgroup. Many algorithms take advantage of this organization to explore different search spaces, possibly using different parameters; anyway the can be executed in single thread mode.

ALPS, otherwise, is conceived to exchange information among subgroups and, even if started with a single layer, will add further layers during the evolution (thus enabling concurrency).

Concurrency is a great performance boost but makes evolution non-repeatable.

There isn't anymore a fixed type for representing fitness. User can employ the type more appropriate for the specific task provided that it satisfies the Fitness concept. However a fitnd class is available for filling the gap.

Serialisation of floating point values now uses hexadecimal format (std::hexfloat) for impreoved precision and performance: - no rounding occurs when writing or reading a value formatted in this way; - operations on such values can be faster with a well tuned I/O library; - fewer digits are required to represent values exactly.

Existing serialisation files have to accomodate this change.