Skip to content

Commit

Permalink
simplify Algorithm::nextPopulation() interface
Browse files Browse the repository at this point in the history
  • Loading branch information
KRM7 committed Dec 7, 2023
1 parent db5f69d commit f9b7999
Show file tree
Hide file tree
Showing 11 changed files with 53 additions and 70 deletions.
2 changes: 1 addition & 1 deletion src/algorithm/algorithm_base.impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace gapp::algorithm
std::move(children.begin(), children.end(), std::back_inserter(parents));
const FitnessMatrix fmat = detail::toFitnessMatrix(parents);

const auto next_indices = nextPopulationImpl(ga, fmat.begin(), fmat.begin() + ga.population_size(), fmat.end());
const auto next_indices = nextPopulationImpl(ga, fmat);

GAPP_ASSERT(std::all_of(next_indices.begin(), next_indices.end(), detail::less_than(parents.size())),
"An invalid index was returned by nextPopulationImpl().");
Expand Down
28 changes: 12 additions & 16 deletions src/algorithm/nsga2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ namespace gapp::algorithm
using namespace dtl;

/* Calculate the crowding distances of the solutions in pfronts. */
static std::vector<double> crowdingDistances(FitnessMatrix::const_iterator first, FitnessMatrix::const_iterator last, ParetoFronts pfronts)
static std::vector<double> crowdingDistances(const FitnessMatrix& fmat, ParetoFronts pfronts)
{
GAPP_ASSERT(std::distance(first, last) >= 0);
GAPP_ASSERT(!fmat.empty());

std::vector<double> crowding_distances(last - first, 0.0);
std::vector<double> crowding_distances(fmat.size(), 0.0);
const auto front_bounds = paretoFrontBounds(pfronts);

for (size_t obj = 0; obj < first->size(); obj++)
for (size_t obj = 0; obj < fmat.ncols(); obj++)
{
FitnessVector fvec(last - first, 0.0);
std::transform(first, last, fvec.begin(), detail::element_at(obj));
FitnessVector fvec(fmat.size(), 0.0);
std::transform(fmat.begin(), fmat.end(), fvec.begin(), detail::element_at(obj));

for (auto [front_first, front_last] : front_bounds)
{
Expand Down Expand Up @@ -68,7 +68,7 @@ namespace gapp::algorithm
auto pfronts = nonDominatedSort(fmat.begin(), fmat.end());

ranks_ = paretoRanks(pfronts);
dists_ = crowdingDistances(fmat.begin(), fmat.end(), std::move(pfronts));
dists_ = crowdingDistances(fmat, std::move(pfronts));
}

size_t NSGA2::selectImpl(const GaInfo&, const FitnessMatrix& fmat) const
Expand All @@ -87,22 +87,18 @@ namespace gapp::algorithm
return first_is_better ? idx1 : idx2;
}

std::vector<size_t> NSGA2::nextPopulationImpl(const GaInfo& ga,
FitnessMatrix::const_iterator parents_first,
[[maybe_unused]] FitnessMatrix::const_iterator children_first,
FitnessMatrix::const_iterator children_last)
std::vector<size_t> NSGA2::nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat)
{
const size_t popsize = ga.population_size();

GAPP_ASSERT(ga.num_objectives() > 1);
GAPP_ASSERT(size_t(children_first - parents_first) == popsize);
GAPP_ASSERT(parents_first->size() == ga.num_objectives());
GAPP_ASSERT(fmat.ncols() == ga.num_objectives());

auto pfronts = nonDominatedSort(parents_first, children_last);
auto pfronts = nonDominatedSort(fmat.begin(), fmat.end());
auto [partial_front_first, partial_front_last] = findPartialFront(pfronts.begin(), pfronts.end(), popsize);

/* Crowding distances of just the partial front for sorting. */
dists_ = crowdingDistances(parents_first, children_last, ParetoFronts(partial_front_first, partial_front_last));
dists_ = crowdingDistances(fmat, ParetoFronts(partial_front_first, partial_front_last));

std::sort(partial_front_first, partial_front_last,
[this](const FrontInfo& lhs, const FrontInfo& rhs) noexcept
Expand All @@ -111,7 +107,7 @@ namespace gapp::algorithm
});

/* Crowding distances of all of the solutions. */
dists_ = crowdingDistances(parents_first, children_last, ParetoFronts(pfronts.begin(), pfronts.begin() + popsize));
dists_ = crowdingDistances(fmat, ParetoFronts(pfronts.begin(), pfronts.begin() + popsize));
dists_.resize(popsize);

/* Add the first popsize elements from pfronts to the next pop. */
Expand Down
5 changes: 1 addition & 4 deletions src/algorithm/nsga2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ namespace gapp::algorithm
void prepareSelectionsImpl(const GaInfo&, const FitnessMatrix&) override {}
size_t selectImpl(const GaInfo& ga, const FitnessMatrix& fmat) const override;

std::vector<size_t> nextPopulationImpl(const GaInfo& ga,
FitnessMatrix::const_iterator first,
FitnessMatrix::const_iterator children_first,
FitnessMatrix::const_iterator last) override;
std::vector<size_t> nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat) override;

std::vector<size_t> optimalSolutionsImpl(const GaInfo& ga) const override;

Expand Down
13 changes: 5 additions & 8 deletions src/algorithm/nsga3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,24 +343,21 @@ namespace gapp::algorithm
pimpl_->recalcNicheCounts(pfronts.begin(), pfronts.end());
}

std::vector<size_t> NSGA3::nextPopulationImpl(const GaInfo& ga, FitnessMatrix::const_iterator parents_first,
FitnessMatrix::const_iterator /* parents_last */,
FitnessMatrix::const_iterator children_last)
std::vector<size_t> NSGA3::nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat)
{
GAPP_ASSERT(ga.num_objectives() > 1);
GAPP_ASSERT(size_t(children_last - parents_first) >= ga.population_size());
GAPP_ASSERT(parents_first->size() == ga.num_objectives());
GAPP_ASSERT(fmat.ncols() == ga.num_objectives());

const size_t popsize = ga.population_size();

auto pfronts = dtl::nonDominatedSort(parents_first, children_last);
auto pfronts = dtl::nonDominatedSort(fmat.begin(), fmat.end());
auto [partial_first, partial_last] = findPartialFront(pfronts.begin(), pfronts.end(), popsize);

pimpl_->sol_info_.resize(children_last - parents_first);
pimpl_->sol_info_.resize(fmat.size());
std::for_each(pfronts.begin(), pfronts.end(), [this](const FrontInfo& sol) { pimpl_->sol_info_[sol.idx].rank = sol.rank; });

/* The ref lines of the candidates after partial_last are irrelevant, as they can never be part of the next population. */
pimpl_->associatePopWithRefs(parents_first, children_last, pfronts.begin(), partial_last);
pimpl_->associatePopWithRefs(fmat.begin(), fmat.end(), pfronts.begin(), partial_last);
/* The niche counts should be calculated excluding the partial front for now. */
pimpl_->recalcNicheCounts(pfronts.begin(), partial_first);

Expand Down
5 changes: 1 addition & 4 deletions src/algorithm/nsga3.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,7 @@ namespace gapp::algorithm
void initializeImpl(const GaInfo& ga) override;
size_t selectImpl(const GaInfo& ga, const FitnessMatrix& fmat) const override;

std::vector<size_t> nextPopulationImpl(const GaInfo& ga,
FitnessMatrix::const_iterator first,
FitnessMatrix::const_iterator children_first,
FitnessMatrix::const_iterator last) override;
std::vector<size_t> nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat) override;

std::vector<size_t> optimalSolutionsImpl(const GaInfo& ga) const override;

Expand Down
20 changes: 7 additions & 13 deletions src/algorithm/replacement_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,18 @@ namespace gapp::replacement
* Select the candidates of the next generation from the candidates of the
* combined current and child populations.
*
* The fitness matrix is given as the contiguous range [first, last), where
* the subrange [first, children_first) belongs to the current population,
* and the [children_first, last) subrange belongs to the child population.
* The fitness matrix is given as the combined fitness matrix of the parent
* and child populations' fitness matrices. The top half (first population_size elements)
* of the matrix corresponds to the parent population, while the rest (another population_size
* elements) is the fitness matrix of the child population.
*
* This method should return (population_size) number of unique indices from this fitness matrix,
* assuming the index of the first iterator is 0.
* The method should return (population_size) number of unique indices from this fitness matrix.
*
* @param ga The %GA that uses the update method.
* @param first The first element of the fitness matrix (first parent).
* @param children_first The first element of the fitness matrix that belongs to a child.
* @param last The end iterator of the fitness matrix.
*
* @param fmat The fitness matrix of the combined parent and child populations.
* @returns The indices of the candidates selected from the fitness matrix.
*/
virtual std::vector<size_t> nextPopulationImpl(const GaInfo& ga,
FitnessMatrix::const_iterator first,
FitnessMatrix::const_iterator children_first,
FitnessMatrix::const_iterator last) = 0;
virtual std::vector<size_t> nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat) = 0;


/** Destructor. */
Expand Down
4 changes: 2 additions & 2 deletions src/algorithm/single_objective.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ namespace gapp::algorithm
selection_->initializeImpl(ga);
}

std::vector<size_t> SingleObjective::nextPopulationImpl(const GaInfo& ga, FitnessMatrix::const_iterator first, FitnessMatrix::const_iterator children_first, FitnessMatrix::const_iterator last)
std::vector<size_t> SingleObjective::nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat)
{
GAPP_ASSERT(replacement_);
GAPP_ASSERT(ga.num_objectives() == 1, "The number of objectives must be 1 for the single-objective algorithms.");

return replacement_->nextPopulationImpl(ga, first, children_first, last);
return replacement_->nextPopulationImpl(ga, fmat);
}

} // namespace gapp::algorithm
4 changes: 2 additions & 2 deletions src/algorithm/single_objective.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ namespace gapp::algorithm
* when not using a replacement policy derived from replacement::Replacement.
* @see replacement_method()
*/
using ReplacementCallable = std::function<std::vector<size_t>(const GaInfo&, FitnessMatrix::const_iterator, FitnessMatrix::const_iterator, FitnessMatrix::const_iterator)>;
using ReplacementCallable = std::function<std::vector<size_t>(const GaInfo&, const FitnessMatrix&)>;


/**
Expand Down Expand Up @@ -153,7 +153,7 @@ namespace gapp::algorithm
void prepareSelectionsImpl(const GaInfo& ga, const FitnessMatrix& fmat) override;
size_t selectImpl(const GaInfo& ga, const FitnessMatrix& fmat) const override;

std::vector<size_t> nextPopulationImpl(const GaInfo& ga, FitnessMatrix::const_iterator first, FitnessMatrix::const_iterator children_first, FitnessMatrix::const_iterator last) override;
std::vector<size_t> nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat) override;

std::unique_ptr<selection::Selection> selection_;
std::unique_ptr<replacement::Replacement> replacement_;
Expand Down
24 changes: 13 additions & 11 deletions src/algorithm/soga_replacement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,35 @@

namespace gapp::replacement
{
std::vector<size_t> KeepChildren::nextPopulationImpl(const GaInfo& ga, FitnessMatrix::const_iterator, FitnessMatrix::const_iterator, FitnessMatrix::const_iterator)
std::vector<size_t> KeepChildren::nextPopulationImpl(const GaInfo& ga, const FitnessMatrix&)
{
return detail::index_vector(ga.population_size(), ga.population_size());
}

std::vector<size_t> Elitism::nextPopulationImpl(const GaInfo& ga, FitnessMatrix::const_iterator first, FitnessMatrix::const_iterator children_first, FitnessMatrix::const_iterator)
std::vector<size_t> Elitism::nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat)
{
const size_t n = std::min(n_, ga.population_size());
GAPP_ASSERT(fmat.size() >= 2 * ga.population_size());

const auto sorted_parent_indices = detail::partial_argsort(first, first + n, children_first,
const size_t elite_count = std::min(n_, ga.population_size());

const auto sorted_parent_indices = detail::partial_argsort(fmat.begin(), fmat.begin() + elite_count, fmat.begin() + ga.population_size(),
[](const auto& lhs, const auto& rhs) noexcept
{
return math::paretoCompareLess(rhs, lhs); // descending
});

std::vector<size_t> indices(ga.population_size());
std::copy(sorted_parent_indices.begin(), sorted_parent_indices.begin() + n, indices.begin());
std::iota(indices.begin() + n, indices.end(), ga.population_size());
std::copy(sorted_parent_indices.begin(), sorted_parent_indices.begin() + elite_count, indices.begin());
std::iota(indices.begin() + elite_count, indices.end(), ga.population_size());

return indices;
}

std::vector<size_t> KeepBest::nextPopulationImpl(const GaInfo& ga, FitnessMatrix::const_iterator first, FitnessMatrix::const_iterator children_first, FitnessMatrix::const_iterator last)
std::vector<size_t> KeepBest::nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat)
{
GAPP_ASSERT(size_t(children_first - first) == ga.population_size());
GAPP_ASSERT(fmat.size() >= ga.population_size());

auto sorted_indices = detail::partial_argsort(first, children_first, last,
auto sorted_indices = detail::partial_argsort(fmat.begin(), fmat.begin() + ga.population_size(), fmat.end(),
[](const auto& lhs, const auto& rhs) noexcept
{
return math::paretoCompareLess(rhs, lhs); // descending
Expand All @@ -56,11 +58,11 @@ namespace gapp::replacement
replacement_ = std::move(f);
}

std::vector<size_t> Lambda::nextPopulationImpl(const GaInfo& ga, FitnessMatrix::const_iterator first, FitnessMatrix::const_iterator children_first, FitnessMatrix::const_iterator last)
std::vector<size_t> Lambda::nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat)
{
GAPP_ASSERT(replacement_);

return replacement_(ga, first, children_first, last);
return replacement_(ga, fmat);
}

} // namespace gapp::replacement
10 changes: 5 additions & 5 deletions src/algorithm/soga_replacement.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace gapp::replacement
class KeepChildren final : public Replacement
{
private:
std::vector<size_t> nextPopulationImpl(const GaInfo& ga, FitnessMatrix::const_iterator first, FitnessMatrix::const_iterator children_first, FitnessMatrix::const_iterator last) override;
std::vector<size_t> nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat) override;
};


Expand Down Expand Up @@ -69,7 +69,7 @@ namespace gapp::replacement
constexpr size_t elite_num() const noexcept { return n_; }

private:
std::vector<size_t> nextPopulationImpl(const GaInfo& ga, FitnessMatrix::const_iterator first, FitnessMatrix::const_iterator children_first, FitnessMatrix::const_iterator last) override;
std::vector<size_t> nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat) override;

size_t n_;
};
Expand All @@ -85,7 +85,7 @@ namespace gapp::replacement
class KeepBest final : public Replacement
{
private:
std::vector<size_t> nextPopulationImpl(const GaInfo& ga, FitnessMatrix::const_iterator first, FitnessMatrix::const_iterator children_first, FitnessMatrix::const_iterator last) override;
std::vector<size_t> nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat) override;
};


Expand All @@ -96,12 +96,12 @@ namespace gapp::replacement
class Lambda final : public Replacement
{
public:
using ReplacementCallable = std::function<std::vector<size_t>(const GaInfo&, FitnessMatrix::const_iterator, FitnessMatrix::const_iterator, FitnessMatrix::const_iterator)>;
using ReplacementCallable = std::function<std::vector<size_t>(const GaInfo&, const FitnessMatrix&)>;

explicit Lambda(ReplacementCallable f) noexcept;

private:
std::vector<size_t> nextPopulationImpl(const GaInfo& ga, FitnessMatrix::const_iterator first, FitnessMatrix::const_iterator children_first, FitnessMatrix::const_iterator last) override;
std::vector<size_t> nextPopulationImpl(const GaInfo& ga, const FitnessMatrix& fmat) override;

ReplacementCallable replacement_;
};
Expand Down
8 changes: 4 additions & 4 deletions test/unit/replacement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ using namespace gapp::replacement;
static constexpr size_t POPSIZE = 10;
static const BinaryGA context = []{ BinaryGA GA(POPSIZE); GA.solve(DummyFitnessFunction<BinaryGene>(10), 1); return GA; }();

static const FitnessMatrix fitness_mat = {
static const FitnessMatrix fitness_matrix = {
// parents
{ math::inf<double> },
{ math::large<double> },
Expand Down Expand Up @@ -44,7 +44,7 @@ TEST_CASE("replacement_best", "[replacement][single-objective]")
math::ScopedTolerances _(0.0, 0.0);

std::unique_ptr<Replacement> replacement = std::make_unique<KeepBest>();
const auto indices = replacement->nextPopulationImpl(context, fitness_mat.begin(), fitness_mat.begin() + POPSIZE, fitness_mat.end());
const auto indices = replacement->nextPopulationImpl(context, fitness_matrix);

const std::vector<size_t> expected = { 0, 1, 4, 5, 9, 11, 12, 15, 16, 17 };

Expand All @@ -54,7 +54,7 @@ TEST_CASE("replacement_best", "[replacement][single-objective]")
TEST_CASE("replacement_children", "[replacement][single-objective]")
{
std::unique_ptr<Replacement> replacement = std::make_unique<KeepChildren>();
const auto indices = replacement->nextPopulationImpl(context, fitness_mat.begin(), fitness_mat.begin() + POPSIZE, fitness_mat.end());
const auto indices = replacement->nextPopulationImpl(context, fitness_matrix);

const std::vector<size_t> expected = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };

Expand All @@ -64,7 +64,7 @@ TEST_CASE("replacement_children", "[replacement][single-objective]")
TEST_CASE("replacement_elitism", "[replacement][single-objective]")
{
std::unique_ptr<Replacement> replacement = std::make_unique<Elitism>(2);
const auto indices = replacement->nextPopulationImpl(context, fitness_mat.begin(), fitness_mat.begin() + POPSIZE, fitness_mat.end());
const auto indices = replacement->nextPopulationImpl(context, fitness_matrix);

const std::vector<size_t> expected = { 0, 5, 10, 11, 12, 13, 14, 15, 16, 17 };

Expand Down

0 comments on commit f9b7999

Please sign in to comment.