From 56ac78e015fe385d6135e5604edbab40d331dcaa Mon Sep 17 00:00:00 2001 From: Ben Decker Date: Wed, 26 Apr 2023 14:29:40 -0600 Subject: [PATCH 1/4] make library visible in IDE --- .gitignore | 2 ++ CMakeLists.txt | 23 +++++++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 46cab57..52461a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ build*/ +_bld*/ +.vscode/* .ccls-cache/ compile_commands.json .dir-locals.el diff --git a/CMakeLists.txt b/CMakeLists.txt index 613758c..f63686c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,28 @@ -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.23) project(EvolutionNet) - option(BUILD_EXAMPLES "Build Examples" OFF) option(BUILD_TESTS "Build Tests" OFF) - -add_library(${PROJECT_NAME} INTERFACE) +######################### +set(${PROJECT_NAME}_srcs + include/${PROJECT_NAME}/ConnectionGene.hpp + include/${PROJECT_NAME}/EvolutionNet.hpp + include/${PROJECT_NAME}/FlatMap.hpp + include/${PROJECT_NAME}/FlatSet.hpp + include/${PROJECT_NAME}/Genome.hpp + include/${PROJECT_NAME}/Network.hpp + include/${PROJECT_NAME}/Population.hpp + include/${PROJECT_NAME}/Random.hpp + include/${PROJECT_NAME}/Types.hpp +) +add_library(${PROJECT_NAME} INTERFACE ${${PROJECT_NAME}_srcs}) +target_sources(${PROJECT_NAME} INTERFACE FILE_SET header_files TYPE HEADERS FILES ${${PROJECT_NAME}_srcs}) +source_group("" FILES ${${PROJECT_NAME}_srcs}) target_include_directories(${PROJECT_NAME} INTERFACE include/) target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17) - +######################### if(${BUILD_EXAMPLES}) add_subdirectory(${PROJECT_SOURCE_DIR}/examples) endif() - if(${BUILD_TESTS}) add_subdirectory(${PROJECT_SOURCE_DIR}/tests) endif() From 4ae7842f81c08178ec73c6ba86063b2a39081954 Mon Sep 17 00:00:00 2001 From: Ben Decker Date: Wed, 26 Apr 2023 14:41:54 -0600 Subject: [PATCH 2/4] edit .clang-format --- .clang-format | 3 +- include/EvolutionNet/ConnectionGene.hpp | 5 +- include/EvolutionNet/EvolutionNet.hpp | 65 ++++++++----- include/EvolutionNet/FlatMap.hpp | 39 +++++--- include/EvolutionNet/FlatSet.hpp | 5 +- include/EvolutionNet/Genome.hpp | 117 ++++++++++++++++-------- include/EvolutionNet/Network.hpp | 53 +++++++---- include/EvolutionNet/Population.hpp | 101 +++++++++++++------- include/EvolutionNet/Types.hpp | 13 ++- 9 files changed, 271 insertions(+), 130 deletions(-) diff --git a/.clang-format b/.clang-format index 0e88882..d237480 100644 --- a/.clang-format +++ b/.clang-format @@ -6,7 +6,8 @@ AllowAllParametersOfDeclarationOnNextLine: false BinPackArguments: false BinPackParameters: false IncludeBlocks: Merge -ColumnLimit: 120 +ColumnLimit: 80 +IndentWidth: 2 PointerAlignment: Left AllowShortFunctionsOnASingleLine: Empty ... diff --git a/include/EvolutionNet/ConnectionGene.hpp b/include/EvolutionNet/ConnectionGene.hpp index 5464d64..c27eee4 100644 --- a/include/EvolutionNet/ConnectionGene.hpp +++ b/include/EvolutionNet/ConnectionGene.hpp @@ -29,7 +29,10 @@ class ConnectionGene final { ConnectionGene(const ConnectionGene&) = default; //! \brief Construct the aggregation. - inline ConnectionGene(const NodeId from, const NodeId to, const float weight, const bool enabled) noexcept; + inline ConnectionGene(const NodeId from, + const NodeId to, + const float weight, + const bool enabled) noexcept; //! \return the weight of the link. inline float getWeight() const noexcept; diff --git a/include/EvolutionNet/EvolutionNet.hpp b/include/EvolutionNet/EvolutionNet.hpp index e0b7339..1127cc9 100644 --- a/include/EvolutionNet/EvolutionNet.hpp +++ b/include/EvolutionNet/EvolutionNet.hpp @@ -27,7 +27,7 @@ #include namespace EvolutionNet { - +// clang-format off /*! \class EvolutionNet * \brief This class represents the main interface of this library. * Some of the network features are set at compile time with template logic. @@ -40,6 +40,7 @@ namespace EvolutionNet { * net.evolve(); // Evolve in the next generation and loop! * } */ +// clang-format on template class EvolutionNet { public: @@ -51,10 +52,12 @@ class EvolutionNet { /*! \brief Initialize the Evolution Net. * You need to call this function before any other operation. - * Here you can set the size of the population and the random seed used internally. - * \note `populationSize` must be greater than zero, otherwise Undefined Behavior. + * Here you can set the size of the population and the random seed used + * internally. \note `populationSize` must be greater than zero, otherwise + * Undefined Behavior. */ - void initialize(const std::size_t populationSize, const SeedT rndSeed = DefaultSeed); + void initialize(const std::size_t populationSize, + const SeedT rndSeed = DefaultSeed); //! \return all the networks (size of the population). inline std::vector& getNetworks() noexcept; @@ -68,19 +71,23 @@ class EvolutionNet { template inline void evaluateAll(Fn&& fn); - //! \return the best fitness score for this current generation. Call this only after ending evaluation. + //! \return the best fitness score for this current generation. Call this only + //! after ending evaluation. inline FitnessScore getBestFitness() const noexcept; - //! \return the best network accordinlying with the max fitness score. Call this only after ending evaluation. + //! \return the best network accordinlying with the max fitness score. Call + //! this only after ending evaluation. inline const NetworkT& getBestNetwork() const noexcept; inline NetworkT* getBestNetworkMutable() noexcept; /*! \brief Evolve the population. - * Call this only after ending evaluation, thus, fitness for each network has been set. + * Call this only after ending evaluation, thus, fitness for each network has + * been set. */ inline void evolve(); - //! \return the size of the population. This is constant (does not change during evolution). + //! \return the size of the population. This is constant (does not change + //! during evolution). inline std::size_t getPopulationSize() const noexcept; //! \return the generation counter. First generation stats from 0. @@ -91,7 +98,9 @@ class EvolutionNet { struct IsValidEvaluationFunction : std::false_type {}; template - struct IsValidEvaluationFunction()(std::declval()))>> + struct IsValidEvaluationFunction< + Fn, + std::void_t()(std::declval()))>> : std::true_type {}; RndEngine rndEngine_; @@ -104,8 +113,9 @@ class EvolutionNet { }; template -void EvolutionNet::initialize(const std::size_t populationSize, - const SeedT rndSeed) { +void EvolutionNet::initialize( + const std::size_t populationSize, + const SeedT rndSeed) { assert(populationSize > 0); rndEngine_.seed(rndSeed); @@ -120,23 +130,27 @@ void EvolutionNet::initialize(const std: } template -inline std::vector::NetworkT>& +inline std::vector< + typename EvolutionNet::NetworkT>& EvolutionNet::getNetworks() noexcept { return networks_; } template inline typename EvolutionNet::NetworkT& -EvolutionNet::getNetworkNth(const std::size_t i) noexcept { +EvolutionNet::getNetworkNth( + const std::size_t i) noexcept { assert(i < networks_.size()); return networks_[i]; } template template -inline void EvolutionNet::evaluateAll(Fn&& fn) { +inline void EvolutionNet::evaluateAll( + Fn&& fn) { static_assert(IsValidEvaluationFunction::value, - "Fn is not a valid evaluation function. It should be something like `void(*)(Network*)`"); + "Fn is not a valid evaluation function. It should be something " + "like `void(*)(Network*)`"); const std::size_t numNetworks = networks_.size(); FitnessScore fitness, fitnessMax; @@ -163,21 +177,25 @@ inline void EvolutionNet::evaluateAll(Fn } template -FitnessScore EvolutionNet::getBestFitness() const noexcept { +FitnessScore +EvolutionNet::getBestFitness() + const noexcept { assert(bestNetwork_ != nullptr); return bestNetwork_->getFitness(); } template const typename EvolutionNet::NetworkT& -EvolutionNet::getBestNetwork() const noexcept { +EvolutionNet::getBestNetwork() + const noexcept { assert(bestNetwork_ != nullptr); return *bestNetwork_; } template typename EvolutionNet::NetworkT* -EvolutionNet::getBestNetworkMutable() noexcept { +EvolutionNet:: + getBestNetworkMutable() noexcept { assert(bestNetwork_ != nullptr); return bestNetwork_; } @@ -192,17 +210,22 @@ inline void EvolutionNet::evolve() { } template -inline std::size_t EvolutionNet::getPopulationSize() const noexcept { +inline std::size_t +EvolutionNet::getPopulationSize() + const noexcept { return population_.getPopulationSize(); } template -inline std::size_t EvolutionNet::getCounterGeneration() const noexcept { +inline std::size_t +EvolutionNet::getCounterGeneration() + const noexcept { return counterGeneration_; } template -inline void EvolutionNet::computeNetworks() { +inline void +EvolutionNet::computeNetworks() { const std::size_t popSize = population_.getPopulationSize(); assert(networks_.size() == popSize); diff --git a/include/EvolutionNet/FlatMap.hpp b/include/EvolutionNet/FlatMap.hpp index 385b880..03e1f16 100644 --- a/include/EvolutionNet/FlatMap.hpp +++ b/include/EvolutionNet/FlatMap.hpp @@ -65,7 +65,8 @@ class FlatMap { inline Value& nthValue(const std::size_t nth) noexcept; /*! \return the vector containing all values. - * \note The order of values is not sorted! They are sorted by order of insertion. + * \note The order of values is not sorted! They are sorted by order of + * insertion. */ inline const std::vector& valuesVector() const noexcept; inline std::vector& valuesVector() noexcept; @@ -77,7 +78,8 @@ class FlatMap { template template -inline Value* FlatMap::insert(const Key key, Args&&... args) noexcept { +inline Value* FlatMap::insert(const Key key, + Args&&... args) noexcept { assert(this->operator[](key) == nullptr); keys_.emplace_back(key, values_.size()); @@ -86,8 +88,10 @@ inline Value* FlatMap::insert(const Key key, Args&&... args) noexcep KeyIndex* beg = keys_.data(); KeyIndex* end = beg + keys_.size(); KeyIndex* last = end - 1; - KeyIndex* bound = - std::lower_bound(beg, last, key, [](const KeyIndex& el, const Key& key) noexcept { return el.first < key; }); + KeyIndex* bound = std::lower_bound( + beg, last, key, [](const KeyIndex& el, const Key& key) noexcept { + return el.first < key; + }); std::rotate(bound, last, end); assert(std::is_sorted(keys_.cbegin(), keys_.cend())); @@ -99,8 +103,13 @@ inline Value* FlatMap::insert(const Key key, Args&&... args) noexcep template inline Value* FlatMap::operator[](const Key key) noexcept { - const auto finder = std::lower_bound( - keys_.begin(), keys_.end(), key, [](const KeyIndex& el, const Key& key) noexcept { return el.first < key; }); + const auto finder = + std::lower_bound(keys_.begin(), + keys_.end(), + key, + [](const KeyIndex& el, const Key& key) noexcept { + return el.first < key; + }); if (finder != keys_.end() && finder->first == key) { return &(values_[finder->second]); } @@ -108,9 +117,15 @@ inline Value* FlatMap::operator[](const Key key) noexcept { } template -inline const Value* FlatMap::operator[](const Key key) const noexcept { - const auto finder = std::lower_bound( - keys_.cbegin(), keys_.cend(), key, [](const KeyIndex& el, const Key& key) noexcept { return el.first < key; }); +inline const Value* FlatMap::operator[]( + const Key key) const noexcept { + const auto finder = + std::lower_bound(keys_.cbegin(), + keys_.cend(), + key, + [](const KeyIndex& el, const Key& key) noexcept { + return el.first < key; + }); if (finder != keys_.cend() && finder->first == key) { return &(values_[finder->second]); } @@ -141,7 +156,8 @@ inline Key FlatMap::nthKey(const std::size_t nth) const noexcept { } template -inline const Value& FlatMap::nthValue(const std::size_t nth) const noexcept { +inline const Value& FlatMap::nthValue( + const std::size_t nth) const noexcept { assert(nth < keys_.size()); assert(keys_.size() == values_.size()); assert(keys_[nth].second < values_.size()); @@ -157,7 +173,8 @@ inline Value& FlatMap::nthValue(const std::size_t nth) noexcept { } template -inline const std::vector& FlatMap::valuesVector() const noexcept { +inline const std::vector& FlatMap::valuesVector() + const noexcept { return values_; } diff --git a/include/EvolutionNet/FlatSet.hpp b/include/EvolutionNet/FlatSet.hpp index 92e18fd..50890a2 100644 --- a/include/EvolutionNet/FlatSet.hpp +++ b/include/EvolutionNet/FlatSet.hpp @@ -55,8 +55,9 @@ class FlatSet { inline bool empty() const noexcept; /*! \brief Insert a new element in the container. - * The new element will be inserted in a "sorted way" (the container will keep elements sorted) - * \note The element must not already exist in the container. Otherwise undefined behavior! + * The new element will be inserted in a "sorted way" (the container will + * keep elements sorted) \note The element must not already exist in the + * container. Otherwise undefined behavior! */ inline void insert(T value); diff --git a/include/EvolutionNet/Genome.hpp b/include/EvolutionNet/Genome.hpp index f241d2d..c87af83 100644 --- a/include/EvolutionNet/Genome.hpp +++ b/include/EvolutionNet/Genome.hpp @@ -57,7 +57,9 @@ class Genome final { * \note This assume `genome1` has better fitness. */ template - void crossover(const Genome& genome1, const Genome& genome2, RndEngine* rndEngine); + void crossover(const Genome& genome1, + const Genome& genome2, + RndEngine* rndEngine); //! \return the distance (0 means they are equivalents) among genomes. template @@ -87,7 +89,8 @@ class Genome final { std::vector layerOfNodes_; template - static void mutateWeight(RndEngine* rndEngine, ConnectionGene* connectionGene); + static void mutateWeight(RndEngine* rndEngine, + ConnectionGene* connectionGene); template void mutateAddNewNode(RndEngine* rndEngine); @@ -97,13 +100,17 @@ class Genome final { bool validate() const; - inline void emplaceNewConnectionGene(const NodeId from, const NodeId to, const float weight); + inline void emplaceNewConnectionGene(const NodeId from, + const NodeId to, + const float weight); inline void assignLayerNewNode(const NodeId nodeId, const LayerId layerId); - inline ConnectionGene* existConnection(const NodeId from, const NodeId to) noexcept; + inline ConnectionGene* existConnection(const NodeId from, + const NodeId to) noexcept; - static inline ConnectionHash hashConnection(const NodeId from, const NodeId to) noexcept; + static inline ConnectionHash hashConnection(const NodeId from, + const NodeId to) noexcept; }; template @@ -163,7 +170,9 @@ void Genome::mutate(RndEngine* rndEngine) { template template -void Genome::crossover(const Genome& genome1, const Genome& genome2, RndEngine* rndEngine) { +void Genome::crossover(const Genome& genome1, + const Genome& genome2, + RndEngine* rndEngine) { connections_.clear(); const ConnectionGene* inheritGene; @@ -175,7 +184,8 @@ void Genome::crossover(const Genome& genome1, const G hashConn = hashConnection(gene1.getFrom(), gene1.getTo()); if (const ConnectionGene* gene2 = genome2.connections_[hashConn]; - gene2 && !CheckProbability(rndEngine, ParamConfig::ProbInheritOnFitterGenome)) { + gene2 && + !CheckProbability(rndEngine, ParamConfig::ProbInheritOnFitterGenome)) { inheritGene = gene2; } @@ -195,7 +205,8 @@ void Genome::crossover(const Genome& genome1, const G template template -float Genome::computeSimilarity(const Genome& oth) const noexcept { +float Genome::computeSimilarity( + const Genome& oth) const noexcept { int N; int numExcess = 0, numDisjoint = 0, numMatching = 0; float weightDifference = 0.f; @@ -208,9 +219,12 @@ float Genome::computeSimilarity(const Genome& oth) co } else if (j == oth.connections_.size()) { ++numExcess; ++i; - } else if (ConnectionHash hashI = connections_.nthKey(i), hashJ = oth.connections_.nthKey(j); hashI == hashJ) { + } else if (ConnectionHash hashI = connections_.nthKey(i), + hashJ = oth.connections_.nthKey(j); + hashI == hashJ) { ++numMatching; - weightDifference += std::abs(connections_.nthValue(i).getWeight() - oth.connections_.nthValue(j).getWeight()); + weightDifference += std::abs(connections_.nthValue(i).getWeight() - + oth.connections_.nthValue(j).getWeight()); ++i; ++j; } else if (connections_.nthKey(i) < oth.connections_.nthKey(j)) { @@ -227,46 +241,57 @@ float Genome::computeSimilarity(const Genome& oth) co N = std::max(getNumConnectionGenes(), oth.getNumConnectionGenes()); N = N < ParamConfig::NormalizedSizeGene ? 1 : N; - const float t1 = ParamConfig::SimilarityCoefExcess * static_cast(numExcess) / static_cast(N); - const float t2 = ParamConfig::SimilarityCoefDisj * static_cast(numDisjoint) / static_cast(N); + const float t1 = ParamConfig::SimilarityCoefExcess * + static_cast(numExcess) / static_cast(N); + const float t2 = ParamConfig::SimilarityCoefDisj * + static_cast(numDisjoint) / static_cast(N); const float t3 = ParamConfig::SimilarityCoefWeight * weightDifference; return t1 + t2 + t3; } template -inline std::size_t Genome::getNumConnectionGenes() const noexcept { +inline std::size_t Genome::getNumConnectionGenes() + const noexcept { return connections_.size(); } template -inline std::size_t Genome::getNumNodes() const noexcept { +inline std::size_t Genome::getNumNodes() + const noexcept { return static_cast(nextNodeID_ - !Bias); } template -inline LayerId Genome::getLayerOfNode(const NodeId nodeId) const noexcept { +inline LayerId Genome::getLayerOfNode( + const NodeId nodeId) const noexcept { assert(static_cast(nodeId) < layerOfNodes_.size()); assert(Bias || nodeId != 0); return layerOfNodes_[nodeId]; } template -inline const FlatMap>& Genome::getLayers() const noexcept { +inline const FlatMap>& +Genome::getLayers() const noexcept { return layers_; } template -inline const std::vector& Genome::getConnections() const noexcept { +inline const std::vector& +Genome::getConnections() const noexcept { return connections_.valuesVector(); } template template -void Genome::mutateWeight(RndEngine* rndEngine, ConnectionGene* connectionGene) { - const float newWeight = CheckProbability(rndEngine, ParamConfig::ProbMutationWeightPerturbation) - ? ParamConfig::WeightPerturbation(connectionGene->getWeight(), rndEngine) - : ParamConfig::NewRndWeight(rndEngine); +void Genome::mutateWeight( + RndEngine* rndEngine, + ConnectionGene* connectionGene) { + const float newWeight = + CheckProbability(rndEngine, ParamConfig::ProbMutationWeightPerturbation) + ? ParamConfig::WeightPerturbation(connectionGene->getWeight(), + rndEngine) + : ParamConfig::NewRndWeight(rndEngine); connectionGene->setWeight(newWeight); assert(newWeight >= -1.f && newWeight <= 1.f); @@ -276,8 +301,8 @@ template template void Genome::mutateAddNewNode(RndEngine* rndEngine) { // Pick random connection - const std::size_t index = - std::uniform_int_distribution{static_cast(0), connections_.size() - 1}(*rndEngine); + const std::size_t index = std::uniform_int_distribution{ + static_cast(0), connections_.size() - 1}(*rndEngine); ConnectionGene& oldConnection = connections_.nthValue(index); const NodeId prevNode = oldConnection.getFrom(); const NodeId succNode = oldConnection.getTo(); @@ -285,14 +310,16 @@ void Genome::mutateAddNewNode(RndEngine* rndEngine) { // TODO(bfesta): if connection not enable we skip. is that correct? if ((Bias && prevNode == 0) || !oldConnection.getEnabled()) { - // TODO(bfesta): this is the fastest way. However, slower way in order to guarantee the adding. + // TODO(bfesta): this is the fastest way. However, slower way in order to + // guarantee the adding. return; } // Disable old connection oldConnection.setEnabled(false); - // Create two new connections (note: oldConnection is invalid reference now because of emplace) + // Create two new connections (note: oldConnection is invalid reference now + // because of emplace) emplaceNewConnectionGene(prevNode, nextNodeID_, 1.f); emplaceNewConnectionGene(nextNodeID_, succNode, weight); @@ -302,7 +329,8 @@ void Genome::mutateAddNewNode(RndEngine* rndEngine) { } // Assign the layer - const LayerId layer = (getLayerOfNode(prevNode) + getLayerOfNode(succNode)) / 2; + const LayerId layer = + (getLayerOfNode(prevNode) + getLayerOfNode(succNode)) / 2; assignLayerNewNode(nextNodeID_, layer); // Increment Next Node ID counter @@ -311,8 +339,10 @@ void Genome::mutateAddNewNode(RndEngine* rndEngine) { template template -void Genome::mutateAddNewConnection(RndEngine* rndEngine) { - std::uniform_int_distribution rndLayer{static_cast(0), layers_.size() - 1}; +void Genome::mutateAddNewConnection( + RndEngine* rndEngine) { + std::uniform_int_distribution rndLayer{ + static_cast(0), layers_.size() - 1}; std::size_t layer1, layer2; do { @@ -327,8 +357,10 @@ void Genome::mutateAddNewConnection(RndEngine* rndEng const FlatSet& layer1_data = layers_.nthValue(layer1); const FlatSet& layer2_data = layers_.nthValue(layer2); - std::uniform_int_distribution rndFrom{static_cast(0), layer1_data.size() - 1}; - std::uniform_int_distribution rndTo{static_cast(0), layer2_data.size() - 1}; + std::uniform_int_distribution rndFrom{ + static_cast(0), layer1_data.size() - 1}; + std::uniform_int_distribution rndTo{static_cast(0), + layer2_data.size() - 1}; const NodeId from = layer1_data.nth(rndFrom(*rndEngine)); const NodeId to = layer2_data.nth(rndTo(*rndEngine)); @@ -357,7 +389,8 @@ bool Genome::validate() const { assert(false); return false; } - if (layerOfNodes_[connectionGene.getTo()] <= layerOfNodes_[connectionGene.getFrom()]) { + if (layerOfNodes_[connectionGene.getTo()] <= + layerOfNodes_[connectionGene.getFrom()]) { assert(false); return false; } @@ -367,9 +400,10 @@ bool Genome::validate() const { } template -inline void Genome::emplaceNewConnectionGene(const NodeId from, - const NodeId to, - const float weight) { +inline void Genome::emplaceNewConnectionGene( + const NodeId from, + const NodeId to, + const float weight) { const ConnectionHash connectionHash = hashConnection(from, to); assert(existConnection(from, to) == nullptr); @@ -377,11 +411,14 @@ inline void Genome::emplaceNewConnectionGene(const No } template -inline void Genome::assignLayerNewNode(const NodeId node, const LayerId layer) { +inline void Genome::assignLayerNewNode( + const NodeId node, + const LayerId layer) { assert(node == static_cast(layerOfNodes_.size())); if (auto* layerNodes = layers_[layer]; layerNodes != nullptr) { - assert(std::find(layerNodes->begin(), layerNodes->end(), node) == layerNodes->end()); + assert(std::find(layerNodes->begin(), layerNodes->end(), node) == + layerNodes->end()); layerNodes->insert(node); } else { layers_.insert(layer)->insert(node); @@ -390,12 +427,16 @@ inline void Genome::assignLayerNewNode(const NodeId n } template -inline ConnectionGene* Genome::existConnection(const NodeId from, const NodeId to) noexcept { +inline ConnectionGene* Genome::existConnection( + const NodeId from, + const NodeId to) noexcept { return connections_[hashConnection(from, to)]; } template -inline ConnectionHash Genome::hashConnection(const NodeId from, const NodeId to) noexcept { +inline ConnectionHash Genome::hashConnection( + const NodeId from, + const NodeId to) noexcept { ConnectionHash hash = 0; hash |= static_cast(from); hash |= static_cast(to) << 32; diff --git a/include/EvolutionNet/Network.hpp b/include/EvolutionNet/Network.hpp index 42e2227..6b53234 100644 --- a/include/EvolutionNet/Network.hpp +++ b/include/EvolutionNet/Network.hpp @@ -37,16 +37,19 @@ class Network final { void initializeFromGenome(const Genome& genome); //! \brief Set the input value (input-th). - inline void setInputValue(const std::size_t input, const float value) noexcept; + inline void setInputValue(const std::size_t input, + const float value) noexcept; - //! \return the output value (output-th). Remember to `feedforward` before take this value. + //! \return the output value (output-th). Remember to `feedforward` before + //! take this value. inline float getOutputValue(const std::size_t output) const noexcept; //! \brief simply, feed forwars algorithm network. template void feedForward(); - //! \brief assign a fitness value to this network. Higher value means better score. + //! \brief assign a fitness value to this network. Higher value means better + //! score. inline void setFitness(const FitnessScore fitness) noexcept; //! \return the fitness score of this network. @@ -65,7 +68,8 @@ class Network final { bool deserialize(std::istream* is); /*! \brief Check equality between two network structures. - * \note Only structure will be checked. Fitness value and current node values will not be checked. + * \note Only structure will be checked. Fitness value and current node + * values will not be checked. */ bool sameStructure(const Network& rhs) const noexcept; @@ -77,7 +81,7 @@ class Network final { std::vector nodes_; std::vector nodeValues_; std::vector> inConnections_; - FitnessScore fitness_; + FitnessScore fitness_ = 0.f; }; template @@ -104,7 +108,8 @@ void Network::initializeFromGenome(const Genome& genome) { for (const auto& connGene : genome.getConnections()) { assert(connGene.getTo() < inConnections_.size()); if (connGene.getEnabled()) { - inConnections_[connGene.getTo()].emplace_back(connGene.getFrom(), connGene.getWeight()); + inConnections_[connGene.getTo()].emplace_back(connGene.getFrom(), + connGene.getWeight()); } } @@ -112,14 +117,16 @@ void Network::initializeFromGenome(const Genome& genome) { } template -inline void Network::setInputValue(const std::size_t input, const float value) noexcept { +inline void Network::setInputValue(const std::size_t input, + const float value) noexcept { assert(input < Genome::NumInputValue); assert(input + 1 < nodeValues_.size()); nodeValues_[input + 1] = value; } template -inline float Network::getOutputValue(const std::size_t output) const noexcept { +inline float Network::getOutputValue( + const std::size_t output) const noexcept { assert(output < Genome::NumOutputValue); assert(Genome::NumInputValue + 1 + output < nodeValues_.size()); return nodeValues_[Genome::NumInputValue + 1 + output]; @@ -156,17 +163,21 @@ inline FitnessScore Network::getFitness() const noexcept { } template -void Network::serialize(std::ostream* os, const std::size_t bufferSize) const { +void Network::serialize(std::ostream* os, + const std::size_t bufferSize) const { const std::size_t TotalNumConnections = std::accumulate(inConnections_.cbegin(), inConnections_.cend(), std::size_t{0}, - [](const auto& acc, const auto& inConnections) noexcept { return acc + inConnections.size(); }); - const std::size_t TotalSize = (4) // Magic number - + (4) // Num of nodes - + (4 * nodes_.size()) // Node IDs - + (4 * nodes_.size()) // Num In Connections for each node - + ((4 + 4) * TotalNumConnections); // Connection Data + [](const auto& acc, const auto& inConnections) noexcept { + return acc + inConnections.size(); + }); + const std::size_t TotalSize = + (4) // Magic number + + (4) // Num of nodes + + (4 * nodes_.size()) // Node IDs + + (4 * nodes_.size()) // Num In Connections for each node + + ((4 + 4) * TotalNumConnections); // Connection Data std::size_t offset = 0, bytesToWrite; auto data = std::make_unique(TotalSize); @@ -178,7 +189,8 @@ void Network::serialize(std::ostream* os, const std::size_t bufferSize) // Num of Nodes (4 bytes) assert(offset + 4 <= TotalSize); - *reinterpret_cast(data.get() + 4) = static_cast(nodes_.size()); + *reinterpret_cast(data.get() + 4) = + static_cast(nodes_.size()); offset += 4; for (const NodeId nodeId : nodes_) { @@ -189,7 +201,8 @@ void Network::serialize(std::ostream* os, const std::size_t bufferSize) for (const auto& inConnections : inConnections_) { assert(offset + 4 <= TotalSize); - *reinterpret_cast(data.get() + offset) = static_cast(inConnections.size()); + *reinterpret_cast(data.get() + offset) = + static_cast(inConnections.size()); offset += 4; for (const auto& [nodeFrom, weight] : inConnections) { @@ -199,7 +212,8 @@ void Network::serialize(std::ostream* os, const std::size_t bufferSize) assert(offset + 4 <= TotalSize); static_assert(sizeof(float) == sizeof(std::uint32_t)); - *reinterpret_cast(data.get() + offset) = *reinterpret_cast(&weight); + *reinterpret_cast(data.get() + offset) = + *reinterpret_cast(&weight); offset += 4; } } @@ -265,7 +279,8 @@ bool Network::deserialize(std::istream* is) { inConnections_[i].emplace_back( *reinterpret_cast(&buffer8), - *reinterpret_cast(reinterpret_cast(&buffer8) + sizeof(NodeId))); + *reinterpret_cast(reinterpret_cast(&buffer8) + + sizeof(NodeId))); } } diff --git a/include/EvolutionNet/Population.hpp b/include/EvolutionNet/Population.hpp index 4b824d2..9e8c06e 100644 --- a/include/EvolutionNet/Population.hpp +++ b/include/EvolutionNet/Population.hpp @@ -40,7 +40,8 @@ class Population final { void initialize(const std::size_t size, RndEngine* rndEngine); //! \brief Assign a fitness to the genomeIndex-th member of the population. - inline void assignFitness(const std::size_t genomeIndex, const FitnessScore fitness) noexcept; + inline void assignFitness(const std::size_t genomeIndex, + const FitnessScore fitness) noexcept; //! \brief Evolve the population to the next generation. template @@ -86,12 +87,14 @@ class Population final { template void computeStagnation(); - inline FitnessScore computeMaxFitnessInSpecie(const std::size_t specieIndex) const noexcept; + inline FitnessScore computeMaxFitnessInSpecie( + const std::size_t specieIndex) const noexcept; }; template template -void Population::initialize(const std::size_t size, RndEngine* rndEngine) { +void Population::initialize(const std::size_t size, + RndEngine* rndEngine) { population_.clear(); population_.resize(size); for (std::size_t i = 0; i < size; ++i) { @@ -110,7 +113,9 @@ void Population::initialize(const std::size_t size, RndEngine* rndEngine } template -inline void Population::assignFitness(const std::size_t genomeIndex, const FitnessScore fitness) noexcept { +inline void Population::assignFitness( + const std::size_t genomeIndex, + const FitnessScore fitness) noexcept { assert(genomeIndex < fitness_.size()); fitness_[genomeIndex] = fitness; } @@ -134,7 +139,8 @@ inline std::size_t Population::getPopulationSize() const noexcept { } template -inline const Genome& Population::getGenomeNth(const std::size_t index) const noexcept { +inline const Genome& Population::getGenomeNth( + const std::size_t index) const noexcept { assert(index < population_.size()); return population_[index]; } @@ -154,13 +160,17 @@ void Population::speciate() { // Assign each genome to a specie bool found; - for (std::size_t genomeIndex = 0; genomeIndex < population_.size(); ++genomeIndex) { + for (std::size_t genomeIndex = 0; genomeIndex < population_.size(); + ++genomeIndex) { const auto& genome = population_[genomeIndex]; found = false; - for (std::size_t specieIndex = 0; specieIndex < specieRapresentatives_.size() && !found; ++specieIndex) { + for (std::size_t specieIndex = 0; + specieIndex < specieRapresentatives_.size() && !found; + ++specieIndex) { const Genome* representative = specieRapresentatives_[specieIndex]; - if (genome.template computeSimilarity(*representative) < ParamConfig::SpeciesSimilarityThreshold) { + if (genome.template computeSimilarity(*representative) < + ParamConfig::SpeciesSimilarityThreshold) { species_[specieIndex].push_back(genomeIndex); specieOfGenomes_[genomeIndex] = specieIndex; found = true; @@ -186,10 +196,12 @@ void Population::computeAdjustedFitness() { adjustedFitness_.resize(fitness_.size()); - for (std::size_t genomeIndex = 0; genomeIndex < population_.size(); ++genomeIndex) { + for (std::size_t genomeIndex = 0; genomeIndex < population_.size(); + ++genomeIndex) { const std::size_t specieIndex = specieOfGenomes_[genomeIndex]; assert(specieIndex < species_.size()); - const float adjustedFitness = fitness_[genomeIndex] / species_[specieIndex].size(); + const float adjustedFitness = + fitness_[genomeIndex] / species_[specieIndex].size(); adjustedFitness_[genomeIndex] = adjustedFitness; specieFitnessSum_[specieIndex] += adjustedFitness; @@ -199,21 +211,27 @@ void Population::computeAdjustedFitness() { template void Population::sortSpecies() { for (auto& specie : species_) { - std::sort(specie.begin(), specie.end(), [this](const std::size_t i, const std::size_t j) noexcept { - return adjustedFitness_[j] < adjustedFitness_[i]; - }); + std::sort(specie.begin(), + specie.end(), + [this](const std::size_t i, const std::size_t j) noexcept { + return adjustedFitness_[j] < adjustedFitness_[i]; + }); } } template void Population::computeSpeciesBounds() { assert(specieFitnessSum_.size() == species_.size()); - const FitnessScore sumFitness = - std::accumulate(specieFitnessSum_.cbegin(), specieFitnessSum_.cend(), static_cast(0)); + const FitnessScore sumFitness = std::accumulate(specieFitnessSum_.cbegin(), + specieFitnessSum_.cend(), + static_cast(0)); specieBounds_.resize(species_.size()); - for (std::size_t specieIndex = 0; specieIndex < species_.size(); ++specieIndex) { - const std::size_t bound = std::ceil(specieFitnessSum_[specieIndex] / sumFitness * species_[specieIndex].size()); + for (std::size_t specieIndex = 0; specieIndex < species_.size(); + ++specieIndex) { + const std::size_t bound = static_cast( + std::ceil(specieFitnessSum_[specieIndex] / sumFitness * + species_[specieIndex].size())); assert(bound <= species_[specieIndex].size()); specieBounds_[specieIndex] = bound; } @@ -225,7 +243,10 @@ void Population::offspringSpecies(RndEngine* rndEngine) { assert(species_.size() == specieBounds_.size()); const Genome *parent1, *parent2; - if (std::all_of(species_.cbegin(), species_.cend(), [](const Specie& specie) noexcept { return specie.empty(); })) { + if (std::all_of( + species_.cbegin(), + species_.cend(), + [](const Specie& specie) noexcept { return specie.empty(); })) { const std::size_t bound = population_.size() / 2; for (std::size_t i = 0; i < bound; ++i) { population_[i].template mutate(rndEngine); @@ -236,7 +257,8 @@ void Population::offspringSpecies(RndEngine* rndEngine) { return; } - for (std::size_t specieIndex = 0; specieIndex < species_.size(); ++specieIndex) { + for (std::size_t specieIndex = 0; specieIndex < species_.size(); + ++specieIndex) { const Specie& specie = species_[specieIndex]; if (specie.empty()) { continue; @@ -245,7 +267,10 @@ void Population::offspringSpecies(RndEngine* rndEngine) { assert(bound > 0); // old offspring (to keep) - for (std::size_t i = (ParamConfig::SizeSpecieForChampion < specie.size() ? 1 : 0); i < bound; ++i) { + for (std::size_t i = + (ParamConfig::SizeSpecieForChampion < specie.size() ? 1 : 0); + i < bound; + ++i) { Genome& genome = population_[specie[i]]; genome.template mutate(rndEngine); } @@ -257,16 +282,21 @@ void Population::offspringSpecies(RndEngine* rndEngine) { if (CheckProbability(rndEngine, ParamConfig::ProbOffspringCrossover)) { if (CheckProbability(rndEngine, ParamConfig::ProbMatingInterspecies)) { // Interspecie mating - std::uniform_int_distribution rndOthSpecie{0, species_.size() - 1}; + std::uniform_int_distribution rndOthSpecie{ + 0, species_.size() - 1}; std::size_t othSpecieIndex; do { othSpecieIndex = rndOthSpecie(*rndEngine); - } while (species_[othSpecieIndex].empty() || specieBounds_[othSpecieIndex] == 0); + } while (species_[othSpecieIndex].empty() || + specieBounds_[othSpecieIndex] == 0); const Specie& othSpecie = species_[othSpecieIndex]; const std::size_t bound2 = specieBounds_[othSpecieIndex]; - const std::size_t id1 = std::uniform_int_distribution{0, bound - 1}(*rndEngine); - const std::size_t id2 = std::uniform_int_distribution{0, bound2 - 1}(*rndEngine); - if (adjustedFitness_[specie[id1]] < adjustedFitness_[othSpecie[id2]]) { + const std::size_t id1 = std::uniform_int_distribution{ + 0, bound - 1}(*rndEngine); + const std::size_t id2 = std::uniform_int_distribution{ + 0, bound2 - 1}(*rndEngine); + if (adjustedFitness_[specie[id1]] < + adjustedFitness_[othSpecie[id2]]) { parent1 = &(population_[othSpecie[id2]]); parent2 = &(population_[specie[id1]]); } else { @@ -313,7 +343,8 @@ void Population::cleanSpecies() { if ((!species_[i].empty()) && (specieBounds_[i] > 0)) { newSpecies.push_back(std::move(species_[i])); newSpecieFitnessMax.push_back(std::move(specieFitnessMax_[i])); - newSpecieStagnantGenerations.push_back(std::move(specieStagnantGenerations_[i])); + newSpecieStagnantGenerations.push_back( + std::move(specieStagnantGenerations_[i])); newSpecieRapresentatives.push_back(std::move(specieRapresentatives_[i])); } } @@ -331,7 +362,8 @@ void Population::electRapresentativeForSpecies(RndEngine* rndEngine) { const Specie& specie = species_[i]; const std::size_t numToKeep = specieBounds_[i]; if (!specie.empty() && numToKeep > 0) { - const std::size_t rndElem = std::uniform_int_distribution{0, numToKeep - 1}(*rndEngine); + const std::size_t rndElem = std::uniform_int_distribution{ + 0, numToKeep - 1}(*rndEngine); specieRapresentatives_[i] = &(population_[specie[rndElem]]); } } @@ -343,16 +375,20 @@ void Population::computeStagnation() { assert(specieFitnessMax_.size() == species_.size()); assert(specieStagnantGenerations_.size() == species_.size()); - for (std::size_t specieIndex = 0; specieIndex < species_.size(); ++specieIndex) { + for (std::size_t specieIndex = 0; specieIndex < species_.size(); + ++specieIndex) { if (!species_[specieIndex].empty()) { - const FitnessScore currentMaxFitness = computeMaxFitnessInSpecie(specieIndex); + const FitnessScore currentMaxFitness = + computeMaxFitnessInSpecie(specieIndex); const FitnessScore historicalMaxFitness = specieFitnessMax_[specieIndex]; - if (specieStagnantGenerations_[specieIndex] == 0 || historicalMaxFitness < currentMaxFitness) { + if (specieStagnantGenerations_[specieIndex] == 0 || + historicalMaxFitness < currentMaxFitness) { specieFitnessMax_[specieIndex] = currentMaxFitness; specieStagnantGenerations_[specieIndex] = 1; } else { - if (ParamConfig::GenForStagningSpecies < (++specieStagnantGenerations_[specieIndex])) { + if (ParamConfig::GenForStagningSpecies < + (++specieStagnantGenerations_[specieIndex])) { species_[specieIndex].clear(); } } @@ -361,7 +397,8 @@ void Population::computeStagnation() { } template -inline FitnessScore Population::computeMaxFitnessInSpecie(const std::size_t specieIndex) const noexcept { +inline FitnessScore Population::computeMaxFitnessInSpecie( + const std::size_t specieIndex) const noexcept { assert(specieIndex < species_.size()); assert(!species_[specieIndex].empty()); assert(species_[specieIndex][0] < adjustedFitness_.size()); diff --git a/include/EvolutionNet/Types.hpp b/include/EvolutionNet/Types.hpp index 003a0b4..8978909 100644 --- a/include/EvolutionNet/Types.hpp +++ b/include/EvolutionNet/Types.hpp @@ -20,8 +20,8 @@ #define EVOLUTION_NET_TYPES_HPP #include #include -#include #include +#include namespace EvolutionNet { @@ -43,12 +43,15 @@ struct DefaultParamConfig { static constexpr float SimilarityCoefExcess = 1.f; static constexpr float SimilarityCoefDisj = 1.f; static constexpr float SimilarityCoefWeight = 3.f; - static constexpr float ProbOffspringCrossover = 0.75; - static constexpr float ProbMatingInterspecies = 0.001; + static constexpr float ProbOffspringCrossover = 0.75f; + static constexpr float ProbMatingInterspecies = 0.001f; static constexpr std::size_t SizeSpecieForChampion = 5; static constexpr std::size_t GenForStagningSpecies = 15; - static inline std::uniform_real_distribution DistributionNewWeight{-1.f, 1.f}; - static inline std::normal_distribution DistributionPertWeight{0.f, 1.f}; + static inline std::uniform_real_distribution DistributionNewWeight{ + -1.f, + 1.f}; + static inline std::normal_distribution DistributionPertWeight{0.f, + 1.f}; static float WeightPerturbation(float weight, RndEngine* rndEngine) noexcept { weight += DistributionPertWeight(*rndEngine) / 50; From 74655f5fab9e1622bab7f3961dc901e87a080f02 Mon Sep 17 00:00:00 2001 From: Ben Decker Date: Wed, 26 Apr 2023 14:55:19 -0600 Subject: [PATCH 3/4] fix windows build warnings --- include/EvolutionNet/Genome.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/EvolutionNet/Genome.hpp b/include/EvolutionNet/Genome.hpp index c87af83..3524df0 100644 --- a/include/EvolutionNet/Genome.hpp +++ b/include/EvolutionNet/Genome.hpp @@ -238,7 +238,8 @@ float Genome::computeSimilarity( weightDifference = numMatching ? weightDifference / numMatching : 0.f; - N = std::max(getNumConnectionGenes(), oth.getNumConnectionGenes()); + N = static_cast( + std::max(getNumConnectionGenes(), oth.getNumConnectionGenes())); N = N < ParamConfig::NormalizedSizeGene ? 1 : N; const float t1 = ParamConfig::SimilarityCoefExcess * From 5a7e3d1d865bcc18f71dfe36df24b23433422beb Mon Sep 17 00:00:00 2001 From: Ben Decker Date: Wed, 26 Apr 2023 15:04:49 -0600 Subject: [PATCH 4/4] change test target names and add source grouping --- examples/CMakeLists.txt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index df95108..2019fca 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,5 +1,10 @@ -add_executable(${PROJECT_NAME}_example_xor_minimal xor_minimal.cpp) -target_link_libraries(${PROJECT_NAME}_example_xor_minimal PRIVATE ${PROJECT_NAME}) - -add_executable(${PROJECT_NAME}_example_xor_advanced xor_advanced.cpp) -target_link_libraries(${PROJECT_NAME}_example_xor_advanced PRIVATE ${PROJECT_NAME}) +set(xor_advanced_srcs xor_advanced.cpp) +set(xor_minimal_srcs xor_minimal.cpp) +#################### +add_executable(xor_advanced_example ${xor_advanced_srcs}) +target_link_libraries(xor_advanced_example PRIVATE ${PROJECT_NAME}) +source_group("" FILES ${xor_advanced_srcs}) +#################### +add_executable(xor_minimal_example ${xor_minimal_srcs}) +target_link_libraries(xor_minimal_example PRIVATE ${PROJECT_NAME}) +source_group("" FILES ${xor_minimal_srcs})