From b7c07325351ed4752435a4ec574c6481b87fb32e Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Wed, 21 Aug 2024 16:50:41 +0200 Subject: [PATCH 01/31] feat(solver): adds moCombination Generic encoding for sequence of indices pointing toward actual Operator instances. --- CMakeLists.txt | 60 ++++++++++++- app/example.cpp | 10 ++- src/include/log.h | 16 ++++ src/include/moCombination.hpp | 159 ++++++++++++++++++++++++++++++++++ test/moCombination_T.cpp | 61 +++++++++++++ 5 files changed, 300 insertions(+), 6 deletions(-) create mode 100644 src/include/log.h create mode 100644 src/include/moCombination.hpp create mode 100644 test/moCombination_T.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f60a2a..d172893 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,11 +5,65 @@ project(rHashGen) # Set the C++ standard to use set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + + +###################################################################################### +# Configurable user settings +###################################################################################### + +# Compilation options +option(BUILD_DOCS "Enable building of documentation" OFF) +option(BUILD_TESTING "Enable building of tests" OFF) +option(BUILD_FOR_LOCAL "Whether to make the executables dependant on the environment of the building computer (enables CMake's build with RPATH), may be necessary on HPC clusters" OFF) +option(USE_LOCAL_PARADISEO "Use a local version of Paradiseo rather than the one installed on the system" OFF) + +# Common +add_compile_options(-Wall -Wextra -pedantic) +# Clang +#add_compile_options( -Wno-c++98-compat-pedantic -Wno-old-style-cast -Wno-padded -Wno-extra-semi-stmt -Wno-weak-vtables) + +add_compile_definitions(-DWITH_CLUTCHLOG) + +if(BUILD_FOR_LOCAL) + set(CMAKE_BUILD_WITH_INSTALL_RPATH ON) +endif() + + +###################################################################################### +# Dependencies +###################################################################################### + +# Own lib +include_directories(include) + +# ParadisEO +if(USE_LOCAL_PARADISEO) + set(PARADISEO_ROOT "PARADISEO_NOT_FOUND" CACHE PATH "Where to find ParadisEO") + set(PARADISEO_BUILD "${PARADISEO_ROOT}/build" CACHE PATH "Build dir of ParadisEO") + + include_directories(${PARADISEO_ROOT}) + include_directories(${PARADISEO_ROOT}/eo/src) + include_directories(${PARADISEO_ROOT}/mo/src) + link_directories(${PARADISEO_BUILD}/lib) +else() + include_directories($ENV{PARADISEO_ROOT}/include/paradiseo/eo) + include_directories($ENV{PARADISEO_ROOT}/include/paradiseo/mo) + link_directories($ENV{PARADISEO_ROOT}/lib64) +endif() +set(PARADISEO_LIBRARIES ga eoutils eo) + +# Single-header dependencies. +include_directories(external/clutchlog) # Include the src directory include_directories(${PROJECT_SOURCE_DIR}/src/include) + +###################################################################################### +# Start building +###################################################################################### + # Add subdirectories add_subdirectory(src) @@ -24,10 +78,10 @@ file(GLOB APP_SOURCES ${PROJECT_SOURCE_DIR}/app/*.cpp) foreach(APP_SOURCE ${APP_SOURCES}) # Get the file name without the extension get_filename_component(APP_NAME ${APP_SOURCE} NAME_WE) - + # Add the executable add_executable(${APP_NAME} ${APP_SOURCE}) - + # Link the necessary libraries target_link_libraries(${APP_NAME} PRIVATE rHashGenLib) endforeach() diff --git a/app/example.cpp b/app/example.cpp index 9071eec..aec6d14 100644 --- a/app/example.cpp +++ b/app/example.cpp @@ -10,6 +10,7 @@ #include "AddShift.hpp" #include "Masking.hpp" #include "Multiply.hpp" +#include "moCombination.hpp" int main() @@ -17,6 +18,7 @@ int main() // The size of the values to manipulate is 57 bits. size_t value_size{31}; using myuint = uint32_t; + using myfit = double; // Create an instance of HashFunction with a value size of 64 bits HashFunction hashFunc("hash", value_size); @@ -37,7 +39,7 @@ int main() // Get the inverse function HashFunction revHashFunc{hashFunc.invert()}; - + // Print the string representation of the inverted hash function std::cout << "Inverted function:" << std::endl; std::cout << revHashFunc.to_string() << std::endl; @@ -46,10 +48,12 @@ int main() myuint value {0xDADBEEF}; myuint hashed {hashFunc.apply(value)}; std::cout << hashFunc.get_name() << "(0x" << std::hex << value << ") = 0x" << hashed << std::endl; - + // Apply the inverse function to the hashed value myuint recovered {revHashFunc.apply(hashed)}; std::cout << revHashFunc.get_name() << "(0x" << std::hex << hashed << ") = 0x" << recovered << std::dec << std::endl; + moCombination({0,1,2,3,4,5}, 6); + return 0; -} \ No newline at end of file +} diff --git a/src/include/log.h b/src/include/log.h new file mode 100644 index 0000000..068077a --- /dev/null +++ b/src/include/log.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace frictionless { + +// Make asserts (de)clutchable. +#define ASSERT(EXPR) { CLUTCHFUNC(critical, assert, EXPR); } + +/** Generic parts of the configuration for clutchlog. + * + * Mainly colors and line template. + */ +//void clutchlog_config(); + +} // frictionless diff --git a/src/include/moCombination.hpp b/src/include/moCombination.hpp new file mode 100644 index 0000000..3a99109 --- /dev/null +++ b/src/include/moCombination.hpp @@ -0,0 +1,159 @@ +#ifndef MOCOMBINATION_HPP +#define MOCOMBINATION_HPP + +#include + +#include +#include +#include + +/** A sequence of (possibly duplicated) indices drawn from a fixed set. + * + * That is, for a sequence of size n drawing from m indices: + * \f[ + * \{x_0, \dots, x_i, \dots, x_{n-1} \| x_i \in \{0, \dots, m-1\} \subset \mathrm{N}\} + * \f] + * + * For a sequence of size 3, drawing from a set of size 4, valid sequences could be: + * - {0,1,2} + * - {1,1,1} + * - {3,1,0} + * - {3,1,3} + */ +template +class moCombination : public EO +{ + public: + /** The type for indices. */ + using AtomType = size_t; + + /** The data structures holding the indices. */ + using ContainerType = std::vector; + + /** Constructor with initial sequence. + * + * @param combination A container. + * @param nb_options The past-the-maximum value that can be set. + */ + moCombination(ContainerType combination, size_t nb_options) : + #ifndef NDEBUG + _is_init(true), + #endif + _nb_options(nb_options), + _combination(combination) + { + CLUTCHLOG(debug, "instantiated with combination of size " << _combination.size() << " and " << _nb_options << " options"); + assert(_nb_options >= 2); // At least 2 options. + for(const auto i : _combination) { + assert(i < _nb_options); + } + } + + /** Constructor with sizes. + * + * @param size The size of the sequence. + * @param nb_options The past-the-maximum value that can be set. + */ + moCombination(size_t size, size_t nb_options) : + #ifndef NDEBUG + _is_init(false), + #endif + _nb_options(nb_options), + _combination(size, nb_options) + { + CLUTCHLOG(debug, "instantiated with size " << _combination.size() << " and " << _nb_options << "options"); + assert(_nb_options >= 2); + } + + /** Empty constructor. + */ + moCombination() : + #ifndef NDEBUG + _is_init(false), + #endif + _nb_options(0) + { + CLUTCHLOG(debug, "empty instantiated"); + assert(_combination.size() == 0); + } + + /** Setter. + * + * @param index The index at which to change the atom. + * @param value The atom value to put there. + */ + void set(size_t index, size_t value) + { + CLUTCHLOG(debug, "set combination: @" << index << "/" << _combination.size() << " =" << value << "/" << _nb_options); + assert(_is_init); + assert(index < _combination.size()); + assert(value < _nb_options); + if(_combination[index] == value) { + std::clog << "call to `set` produces identical state" << std::endl; + // FIXME: eo::log << eo::warnings << "call to `set` produces identical state" << std::endl; + } + _combination[index] = value; + } + + /** Accessor to the sequence. */ + ContainerType& get() { + return _combination; + } + + /** Accessor to an item of the sequence. */ + AtomType& at(size_t index) { + return _combination.at(index); + } + + /** Accessor to an item of the sequence. */ + AtomType& operator[](size_t index) { + return _combination[index]; + } + + /** The current size of the sequence. */ + size_t size() const { + return _combination.size(); + } + + /** The size of the possible indices. */ + size_t nb_options() const { + return _nb_options; + } + + /** Fancy print. */ + virtual void printOn(std::ostream& out) const override { + assert(_is_init); + EO::printOn(out); // Fitness. + out << _combination.size(); + for(const auto i : _combination) { + out << " " << i; + } + } + + //! Class name for state management. + virtual std::string className() const override { + return "moCombination"; + } + +#ifndef NDEBUG + public: +#else + protected: +#endif + #ifndef NDEBUG + /** Sanity flag. + * + * Used in debug builds to ensure that the neighbor + * have been set before being used. + */ + bool _is_init; + #endif + + //! The past-the-maximum value of the indices that can be set. + size_t _nb_options; + + //! The sequence of indices. + ContainerType _combination; +}; + +#endif // MOCOMBINATION_HPP diff --git a/test/moCombination_T.cpp b/test/moCombination_T.cpp new file mode 100644 index 0000000..3fc12b6 --- /dev/null +++ b/test/moCombination_T.cpp @@ -0,0 +1,61 @@ +#include + +#include "moCombination.hpp" + +TEST(moCombination, Instanciation) +{ + moCombination::ContainerType u{0,1,2,3,4,5}; + moCombination c(u,6); + c.set(0,1); + c.set(1,2); + c.set(5,4); + moCombination::ContainerType v{1,2,2,3,4,4}; + moCombination::ContainerType w = c.get(); + ASSERT_EQ(v.size(), w.size()) << "vectors have different size"; + for (size_t i=0; i < w.size(); ++i) { + ASSERT_EQ(v[i], w[i]) << "vector not correctly set at index " << i; + } + +} + +TEST(moCombination_DeathTest, Instantiation) +{ + moCombination::ContainerType v{0,1,2,3,4,5}; + // Constructor with container. + ASSERT_DEATH({ + moCombination c(v,0); // Not enough options + }, ""); + ASSERT_DEATH({ + moCombination c(v,1); // Not enough options + }, ""); + ASSERT_DEATH({ + moCombination c(v,3); // Vector inconsistent with options number. + }, ""); + ASSERT_DEATH({ + moCombination c(v,3); // Vector inconsistent with options number. + }, ""); + ASSERT_DEATH({ + moCombination c(v,6); + c.set(10,0); // Out of container bounds. + }, ""); + ASSERT_DEATH({ + moCombination c(v,6); + c.set(0,6); // Out of options bounds. + }, ""); + + // Constructor with sizes. + ASSERT_DEATH({ + moCombination c(6,0); // Not enough options. + }, ""); + ASSERT_DEATH({ + moCombination c(6,1); // Not enough options. + }, ""); + + // Empty constructor. + ASSERT_DEATH({ + moCombination c; + c.set(0,0); // Not initialized. + }, ""); + +} + From 9b8f940fd3d06d483ba82cd8ca21e5cc5c0c2eff Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 22 Aug 2024 10:12:57 +0200 Subject: [PATCH 02/31] feat(solver): adds moCombinationNeighbor Defines a neighbor as changing a single operation in the sequence. --- app/example.cpp | 6 +- src/include/moCombinationNeighbor.hpp | 162 ++++++++++++++++++++++++++ test/moCombinationNeighbor_T.cpp | 13 +++ 3 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 src/include/moCombinationNeighbor.hpp create mode 100644 test/moCombinationNeighbor_T.cpp diff --git a/app/example.cpp b/app/example.cpp index aec6d14..cfc0b91 100644 --- a/app/example.cpp +++ b/app/example.cpp @@ -11,6 +11,7 @@ #include "Masking.hpp" #include "Multiply.hpp" #include "moCombination.hpp" +#include "moCombinationNeighbor.hpp" int main() @@ -53,7 +54,10 @@ int main() myuint recovered {revHashFunc.apply(hashed)}; std::cout << revHashFunc.get_name() << "(0x" << std::hex << hashed << ") = 0x" << recovered << std::dec << std::endl; - moCombination({0,1,2,3,4,5}, 6); + moCombination sol({0,1,2,3,4,5}, 6); + moCombinationNeighbor> neighb; + neighb.set(0,1); + neighb.move(sol); return 0; } diff --git a/src/include/moCombinationNeighbor.hpp b/src/include/moCombinationNeighbor.hpp new file mode 100644 index 0000000..d436704 --- /dev/null +++ b/src/include/moCombinationNeighbor.hpp @@ -0,0 +1,162 @@ +#ifndef MOCOMBINATIONNEIGHBOR_HPP +#define MOCOMBINATIONNEIGHBOR_HPP + +#include + +#include "moCombination.hpp" + +/** Stable neighbor for sequences of indices. + * + * Models how to move a solution to a neighbor, + * by changing a single value in the sequence. + * The size of the sequence is thus guaranted to be stable. + * + * The core data structure is tww indices: + * - where in the sequence to change something, + * - what new index to put there. + */ +template +class moCombinationNeighbor : public moIndexNeighbor +{ + public: + /** Shortcut for Atom’s type. */ + using AtomType = typename EOT::AtomType; + + /** Shortcut for container’s type. */ + using ContainerType = typename EOT::ContainerType; + + /** Shortcut for fitness. */ + using moIndexNeighbor::fitness; + + /** Shortcut for key. */ + using moIndexNeighbor::key; + + /** Shortcut for index. */ + using moIndexNeighbor::index; + + /** Default constructor. + */ + moCombinationNeighbor() : + #ifndef NDEBUG + _is_init(false) + #endif + { } + + /** Copy constructor. + */ + moCombinationNeighbor( const moCombinationNeighbor& other) : + #ifndef NDEBUG + _is_init(other._is_init), + #endif + _index(other._index), + _value(other._value) + { + this->fitness(other.fitness()); + } + + /** Default assignment operator. + */ + moCombinationNeighbor& operator=( + const moCombinationNeighbor& other) + { + #ifndef NDEBUG + this->_is_init = other._is_init; + #endif + this->_index = other._index; + this->_value = other._value; + this->fitness(other.fitness()); + return *this; + } + + /** Apply the currently stored move. + * + * That is: change item at `index` for `value`. + */ + virtual void move(EOT& solution) override { + assert(_is_init); + solution.set(_index, _value); + } + + /** Set the considered move. + * + * @param index The index at which to change the atom. + * @param value The atom value to put there. + */ + void set(size_t index, size_t value) + { + _index = index; + _value = value; + _is_init = true; + } + + /** Set the considered move. + * + * @param idx_val A pair {index,value}. + */ + void set(std::pair idx_val) { + this->set(idx_val.first, idx_val.second); + } + + /** Get the considered move. + * + * @returns A pair {index, value}. + */ + std::pair get() { + assert(_is_init); + return std::make_pair(_index, _value); + } + + /** Get the considered index. + * + * @returns The index. + */ + AtomType index() { + assert(_is_init); + return _index; + } + + /** Get the considered value. + * + * @returns The value. + */ + AtomType value() { + assert(_is_init); + return _value; + } + + //! Class name for state management. + virtual std::string className() const override { + return "moCombinationNeighbor"; + } + + /** Fancy print. */ + virtual void printOn(std::ostream& out) const override { + assert(_is_init); + EO::printOn(out); // Fitness. + out << " " + << " @" << _index + << " =" << _value; + } + +#ifndef NDEBUG + public: +#else + protected: +#endif + #ifndef NDEBUG + /** Sanity flag. + * + * Used in debug builds to ensure that the neighbor + * have been set before being used. + */ + bool _is_init; + #endif + + //! The index of the targeted sequence at which the neighbor operates. + size_t _index; + + //! The value to put at the considered index. + size_t _value; +}; + +#endif // MOCOMBINATIONNEIGHBOR_HPP diff --git a/test/moCombinationNeighbor_T.cpp b/test/moCombinationNeighbor_T.cpp new file mode 100644 index 0000000..2010b16 --- /dev/null +++ b/test/moCombinationNeighbor_T.cpp @@ -0,0 +1,13 @@ +#include + +#include "moCombinationNeighbor.hpp" + +TEST(moCombinationNeighbor, Move) +{ + moCombination sol({0,1,2,3,4,5}, 6); + moCombinationNeighbor> neighb; + neighb.set(0,1); + neighb.move(sol); + ASSERT_EQ(sol.at(0), 1); +} + From 9a8e6ee8e3ce558cc30df6e811fa4f2cad77e17f Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 22 Aug 2024 11:41:26 +0200 Subject: [PATCH 03/31] feat(solver): adds moCombinationNeighborhood Generic enumeration of neighbors of an moCombination. --- src/include/moCombinationNeighborhood.hpp | 106 ++++++++++++++++++++++ test/moCombinationNeighborhood_T.cpp | 55 +++++++++++ 2 files changed, 161 insertions(+) create mode 100644 src/include/moCombinationNeighborhood.hpp create mode 100644 test/moCombinationNeighborhood_T.cpp diff --git a/src/include/moCombinationNeighborhood.hpp b/src/include/moCombinationNeighborhood.hpp new file mode 100644 index 0000000..4a57d35 --- /dev/null +++ b/src/include/moCombinationNeighborhood.hpp @@ -0,0 +1,106 @@ +#ifndef MOCOMBINATIONNEIGHBORHOOD_HPP +#define MOCOMBINATIONNEIGHBORHOOD_HPP + +#include + +#include "moCombinationNeighbor.hpp" + +template +class moCombinationNeighborhood : public moNeighborhood< moCombinationNeighbor > +{ + public: + /** Shortcut for neighbor's type. */ + using Neighbor = moCombinationNeighbor; + + /** Shortcut for Atom’s type. */ + using AtomType = typename EOT::AtomType; + + moCombinationNeighborhood() : _is_init(false) { } + + /** Initialize the neighborhood. + * + * This actually make the neighborhood point to the first possible change: + * at the first index, using the first value. + */ + virtual void init(EOT& /*from*/, Neighbor& to) override { + _i_index = 0; + _j_value = 0; + to.set(_i_index, _j_value); + to.invalidate(); + _is_init = true; + } + + /** Point to the next neighbor. */ + virtual void next(EOT& from, Neighbor& to) override { + assert(_is_init); + assert(cont(from)); + + if( _j_value >= from.nb_options()-1) { + _j_value = 0; // Reset inner loop. + _i_index++; // Next outer loop. + } else { + _j_value++; // Next inner loop. + } + + // Skip identity moves. + #ifndef NDEBUG + if(from.at(_i_index) == _j_value) + #else + if(from[_i_index] == _j_value) + #endif + { + next(from, to); + } + + // Implant this move in the neighbor. + to.set(_i_index, _j_value); + to.invalidate(); + } + + /** Returns true if there is more neighbors to be enumerated. */ + virtual bool cont(EOT& from) override { + assert(_is_init); + + // If reached the last item of the outer loop. + if( _i_index >= from.size()-1 + and _j_value >= from.nb_options()-1) { + return false; + } else { // There is still some items in the outer loop. + // and thus also in the inner loop. + assert( _i_index < from.size() ); + return true; + } + } + + /** Returns true if there is actual neighbors in the neighborhood. + * + * Essentially just tells if the sequence is not empty + * and there is options to choose. + * + * Should be always true in normal conditions. + */ + virtual bool hasNeighbor(EOT& solution) override { + // assert(_is_init); + return solution.size() > 0 and solution.nb_options() > 0; + } + + //! Class name for state management. + virtual std::string className() const override { + return "moCombinationNeighborhood"; + } + +#ifndef NDEBUG + public: + bool _is_init; +#else + protected: +#endif + /** Index of the currently pointed index. */ + size_t _i_index; + + /** Index of the currently pointed value. */ + size_t _j_value; +}; + + +#endif // MOCOMBINATIONNEIGHBORHOOD_HPP diff --git a/test/moCombinationNeighborhood_T.cpp b/test/moCombinationNeighborhood_T.cpp new file mode 100644 index 0000000..a1ef48b --- /dev/null +++ b/test/moCombinationNeighborhood_T.cpp @@ -0,0 +1,55 @@ +#include + +#include "moCombinationNeighborhood.hpp" + +TEST(moCombinationNeighborhood, TwoValues) +{ + moCombination sol({0,0}, 2); + moCombinationNeighbor> to; + moCombinationNeighborhood> hood; + + hood.init(sol, to); // {0,0} + EXPECT_TRUE(hood.hasNeighbor(sol)); + EXPECT_EQ(to.index(), 0); + EXPECT_EQ(to.value(), 0); + + hood.next(sol, to); // {1,0} + EXPECT_EQ(to.index(), 0); + EXPECT_EQ(to.value(), 1); + + hood.next(sol, to); // {0,1} + EXPECT_EQ(to.index(), 1); + EXPECT_EQ(to.value(), 1); + + EXPECT_FALSE(hood.cont(sol)); +} + +TEST(moCombinationNeighborhood, ThreeValues) +{ + moCombination sol({0,0}, 3); + moCombinationNeighbor> to; + moCombinationNeighborhood> hood; + + hood.init(sol, to); // {0,0} + EXPECT_TRUE(hood.hasNeighbor(sol)); + EXPECT_EQ(to.index(), 0); + EXPECT_EQ(to.value(), 0); + + hood.next(sol, to); // {1,0} + EXPECT_EQ(to.index(), 0); + EXPECT_EQ(to.value(), 1); + + hood.next(sol, to); // {2,0} + EXPECT_EQ(to.index(), 0); + EXPECT_EQ(to.value(), 2); + + hood.next(sol, to); // {0,1} + EXPECT_EQ(to.index(), 1); + EXPECT_EQ(to.value(), 1); + + hood.next(sol, to); // {0,2} + EXPECT_EQ(to.index(), 1); + EXPECT_EQ(to.value(), 2); + + EXPECT_FALSE(hood.cont(sol)); +} From 813baf96428c1c79fdbf00bcb588f719dd1d0a53 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 22 Aug 2024 18:08:23 +0200 Subject: [PATCH 04/31] FIX!(HashFunction): use shared_ptr instead of unique_ptr Operators being instantiated by a third party library (Paradiseo), we cannot manage the lifespan and uniqueness of the instances. BREAKING CHANGE --- src/include/HashFunction.hpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/include/HashFunction.hpp b/src/include/HashFunction.hpp index 5d300a3..4e2aca3 100644 --- a/src/include/HashFunction.hpp +++ b/src/include/HashFunction.hpp @@ -5,6 +5,7 @@ #include "Operator.hpp" #include "Masking.hpp" +#include "log.h" #ifndef HASHFUNCTION_HPP #define HASHFUNCTION_HPP @@ -17,7 +18,7 @@ class HashFunction std::string m_function_name; // A list of operations that constitute the hash function - std::vector>> m_operators; + std::vector>> m_operators; // The size (in bits) of the values to manipulate size_t m_value_size; @@ -38,7 +39,7 @@ class HashFunction /** Add an operator to the hash function * @param op The operator to add */ - void add_operator(std::unique_ptr> op) + void add_operator(std::shared_ptr>&& op) { m_operators.push_back(std::move(op)); } @@ -47,7 +48,7 @@ class HashFunction * @param value The value to hash * @return The hashed value */ - myuint apply (myuint value) const + myuint apply(myuint value) const { for (auto const & op : m_operators) { @@ -57,20 +58,21 @@ class HashFunction return value; } - /** Fill the vector of operations with the necessary masking operators to complete the hash function * The previous list of operations is replaced by a new one. */ void complete_with_masks() { - std::vector>> new_operations; + std::vector>> new_operations; // We add a masking operators when it is needed bool ongoing_overflow{false}; - for (std::unique_ptr> & op_ptr : m_operators) + for (std::shared_ptr> & op_ptr : m_operators) { + ASSERT(op_ptr != nullptr); + // Skip the loop if the operator is a masking operator if (auto const* masking_ptr = dynamic_cast*>(op_ptr.get())) { - // op_ptr est de type std::unique_ptr> + // op_ptr est de type std::shared_ptr> continue; } @@ -102,7 +104,7 @@ class HashFunction /** * Inverts the hash function by reversing the order of operators and applying their inverses. - * + * * @return The inverted hash function. */ HashFunction invert() const @@ -125,7 +127,7 @@ class HashFunction // Complete the inverted hash function with masking operators inverted.complete_with_masks(); - + // Return the inverted hash function return inverted; } @@ -150,6 +152,11 @@ class HashFunction return ss.str(); } + + size_t size() const { + return m_operators.size(); + } + }; #endif // HASHFUNCTION_HPP From 519ddcdd1129d0322d5dd95e3be2bf1b6deb8cd4 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 22 Aug 2024 18:12:03 +0200 Subject: [PATCH 05/31] fix(XorRightShift): copy construct from existing member --- src/include/XorRightShift.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/include/XorRightShift.hpp b/src/include/XorRightShift.hpp index 3287cc3..b44dca8 100644 --- a/src/include/XorRightShift.hpp +++ b/src/include/XorRightShift.hpp @@ -21,7 +21,7 @@ class XorRightShift : public Operator public: XorRightShift(size_t shifts, size_t value_size) : m_shifts(shifts), m_value_size(value_size) {} - XorRightShift(XorRightShift const & other) : XorRightShift(other.shifts, other.m_value_size) {} + XorRightShift(XorRightShift const & other) : XorRightShift(other.m_shifts, other.m_value_size) {} ~XorRightShift() {} // Implement the invert function From 68594144f83d5f10a7cf643ef55ec6b89fcc4af2 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 22 Aug 2024 18:12:41 +0200 Subject: [PATCH 06/31] feat(solver): add EvalFunc and EvalTest --- CMakeLists.txt | 4 +- app/example.cpp | 48 ++++++++++++++++-- src/include/EvalFunc.hpp | 103 +++++++++++++++++++++++++++++++++++++++ src/include/log.h | 9 ++-- src/log.cpp | 85 ++++++++++++++++++++++++++++++++ test/CMakeLists.txt | 4 +- test/EvalFull_T.cpp | 45 +++++++++++++++++ 7 files changed, 285 insertions(+), 13 deletions(-) create mode 100644 src/include/EvalFunc.hpp create mode 100644 src/log.cpp create mode 100644 test/EvalFull_T.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d172893..500b704 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ else() include_directories($ENV{PARADISEO_ROOT}/include/paradiseo/mo) link_directories($ENV{PARADISEO_ROOT}/lib64) endif() -set(PARADISEO_LIBRARIES ga eoutils eo) +set(PARADISEO_LIBRARIES eoutils eo) # Single-header dependencies. include_directories(external/clutchlog) @@ -83,7 +83,7 @@ foreach(APP_SOURCE ${APP_SOURCES}) add_executable(${APP_NAME} ${APP_SOURCE}) # Link the necessary libraries - target_link_libraries(${APP_NAME} PRIVATE rHashGenLib) + target_link_libraries(${APP_NAME} PRIVATE rHashGenLib ${PARADISEO_LIBRARIES}) endforeach() # Add the test directory diff --git a/app/example.cpp b/app/example.cpp index cfc0b91..29ac1f4 100644 --- a/app/example.cpp +++ b/app/example.cpp @@ -12,15 +12,23 @@ #include "Multiply.hpp" #include "moCombination.hpp" #include "moCombinationNeighbor.hpp" +#include "moCombinationNeighborhood.hpp" +#include "EvalFunc.hpp" +#include "log.h" int main() { + CLUTCHLOG(progress, "Set config"); + clutchlog_config(); // common config + auto& log = clutchlog::logger(); + log.threshold("XDebug"); + // The size of the values to manipulate is 57 bits. size_t value_size{31}; using myuint = uint32_t; - using myfit = double; + CLUTCHLOG(progress, "Try HashFunc"); // Create an instance of HashFunction with a value size of 64 bits HashFunction hashFunc("hash", value_size); @@ -32,12 +40,14 @@ int main() hashFunc.add_operator(std::make_unique>(3, value_size)); hashFunc.add_operator(std::make_unique>(9, value_size)); + CLUTCHLOG(note, "Complete with masks"); // Complete with masks if necessary hashFunc.complete_with_masks(); // Print the string representation of the hash function std::cout << hashFunc.to_string() << std::endl; + CLUTCHLOG(note, "Invert"); // Get the inverse function HashFunction revHashFunc{hashFunc.invert()}; @@ -45,6 +55,7 @@ int main() std::cout << "Inverted function:" << std::endl; std::cout << revHashFunc.to_string() << std::endl; + CLUTCHLOG(progress, "Apply HashFunc"); // Apply the hash function to a value myuint value {0xDADBEEF}; myuint hashed {hashFunc.apply(value)}; @@ -54,10 +65,39 @@ int main() myuint recovered {revHashFunc.apply(hashed)}; std::cout << revHashFunc.get_name() << "(0x" << std::hex << hashed << ") = 0x" << recovered << std::dec << std::endl; - moCombination sol({0,1,2,3,4,5}, 6); - moCombinationNeighbor> neighb; - neighb.set(0,1); + CLUTCHLOG(progress, "Try solver"); + using Min = eoMinimizingFitness; + using Combi = moCombination; + + eoForgeVector< EvalFull::OpItf > forge(/*always_reinstantiate*/true); + forge.add< Multiply >( 9, value_size); + forge.add< XorLeftShift >(17, value_size); + forge.add< XorLeftShift >( 5, value_size); + forge.add< AddShift >(19, value_size); + forge.add< XorRightShift >( 3, value_size); + forge.add< Multiply >( 9, value_size); + + CLUTCHLOG(note, "Try solutions"); + Combi sol({0,1,2}, forge.size()); + CLUTCHLOG(debug, "Solution: " << sol); + + CLUTCHLOG(note, "Next in neighborhood"); + moCombinationNeighbor neighb; + moCombinationNeighborhood hood; + hood.init(sol, neighb); + hood.next(sol, neighb); + CLUTCHLOG(debug, "Neighbor: " << neighb); + neighb.move(sol); + CLUTCHLOG(debug, "Solution: " << sol); + + CLUTCHLOG(note, "Evaluate"); + EvalFull eval(value_size, forge); + + eval(sol); + + CLUTCHLOG(progress, "Final solution:"); + std::cout << sol << std::endl; return 0; } diff --git a/src/include/EvalFunc.hpp b/src/include/EvalFunc.hpp new file mode 100644 index 0000000..828749d --- /dev/null +++ b/src/include/EvalFunc.hpp @@ -0,0 +1,103 @@ +#ifndef EVALFUNC_HPP +#define EVALFUNC_HPP + +#include + +#include "log.h" + +template< typename myuint, typename EOT> +class EvalFull : public eoEvalFunc< EOT > +{ +public: + using OpItf = Operator; + +protected: + const size_t m_value_size; + eoForgeVector& m_forge; + +public: + EvalFull(size_t value_size, eoForgeVector& forge) : + m_value_size(value_size), + m_forge(forge) + { } + + virtual void operator()(EOT& sol) { + CLUTCHLOG(xdebug, "Evaluate solution: " << sol); + ASSERT(sol.size() > 0); + ASSERT(sol.nb_options() == m_forge.size()); + + HashFunction hff("optimized_hash", m_value_size); + + CLUTCHLOG(xdebug, "Instantiate " << sol.size() << " operators:"); + for(size_t i : sol.get()) { + // CLUTCHLOG(debug, "Instantiate " << i << "th operator"); + // NOTE: this is why we need a shared_ptr and cannot have a unique_ptr. + hff.add_operator( m_forge.instantiate_ptr(i) ); + } + CLUTCHLOG(xdebug, "Complete with masks"); + hff.complete_with_masks(); + + CLUTCHLOG(xdebug, "Invert"); + HashFunction hfr{hff.invert()}; + + #ifndef NDEBUG + CLUTCHLOG(xdebug, "Check inversion"); + myuint value {0xDADBEEF}; + myuint hashed {hff.apply(value)}; + myuint recovered {hfr.apply(hashed)}; + ASSERT(value == recovered); + #endif + + const double quality = hff.size() + hfr.size(); + sol.fitness( quality ); + CLUTCHLOG(debug, "Evaluated solution: " << sol); + + ASSERT(not sol.invalid()); + } + +}; + +#ifndef NDEBUG +/** Partial evaluator that actually perform a full evaluation. + * + * This is to test that a partial evaluation ends on the same value than a full evaluation. + */ +template +class EvalTest : public moEval> +{ + protected: + //! Underlying full evaluator. + EvalFull& _full_eval; + + public: + /** Constructor. + * + * @param full_eval The full evaluator. + */ + EvalTest(EvalFull& full_eval) : + _full_eval(full_eval) + { } + + /** Incremental evaluation. + * + * @param solution the solution on which to apply the move. + * @param neighbor the move to consider. + */ + void operator()(EOT& solution, moCombinationNeighbor & neighbor) + { + CLUTCHLOG(debug, "Full Eval of: " << solution << " moved as " << neighbor); + // Apply the neighbor move on a temp solution. + EOT newsol = solution; + neighbor.move(newsol); + // Full evaluation. + _full_eval(newsol); + neighbor.fitness( newsol.fitness() ); + ASSERT(not neighbor.invalid()); + } + + //! Accessor to the underlying full evaluator. + EvalFull& full_eval(); +}; +#endif + +#endif // EVALFUNC_HPP diff --git a/src/include/log.h b/src/include/log.h index 068077a..47f5e6d 100644 --- a/src/include/log.h +++ b/src/include/log.h @@ -1,9 +1,8 @@ -#pragma once +#ifndef LOG_H +#define LOG_H #include -namespace frictionless { - // Make asserts (de)clutchable. #define ASSERT(EXPR) { CLUTCHFUNC(critical, assert, EXPR); } @@ -11,6 +10,6 @@ namespace frictionless { * * Mainly colors and line template. */ -//void clutchlog_config(); +void clutchlog_config(); -} // frictionless +#endif // LOG_H diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..1eb9173 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,85 @@ +#include "log.h" + +void clutchlog_config() +{ + using level = clutchlog::level; + using fmt = clutchlog::fmt; + using fg = clutchlog::fmt::fg; + using bg = clutchlog::fmt::bg; + using typo = clutchlog::fmt::typo; + auto& log = clutchlog::logger(); + + log.out(std::cerr); + log.hfill_min(0); + log.hfill_max(200); + log.depth_mark(" |"); + + log.style(level::critical, + fg::black, + bg::red, + typo::bold); + log.style(level::error, + fg::black, + bg::red); + log.style(level::warning, + fg::black, + bg::magenta, + typo::bold); + log.style(level::progress, + fg::yellow); + log.style(level::note, + fg::green); + log.style(level::info, + fg::magenta); + log.style(level::debug, + fg::cyan); + log.style(level::xdebug, + fg::blue); + + std::vector greys = {fmt(15)}; + for(unsigned short i=239; i>235; i-=1) { + greys.push_back( fmt(i) ); + } + log.depth_styles(greys); + + log.filehash_styles( + {fmt(19),fmt(21),fmt(24),fmt(37), + fmt(55),fmt(57),fmt(61),fmt(63), + fmt(91),fmt(93),fmt(97),fmt(99)} ); + + log.funchash_styles( + {fmt(130),fmt(136),fmt(142), + fmt(166),fmt(172),fmt(178), + fmt(202),fmt(208),fmt(214), + fmt(203),fmt(209),fmt(215), + fmt(204),fmt(210),fmt(216)} ); + + std::ostringstream format; + fmt reset(typo::reset); + fmt bold(typo::bold); + #ifndef NDEBUG + fmt discreet(fg::black); + format + // << fmt(typo::inverse) + << "{level_fmt}" + << "{level_short}" << reset + << bold << "{funchash_fmt} |" << reset + << "{depth_marks} " + << "{level_fmt}" << bold("{msg}") + << discreet(" {hfill} ") + << "{funchash_fmt}{func}" + << discreet(" @ ") << reset + << "{filehash_fmt}{file}" + << discreet(":") + << "{filehash_fmt}{line}" + << "\n"; + log.format(format.str()); + #else + format << "[{name}] {level_fmt}{level_letter}" << reset + << ":{depth_marks} " + << bold("{level_fmt}{msg}") << "\n"; + log.format(format.str()); + #endif + +} + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 657a058..e24f47b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,7 +1,7 @@ file(GLOB_RECURSE TEST_SOURCES "*.cpp") add_executable(rHashGen-tests ${TEST_SOURCES}) -target_link_libraries(rHashGen-tests PUBLIC gtest rHashGenLib) +target_link_libraries(rHashGen-tests PUBLIC gtest rHashGenLib ${PARADISEO_LIBRARIES}) if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0.0) target_link_libraries(rhashgen-tests PUBLIC stdc++fs) @@ -11,4 +11,4 @@ endif() add_test( NAME rhashgen:all COMMAND rHashGen-tests -) \ No newline at end of file +) diff --git a/test/EvalFull_T.cpp b/test/EvalFull_T.cpp new file mode 100644 index 0000000..61d6254 --- /dev/null +++ b/test/EvalFull_T.cpp @@ -0,0 +1,45 @@ +#include + +#include "HashFunction.hpp" +#include "Operator.hpp" +#include "XorLeftShift.hpp" +#include "XorRightShift.hpp" +#include "Multiply.hpp" +#include "AddShift.hpp" +#include "Masking.hpp" +#include "Multiply.hpp" +#include "moCombination.hpp" +#include "moCombinationNeighbor.hpp" +#include "moCombinationNeighborhood.hpp" +#include "EvalFunc.hpp" + +TEST(EvalFull, LargeNeighborhood) +{ + const size_t value_size = 31; + using myuint = uint32_t; + using Min = eoMinimizingFitness; + using Combi = moCombination; + + eoForgeVector< EvalFull::OpItf > forge(/*always_reinstantiate*/true); + forge.add< Multiply >( 9, value_size); + forge.add< XorLeftShift >(17, value_size); + forge.add< XorLeftShift >( 5, value_size); + forge.add< AddShift >(19, value_size); + forge.add< XorRightShift >( 3, value_size); + forge.add< Multiply >( 9, value_size); + + moCombination sol({0,0,0,0}, forge.size()); + moCombinationNeighbor> to; + moCombinationNeighborhood> hood; + + hood.init(sol, to); // {0,0} + EXPECT_TRUE(hood.hasNeighbor(sol)); + + EvalFull eval(value_size, forge); + + while( hood.cont(sol) ) { + eval(sol); + hood.next(sol, to); + } +} + From 1c2fc9034c86f102f76f97ebd40f798315bc9d7a Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 22 Aug 2024 22:09:39 +0200 Subject: [PATCH 07/31] feat(solver): add a search app PoC --- app/search.cpp | 102 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 app/search.cpp diff --git a/app/search.cpp b/app/search.cpp new file mode 100644 index 0000000..3fce9da --- /dev/null +++ b/app/search.cpp @@ -0,0 +1,102 @@ +#include +#include +#include + +#include + +#include "HashFunction.hpp" +#include "Operator.hpp" +#include "XorLeftShift.hpp" +#include "XorRightShift.hpp" +#include "Multiply.hpp" +#include "AddShift.hpp" +#include "Masking.hpp" +#include "Multiply.hpp" +#include "moCombination.hpp" +#include "moCombinationNeighbor.hpp" +#include "moCombinationNeighborhood.hpp" +#include "EvalFunc.hpp" +#include "log.h" + +int main() +{ + using myuint = uint32_t; + using Min = eoMinimizingFitness; + using Combi = moCombination; + using Nb = moCombinationNeighbor; + using NbHood = moCombinationNeighborhood; + + CLUTCHLOG(progress, "Set config"); + clutchlog_config(); // common config + auto& log = clutchlog::logger(); + log.threshold("Debug"); + + const size_t sol_size = 10; + const size_t value_size = 31; + size_t seed = 0; + + eoForgeVector< EvalFull::OpItf > forge(/*always_reinstantiate*/true); + forge.add< Multiply >( 9, value_size); + forge.add< XorLeftShift >(17, value_size); + forge.add< XorLeftShift >( 5, value_size); + forge.add< AddShift >(19, value_size); + forge.add< XorRightShift >( 3, value_size); + forge.add< Multiply >( 9, value_size); + + CLUTCHLOG(progress, "Pick a random solution..."); + std::vector v; + v.reserve(sol_size); + std::mt19937 rng(seed); + std::uniform_int_distribution uni(0, forge.size()-1); + for(size_t i=0; i feval(value_size, forge); + EvalTest peval(feval); + + NbHood neighborhood; + + // Continue search until exhaustion of the neighborhood. + moTrueContinuator until_end; + moCheckpoint check(until_end); + moBestFitnessStat best; + check.add(best); // Update the best state. + + // Hill climber, selecting a random solution among the equal-best ones. + moRandomBestHC search(neighborhood, feval, peval, check); + CLUTCHLOG(note, "OK"); + + CLUTCHLOG(progress, "Evaluate first signature..."); + feval(sol); + CLUTCHLOG(note, "Initial signature: " << sol); + CLUTCHLOG(note, "OK"); + + CLUTCHLOG(progress, "Solver run..."); + search(sol); + CLUTCHLOG(note, "OK"); + + CLUTCHLOG(progress, "Found signature:"); + CLUTCHLOG(note, sol ); + + // Real output. + HashFunction hff("optimized_hash", value_size); + for(size_t i : sol.get()) { + CLUTCHLOG(xdebug, "Instantiate " << i << "th operator"); + hff.add_operator( forge.instantiate_ptr(i) ); + } + CLUTCHLOG(debug, "Complete with masks"); + hff.complete_with_masks(); + + CLUTCHLOG(debug, "Invert"); + HashFunction hfr{hff.invert()}; + + std::cout << hff.to_string() << std::endl; + std::cout << hfr.to_string() << std::endl; + + CLUTCHLOG(progress, "Done."); +} From e55a3d4d3c0f0b414dd5a92cf2533ecba9f8d1e5 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Wed, 4 Sep 2024 09:37:06 +0200 Subject: [PATCH 08/31] feat(cmake): add Paradiseo and clutchlog as submodules Now everything can be built in a single pass. --- .gitmodules | 6 ++++++ CMakeLists.txt | 10 +++++++--- external/clutchlog | 1 + external/paradiseo | 1 + 4 files changed, 15 insertions(+), 3 deletions(-) create mode 160000 external/clutchlog create mode 160000 external/paradiseo diff --git a/.gitmodules b/.gitmodules index bab1865..68e650f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "external/googletest"] path = external/googletest url = https://github.com/google/googletest.git +[submodule "external/paradiseo"] + path = external/paradiseo + url = https://github.com/nojhan/paradiseo.git +[submodule "external/clutchlog"] + path = external/clutchlog + url = https://github.com/nojhan/clutchlog.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 500b704..4f1298c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) option(BUILD_DOCS "Enable building of documentation" OFF) option(BUILD_TESTING "Enable building of tests" OFF) option(BUILD_FOR_LOCAL "Whether to make the executables dependant on the environment of the building computer (enables CMake's build with RPATH), may be necessary on HPC clusters" OFF) -option(USE_LOCAL_PARADISEO "Use a local version of Paradiseo rather than the one installed on the system" OFF) +option(USE_LOCAL_PARADISEO "Use a local version of Paradiseo rather than the one installed on the system" ON) # Common add_compile_options(-Wall -Wextra -pedantic) @@ -39,8 +39,8 @@ include_directories(include) # ParadisEO if(USE_LOCAL_PARADISEO) - set(PARADISEO_ROOT "PARADISEO_NOT_FOUND" CACHE PATH "Where to find ParadisEO") - set(PARADISEO_BUILD "${PARADISEO_ROOT}/build" CACHE PATH "Build dir of ParadisEO") + set(PARADISEO_ROOT "${PROJECT_SOURCE_DIR}/external/paradiseo" CACHE PATH "Where to find ParadisEO") + set(PARADISEO_BUILD "${CMAKE_CURRENT_BINARY_DIR}" CACHE PATH "Build dir of ParadisEO") include_directories(${PARADISEO_ROOT}) include_directories(${PARADISEO_ROOT}/eo/src) @@ -71,6 +71,10 @@ add_subdirectory(src) add_subdirectory(external/googletest) include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) +# ParadisEO +add_subdirectory(external/paradiseo) + + # Find all main files in the app directory file(GLOB APP_SOURCES ${PROJECT_SOURCE_DIR}/app/*.cpp) diff --git a/external/clutchlog b/external/clutchlog new file mode 160000 index 0000000..7d0252b --- /dev/null +++ b/external/clutchlog @@ -0,0 +1 @@ +Subproject commit 7d0252b659aa989ff71ff80aacfa08d389b46a10 diff --git a/external/paradiseo b/external/paradiseo new file mode 160000 index 0000000..732fe09 --- /dev/null +++ b/external/paradiseo @@ -0,0 +1 @@ +Subproject commit 732fe097cb8855f24e311ab4fc9092ace90f4995 From c28142f7ce8a03bc10c564d40df23e75c9766028 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Wed, 4 Sep 2024 10:20:46 +0200 Subject: [PATCH 09/31] feat(search): make_domain --- app/search.cpp | 51 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/app/search.cpp b/app/search.cpp index 3fce9da..b9be102 100644 --- a/app/search.cpp +++ b/app/search.cpp @@ -18,13 +18,44 @@ #include "EvalFunc.hpp" #include "log.h" +using myuint = uint32_t; +using Min = eoMinimizingFitness; +using Combi = moCombination; +using Nb = moCombinationNeighbor; +using NbHood = moCombinationNeighborhood; + + +size_t make_domain(eoForgeVector< EvalFull::OpItf >& forge, size_t value_size) +{ + std::vector sizes(value_size); + std::iota(std::begin(sizes), std::end(sizes), 1); // 1, 2, 3, …, until value_size. + + for(auto size : sizes) { // TODO what shift/mask sizes do we want? + forge.add< XorLeftShift >(size, value_size); + CLUTCHLOG(xdebug, "XorLeftShift << " << size); + + forge.add< XorRightShift >(size, value_size); + CLUTCHLOG(xdebug, "XorRightShift << " << size); + + forge.add< AddShift >(size, value_size); + CLUTCHLOG(xdebug, "AddShift << " << size); + + forge.add< Masking >(size); + CLUTCHLOG(xdebug, "Masking & " << size); + } + + for(auto size : sizes) { // TODO what multipliers do we want? + if(size % 2 == 1) { // Only odd multipliers are allowed. + forge.add< Multiply >(size, value_size); + CLUTCHLOG(xdebug, "Multiply * " << size); + } + } + + return sizes.size(); +} + int main() { - using myuint = uint32_t; - using Min = eoMinimizingFitness; - using Combi = moCombination; - using Nb = moCombinationNeighbor; - using NbHood = moCombinationNeighborhood; CLUTCHLOG(progress, "Set config"); clutchlog_config(); // common config @@ -35,13 +66,11 @@ int main() const size_t value_size = 31; size_t seed = 0; + CLUTCHLOG(progress, "Create the search domain..."); eoForgeVector< EvalFull::OpItf > forge(/*always_reinstantiate*/true); - forge.add< Multiply >( 9, value_size); - forge.add< XorLeftShift >(17, value_size); - forge.add< XorLeftShift >( 5, value_size); - forge.add< AddShift >(19, value_size); - forge.add< XorRightShift >( 3, value_size); - forge.add< Multiply >( 9, value_size); + size_t nb_ops = make_domain(forge, value_size); + CLUTCHLOG(info, forge.size() << " operators"); + CLUTCHLOG(note, "OK"); CLUTCHLOG(progress, "Pick a random solution..."); std::vector v; From ac2281d4443bc484ebd525a45b57822eca4cedaf Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Wed, 4 Sep 2024 14:21:04 +0200 Subject: [PATCH 10/31] feat(search): CLI arguments --- app/search.cpp | 167 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 141 insertions(+), 26 deletions(-) diff --git a/app/search.cpp b/app/search.cpp index b9be102..83eeebe 100644 --- a/app/search.cpp +++ b/app/search.cpp @@ -24,52 +24,167 @@ using Combi = moCombination; using Nb = moCombinationNeighbor; using NbHood = moCombinationNeighborhood; +//! Error codes returned on exit. +enum class Error : unsigned char { + No_Error = 0, + // No_File = 2, // ENOENT + Invalid_Argument = 22, // EINVAL + // Unreadable = 77, // EBADFD + Missing_Argument = 132, + InconsistentDomain, + Unknown = 255 +}; + +//! Macro to hide the necessary verbose casting. +#ifdef WITH_CLUTCHLOG + #define EXIT_ON_ERROR(err_code, msg) { \ + if(static_cast(Error::err_code) > 0) { \ + CLUTCHLOG(critical, "CRITICAL ERROR"); \ + CLUTCHLOG(critical, msg); \ + } \ + exit(static_cast(Error::err_code)); \ + } +#else + #define EXIT_ON_ERROR(err_code, msg) { \ + if(static_cast(Error::err_code) > 0) { \ + std::cerr << "CRITICAL ERROR: " << msg << std::endl; \ + } \ + exit(static_cast(Error::err_code)); \ + } +#endif -size_t make_domain(eoForgeVector< EvalFull::OpItf >& forge, size_t value_size) -{ - std::vector sizes(value_size); - std::iota(std::begin(sizes), std::end(sizes), 1); // 1, 2, 3, …, until value_size. +struct Range { + Range(size_t mi, size_t ma, size_t st) : min(mi), max(ma), step(st) {} + size_t min; + size_t max; + size_t step; +}; - for(auto size : sizes) { // TODO what shift/mask sizes do we want? - forge.add< XorLeftShift >(size, value_size); - CLUTCHLOG(xdebug, "XorLeftShift << " << size); +void make_domain(eoForgeVector< EvalFull::OpItf >& forge, size_t value_size, Range shift_range, Range mult_range) +{ + for(size_t i = shift_range.min; i < shift_range.max; i+=shift_range.step) { + forge.add< XorLeftShift >(i, value_size); + CLUTCHLOG(xdebug, "XorLeftShift << " << i); - forge.add< XorRightShift >(size, value_size); - CLUTCHLOG(xdebug, "XorRightShift << " << size); + forge.add< XorRightShift >(i, value_size); + CLUTCHLOG(xdebug, "XorRightShift << " << i); - forge.add< AddShift >(size, value_size); - CLUTCHLOG(xdebug, "AddShift << " << size); + forge.add< AddShift >(i, value_size); + CLUTCHLOG(xdebug, "AddShift << " << i); - forge.add< Masking >(size); - CLUTCHLOG(xdebug, "Masking & " << size); + forge.add< Masking >(i); + CLUTCHLOG(xdebug, "Masking & " << i); } - for(auto size : sizes) { // TODO what multipliers do we want? - if(size % 2 == 1) { // Only odd multipliers are allowed. - forge.add< Multiply >(size, value_size); - CLUTCHLOG(xdebug, "Multiply * " << size); + #ifndef NDEBUG + size_t nb_multipliers = 0; + #endif + for(size_t i = mult_range.min; i < shift_range.max; i+=mult_range.step) { + if(i % 2 == 1) { // Only odd multipliers are allowed. + forge.add< Multiply >(i, value_size); + CLUTCHLOG(xdebug, "Multiply * " << i); + #ifndef NDEBUG + nb_multipliers += 1; + #endif } } - - return sizes.size(); + ASSERT(nb_multipliers > 0); } -int main() +int main(int argc, char* argv[]) { + CLUTCHLOG(progress, "Set config..."); + eoParser argparser(argc, argv); + + /***** Classical arguments *****/ + + const std::string log_level = argparser.createParam("Progress", "log-level", + "Maximum depth level of logging (Critical(".*", "log-file", + "Regexp indicating which source file is allowed logging (default=all)", 'f', "Logging").value(); + + const std::string log_func = argparser.createParam(".*", "log-func", + "Regexp indicating which function is allowed logging (default=all)", 'F', "Logging").value(); + + const size_t log_depth = argparser.createParam(9999, "log-depth", + "Maximum stack depth above which logging is not allowed (default=no limit)", 'D', "Logging").value(); + + unsigned long long seed = argparser.createParam(0, "seed", + "Seed of the pseudo-random generator (0 = Epoch)", 's', "Parameters").value(); + + /***** Search domain arguments *****/ + + const size_t value_size = argparser.createParam(31, "value-size", + "Value size (in bits)", 'v', "Search domain").value(); + + const size_t sol_size = argparser.createParam(10, "sol-size", + "Number of operations in the hash function", 'n', "Search domain").value(); + + const size_t shift_min = argparser.createParam(2, "shift-min", + "Minimum number of shifts", 't', "Search domain").value(); + const size_t shift_max = argparser.createParam(31, "shift-max", + "Maximum number of shifts", 'T', "Search domain").value(); + const size_t shift_step = argparser.createParam(1, "shift-step", + "Increment step for number of shifts", 'i', "Search domain").value(); + Range shift_range(shift_min, shift_max, shift_step); + + const size_t mult_min = argparser.createParam(3, "mult-min", + "Smallest multiplier", 'm', "Search domain").value(); + const size_t mult_max = argparser.createParam(65, "mult-max", + "Largest multiplier", 'M', "Search domain").value(); + const size_t mult_step = argparser.createParam(2, "mult-step", + "Increment step for multipliers (note: only odd multipliers will be allowed)", 'u', "Search domain").value(); + Range mult_range(mult_min, mult_max, mult_step); + + make_verbose(argparser); + make_help(argparser); - CLUTCHLOG(progress, "Set config"); clutchlog_config(); // common config auto& log = clutchlog::logger(); - log.threshold("Debug"); + ASSERT(log.levels().contains(log_level)); + log.threshold(log_level); + log.depth(log_depth); + log.file(log_file); + log.func(log_func); + + if(seed == 0) { + seed = std::time(nullptr); // Epoch + } + CLUTCHLOG(info, "seed = " << seed); + CLUTCHLOG(info, "log-level = " << log_level); + CLUTCHLOG(info, "log-file = " << log_file); + CLUTCHLOG(info, "log-func = " << log_func); + CLUTCHLOG(info, "log-depth = " << log_depth); + CLUTCHLOG(info, "value-size = " << value_size); + CLUTCHLOG(info, "sol-size = " << sol_size); + CLUTCHLOG(info, "shift-min = " << shift_min); + CLUTCHLOG(info, "shift-max = " << shift_max); + CLUTCHLOG(info, "shift-step = " << shift_step); + CLUTCHLOG(info, "mult-min = " << mult_min); + CLUTCHLOG(info, "mult-max = " << mult_max); + CLUTCHLOG(info, "mult-step = " << mult_step); + + if(shift_min == 0) { + EXIT_ON_ERROR(InconsistentDomain, "It makes no sense to set `--shift-min` to zero."); + } + if(shift_max < value_size) { + EXIT_ON_ERROR(InconsistentDomain, "It makes no sense to set --shift-max=" << shift_max << " < --value-size=" << value_size <<""); + } + if(mult_min < 3) { + CLUTCHLOG(warning, "It is probably wrong that `--mult-min` is less than 3."); + } + if(mult_step % 2 == 1) { + CLUTCHLOG(warning, "It is probably wrong that `--mult-step` is not even. Remember that only odd multipliers are actually allowed. Even multipliers will be filtered out."); + } - const size_t sol_size = 10; - const size_t value_size = 31; - size_t seed = 0; + CLUTCHLOG(note, "OK"); CLUTCHLOG(progress, "Create the search domain..."); eoForgeVector< EvalFull::OpItf > forge(/*always_reinstantiate*/true); - size_t nb_ops = make_domain(forge, value_size); + make_domain(forge, value_size, shift_range, mult_range); CLUTCHLOG(info, forge.size() << " operators"); + ASSERT(forge.size() > 0); CLUTCHLOG(note, "OK"); CLUTCHLOG(progress, "Pick a random solution..."); From ebc2d19e32c11d846aca9f8d85973a7507f93d54 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 5 Sep 2024 14:47:28 +0200 Subject: [PATCH 11/31] update(paradiseo) --- external/paradiseo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/paradiseo b/external/paradiseo index 732fe09..51be7e3 160000 --- a/external/paradiseo +++ b/external/paradiseo @@ -1 +1 @@ -Subproject commit 732fe097cb8855f24e311ab4fc9092ace90f4995 +Subproject commit 51be7e324b07c90d9b1b4b93c7fa9f8d668f76b5 From a243abf6611cc050b0291fb2549a63c5d3c39c08 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 5 Sep 2024 14:57:32 +0200 Subject: [PATCH 12/31] REFACTOR!(Operators): adds Operator::to_short Allows for having a hasf function name showing a very short form of the sequence of operator, consisting in one letter and the parameter. --- app/example.cpp | 2 +- src/include/AddShift.hpp | 19 +++++++++++++------ src/include/HashFunction.hpp | 34 ++++++++++++++++++++++++++++------ src/include/Masking.hpp | 7 +++++++ src/include/Multiply.hpp | 13 ++++++++++--- src/include/Operator.hpp | 1 + src/include/XorLeftShift.hpp | 7 +++++++ src/include/XorRightShift.hpp | 7 +++++++ test/Hash_T.cpp | 2 +- 9 files changed, 75 insertions(+), 17 deletions(-) diff --git a/app/example.cpp b/app/example.cpp index 29ac1f4..ca103e9 100644 --- a/app/example.cpp +++ b/app/example.cpp @@ -30,7 +30,7 @@ int main() CLUTCHLOG(progress, "Try HashFunc"); // Create an instance of HashFunction with a value size of 64 bits - HashFunction hashFunc("hash", value_size); + HashFunction hashFunc(value_size, "hash"); // Add shift operators hashFunc.add_operator(std::make_unique>(9, value_size)); diff --git a/src/include/AddShift.hpp b/src/include/AddShift.hpp index 16b9c28..55ec355 100644 --- a/src/include/AddShift.hpp +++ b/src/include/AddShift.hpp @@ -28,7 +28,7 @@ class AddShift : public Operator std::vector>> invert() const override { std::vector>> inverted; - + Multiply const mlt(1 + (1 << m_shifts), m_val_size, true); std::vector>> invert = mlt.invert(); Multiply const *invert_ptr = dynamic_cast const *>(invert[0].get()); @@ -40,9 +40,16 @@ class AddShift : public Operator // Implement the to_string function std::string to_string() const override { - std::stringstream ss; - ss << "val += val << " << m_shifts << ";"; - return ss.str(); + std::ostringstream os; + os << "val += val << " << m_shifts << ";"; + return os.str(); + } + + std::string to_short() const override + { + std::ostringstream os; + os << "a" << m_shifts; + return os.str(); } myuint apply (myuint value) const override @@ -54,7 +61,7 @@ class AddShift : public Operator { return true; } - + bool clean_leftbits_needed() const override { return false; @@ -63,4 +70,4 @@ class AddShift : public Operator -#endif // ADDSHIFT_HPP \ No newline at end of file +#endif // ADDSHIFT_HPP diff --git a/src/include/HashFunction.hpp b/src/include/HashFunction.hpp index 4e2aca3..5de9710 100644 --- a/src/include/HashFunction.hpp +++ b/src/include/HashFunction.hpp @@ -24,8 +24,11 @@ class HashFunction size_t m_value_size; public: - HashFunction(std::string const & function_name, size_t value_size) : m_function_name(function_name), m_value_size(value_size) {}; - HashFunction(HashFunction & other) : m_function_name(other.m_function_name), m_operators{std::move(other.m_operators)}, m_value_size(other.m_value_size) {}; + HashFunction(size_t value_size, std::string const & function_name = "") : + m_function_name(function_name), + m_value_size(value_size) + {}; + ~HashFunction() = default; /** Get the name of the hash function @@ -33,7 +36,24 @@ class HashFunction */ std::string get_name() const { - return m_function_name; + if(m_function_name.size() == 0) { + // Symbols from unicode codepages allowed in identifiers: 𓃊ㄍ𓉘𓉝𐙤 + // Allows to copy-paste the name as a legit C++ function name + // while still being readable. + const std::string sep = "𐙤"; + std::ostringstream os; + os << "hash𓐅" << m_operators.size() << "𓉘"; + if(m_operators.size() > 0) { + os << m_operators[0]->to_short(); + for(size_t i=1; ito_short(); + } + } + os << "𓉝"; + return os.str(); + } else { + return m_function_name; + } } /** Add an operator to the hash function @@ -105,12 +125,14 @@ class HashFunction /** * Inverts the hash function by reversing the order of operators and applying their inverses. * + * @note This keeps the name of the forward hash function, prefixed by "inverted_". + * * @return The inverted hash function. */ HashFunction invert() const { // Create a new HashFunction object with the same value size as the current hash function - HashFunction inverted{std::string("inverted_") + m_function_name, m_value_size}; + HashFunction inverted{m_value_size, std::string("inverted_") + this->get_name()}; // Iterate over each operator in the current hash function for (size_t i = m_operators.size(); i > 0; --i) @@ -137,9 +159,9 @@ class HashFunction */ std::string to_string() const { - std::stringstream ss; + std::ostringstream ss; ss << "template \n"; - ss << "myuint " << m_function_name << "(myuint val)\n"; + ss << "myuint " << this->get_name() << "(myuint val)\n"; ss << "{\n"; for (auto const & op : m_operators) diff --git a/src/include/Masking.hpp b/src/include/Masking.hpp index ecfd69f..6f6e81b 100644 --- a/src/include/Masking.hpp +++ b/src/include/Masking.hpp @@ -40,6 +40,13 @@ class Masking : public Operator return ss.str(); } + std::string to_short() const override + { + std::ostringstream os; + os << "m" << m_mask_size; + return os.str(); + } + myuint apply(myuint value) const override { return value & m_mask; diff --git a/src/include/Multiply.hpp b/src/include/Multiply.hpp index 3918b57..97934dc 100644 --- a/src/include/Multiply.hpp +++ b/src/include/Multiply.hpp @@ -94,6 +94,13 @@ class Multiply : public Operator return ss.str(); } + std::string to_short() const override + { + std::ostringstream os; + os << "x" << m_multiplier; + return os.str(); + } + myuint apply (myuint value) const override { return value * m_multiplier; @@ -103,11 +110,11 @@ class Multiply : public Operator { return true; } - + bool clean_leftbits_needed() const override { return true; } -}; +}; -#endif // MULTIPLY_HPP \ No newline at end of file +#endif // MULTIPLY_HPP diff --git a/src/include/Operator.hpp b/src/include/Operator.hpp index f7f0a1f..793e930 100644 --- a/src/include/Operator.hpp +++ b/src/include/Operator.hpp @@ -13,6 +13,7 @@ class Operator public: virtual std::vector>> invert() const = 0; // This is a pure virtual function, which means that it must be implemented by any derived class. This is a way to enforce that all derived classes have a reverse function. virtual std::string to_string() const = 0; + virtual std::string to_short() const = 0; virtual myuint apply(myuint value) const = 0; virtual bool left_overflowing() const = 0; virtual bool clean_leftbits_needed() const = 0; diff --git a/src/include/XorLeftShift.hpp b/src/include/XorLeftShift.hpp index 8f5a99b..bbc7e3a 100644 --- a/src/include/XorLeftShift.hpp +++ b/src/include/XorLeftShift.hpp @@ -47,6 +47,13 @@ class XorLeftShift : public Operator return ss.str(); } + std::string to_short() const override + { + std::ostringstream os; + os << "l" << m_shifts; + return os.str(); + } + myuint apply(myuint value) const override { return value ^ (value << m_shifts); diff --git a/src/include/XorRightShift.hpp b/src/include/XorRightShift.hpp index b44dca8..f2b1780 100644 --- a/src/include/XorRightShift.hpp +++ b/src/include/XorRightShift.hpp @@ -47,6 +47,13 @@ class XorRightShift : public Operator return ss.str(); } + std::string to_short() const override + { + std::ostringstream os; + os << "r" << m_shifts; + return os.str(); + } + myuint apply(myuint value) const override { return value ^ (value >> m_shifts); diff --git a/test/Hash_T.cpp b/test/Hash_T.cpp index 622cc88..708ab45 100644 --- a/test/Hash_T.cpp +++ b/test/Hash_T.cpp @@ -23,7 +23,7 @@ TEST(HashingTest, uint32_t) // Generate 100 random hash functions to test them for (size_t idx{0} ; idx<100 ; idx++) { - HashFunction hashFunc(std::string("hash_") + std::to_string(idx), 31); + HashFunction hashFunc(31, std::string("hash_") + std::to_string(idx)); // Compose the hash function with 30 Operators for (size_t i{0} ; i<30 ; i++) { From a069fed1b0b57345cdacf332dfc47356aef0b292 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 5 Sep 2024 14:59:29 +0200 Subject: [PATCH 13/31] refactor(Multiplier): use value_size for consistency All other operators are using value_size for the same meaning. --- src/include/Multiply.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/include/Multiply.hpp b/src/include/Multiply.hpp index 97934dc..0c70be2 100644 --- a/src/include/Multiply.hpp +++ b/src/include/Multiply.hpp @@ -17,17 +17,17 @@ class Multiply : public Operator // Mutliplier constant myuint m_multiplier; // The size of the variable to manipulate - size_t m_val_size; + size_t m_value_size; public: - Multiply(myuint multiplier, size_t val_size, bool trusted_values=false) : m_multiplier(multiplier), m_val_size(val_size) { + Multiply(myuint multiplier, size_t value_size, bool trusted_values=false) : m_multiplier(multiplier), m_value_size(value_size) { // Skip verification of calculability of the inverse if the values are trusted if (trusted_values) return; - // Compute the pgcd between the multiplier and pow(2, val_size) to make sure that the multiplier is invertible + // Compute the pgcd between the multiplier and pow(2, value_size) to make sure that the multiplier is invertible myuint a = m_multiplier; - myuint b = 1 << m_val_size; + myuint b = 1 << m_value_size; myuint r; while (b != 0) { @@ -41,18 +41,18 @@ class Multiply : public Operator } } - Multiply(Multiply const & other) : Multiply(other.m_multiplier, other.m_val_size, true) {} + Multiply(Multiply const & other) : Multiply(other.m_multiplier, other.m_value_size, true) {} ~Multiply() {} myuint get_invert_multiplier() const { - if (m_val_size >= sizeof(myuint) * 8) + if (m_value_size >= sizeof(myuint) * 8) throw std::runtime_error("Integer size is too small. Multiplication inverse impossible. 1 more bit is needed."); // Compute the inverse of the multiplier by using the extended euclidean algorithm (cf https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm) // The algorithm is modified for unsigned integers. Explanation at https://stackoverflow.com/questions/67097428 myuint r0 = m_multiplier; - myuint r1 = 1 << m_val_size; + myuint r1 = 1 << m_value_size; myuint s0 = 1; myuint s1 = 0; myuint t0 = 0; @@ -67,7 +67,7 @@ class Multiply : public Operator ++n; } // gcd = r0 - if (n%2) s0=(1 << m_val_size)-s0; + if (n%2) s0=(1 << m_value_size)-s0; else t0=m_multiplier-t0; return s0; @@ -77,11 +77,11 @@ class Multiply : public Operator std::vector>> invert() const override { std::vector>> inverted; - + // Compute the inverse of the multiplier myuint inv_multiplier = get_invert_multiplier(); // Register the inverse operator - inverted.push_back(std::make_unique>(inv_multiplier, m_val_size)); + inverted.push_back(std::make_unique>(inv_multiplier, m_value_size)); return inverted; } From 64291d38518d3d6a8d6d7fee13c81c2a4edcb4d4 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 5 Sep 2024 15:04:30 +0200 Subject: [PATCH 14/31] fix(HashFunction): add explicit copy constructor and assignememt Gets rid of warnings. --- src/include/HashFunction.hpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/include/HashFunction.hpp b/src/include/HashFunction.hpp index 5de9710..d560a56 100644 --- a/src/include/HashFunction.hpp +++ b/src/include/HashFunction.hpp @@ -29,6 +29,20 @@ class HashFunction m_value_size(value_size) {}; + HashFunction(HashFunction & other) : + m_function_name(other.m_function_name), + m_operators{std::move(other.m_operators)}, + m_value_size(other.m_value_size) + {}; + + HashFunction& operator=(const HashFunction& other) + { + this->m_function_name = other.m_function_name; + this->m_operators = std::move(other.m_operators); + this->m_value_size = other.m_value_size; + return *this; + } + ~HashFunction() = default; /** Get the name of the hash function From d605fa4e51bbda7b9c8b3ff3d8c5c0a76e185d08 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 5 Sep 2024 15:05:20 +0200 Subject: [PATCH 15/31] refactor(EvalFunc): adds a separate function for making the hash functions --- src/include/EvalFunc.hpp | 30 ++++++++++++++++++++++++++---- src/include/HashFunction.hpp | 9 +++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/include/EvalFunc.hpp b/src/include/EvalFunc.hpp index 828749d..461450f 100644 --- a/src/include/EvalFunc.hpp +++ b/src/include/EvalFunc.hpp @@ -21,24 +21,31 @@ class EvalFull : public eoEvalFunc< EOT > m_forge(forge) { } - virtual void operator()(EOT& sol) { - CLUTCHLOG(xdebug, "Evaluate solution: " << sol); + //! Instantiate the forward and reverse HashFunc from the given solution. + HashFunctionPair make_hashfuncs( EOT& sol ) const + { ASSERT(sol.size() > 0); ASSERT(sol.nb_options() == m_forge.size()); - HashFunction hff("optimized_hash", m_value_size); + HashFunction hff(m_value_size); CLUTCHLOG(xdebug, "Instantiate " << sol.size() << " operators:"); + ASSERT(sol.size() > 0); for(size_t i : sol.get()) { // CLUTCHLOG(debug, "Instantiate " << i << "th operator"); // NOTE: this is why we need a shared_ptr and cannot have a unique_ptr. hff.add_operator( m_forge.instantiate_ptr(i) ); } + ASSERT(hff.size() == sol.size()); + CLUTCHLOG(xdebug, "Got hash function: " << hff.get_name()); CLUTCHLOG(xdebug, "Complete with masks"); hff.complete_with_masks(); + // ASSERT(hff.size() >= sol.size()); CLUTCHLOG(xdebug, "Invert"); HashFunction hfr{hff.invert()}; + // ASSERT(hfr.size() > 0); + CLUTCHLOG(xdebug, "Got inverted hash function: " << hfr.get_name()); #ifndef NDEBUG CLUTCHLOG(xdebug, "Check inversion"); @@ -48,9 +55,24 @@ class EvalFull : public eoEvalFunc< EOT > ASSERT(value == recovered); #endif + return HashFunctionPair(hff, hfr); + } + + //! Call interface. + virtual void operator()(EOT& sol) { + CLUTCHLOG(xdebug, "Evaluate solution: " << sol); + + auto hffr = make_hashfuncs(sol); + + HashFunction hff = hffr.forward; + HashFunction hfr = hffr.reverse; + + // TODO: have a real objective function. const double quality = hff.size() + hfr.size(); + sol.fitness( quality ); - CLUTCHLOG(debug, "Evaluated solution: " << sol); + CLUTCHLOG(xdebug, "Evaluated solution: " << sol); + CLUTCHLOG(xdebug, "Evaluated hash function: " << hff.get_name()); ASSERT(not sol.invalid()); } diff --git a/src/include/HashFunction.hpp b/src/include/HashFunction.hpp index d560a56..65c2b5a 100644 --- a/src/include/HashFunction.hpp +++ b/src/include/HashFunction.hpp @@ -195,4 +195,13 @@ class HashFunction }; +template +class HashFunctionPair +{ +public: + HashFunctionPair( HashFunction f, HashFunction r ) : forward(f), reverse(r) {} + HashFunction forward; + HashFunction reverse; +}; + #endif // HASHFUNCTION_HPP From ac00029dc7645938f54bd917e8d3ff916761e8a8 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 5 Sep 2024 15:06:07 +0200 Subject: [PATCH 16/31] fix(moCombinationNeighbor): adds explicit calls to virtual base classes Gets rid of warnings. --- src/include/moCombinationNeighbor.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/include/moCombinationNeighbor.hpp b/src/include/moCombinationNeighbor.hpp index d436704..73c29da 100644 --- a/src/include/moCombinationNeighbor.hpp +++ b/src/include/moCombinationNeighbor.hpp @@ -37,14 +37,17 @@ class moCombinationNeighbor : public moIndexNeighbor /** Default constructor. */ moCombinationNeighbor() : - #ifndef NDEBUG - _is_init(false) - #endif + moIndexNeighbor(), + #ifndef NDEBUG + _is_init(false) + #endif { } /** Copy constructor. */ moCombinationNeighbor( const moCombinationNeighbor& other) : + moNeighbor(other), + moIndexNeighbor(other), #ifndef NDEBUG _is_init(other._is_init), #endif @@ -59,6 +62,7 @@ class moCombinationNeighbor : public moIndexNeighbor moCombinationNeighbor& operator=( const moCombinationNeighbor& other) { + moIndexNeighbor::operator=(other); #ifndef NDEBUG this->_is_init = other._is_init; #endif From b2e3abd938be35ea4de149939e8d1dec53cdd5bd Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 5 Sep 2024 15:09:02 +0200 Subject: [PATCH 17/31] feat(search): make use of lasts patches - Rename "sol-size" as "func-len". - Reorder initialization. - Use make_hashfuncs. --- app/search.cpp | 53 +++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/app/search.cpp b/app/search.cpp index 83eeebe..328ece1 100644 --- a/app/search.cpp +++ b/app/search.cpp @@ -118,7 +118,7 @@ int main(int argc, char* argv[]) const size_t value_size = argparser.createParam(31, "value-size", "Value size (in bits)", 'v', "Search domain").value(); - const size_t sol_size = argparser.createParam(10, "sol-size", + const size_t func_len = argparser.createParam(3, "func-len", "Number of operations in the hash function", 'n', "Search domain").value(); const size_t shift_min = argparser.createParam(2, "shift-min", @@ -157,7 +157,7 @@ int main(int argc, char* argv[]) CLUTCHLOG(info, "log-func = " << log_func); CLUTCHLOG(info, "log-depth = " << log_depth); CLUTCHLOG(info, "value-size = " << value_size); - CLUTCHLOG(info, "sol-size = " << sol_size); + CLUTCHLOG(info, "func-len = " << func_len); CLUTCHLOG(info, "shift-min = " << shift_min); CLUTCHLOG(info, "shift-max = " << shift_max); CLUTCHLOG(info, "shift-step = " << shift_step); @@ -187,17 +187,6 @@ int main(int argc, char* argv[]) ASSERT(forge.size() > 0); CLUTCHLOG(note, "OK"); - CLUTCHLOG(progress, "Pick a random solution..."); - std::vector v; - v.reserve(sol_size); - std::mt19937 rng(seed); - std::uniform_int_distribution uni(0, forge.size()-1); - for(size_t i=0; i feval(value_size, forge); @@ -215,6 +204,24 @@ int main(int argc, char* argv[]) moRandomBestHC search(neighborhood, feval, peval, check); CLUTCHLOG(note, "OK"); + CLUTCHLOG(progress, "Pick a random solution..."); + std::vector v; + v.reserve(func_len); + std::mt19937 rng(seed); + std::uniform_int_distribution uni(0, forge.size()-1); + for(size_t i=0; i hff("optimized_hash", value_size); - for(size_t i : sol.get()) { - CLUTCHLOG(xdebug, "Instantiate " << i << "th operator"); - hff.add_operator( forge.instantiate_ptr(i) ); - } - CLUTCHLOG(debug, "Complete with masks"); - hff.complete_with_masks(); - - CLUTCHLOG(debug, "Invert"); - HashFunction hfr{hff.invert()}; + hf = feval.make_hashfuncs(sol); + ASSERT(hf.forward.size() > 0); + ASSERT(hf.reverse.size() > 0); - std::cout << hff.to_string() << std::endl; - std::cout << hfr.to_string() << std::endl; + std::cout << hf.forward.to_string() << std::endl; + std::cout << hf.reverse.to_string() << std::endl; CLUTCHLOG(progress, "Done."); } From 65b67d5da0ff4376894673a3912368add650b4b8 Mon Sep 17 00:00:00 2001 From: Yoann Dufresne Date: Mon, 9 Sep 2024 15:07:47 +0200 Subject: [PATCH 18/31] :bug: paradiseao update --- external/paradiseo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/paradiseo b/external/paradiseo index 732fe09..51be7e3 160000 --- a/external/paradiseo +++ b/external/paradiseo @@ -1 +1 @@ -Subproject commit 732fe097cb8855f24e311ab4fc9092ace90f4995 +Subproject commit 51be7e324b07c90d9b1b4b93c7fa9f8d668f76b5 From abe42a4a267281a513303b0a78759d5ed95bf38b Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Wed, 11 Sep 2024 10:24:13 +0200 Subject: [PATCH 19/31] feat(github): add build/test workflow --- .github/workflows/build_test.yml | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/build_test.yml diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml new file mode 100644 index 0000000..618f898 --- /dev/null +++ b/.github/workflows/build_test.yml @@ -0,0 +1,38 @@ +name: Build Debug (Ubuntu) +on: [push, pull_request] + +env: + # CMake build types: Release, Debug, RelWithDebInfo + BUILD_TYPE: Debug + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ${{ matrix.os }} + strategy: + matrix: + compiler: [g++-10, g++-9, g++-8, g++-7, clang-6, clang-7, clang-8, clang-9, clang-10, clang-11, clang-12] + include: + - os: macos-latest + - os: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Configure + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_CMAKE_TESTING=ON + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + From 8b72f1d89c6cb647865982ca718fd3440703ca51 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Wed, 11 Sep 2024 10:24:13 +0200 Subject: [PATCH 20/31] feat(github): add build/test workflow --- .github/workflows/build_test.yml | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 618f898..706dad1 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -10,21 +10,32 @@ jobs: # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. # You can convert this to a matrix build if you need cross-platform coverage. # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix - runs-on: ${{ matrix.os }} strategy: matrix: - compiler: [g++-10, g++-9, g++-8, g++-7, clang-6, clang-7, clang-8, clang-9, clang-10, clang-11, clang-12] - include: - - os: macos-latest - - os: ubuntu-latest + compiler: + - g++-10 + - g++-11 + - g++-12 + - g++-13 + - clang-13 + - clang-14 + - clang-15 + - clang-16 + - clang-17 + os: + - macos-latest + - ubuntu-latest + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 + with: + submodules: true - name: Configure # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_CMAKE_TESTING=ON + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build # Build your program with the given configuration From 0bd2ffafb492dbbc44c8ca5ee3195a5ea89df667 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Tue, 10 Sep 2024 11:13:45 +0200 Subject: [PATCH 21/31] fix(cmake): remove useless definition Avoid an error using clang on MacOS. --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f1298c..e71189a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,8 +23,6 @@ add_compile_options(-Wall -Wextra -pedantic) # Clang #add_compile_options( -Wno-c++98-compat-pedantic -Wno-old-style-cast -Wno-padded -Wno-extra-semi-stmt -Wno-weak-vtables) -add_compile_definitions(-DWITH_CLUTCHLOG) - if(BUILD_FOR_LOCAL) set(CMAKE_BUILD_WITH_INSTALL_RPATH ON) endif() From 94f210dfd93d872bc56f4468c05fb38e277055d0 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Tue, 10 Sep 2024 11:13:45 +0200 Subject: [PATCH 22/31] update(external): latest clutchlog (fix) and paradiseo (feat) --- external/clutchlog | 2 +- external/paradiseo | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/clutchlog b/external/clutchlog index 7d0252b..28f50d0 160000 --- a/external/clutchlog +++ b/external/clutchlog @@ -1 +1 @@ -Subproject commit 7d0252b659aa989ff71ff80aacfa08d389b46a10 +Subproject commit 28f50d0badd8471e8ae3221ecd2ac9338ffa9bd3 diff --git a/external/paradiseo b/external/paradiseo index 51be7e3..f6dc125 160000 --- a/external/paradiseo +++ b/external/paradiseo @@ -1 +1 @@ -Subproject commit 51be7e324b07c90d9b1b4b93c7fa9f8d668f76b5 +Subproject commit f6dc1254ceee8d47b135284bdb5705fa3ccbd987 From 6b925f81e717dd45e9379e25e242ef5fa489b4db Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Sat, 21 Sep 2024 13:42:51 +0200 Subject: [PATCH 23/31] fix(search): remove EO's verbose conf + better help doc --- app/search.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/search.cpp b/app/search.cpp index 328ece1..d94e320 100644 --- a/app/search.cpp +++ b/app/search.cpp @@ -99,19 +99,19 @@ int main(int argc, char* argv[]) /***** Classical arguments *****/ const std::string log_level = argparser.createParam("Progress", "log-level", - "Maximum depth level of logging (Critical(".*", "log-file", - "Regexp indicating which source file is allowed logging (default=all)", 'f', "Logging").value(); + "Regexp indicating which source file is allowed logging (use '.*' to allow all)", 'f', "Logging").value(); const std::string log_func = argparser.createParam(".*", "log-func", - "Regexp indicating which function is allowed logging (default=all)", 'F', "Logging").value(); + "Regexp indicating which function is allowed logging (use '.*' to allow all)", 'F', "Logging").value(); - const size_t log_depth = argparser.createParam(9999, "log-depth", - "Maximum stack depth above which logging is not allowed (default=no limit)", 'D', "Logging").value(); + const size_t log_depth = argparser.createParam(std::numeric_limits::max(), "log-depth", + "Maximum stack depth above which logging is not allowed (the larger, the more is displayed)", 'D', "Logging").value(); unsigned long long seed = argparser.createParam(0, "seed", - "Seed of the pseudo-random generator (0 = Epoch)", 's', "Parameters").value(); + "Seed of the pseudo-random generator (0 = use number of seconds since The Epoch)", 's', "Parameters").value(); /***** Search domain arguments *****/ @@ -137,7 +137,7 @@ int main(int argc, char* argv[]) "Increment step for multipliers (note: only odd multipliers will be allowed)", 'u', "Search domain").value(); Range mult_range(mult_min, mult_max, mult_step); - make_verbose(argparser); + // make_verbose(argparser); make_help(argparser); clutchlog_config(); // common config @@ -151,6 +151,7 @@ int main(int argc, char* argv[]) if(seed == 0) { seed = std::time(nullptr); // Epoch } + CLUTCHLOG(info, "seed = " << seed); CLUTCHLOG(info, "log-level = " << log_level); CLUTCHLOG(info, "log-file = " << log_file); From b657bb57ff513aae2148b78d57d4763bebe4355a Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Sat, 21 Sep 2024 14:40:11 +0200 Subject: [PATCH 24/31] feat(search): choose between HC or SA algo --- app/search.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/app/search.cpp b/app/search.cpp index d94e320..22ac6bf 100644 --- a/app/search.cpp +++ b/app/search.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include @@ -137,6 +139,19 @@ int main(int argc, char* argv[]) "Increment step for multipliers (note: only odd multipliers will be allowed)", 'u', "Search domain").value(); Range mult_range(mult_min, mult_max, mult_step); + /***** Search domain arguments *****/ + + std::map algorithms; + algorithms["HC"] = "Hill-Climbing"; + algorithms["SA"] = "Simulated-Annealing"; + std::ostringstream msg; msg << " ("; + for(auto& kv : algorithms) { + msg << kv.first << ":" << kv.second << ", "; + } + msg << ")"; + const std::string algo = argparser.createParam("HC", "algo", + "Search metaheuristic"+msg.str(), 'a', "Algorithm").value(); + // make_verbose(argparser); make_help(argparser); @@ -202,7 +217,13 @@ int main(int argc, char* argv[]) check.add(best); // Update the best state. // Hill climber, selecting a random solution among the equal-best ones. - moRandomBestHC search(neighborhood, feval, peval, check); + std::unique_ptr< moLocalSearch > palgo; + if( algo == "HC" ) { + palgo = std::make_unique< moRandomBestHC >(neighborhood, feval, peval, check); + } else { + palgo = std::make_unique< moSA >(neighborhood, feval, peval, check); + } + moLocalSearch& search = *palgo; CLUTCHLOG(note, "OK"); CLUTCHLOG(progress, "Pick a random solution..."); From 65216922f9c53008f379b78c57990eb5d2735707 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Sat, 21 Sep 2024 14:41:36 +0200 Subject: [PATCH 25/31] fix(search): remove Masking from the search domain --- app/search.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/search.cpp b/app/search.cpp index 22ac6bf..834e253 100644 --- a/app/search.cpp +++ b/app/search.cpp @@ -12,7 +12,6 @@ #include "XorRightShift.hpp" #include "Multiply.hpp" #include "AddShift.hpp" -#include "Masking.hpp" #include "Multiply.hpp" #include "moCombination.hpp" #include "moCombinationNeighbor.hpp" @@ -73,9 +72,6 @@ void make_domain(eoForgeVector< EvalFull::OpItf >& forge, size_t v forge.add< AddShift >(i, value_size); CLUTCHLOG(xdebug, "AddShift << " << i); - - forge.add< Masking >(i); - CLUTCHLOG(xdebug, "Masking & " << i); } #ifndef NDEBUG From afddc136ba9578a3597de82667fbd79f891d7bcf Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Sun, 22 Sep 2024 16:20:48 +0200 Subject: [PATCH 26/31] fix(search): raise an error if unknown algo --- app/search.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/search.cpp b/app/search.cpp index 834e253..68876b0 100644 --- a/app/search.cpp +++ b/app/search.cpp @@ -216,8 +216,15 @@ int main(int argc, char* argv[]) std::unique_ptr< moLocalSearch > palgo; if( algo == "HC" ) { palgo = std::make_unique< moRandomBestHC >(neighborhood, feval, peval, check); - } else { + } else if( algo == "SA" ) { palgo = std::make_unique< moSA >(neighborhood, feval, peval, check); + } else { + std::ostringstream msg; + msg << "Unknown algorithm: " << algo << ", valid candidates are"; + for( auto& kv : algorithms) { + msg << ", " << kv.first << " (" << kv.second << ")"; + } + EXIT_ON_ERROR( Invalid_Argument, msg.str() ); } moLocalSearch& search = *palgo; CLUTCHLOG(note, "OK"); From 6093d44ddca35b83e7e4ed7dcf8f953907844137 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Thu, 26 Sep 2024 18:42:27 +0200 Subject: [PATCH 27/31] refactor(moCombination): inherit from eoInt For future compatibility with Paradiseo-moeo --- external/paradiseo | 2 +- src/include/EvalFunc.hpp | 2 +- src/include/moCombination.hpp | 71 ++++++++++++++++++----------------- test/moCombination_T.cpp | 2 +- 4 files changed, 39 insertions(+), 38 deletions(-) diff --git a/external/paradiseo b/external/paradiseo index f6dc125..19ec4c4 160000 --- a/external/paradiseo +++ b/external/paradiseo @@ -1 +1 @@ -Subproject commit f6dc1254ceee8d47b135284bdb5705fa3ccbd987 +Subproject commit 19ec4c4ff760b5470163f0e940d1148449b6f3ad diff --git a/src/include/EvalFunc.hpp b/src/include/EvalFunc.hpp index 461450f..ee85974 100644 --- a/src/include/EvalFunc.hpp +++ b/src/include/EvalFunc.hpp @@ -31,7 +31,7 @@ class EvalFull : public eoEvalFunc< EOT > CLUTCHLOG(xdebug, "Instantiate " << sol.size() << " operators:"); ASSERT(sol.size() > 0); - for(size_t i : sol.get()) { + for(size_t i : sol) { // CLUTCHLOG(debug, "Instantiate " << i << "th operator"); // NOTE: this is why we need a shared_ptr and cannot have a unique_ptr. hff.add_operator( m_forge.instantiate_ptr(i) ); diff --git a/src/include/moCombination.hpp b/src/include/moCombination.hpp index 3a99109..086c1e8 100644 --- a/src/include/moCombination.hpp +++ b/src/include/moCombination.hpp @@ -18,33 +18,33 @@ * - {0,1,2} * - {1,1,1} * - {3,1,0} - * - {3,1,3} + * - {3,1,3} */ template -class moCombination : public EO +class moCombination : public eoInt { public: /** The type for indices. */ using AtomType = size_t; /** The data structures holding the indices. */ - using ContainerType = std::vector; + // using ContainerType = std::vector; /** Constructor with initial sequence. * * @param combination A container. * @param nb_options The past-the-maximum value that can be set. */ - moCombination(ContainerType combination, size_t nb_options) : + moCombination(std::vector combination, size_t nb_options) : + eoInt(combination), #ifndef NDEBUG _is_init(true), #endif - _nb_options(nb_options), - _combination(combination) + _nb_options(nb_options) { - CLUTCHLOG(debug, "instantiated with combination of size " << _combination.size() << " and " << _nb_options << " options"); + CLUTCHLOG(debug, "instantiated with combination of size " << this->size() << " and " << _nb_options << " options"); assert(_nb_options >= 2); // At least 2 options. - for(const auto i : _combination) { + for(const auto i : *this) { assert(i < _nb_options); } } @@ -55,26 +55,27 @@ class moCombination : public EO * @param nb_options The past-the-maximum value that can be set. */ moCombination(size_t size, size_t nb_options) : + eoInt(size, nb_options), #ifndef NDEBUG _is_init(false), #endif - _nb_options(nb_options), - _combination(size, nb_options) + _nb_options(nb_options) { - CLUTCHLOG(debug, "instantiated with size " << _combination.size() << " and " << _nb_options << "options"); + CLUTCHLOG(debug, "instantiated with size " << this->size() << " and " << _nb_options << "options"); assert(_nb_options >= 2); } /** Empty constructor. */ moCombination() : + eoInt(), #ifndef NDEBUG _is_init(false), #endif _nb_options(0) { CLUTCHLOG(debug, "empty instantiated"); - assert(_combination.size() == 0); + assert(this->size() == 0); } /** Setter. @@ -84,36 +85,36 @@ class moCombination : public EO */ void set(size_t index, size_t value) { - CLUTCHLOG(debug, "set combination: @" << index << "/" << _combination.size() << " =" << value << "/" << _nb_options); + CLUTCHLOG(debug, "set combination: @" << index << "/" << this->size() << " =" << value << "/" << _nb_options); assert(_is_init); - assert(index < _combination.size()); + assert(index < this->size()); assert(value < _nb_options); - if(_combination[index] == value) { + if((*this)[index] == value) { std::clog << "call to `set` produces identical state" << std::endl; // FIXME: eo::log << eo::warnings << "call to `set` produces identical state" << std::endl; } - _combination[index] = value; + (*this)[index] = value; } - /** Accessor to the sequence. */ - ContainerType& get() { - return _combination; - } + /** Accessor to the sequence. */ // FIXME remove + // ContainerType& get() { + // return *this; + // } - /** Accessor to an item of the sequence. */ - AtomType& at(size_t index) { - return _combination.at(index); - } + // /** Accessor to an item of the sequence. */ + // AtomType& at(size_t index) { + // return _combination.at(index); + // } - /** Accessor to an item of the sequence. */ - AtomType& operator[](size_t index) { - return _combination[index]; - } + // /** Accessor to an item of the sequence. */ + // AtomType& operator[](size_t index) { + // return _combination[index]; + // } - /** The current size of the sequence. */ - size_t size() const { - return _combination.size(); - } + // /** The current size of the sequence. */ + // size_t size() const { + // return _combination.size(); + // } /** The size of the possible indices. */ size_t nb_options() const { @@ -124,8 +125,8 @@ class moCombination : public EO virtual void printOn(std::ostream& out) const override { assert(_is_init); EO::printOn(out); // Fitness. - out << _combination.size(); - for(const auto i : _combination) { + out << this->size(); + for(const auto i : *this) { out << " " << i; } } @@ -153,7 +154,7 @@ class moCombination : public EO size_t _nb_options; //! The sequence of indices. - ContainerType _combination; + // ContainerType _combination; }; #endif // MOCOMBINATION_HPP diff --git a/test/moCombination_T.cpp b/test/moCombination_T.cpp index 3fc12b6..eb25d02 100644 --- a/test/moCombination_T.cpp +++ b/test/moCombination_T.cpp @@ -10,7 +10,7 @@ TEST(moCombination, Instanciation) c.set(1,2); c.set(5,4); moCombination::ContainerType v{1,2,2,3,4,4}; - moCombination::ContainerType w = c.get(); + moCombination::ContainerType w = c; ASSERT_EQ(v.size(), w.size()) << "vectors have different size"; for (size_t i=0; i < w.size(); ++i) { ASSERT_EQ(v[i], w[i]) << "vector not correctly set at index " << i; From 1ee7dc4b1f4af6e97b705e6de779fbc98430d47e Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Fri, 27 Sep 2024 10:46:45 +0200 Subject: [PATCH 28/31] feat(moeo): adds EvalMO Refactor out make_hashfuncs to be used by all evals. --- CMakeLists.txt | 1 + app/search.cpp | 6 +- src/include/EvalFunc.hpp | 157 +++++++++++++++++++++++++++++---------- test/EvalMO_T.cpp | 51 +++++++++++++ 4 files changed, 171 insertions(+), 44 deletions(-) create mode 100644 test/EvalMO_T.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e71189a..beec0b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ if(USE_LOCAL_PARADISEO) include_directories(${PARADISEO_ROOT}) include_directories(${PARADISEO_ROOT}/eo/src) include_directories(${PARADISEO_ROOT}/mo/src) + include_directories(${PARADISEO_ROOT}/moeo/src) link_directories(${PARADISEO_BUILD}/lib) else() include_directories($ENV{PARADISEO_ROOT}/include/paradiseo/eo) diff --git a/app/search.cpp b/app/search.cpp index 68876b0..a426b2f 100644 --- a/app/search.cpp +++ b/app/search.cpp @@ -239,10 +239,10 @@ int main(int argc, char* argv[]) } Combi sol(v, forge.size()); CLUTCHLOG(info, "Initial solution: " << sol); - auto hf = feval.make_hashfuncs(sol); + auto hf = make_hashfuncs(sol, value_size, forge); CLUTCHLOG(info, "Initial hash function: " << hf.forward.get_name() << " / " << hf.reverse.get_name()); - hf = feval.make_hashfuncs(sol); + hf = make_hashfuncs(sol, value_size, forge); std::clog << hf.forward.to_string() << std::endl; std::clog << hf.reverse.to_string() << std::endl; CLUTCHLOG(note, "OK"); @@ -261,7 +261,7 @@ int main(int argc, char* argv[]) CLUTCHLOG(progress, "Output optimized hash function:"); // Real output. - hf = feval.make_hashfuncs(sol); + hf = make_hashfuncs(sol, value_size, forge); ASSERT(hf.forward.size() > 0); ASSERT(hf.reverse.size() > 0); diff --git a/src/include/EvalFunc.hpp b/src/include/EvalFunc.hpp index ee85974..e98f2a9 100644 --- a/src/include/EvalFunc.hpp +++ b/src/include/EvalFunc.hpp @@ -2,10 +2,50 @@ #define EVALFUNC_HPP #include +#include #include "log.h" -template< typename myuint, typename EOT> +//! Instantiate the forward and reverse HashFunc from the given solution. +template +HashFunctionPair make_hashfuncs( EOT& sol, size_t value_size, eoForgeVector>& forge ) +{ + ASSERT(sol.size() > 0); + // ASSERT(sol.nb_options() == forge.size()); + + HashFunction hff(value_size); + + CLUTCHLOG(xdebug, "Instantiate " << sol.size() << " operators:"); + ASSERT(sol.size() > 0); + for(size_t i : sol) { + // CLUTCHLOG(debug, "Instantiate " << i << "th operator"); + // NOTE: this is why we need a shared_ptr and cannot have a unique_ptr. + hff.add_operator( forge.instantiate_ptr(i) ); + } + ASSERT(hff.size() == sol.size()); + CLUTCHLOG(xdebug, "Got hash function: " << hff.get_name()); + CLUTCHLOG(xdebug, "Complete with masks"); + hff.complete_with_masks(); + // ASSERT(hff.size() >= sol.size()); + + CLUTCHLOG(xdebug, "Invert"); + HashFunction hfr{hff.invert()}; + // ASSERT(hfr.size() > 0); + CLUTCHLOG(xdebug, "Got inverted hash function: " << hfr.get_name()); + + #ifndef NDEBUG + CLUTCHLOG(xdebug, "Check inversion"); + myuint value {0xDADBEEF}; + myuint hashed {hff.apply(value)}; + myuint recovered {hfr.apply(hashed)}; + ASSERT(value == recovered); + #endif + + return HashFunctionPair(hff, hfr); +} + + +template class EvalFull : public eoEvalFunc< EOT > { public: @@ -21,48 +61,11 @@ class EvalFull : public eoEvalFunc< EOT > m_forge(forge) { } - //! Instantiate the forward and reverse HashFunc from the given solution. - HashFunctionPair make_hashfuncs( EOT& sol ) const - { - ASSERT(sol.size() > 0); - ASSERT(sol.nb_options() == m_forge.size()); - - HashFunction hff(m_value_size); - - CLUTCHLOG(xdebug, "Instantiate " << sol.size() << " operators:"); - ASSERT(sol.size() > 0); - for(size_t i : sol) { - // CLUTCHLOG(debug, "Instantiate " << i << "th operator"); - // NOTE: this is why we need a shared_ptr and cannot have a unique_ptr. - hff.add_operator( m_forge.instantiate_ptr(i) ); - } - ASSERT(hff.size() == sol.size()); - CLUTCHLOG(xdebug, "Got hash function: " << hff.get_name()); - CLUTCHLOG(xdebug, "Complete with masks"); - hff.complete_with_masks(); - // ASSERT(hff.size() >= sol.size()); - - CLUTCHLOG(xdebug, "Invert"); - HashFunction hfr{hff.invert()}; - // ASSERT(hfr.size() > 0); - CLUTCHLOG(xdebug, "Got inverted hash function: " << hfr.get_name()); - - #ifndef NDEBUG - CLUTCHLOG(xdebug, "Check inversion"); - myuint value {0xDADBEEF}; - myuint hashed {hff.apply(value)}; - myuint recovered {hfr.apply(hashed)}; - ASSERT(value == recovered); - #endif - - return HashFunctionPair(hff, hfr); - } - //! Call interface. virtual void operator()(EOT& sol) { CLUTCHLOG(xdebug, "Evaluate solution: " << sol); - auto hffr = make_hashfuncs(sol); + auto hffr = make_hashfuncs(sol, m_value_size, m_forge); HashFunction hff = hffr.forward; HashFunction hfr = hffr.reverse; @@ -79,7 +82,6 @@ class EvalFull : public eoEvalFunc< EOT > }; -#ifndef NDEBUG /** Partial evaluator that actually perform a full evaluation. * * This is to test that a partial evaluation ends on the same value than a full evaluation. @@ -120,6 +122,79 @@ class EvalTest : public moEval> //! Accessor to the underlying full evaluator. EvalFull& full_eval(); }; -#endif + + + +class QualityAndRuntimeTraits : public moeoObjectiveVectorTraits +{ +public: + static bool minimizing (int d) + { + switch(d) { + case 0: // quality + return false; + case 1: // runtime + return true; + } + } + static bool maximizing (int d) + { + switch(d) { + case 0: // quality + return true; + case 1: // runtime + return false; + } + } + static unsigned int nObjectives () + { + return 2; + } +}; + +using QualityAndRuntime = moeoRealObjectiveVector; + +template +class EvalMO : public moeoEvalFunc +{ +public: +public: + using OpItf = Operator; + +protected: + const size_t m_value_size; + eoForgeVector& m_forge; + +public: + EvalMO(size_t value_size, eoForgeVector& forge) : + m_value_size(value_size), + m_forge(forge) + { } + + //! Call interface. + void operator()(MOEOT& sol) + { + CLUTCHLOG(xdebug, "Evaluate solution: " << sol); + + auto hffr = make_hashfuncs(sol, m_value_size, m_forge); + + HashFunction hff = hffr.forward; + HashFunction hfr = hffr.reverse; + + // TODO: have a real objective function. + const QualityAndRuntime::Type quality = hff.size() * hfr.size(); + const QualityAndRuntime::Type runtime = hff.size() + hfr.size(); + + // moeoRealObjectiveVector qualrunt; + + sol.objectiveVector(0, quality ); + sol.objectiveVector(1, runtime ); + CLUTCHLOG(xdebug, "Evaluated solution: " << sol); + CLUTCHLOG(xdebug, "Evaluated hash function: " << hff.get_name()); + + ASSERT(not sol.invalid()); + } +}; + #endif // EVALFUNC_HPP diff --git a/test/EvalMO_T.cpp b/test/EvalMO_T.cpp new file mode 100644 index 0000000..f88e4e7 --- /dev/null +++ b/test/EvalMO_T.cpp @@ -0,0 +1,51 @@ +#include + +#include "HashFunction.hpp" +#include "Operator.hpp" +#include "XorLeftShift.hpp" +#include "XorRightShift.hpp" +#include "Multiply.hpp" +#include "AddShift.hpp" +#include "Masking.hpp" +#include "Multiply.hpp" +#include "moCombination.hpp" +#include "moCombinationNeighbor.hpp" +#include "moCombinationNeighborhood.hpp" +#include "EvalFunc.hpp" + +#include +#include +#include + +TEST(EvalMO, LargeNeighborhood) +{ + const size_t length = 5; + const size_t value_size = 31; + using myuint = uint32_t; + using Combi = moeoIntVector; + + eoForgeVector< EvalMO::OpItf > forge(/*always_reinstantiate*/true); + forge.add< Multiply >( 9, value_size); + forge.add< XorLeftShift >(17, value_size); + forge.add< XorLeftShift >( 5, value_size); + forge.add< AddShift >(19, value_size); + forge.add< XorRightShift >( 3, value_size); + forge.add< Multiply >( 9, value_size); + + Combi sol(length, 0); + + EvalMO eval(value_size, forge); + + using MutWrapper = eoRealToIntMonOp>; + eoDetUniformMutation< typename MutWrapper::EOTreal > mutreal(/*range*/forge.size(), /*nb*/length); + eoIntInterval bounds(0,forge.size()-1); + MutWrapper mutint(mutreal, bounds); + + for(size_t i=0; i < 100; ++i) { + const bool changed = mutint(sol); + assert(changed); + eval(sol); + } +} + + From febf0bf5abdc6535ddc9c271e4b63dafa217d9b0 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Fri, 27 Sep 2024 14:48:54 +0200 Subject: [PATCH 29/31] feat(search): adds a multi-objective algo --- app/search.cpp | 200 ++++++++++++++++++++++++++------------- external/paradiseo | 2 +- src/include/EvalFunc.hpp | 4 + 3 files changed, 137 insertions(+), 69 deletions(-) diff --git a/app/search.cpp b/app/search.cpp index a426b2f..c6980eb 100644 --- a/app/search.cpp +++ b/app/search.cpp @@ -5,6 +5,9 @@ #include #include +#include +#include +#include #include "HashFunction.hpp" #include "Operator.hpp" @@ -20,10 +23,6 @@ #include "log.h" using myuint = uint32_t; -using Min = eoMinimizingFitness; -using Combi = moCombination; -using Nb = moCombinationNeighbor; -using NbHood = moCombinationNeighborhood; //! Error codes returned on exit. enum class Error : unsigned char { @@ -61,7 +60,7 @@ struct Range { size_t step; }; -void make_domain(eoForgeVector< EvalFull::OpItf >& forge, size_t value_size, Range shift_range, Range mult_range) +void make_domain(eoForgeVector< Operator >& forge, size_t value_size, Range shift_range, Range mult_range) { for(size_t i = shift_range.min; i < shift_range.max; i+=shift_range.step) { forge.add< XorLeftShift >(i, value_size); @@ -138,8 +137,9 @@ int main(int argc, char* argv[]) /***** Search domain arguments *****/ std::map algorithms; - algorithms["HC"] = "Hill-Climbing"; - algorithms["SA"] = "Simulated-Annealing"; + algorithms["HC"] = "Hill-Climbing [mono-objective]"; + algorithms["SA"] = "Simulated-Annealing [mono-objective]"; + algorithms["NSGA2"] = "NSGAII [bi-objective]"; std::ostringstream msg; msg << " ("; for(auto& kv : algorithms) { msg << kv.first << ":" << kv.second << ", "; @@ -193,7 +193,7 @@ int main(int argc, char* argv[]) CLUTCHLOG(note, "OK"); CLUTCHLOG(progress, "Create the search domain..."); - eoForgeVector< EvalFull::OpItf > forge(/*always_reinstantiate*/true); + eoForgeVector< Operator > forge(/*always_reinstantiate*/true); make_domain(forge, value_size, shift_range, mult_range); CLUTCHLOG(info, forge.size() << " operators"); ASSERT(forge.size() > 0); @@ -201,72 +201,136 @@ int main(int argc, char* argv[]) CLUTCHLOG(progress, "Instantiate solver..."); eo::rng.reseed(seed); - EvalFull feval(value_size, forge); - EvalTest peval(feval); - - NbHood neighborhood; - - // Continue search until exhaustion of the neighborhood. - moTrueContinuator until_end; - moCheckpoint check(until_end); - moBestFitnessStat best; - check.add(best); // Update the best state. - - // Hill climber, selecting a random solution among the equal-best ones. - std::unique_ptr< moLocalSearch > palgo; - if( algo == "HC" ) { - palgo = std::make_unique< moRandomBestHC >(neighborhood, feval, peval, check); - } else if( algo == "SA" ) { - palgo = std::make_unique< moSA >(neighborhood, feval, peval, check); - } else { - std::ostringstream msg; - msg << "Unknown algorithm: " << algo << ", valid candidates are"; - for( auto& kv : algorithms) { - msg << ", " << kv.first << " (" << kv.second << ")"; + + if( algo == "HC" or algo == "SA" ) { + + using Min = eoMinimizingFitness; + using Combi = moCombination; + using Nb = moCombinationNeighbor; + using NbHood = moCombinationNeighborhood; + + EvalFull feval(value_size, forge); + EvalTest peval(feval); + + NbHood neighborhood; + + // Continue search until exhaustion of the neighborhood. + moTrueContinuator until_end; + moCheckpoint check(until_end); + moBestFitnessStat best; + check.add(best); // Update the best state. + + // Hill climber, selecting a random solution among the equal-best ones. + std::unique_ptr< moLocalSearch > palgo; + if( algo == "HC" ) { + palgo = std::make_unique< moRandomBestHC >(neighborhood, feval, peval, check); + } else if( algo == "SA" ) { + palgo = std::make_unique< moSA >(neighborhood, feval, peval, check); } - EXIT_ON_ERROR( Invalid_Argument, msg.str() ); - } - moLocalSearch& search = *palgo; - CLUTCHLOG(note, "OK"); + moLocalSearch& search = *palgo; + CLUTCHLOG(note, "OK"); + + CLUTCHLOG(progress, "Pick a random solution..."); + std::vector v; + v.reserve(func_len); + std::mt19937 rng(seed); + std::uniform_int_distribution uni(0, forge.size()-1); + for(size_t i=0; i v; - v.reserve(func_len); - std::mt19937 rng(seed); - std::uniform_int_distribution uni(0, forge.size()-1); - for(size_t i=0; i 0); + ASSERT(hf.reverse.size() > 0); + + std::cout << hf.forward.to_string() << std::endl; + std::cout << hf.reverse.to_string() << std::endl; - CLUTCHLOG(progress, "Optimized solution:"); - CLUTCHLOG(note, sol ); + CLUTCHLOG(progress, "Done."); - CLUTCHLOG(progress, "Output optimized hash function:"); - // Real output. - hf = make_hashfuncs(sol, value_size, forge); - ASSERT(hf.forward.size() > 0); - ASSERT(hf.reverse.size() > 0); - std::cout << hf.forward.to_string() << std::endl; - std::cout << hf.reverse.to_string() << std::endl; + } else if( algo == "NSGA2" ) { - CLUTCHLOG(progress, "Done."); + using Combi = moeoIntVector; + using ReVec = moeoRealVector; + + EvalMO eval(value_size, forge); + eoPopLoopEval popEval(eval); + + // Crossover + eoQuadCloneOp xover; // TODO use a real crossover + + // Mutation + using MutWrapper = eoRealToIntMonOp; + eoDetUniformMutation mutreal(/*range*/1.5, /*nb*/1); // TODO tune + eoIntInterval bounds(0,forge.size()-1); + MutWrapper mutation(mutreal, bounds); + + using InitWrapper = eoRealToIntInit; + eoRealVectorBounds rebounds(func_len, 0, forge.size()-1); + eoRealInitBounded initreal(rebounds); + InitWrapper init(initreal, bounds); + + eoQuadGenOp genOp(xover); + eoSGATransform transform(xover, 0.1, mutation, 0.1); + eoGenContinue continuator(10); + + // build NSGA-II + moeoNSGAII algo(20, eval, xover, 1.0, mutation, 1.0); + CLUTCHLOG(note, "OK"); + + CLUTCHLOG(progress, "Initialize population..."); + eoPop pop(20, init); + CLUTCHLOG(note, "OK"); + + CLUTCHLOG(progress, "Solver run..."); + algo(pop); + CLUTCHLOG(note, "OK"); + + CLUTCHLOG(progress, "Optimized solution:"); + auto sol = pop.best_element(); + CLUTCHLOG(note, sol ); + auto hf = make_hashfuncs(sol, value_size, forge); + + CLUTCHLOG(progress, "Output optimized hash function:"); + // Real output. + ASSERT(hf.forward.size() > 0); + ASSERT(hf.reverse.size() > 0); + + std::cout << hf.forward.to_string() << std::endl; + std::cout << hf.reverse.to_string() << std::endl; + + CLUTCHLOG(progress, "Done."); + + + } else { + std::ostringstream msg; + msg << "Unknown algorithm: " << algo << ", valid candidates are"; + for( auto& kv : algorithms) { + msg << ", " << kv.first << " (" << kv.second << ")"; + } + EXIT_ON_ERROR( Invalid_Argument, msg.str() ); + } } diff --git a/external/paradiseo b/external/paradiseo index 19ec4c4..8ea6e2b 160000 --- a/external/paradiseo +++ b/external/paradiseo @@ -1 +1 @@ -Subproject commit 19ec4c4ff760b5470163f0e940d1148449b6f3ad +Subproject commit 8ea6e2b680d00f82655584fcc199f90803f00fa2 diff --git a/src/include/EvalFunc.hpp b/src/include/EvalFunc.hpp index e98f2a9..8ea01b2 100644 --- a/src/include/EvalFunc.hpp +++ b/src/include/EvalFunc.hpp @@ -135,6 +135,8 @@ class QualityAndRuntimeTraits : public moeoObjectiveVectorTraits return false; case 1: // runtime return true; + default: + throw(std::out_of_range("Only two objectives are supported")); } } static bool maximizing (int d) @@ -144,6 +146,8 @@ class QualityAndRuntimeTraits : public moeoObjectiveVectorTraits return true; case 1: // runtime return false; + default: + throw(std::out_of_range("Only two objectives are supported")); } } static unsigned int nObjectives () From 0d9b719ec87c6b71ccde9cc8bdadbc1e4484ae7a Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Fri, 27 Sep 2024 15:42:45 +0200 Subject: [PATCH 30/31] feat(search): pretty print output Outputs: Paradiseo's solution, objective(s) value(s) and hash functions as a YAML structure. --- app/search.cpp | 127 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 87 insertions(+), 40 deletions(-) diff --git a/app/search.cpp b/app/search.cpp index c6980eb..47920f5 100644 --- a/app/search.cpp +++ b/app/search.cpp @@ -24,6 +24,10 @@ using myuint = uint32_t; +using Min = eoMinimizingFitness; +using Combi = moCombination; +using CombiMO = moeoIntVector; + //! Error codes returned on exit. enum class Error : unsigned char { No_Error = 0, @@ -88,6 +92,73 @@ void make_domain(eoForgeVector< Operator >& forge, size_t value_size, Ra ASSERT(nb_multipliers > 0); } +std::string format_hashfunc(HashFunctionPair& hf, std::string indent = " ") +{ + ASSERT(hf.forward.size() > 0); + ASSERT(hf.reverse.size() > 0); + + std::ostringstream out; + out << "# YAML\n"; + out << "rHashGen:\n"; + + std::string line; + + out << indent << "forward: |" << indent.size() << "\n"; + std::istringstream isf(hf.forward.to_string()); + while( std::getline(isf, line)) { + out << indent << indent << line << "\n"; + } + + out << indent << "reverse: |" << indent.size() << "\n"; + std::istringstream isr(hf.reverse.to_string()); + while( std::getline(isr, line)) { + out << indent << indent << line << "\n"; + } + + return out.str(); +} + +std::string format_solution(const Combi& sol, const size_t value_size, eoForgeVector< Operator >& forge, std::string indent = " ") +{ + CLUTCHLOG(progress, "Optimized solution:"); + CLUTCHLOG(note, sol ); + auto hf = make_hashfuncs(sol, value_size, forge); + + CLUTCHLOG(progress, "Output optimized hash function:"); + + std::ostringstream out; + out << format_hashfunc(hf, indent); + + out << indent<< "solution: \"" << sol << "\"\n" + << indent<< "quality: " << sol.fitness() << "\n" + << "" << std::endl; + + CLUTCHLOG(progress, "Done."); + + return out.str(); +} + +std::string format_solution(const CombiMO& sol, const size_t value_size, eoForgeVector< Operator >& forge, std::string indent = " ") +{ + CLUTCHLOG(progress, "Optimized solution:"); + CLUTCHLOG(note, sol ); + auto hf = make_hashfuncs(sol, value_size, forge); + + CLUTCHLOG(progress, "Output optimized hash function:"); + + std::ostringstream out; + out << format_hashfunc(hf, indent); + + out << indent<< "solution: \"" << sol << "\"\n" + << indent<< "quality: " << sol.objectiveVector(0) << "\n" + << indent<< "runtime: " << sol.objectiveVector(1) << "\n" + << "" << std::endl; + + CLUTCHLOG(progress, "Done."); + + return out.str(); +} + int main(int argc, char* argv[]) { CLUTCHLOG(progress, "Set config..."); @@ -204,8 +275,6 @@ int main(int argc, char* argv[]) if( algo == "HC" or algo == "SA" ) { - using Min = eoMinimizingFitness; - using Combi = moCombination; using Nb = moCombinationNeighbor; using NbHood = moCombinationNeighborhood; @@ -243,8 +312,8 @@ int main(int argc, char* argv[]) auto hf = make_hashfuncs(sol, value_size, forge); CLUTCHLOG(info, "Initial hash function: " << hf.forward.get_name() << " / " << hf.reverse.get_name()); - std::clog << hf.forward.to_string() << std::endl; - std::clog << hf.reverse.to_string() << std::endl; + // std::clog << hf.forward.to_string() << std::endl; + // std::clog << hf.reverse.to_string() << std::endl; CLUTCHLOG(note, "OK"); CLUTCHLOG(progress, "Evaluate first signature..."); @@ -256,73 +325,51 @@ int main(int argc, char* argv[]) search(sol); CLUTCHLOG(note, "OK"); - CLUTCHLOG(progress, "Optimized solution:"); - CLUTCHLOG(note, sol ); - hf = make_hashfuncs(sol, value_size, forge); - - CLUTCHLOG(progress, "Output optimized hash function:"); - // Real output. - ASSERT(hf.forward.size() > 0); - ASSERT(hf.reverse.size() > 0); - - std::cout << hf.forward.to_string() << std::endl; - std::cout << hf.reverse.to_string() << std::endl; - - CLUTCHLOG(progress, "Done."); + const std::string out = format_solution(sol, value_size, forge); + std::cout << out << std::endl; } else if( algo == "NSGA2" ) { - using Combi = moeoIntVector; using ReVec = moeoRealVector; - EvalMO eval(value_size, forge); - eoPopLoopEval popEval(eval); + EvalMO eval(value_size, forge); + eoPopLoopEval popEval(eval); // Crossover - eoQuadCloneOp xover; // TODO use a real crossover + eoQuadCloneOp xover; // TODO use a real crossover // Mutation - using MutWrapper = eoRealToIntMonOp; + using MutWrapper = eoRealToIntMonOp; eoDetUniformMutation mutreal(/*range*/1.5, /*nb*/1); // TODO tune eoIntInterval bounds(0,forge.size()-1); MutWrapper mutation(mutreal, bounds); - using InitWrapper = eoRealToIntInit; + using InitWrapper = eoRealToIntInit; eoRealVectorBounds rebounds(func_len, 0, forge.size()-1); eoRealInitBounded initreal(rebounds); InitWrapper init(initreal, bounds); - eoQuadGenOp genOp(xover); - eoSGATransform transform(xover, 0.1, mutation, 0.1); - eoGenContinue continuator(10); + eoQuadGenOp genOp(xover); + eoSGATransform transform(xover, 0.1, mutation, 0.1); + eoGenContinue continuator(10); // build NSGA-II - moeoNSGAII algo(20, eval, xover, 1.0, mutation, 1.0); + moeoNSGAII algo(20, eval, xover, 1.0, mutation, 1.0); CLUTCHLOG(note, "OK"); CLUTCHLOG(progress, "Initialize population..."); - eoPop pop(20, init); + eoPop pop(20, init); CLUTCHLOG(note, "OK"); CLUTCHLOG(progress, "Solver run..."); algo(pop); CLUTCHLOG(note, "OK"); - CLUTCHLOG(progress, "Optimized solution:"); auto sol = pop.best_element(); - CLUTCHLOG(note, sol ); - auto hf = make_hashfuncs(sol, value_size, forge); - - CLUTCHLOG(progress, "Output optimized hash function:"); - // Real output. - ASSERT(hf.forward.size() > 0); - ASSERT(hf.reverse.size() > 0); - - std::cout << hf.forward.to_string() << std::endl; - std::cout << hf.reverse.to_string() << std::endl; - CLUTCHLOG(progress, "Done."); + const std::string out = format_solution(sol, value_size, forge); + std::cout << out << std::endl; } else { From 88acaed53fc517d5d6a490a46a9a13672a1a3be8 Mon Sep 17 00:00:00 2001 From: Johann Dreo Date: Fri, 27 Sep 2024 16:13:25 +0200 Subject: [PATCH 31/31] doc: adds missing comments & update README --- README.md | 19 +++++--------- src/include/EvalFunc.hpp | 51 +++++++++++++++++++++++++++--------- src/include/HashFunction.hpp | 22 +++++++++------- 3 files changed, 59 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index ff435ec..37b15b1 100644 --- a/README.md +++ b/README.md @@ -12,22 +12,17 @@ git clone --recursive https://github.com/username/repo.git && cd repo mkdir build && cd build # Configure the build using CMake cmake .. -# Compile an example binary -make example -# Compile the unit tests -make rHashGen-tests +# Compile the solver +make search +# See how to run the solver +./search --help ``` -## Running the Binaries +## Run tests -To run the example and test binaries, use the following commands: +To run the tests, use the following commands: ```bash -# Run the example binary -./example - -# Run the test binary -make test +mkdir -p build && cd build && cmake -CMAKE_BUILD_TYPE=Debug .. && make && make test ``` - diff --git a/src/include/EvalFunc.hpp b/src/include/EvalFunc.hpp index 8ea01b2..7dba965 100644 --- a/src/include/EvalFunc.hpp +++ b/src/include/EvalFunc.hpp @@ -6,7 +6,22 @@ #include "log.h" -//! Instantiate the forward and reverse HashFunc from the given solution. +//! Structure to gather forward and reverse hash functions, as outputed by make_hashfuncs. +template +class HashFunctionPair +{ +public: + HashFunctionPair( HashFunction f, HashFunction r ) : forward(f), reverse(r) {} + HashFunction forward; + HashFunction reverse; +}; + +/** Instantiate the forward and reverse HashFunc from the given solution. + * + * @param sol the Paradiseo solution representing a hash function (i.e. a sequence of indices) + * @param value_size The size (in bits) of the values to manipulate + * @param forge The set of possible parametrized hash operators. + */ template HashFunctionPair make_hashfuncs( EOT& sol, size_t value_size, eoForgeVector>& forge ) { @@ -44,7 +59,7 @@ HashFunctionPair make_hashfuncs( EOT& sol, size_t value_size, eoForgeVec return HashFunctionPair(hff, hfr); } - +//! Evaluates a mono-objective solution from scratch. template class EvalFull : public eoEvalFunc< EOT > { @@ -56,6 +71,11 @@ class EvalFull : public eoEvalFunc< EOT > eoForgeVector& m_forge; public: + /** Constructor + * + * @param value_size The size (in bits) of the values to manipulate + * @param forge The set of possible parametrized hash operators. + */ EvalFull(size_t value_size, eoForgeVector& forge) : m_value_size(value_size), m_forge(forge) @@ -82,10 +102,7 @@ class EvalFull : public eoEvalFunc< EOT > }; -/** Partial evaluator that actually perform a full evaluation. - * - * This is to test that a partial evaluation ends on the same value than a full evaluation. - */ +//! Partial evaluator that actually perform a full evaluation. template class EvalTest : public moEval> { @@ -124,11 +141,14 @@ class EvalTest : public moEval> }; - +/** Multi-objective trait + * + * Indicates the number of objectives, and which one is to be minimized. + */ class QualityAndRuntimeTraits : public moeoObjectiveVectorTraits { public: - static bool minimizing (int d) + static bool minimizing(int d) { switch(d) { case 0: // quality @@ -139,7 +159,7 @@ class QualityAndRuntimeTraits : public moeoObjectiveVectorTraits throw(std::out_of_range("Only two objectives are supported")); } } - static bool maximizing (int d) + static bool maximizing(int d) { switch(d) { case 0: // quality @@ -150,14 +170,18 @@ class QualityAndRuntimeTraits : public moeoObjectiveVectorTraits throw(std::out_of_range("Only two objectives are supported")); } } - static unsigned int nObjectives () + static unsigned int nObjectives() { return 2; } }; +//! Objectives values are stored in a vector of values. using QualityAndRuntime = moeoRealObjectiveVector; + +/** Multi-objective evaluation. + */ template class EvalMO : public moeoEvalFunc { @@ -170,6 +194,11 @@ class EvalMO : public moeoEvalFunc eoForgeVector& m_forge; public: + /** Constructor + * + * @param value_size The size (in bits) of the values to manipulate + * @param forge The set of possible parametrized hash operators. + */ EvalMO(size_t value_size, eoForgeVector& forge) : m_value_size(value_size), m_forge(forge) @@ -189,8 +218,6 @@ class EvalMO : public moeoEvalFunc const QualityAndRuntime::Type quality = hff.size() * hfr.size(); const QualityAndRuntime::Type runtime = hff.size() + hfr.size(); - // moeoRealObjectiveVector qualrunt; - sol.objectiveVector(0, quality ); sol.objectiveVector(1, runtime ); CLUTCHLOG(xdebug, "Evaluated solution: " << sol); diff --git a/src/include/HashFunction.hpp b/src/include/HashFunction.hpp index 65c2b5a..5f84a1c 100644 --- a/src/include/HashFunction.hpp +++ b/src/include/HashFunction.hpp @@ -10,6 +10,11 @@ #ifndef HASHFUNCTION_HPP #define HASHFUNCTION_HPP +/** A sequence of hash operators. + * + * Allows to consolidate a sequence of feasible operators with masks, + * and compute the reverse hash function of a forward one. + */ template class HashFunction { @@ -24,17 +29,24 @@ class HashFunction size_t m_value_size; public: + /** Explicit constructor. + * + * @param value_size The size (in bits) of the values to manipulate + * @param function_name Name given to the output code (if empty, uses a self-describing one) + */ HashFunction(size_t value_size, std::string const & function_name = "") : m_function_name(function_name), m_value_size(value_size) {}; + //! Copy constructor. HashFunction(HashFunction & other) : m_function_name(other.m_function_name), m_operators{std::move(other.m_operators)}, m_value_size(other.m_value_size) {}; + //! Assignement operator. HashFunction& operator=(const HashFunction& other) { this->m_function_name = other.m_function_name; @@ -189,19 +201,11 @@ class HashFunction return ss.str(); } + //! Returns the current number of operators in the sequence. size_t size() const { return m_operators.size(); } }; -template -class HashFunctionPair -{ -public: - HashFunctionPair( HashFunction f, HashFunction r ) : forward(f), reverse(r) {} - HashFunction forward; - HashFunction reverse; -}; - #endif // HASHFUNCTION_HPP