Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Added a generic function optimizer based on Simulated Annealing #148

Merged
merged 56 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
276efd0
:sparkles: Added a rudimentary (largely untested) simulated annealing…
marcelwa Dec 13, 2022
9445319
:sparkles: Basic implementation of SA is done and tested
marcelwa Dec 13, 2022
ace4d5f
:memo: Documentation for SA
marcelwa Dec 13, 2022
2fc799e
:rotating_light: Made optimization test functions non-constexpr
marcelwa Dec 13, 2022
1405a70
:sparkles: Added a multi simulated annealing implementation that gene…
marcelwa Jan 13, 2023
d52cfdf
:green_heart: Enabled Clang to find TBB to fix linking errors
marcelwa Jan 13, 2023
38bd944
:green_heart: Disabled execution policies for compilers that do not s…
marcelwa Jan 14, 2023
c1d4a7d
:green_heart: Added TBB setup
marcelwa Jan 14, 2023
5b788fa
:green_heart: Excluded GCC 9 and 10 from parallel algorithms if TBB 2…
marcelwa Jan 14, 2023
30dd997
:green_heart: Fixed version detection
marcelwa Jan 14, 2023
434c91b
:green_heart: Fixed version detection
marcelwa Jan 15, 2023
ff8641e
:green_heart: Incorporate AppleClang
marcelwa Jan 15, 2023
49a054a
:green_heart: Deactivate multi-config generator build
marcelwa Jan 15, 2023
5caced2
:construction_worker: Setup TBB via choco on Windows for ClangCL tests
marcelwa Jan 15, 2023
f9cb885
:construction_worker: Message regarding Parallel STL on MSVC
marcelwa Jan 15, 2023
f1e5ec5
:construction_worker: Disable fast failing for testing
marcelwa Jan 15, 2023
d9d21af
:green_heart: Test `std::execution::par` instead of `par_unseq`
marcelwa Jan 15, 2023
1b37b89
:green_heart: Remove TBB choco install as it is not supported
marcelwa Jan 15, 2023
a4234f7
:green_heart: Exclude GCC 9 from using parallel execution policies
marcelwa Jan 15, 2023
56ac9dd
Merge remote-tracking branch 'origin/placer' into placer
marcelwa Jan 15, 2023
83e371f
:art: Create macros for execution policies that default to nothing if…
marcelwa Jan 15, 2023
544ab81
:construction_worker: Updated TBB and GCC reporting
marcelwa Jan 15, 2023
762dc7c
:memo: Documentation on execution policy macros
marcelwa Jan 15, 2023
dcb32f5
Merge branch 'main' into placer
marcelwa Jan 15, 2023
986541c
:building_construction: Remove the fail-fast strategy again that was …
marcelwa Jan 16, 2023
ef418c5
:building_construction: Remove multi-config builds
marcelwa Jan 16, 2023
496b3b3
:sparkles: Added a placement_layout type that can be used to quickly …
marcelwa Jan 17, 2023
9a47a7b
:sparkles: Added a swap function to switch a node with the content of…
marcelwa Jan 17, 2023
90ec43f
Merge branch 'main' into placer
marcelwa Jan 18, 2023
67ea6eb
Merge branch 'main' into placer
marcelwa Jan 18, 2023
17325aa
:bug: Fixed distribution type
marcelwa Jan 19, 2023
b67fde9
:bug: Added a failsafe to the combinations call
marcelwa Jan 19, 2023
0e85846
:label: Made sure to convert the cost_delta to double
marcelwa Jan 20, 2023
4ca2000
:label: Made source and target of edges non-const
marcelwa Jan 20, 2023
b437756
:arrow_up: Upgrade mockturtle
marcelwa Jan 20, 2023
0fdbca0
:label: Added type checks to the simulated_annealing template parameters
marcelwa Jan 20, 2023
7aff24a
:arrow_up: updated mockturtle
marcelwa Jan 24, 2023
f71031c
:arrow_up: updated mockturtle
marcelwa Jan 24, 2023
b67839b
:arrow_up: updated mockturtle
marcelwa Jan 24, 2023
969b266
:sparkles: Prototypical implementation of a crossing reduction algori…
marcelwa Jan 24, 2023
f209a8c
Merge branch 'main' into placer
marcelwa Jan 25, 2023
247bf04
Merge branch 'main' into placer
marcelwa Jan 25, 2023
00050a5
:bug: Fixed temperature schedule application in simulated annealing
marcelwa Jan 25, 2023
3aa332d
:bug: crossing_reduction cannot return worse rank orderings anymore
marcelwa Jan 26, 2023
e63dcb0
:arrow_up: update mockturtle
marcelwa Jan 26, 2023
32f1083
:art: small clang-tidy change
marcelwa Jan 26, 2023
1e64d4b
:alembic: Added a crossing reduction experiment
marcelwa Jan 26, 2023
a4b26c9
:construction: WIP save commit
marcelwa Jan 26, 2023
cb5c0eb
:fire: Removed code related to crossing reduction as it is transferre…
marcelwa Mar 17, 2023
e545b9a
Merge branch 'main' into placer
marcelwa Mar 17, 2023
d64aae7
:fire: Removed changes related to placement_layout as it has been mov…
marcelwa Mar 17, 2023
d2322fb
:fire: Removed changes related to placement_layout as it has been mov…
marcelwa Mar 17, 2023
6fecc87
:rewind: Reverted changes made to the mockturtle version
marcelwa Mar 17, 2023
e82d7ad
:green_heart: Re-added the `--config` flag to the `cmake` build comma…
marcelwa Mar 18, 2023
d5c2762
Merge branch 'main' into placer
marcelwa Mar 20, 2023
023a63d
:art: Substituted floating-point comparisons by Catch2's matchers
marcelwa Mar 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ jobs:
with:
submodules: recursive

# Setup TBB for parallel STL algorithms via Homebrew
- name: Setup TBB
run: brew install tbb

# Use XCode 13.2 as a workaround because XCode 14.0 seems broken
- name: Setup XCode version
uses: maxim-lobanov/setup-xcode@v1
Expand Down Expand Up @@ -108,7 +112,7 @@ jobs:

- name: Build
working-directory: ${{github.workspace}}/build
run: cmake --build . --config ${{matrix.build_type}} -j3 # macOS runners provide 3 cores
run: cmake --build . -j3 # macOS runners provide 3 cores

- name: Test
working-directory: ${{github.workspace}}/build
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ jobs:
build_type: Release
cppstandard: -DFICTION_CXX_STANDARD=20
cppname: C++20
- os: ubuntu-22.04
compiler: g++-11

name: ${{matrix.os}} with ${{matrix.compiler}} (${{matrix.build_type}} mode) ${{matrix.cppname}}
runs-on: ${{matrix.os}}
Expand Down Expand Up @@ -105,7 +107,7 @@ jobs:

- name: Build fiction
working-directory: ${{github.workspace}}/build
run: cmake --build . --config ${{matrix.build_type}}
run: cmake --build .

- name: Test
working-directory: ${{github.workspace}}/build
Expand Down
9 changes: 9 additions & 0 deletions docs/algorithms/algorithms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ Graph Algorithms

graph.rst


Optimization
============

.. toctree::
:maxdepth: 1

optimization.rst

Network Transformation
======================

Expand Down
9 changes: 9 additions & 0 deletions docs/algorithms/optimization.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Simulated Annealing
-------------------

**Header:** ``fiction/algorithms/optimization/simulated_annealing.hpp``

.. doxygenfunction:: fiction::linear_temperature_schedule
.. doxygenfunction:: fiction::geometric_temperature_schedule
.. doxygenfunction:: fiction::simulated_annealing
.. doxygenfunction:: fiction::multi_simulated_annealing
28 changes: 28 additions & 0 deletions docs/utils/utils.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,34 @@ STL Extensions
.. doxygenclass:: fiction::searchable_priority_queue


Execution Policy Macros
-----------------------

**Header:** ``fiction/utils/execution_utils.hpp``

Handling parallel STL algorithms is a bit cumbersome due to their platform dependence. The following macros are provided
to simplify the usage of parallel STL algorithms while CMake and some pre-processor magic take care of all the
boilerplate.

One can use the following macros to specify the execution policy for parallel STL algorithms in a (mostly)
platform-independent way::

std::for_each(FICTION_EXECUTION_POLICY_PAR v.begin(), v.end(), lambda);
// ^ note the missing comma

If parallelism or execution policies are not available, this will expand to::

std::for_each(v.begin(), v.end(), lambda);

.. note::
Only include this header and do not include ``<execution>`` directly. This header will include ``<execution>`` if
available and will define the macros accordingly.

.. doxygendefine:: FICTION_EXECUTION_POLICY_SEQ
.. doxygendefine:: FICTION_EXECUTION_POLICY_PAR
.. doxygendefine:: FICTION_EXECUTION_POLICY_PAR_UNSEQ


Ranges
------

Expand Down
187 changes: 187 additions & 0 deletions include/fiction/algorithms/optimization/simulated_annealing.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//
// Created by marcel on 12.12.22.
//

#ifndef FICTION_SIMULATED_ANNEALING_HPP
#define FICTION_SIMULATED_ANNEALING_HPP

#include "fiction/utils/execution_utils.hpp"

#include <algorithm>
#include <cassert>
#include <cmath>
#include <limits>
#include <random>
#include <type_traits>
#include <utility>

namespace fiction
{

/**
* A linearly decreasing temperature schedule. The temperature is altered in decrements of `10`.
*
* @param t The current temperature.
* @return The next temperature, i.e. \f$ \texttt{t} - 10 \f$.
*/
constexpr auto linear_temperature_schedule(const double t) noexcept
{
return t - 10.0;
}
/**
* A logarithmically decreasing temperature schedule. The temperature is altered by multiplying it with `0.99`.
*
* @param t The current temperature.
* @return The next temperature, i.e. \f$ \texttt{t} \cdot 0.99 \f$.
*/
constexpr auto geometric_temperature_schedule(const double t) noexcept
{
return t * 0.99;
}
/**
* Simulated Annealing (SA) is a probabilistic optimization algorithm that is used to find a local minimum of a given
* function. SA was first proposed in \"Optimization by simulated annealing\" by S. Kirkpatrick, C. D. Gelatt Jr, and M.
* P. Vecchi in Science 1983. It is a metaheuristic that is inspired by the annealing process in metallurgy. The
* algorithm starts with a random state and iteratively improves the state by randomly selecting a neighboring state. If
* the neighboring state is better than the current state, it is accepted. If the neighboring state is worse than the
* current state, it is accepted with a probability that decreases over time. The algorithm stops when the temperature
* reaches a certain threshold.
*
* Some pre-defined temperature schedules are provided in this header file.
*
* This implementation is based on:
* https://codereview.stackexchange.com/questions/70310/simple-simulated-annealing-template-in-c11
*
* @tparam State The state type.
* @tparam CostFunc The cost function type (specifies the cost type via its return value).
* @tparam TempFunc The temperature schedule function type.
* @tparam NextFunc The next state function type.
* @param init_state The initial state to optimize.
* @param init_temp The initial temperature.
* @param final_temp The final temperature.
* @param cycles The number of cycles for each temperature value.
* @param cost The cost function to minimize.
* @param schedule The temperature schedule.
* @param next The next state function that determines an adjacent state given a current one.
* @return A pair of the optimized state and its cost value.
*/
template <typename State, typename CostFunc, typename TempFunc, typename NextFunc>
std::pair<State, std::invoke_result_t<CostFunc, State>>
simulated_annealing(const State& init_state, const double init_temp, const double final_temp, const std::size_t cycles,
CostFunc&& cost, TempFunc&& schedule, NextFunc&& next) noexcept
{
static_assert(std::is_invocable_v<CostFunc, State>, "CostFunc must be invocable with objects of type State");
static_assert(std::is_invocable_v<TempFunc, double>, "TempFunc must be invocable with double");
static_assert(std::is_invocable_v<NextFunc, State>, "NextFunc must be invocable with objects of type State");
static_assert(std::is_signed_v<std::invoke_result_t<CostFunc, State>>, "CostFunc must return a signed value");
static_assert(std::is_same_v<std::invoke_result_t<TempFunc, double>, double>, "TempFunc must return a double");
static_assert(std::is_same_v<State, std::invoke_result_t<NextFunc, State>>,
"NextFunc must return an object of type State");

assert(std::isfinite(init_temp) && "init_temp must be a finite number");
assert(std::isfinite(final_temp) && "final_temp must be a finite number");

static std::mt19937_64 generator{std::random_device{}()};

static std::uniform_real_distribution<double> random_functor(0, 1);

auto current_cost = cost(init_state);
auto current_state = init_state;

State best_state = current_state;
auto best_cost = current_cost;

auto temp = init_temp;

while (temp > final_temp)
{
for (std::size_t c = 0; c < cycles; ++c)
{
State new_state = next(current_state);
auto new_cost = cost(new_state);

if (new_cost < best_cost)
{
best_state = new_state;
best_cost = new_cost;
current_state = std::move(new_state);
current_cost = std::move(new_cost);

continue;
}

const auto cost_delta = static_cast<double>(new_cost - current_cost);

// shortcut to skip the expensive std::exp call
if (cost_delta > 10.0 * temp)
{
continue; // as std::exp(-10.0) is a very small number
}

// if the new state is worse, accept it with a probability of exp(-energy_delta/temp)
if (cost_delta <= 0.0 || std::exp(-cost_delta / temp) > random_functor(generator))
{
current_state = std::move(new_state);
current_cost = std::move(new_cost);
}
}

// update temperature
temp = std::clamp(schedule(temp), final_temp, init_temp);
}

return {best_state, best_cost};
}
/**
* This variation of Simulated Annealing (SA) does not start from just one provided initial state, but generates a
* number of random initial states using a provided random state generator. SA as specified above is then run on all
* these random initial states where the best result of all generated states is finally returned.
*
* @note If compiler support for C++17's execution policies is available, the algorithm is parallelized and/or
* vectorized using `std::execution::par_unseq`.
*
* @note The State type must be default constructible.
*
* @tparam RandStateFunc The random state generator function type (specifies the State type via its return value).
* @tparam CostFunc The cost function type (specifies the cost value via its return value).
* @tparam TempFunc The temperature schedule function type.
* @tparam NextFunc The next state function type.
* @param init_temp The initial temperature.
* @param final_temp The final temperature.
* @param cycles The number of cycles for each temperature value.
* @param instances The number of random initial states to generate.
* @param rand_state The random state generator function.
* @param cost The cost function to minimize.
* @param schedule The temperature schedule.
* @param next The next state function that determines an adjacent state given a current one.
* @return A pair of the overall best optimized state and its cost value.
*/
template <typename RandStateFunc, typename CostFunc, typename TempFunc, typename NextFunc>
std::pair<std::invoke_result_t<RandStateFunc>, std::invoke_result_t<CostFunc, std::invoke_result_t<RandStateFunc>>>
multi_simulated_annealing(const double init_temp, const double final_temp, const std::size_t cycles,
const std::size_t instances, RandStateFunc&& rand_state, CostFunc&& cost, TempFunc&& schedule,
NextFunc&& next) noexcept
{
using state_t = std::invoke_result_t<RandStateFunc>;
using cost_t = std::invoke_result_t<CostFunc, state_t>;

static_assert(std::is_invocable_v<RandStateFunc>, "RandStateFunc must be invocable");
static_assert(std::is_invocable_v<CostFunc, state_t>, "CostFunc must be invocable with objects of type State");
static_assert(std::is_default_constructible_v<state_t>, "State must be default-constructible");

assert(std::isfinite(init_temp) && "init_temp must be a finite number");
assert(std::isfinite(final_temp) && "final_temp must be a finite number");

std::vector<std::pair<state_t, cost_t>> results(instances);
std::generate(
FICTION_EXECUTION_POLICY_PAR_UNSEQ results.begin(), results.end(),
[&init_temp, &final_temp, &cycles, &rand_state, &cost, &schedule, &next]() -> std::pair<state_t, cost_t>
{ return simulated_annealing(rand_state(), init_temp, final_temp, cycles, cost, schedule, next); });

return *std::min_element(FICTION_EXECUTION_POLICY_PAR_UNSEQ results.cbegin(), results.cend(),
[](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });
}

} // namespace fiction

#endif // FICTION_SIMULATED_ANNEALING_HPP
13 changes: 6 additions & 7 deletions include/fiction/traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,12 @@ struct is_coordinate_layout : std::false_type
template <class Lyt>
struct is_coordinate_layout<
Lyt,
std::enable_if_t<
std::conjunction_v<std::is_constructible<aspect_ratio<Lyt>, coordinate<Lyt>>, has_cardinal_operations<Lyt>,
has_elevation_operations<Lyt>>,
std::void_t<typename Lyt::base_type, aspect_ratio<Lyt>, coordinate<Lyt>, typename Lyt::storage,
decltype(Lyt::max_fanin_size), decltype(Lyt::min_fanin_size), decltype(std::declval<Lyt>().x()),
decltype(std::declval<Lyt>().y()), decltype(std::declval<Lyt>().z()),
decltype(std::declval<Lyt>().area()), decltype(std::declval<Lyt>().resize(aspect_ratio<Lyt>()))>>>
std::enable_if_t<std::conjunction_v<std::is_constructible<aspect_ratio<Lyt>, coordinate<Lyt>>,
has_cardinal_operations<Lyt>, has_elevation_operations<Lyt>>,
std::void_t<typename Lyt::base_type, aspect_ratio<Lyt>, coordinate<Lyt>, typename Lyt::storage,
decltype(Lyt::max_fanin_size), decltype(Lyt::min_fanin_size),
decltype(std::declval<Lyt>().x()), decltype(std::declval<Lyt>().y()),
decltype(std::declval<Lyt>().z()), decltype(std::declval<Lyt>().area())>>>
: std::true_type
{};

Expand Down
63 changes: 63 additions & 0 deletions include/fiction/utils/execution_utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// Created by marcel on 15.01.23.
//

#ifndef FICTION_EXECUTION_UTILS_HPP
#define FICTION_EXECUTION_UTILS_HPP

// if the library supports parallel algorithms and execution policies
#if (__cpp_lib_parallel_algorithm || __cpp_lib_execution) && (!__GNUC__ || __GNUC__ > 9) // GCC Version >= 9

#include <execution> // include execution policies only if the C++ library supports them

// define the execution policies as macros

/**
* Sequential execution policy for STL algorithms.
*
* @note This macro automatically detetcs whether the C++ library supports execution policies and whether the compiler
* is able to compile them. If not, the macro defaults to nothing.
*/
#define FICTION_EXECUTION_POLICY_SEQ std::execution::seq,
/**
* Parallel execution policy for STL algorithms.
*
* @note This macro automatically detetcs whether the C++ library supports execution policies and whether the compiler
* is able to compile them. If not, the macro defaults to nothing.
*/
#define FICTION_EXECUTION_POLICY_PAR std::execution::par,
/**
* Parallel unsequenced execution policy for STL algorithms.
*
* @note This macro automatically detetcs whether the C++ library supports execution policies and whether the compiler
* is able to compile them. If not, the macro defaults to nothing.
*/
#define FICTION_EXECUTION_POLICY_PAR_UNSEQ std::execution::par_unseq,

#else

/**
* Sequential execution policy for STL algorithms.
*
* @note This macro automatically detetcs whether the C++ library supports execution policies and whether the compiler
* is able to compile them. If not, the macro defaults to nothing.
*/
#define FICTION_EXECUTION_POLICY_SEQ
/**
* Parallel execution policy for STL algorithms.
*
* @note This macro automatically detetcs whether the C++ library supports execution policies and whether the compiler
* is able to compile them. If not, the macro defaults to nothing.
*/
#define FICTION_EXECUTION_POLICY_PAR
/**
* Parallel unsequenced execution policy for STL algorithms.
*
* @note This macro automatically detetcs whether the C++ library supports execution policies and whether the compiler
* is able to compile them. If not, the macro defaults to nothing.
*/
#define FICTION_EXECUTION_POLICY_PAR_UNSEQ

#endif

#endif // FICTION_EXECUTION_UTILS_HPP
Loading