Skip to content

Commit

Permalink
move static crossover helper methods in base class
Browse files Browse the repository at this point in the history
  • Loading branch information
foolnotion committed Feb 16, 2025
1 parent 2bcfc1f commit cdf1f97
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 45 deletions.
33 changes: 11 additions & 22 deletions include/operon/operators/crossover.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,21 @@
namespace Operon {
// crossover takes two parent trees and returns a child
struct CrossoverBase : public OperatorBase<Tree, const Tree&, const Tree&> {
using Limits = std::pair<std::size_t, std::size_t>;
static auto FindCompatibleSwapLocations(Operon::RandomGenerator& random, Tree const& lhs, Tree const& rhs, size_t maxDepth, size_t maxLength, double internalProbability = 1.0) -> std::pair<size_t, size_t>;
static auto SelectRandomBranch(Operon::RandomGenerator& random, Tree const& tree, double internalProb, Limits length, Limits level, Limits depth) -> size_t;
static auto Cross(const Tree& lhs, const Tree& rhs, /* index of subtree 1 */ size_t i, /* index of subtree 2 */ size_t j) -> Tree;
};

class OPERON_EXPORT SubtreeCrossover : public CrossoverBase {
public:
SubtreeCrossover(double p, size_t d, size_t l)
: internalProbability_(p)
, maxDepth_(d)
, maxLength_(l)
{
}
SubtreeCrossover(double internalProbability, size_t maxDepth, size_t maxLength)
: internalProbability_(internalProbability)
, maxDepth_(maxDepth)
, maxLength_(maxLength)
{ }

auto operator()(Operon::RandomGenerator& random, const Tree& lhs, const Tree& rhs) const -> Tree override;
auto FindCompatibleSwapLocations(Operon::RandomGenerator& random, const Tree& lhs, const Tree& rhs) const -> std::pair<size_t, size_t>;

static inline auto Cross(const Tree& lhs, const Tree& rhs, /* index of subtree 1 */ size_t i, /* index of subtree 2 */ size_t j) -> Tree
{
auto const& left = lhs.Nodes();
auto const& right = rhs.Nodes();
Operon::Vector<Node> nodes;
using signed_t = std::make_signed<size_t>::type; // NOLINT
nodes.reserve(right[j].Length - left[i].Length + left.size());
std::copy_n(left.begin(), i - left[i].Length, back_inserter(nodes));
std::copy_n(right.begin() + static_cast<signed_t>(j) - right[j].Length, right[j].Length + 1, back_inserter(nodes));
std::copy_n(left.begin() + static_cast<signed_t>(i) + 1, left.size() - (i + 1), back_inserter(nodes));

auto child = Tree(nodes).UpdateNodes();
return child;
}

[[nodiscard]] auto InternalProbability() const -> double { return internalProbability_; }
[[nodiscard]] auto MaxDepth() const -> size_t { return maxDepth_; }
Expand All @@ -57,5 +45,6 @@ class OPERON_EXPORT SubtreeCrossover : public CrossoverBase {
size_t maxDepth_;
size_t maxLength_;
};

} // namespace Operon
#endif
59 changes: 37 additions & 22 deletions source/operators/crossover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,39 @@
#include <random>

#include "operon/core/contracts.hpp"
#include "operon/formatter/formatter.hpp"
#include "operon/operators/crossover.hpp"
#include "operon/random/random.hpp"

namespace Operon {

namespace {
using Limits = std::pair<size_t, size_t>;

using Limits = std::pair<std::size_t, std::size_t>;
auto NotIn(Limits t, size_t v) -> bool {
auto [a, b] = t;
return v < a || b < v;
}
} // namespace

static auto SelectRandomBranch(Operon::RandomGenerator& random, Tree const& tree, double internalProb, Limits length, Limits level, Limits depth) -> size_t
auto CrossoverBase::FindCompatibleSwapLocations(Operon::RandomGenerator& random, Tree const& lhs, Tree const& rhs, size_t maxDepth, size_t maxLength, double internalProbability) -> std::pair<size_t, size_t>
{
using Signed = std::make_signed_t<size_t>;
auto diff = static_cast<Signed>(lhs.Length() - maxLength + 1); // +1 to account for at least one node that gets swapped in

auto i = SelectRandomBranch(random, lhs, internalProbability, Limits{std::max(diff, Signed{1}), lhs.Length()}, Limits{size_t{1}, lhs.Depth()}, Limits{size_t{1}, lhs.Depth()});
// we have to make some small allowances here due to the fact that the provided trees
// might actually be larger than the maxDepth and maxLength limits given here
auto maxBranchDepth = static_cast<Signed>(maxDepth - lhs[i].Level);
maxBranchDepth = std::max(maxBranchDepth, Signed{1});

auto partialTreeLength = (lhs.Length() - (lhs[i].Length + 1));
auto maxBranchLength = static_cast<Signed>(maxLength - partialTreeLength);
maxBranchLength = std::max(maxBranchLength, Signed{1});

auto j = SelectRandomBranch(random, rhs, internalProbability, Limits{1UL, maxBranchLength}, Limits{1UL, rhs.Depth()}, Limits{1UL, maxBranchDepth});
return std::make_pair(i, j);
}

auto CrossoverBase::SelectRandomBranch(Operon::RandomGenerator& random, Tree const& tree, double internalProb, Limits length, Limits level, Limits depth) -> size_t
{
if (tree.Length() == 1) {
return 0;
Expand Down Expand Up @@ -54,31 +72,27 @@ static auto SelectRandomBranch(Operon::RandomGenerator& random, Tree const& tree
return *Operon::Random::Sample(random, candidates.rbegin(), tail);
}
return *Operon::Random::Sample(random, candidates.begin(), head);

}

auto SubtreeCrossover::FindCompatibleSwapLocations(Operon::RandomGenerator& random, Tree const& lhs, Tree const& rhs) const -> std::pair<size_t, size_t>
{
using Signed = std::make_signed<size_t>::type;
auto diff = static_cast<Signed>(lhs.Length() - maxLength_ + 1); // +1 to account for at least one node that gets swapped in

auto i = SelectRandomBranch(random, lhs, internalProbability_, Limits{std::max(diff, Signed{1}), lhs.Length()}, Limits{size_t{1}, lhs.Depth()}, Limits{size_t{1}, lhs.Depth()});
// we have to make some small allowances here due to the fact that the provided trees
// might actually be larger than the maxDepth and maxLength limits given here
auto maxBranchDepth = static_cast<Signed>(maxDepth_ - lhs[i].Level);
maxBranchDepth = std::max(maxBranchDepth, Signed{1});

auto partialTreeLength = (lhs.Length() - (lhs[i].Length + 1));
auto maxBranchLength = static_cast<Signed>(maxLength_ - partialTreeLength);
maxBranchLength = std::max(maxBranchLength, Signed{1});

auto j = SelectRandomBranch(random, rhs, internalProbability_, Limits{1UL, maxBranchLength}, Limits{1UL, rhs.Depth()}, Limits{1UL, maxBranchDepth});
return std::make_pair(i, j);
auto CrossoverBase::Cross(const Tree& lhs, const Tree& rhs, /* index of subtree 1 */ size_t i, /* index of subtree 2 */ size_t j) -> Tree
{
auto const& left = lhs.Nodes();
auto const& right = rhs.Nodes();
Operon::Vector<Node> nodes;
using Signed = std::make_signed_t<size_t>;
nodes.reserve(right[j].Length - left[i].Length + left.size());
std::copy_n(left.begin(), i - left[i].Length, back_inserter(nodes));
std::copy_n(right.begin() + static_cast<Signed>(j) - right[j].Length, right[j].Length + 1, back_inserter(nodes));
std::copy_n(left.begin() + static_cast<Signed>(i) + 1, left.size() - (i + 1), back_inserter(nodes));

auto child = Tree(nodes).UpdateNodes();
return child;
}

auto SubtreeCrossover::operator()(Operon::RandomGenerator& random, const Tree& lhs, const Tree& rhs) const -> Tree
{
auto [i, j] = FindCompatibleSwapLocations(random, lhs, rhs);
auto [i, j] = FindCompatibleSwapLocations(random, lhs, rhs, maxDepth_, maxLength_, internalProbability_);
auto child = Cross(lhs, rhs, i, j);

auto maxDepth{std::max(maxDepth_, lhs.Depth())};
Expand All @@ -90,3 +104,4 @@ auto SubtreeCrossover::operator()(Operon::RandomGenerator& random, const Tree& l
return child;
}
} // namespace Operon

2 changes: 1 addition & 1 deletion test/source/implementation/crossover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ TEST_CASE("Crossover")
//auto p2 = btc(rng, maxLength, 1, maxDepth);
auto p2 = p1;

auto [i, j] = cx.FindCompatibleSwapLocations(rng, p1, p2);
auto [i, j] = Operon::SubtreeCrossover::FindCompatibleSwapLocations(rng, p1, p2, maxDepth, maxLength);
c1[i]++;
c2[j]++;

Expand Down

0 comments on commit cdf1f97

Please sign in to comment.