diff --git a/.gitignore b/.gitignore index 4a8fa099f9fcfd8230b1c9bf82c12c7c5d53ca46..aabcf599eafcc64dae2b401ca390522780742104 100644 --- a/.gitignore +++ b/.gitignore @@ -290,3 +290,7 @@ pyrightconfig.json # Hidden files .* + +# Downloaded meson subprojects +subprojects/* +!subprojects/*.wrap diff --git a/meson.build b/meson.build index e6d4f65bfb2fe773b8030bed43d8270d201c3007..d68803dc7128f061393141d52315e013e5480e3c 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,7 @@ project( 'praktikum', 'c', + 'cpp', 'fortran', default_options: [ 'warning_level=3', diff --git a/src/benchmarks/zellularautomat/cpp/main.cpp b/src/benchmarks/zellularautomat/cpp/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b7dc60c71f224543a1b46242aa0579639005d2e4 --- /dev/null +++ b/src/benchmarks/zellularautomat/cpp/main.cpp @@ -0,0 +1,164 @@ +/* + * simulate a cellular automaton with periodic boundaries (torus-like) + * OpenMP version + * + * (c) 2024 Dorian Stoll (C++ / Eigen port) + * (c) 2016 Steffen Christgau (C99 port, modularization, parallelization) + * (c) 1996,1997 Peter Sanders, Ingo Boesnach (original source) + * + * command line arguments: + * #1: Number of lines + * #2: Number of iterations to be simulated + * + */ + +/* + * Eigen defaults to using column-major order for its container types. + * + * However, using it like that would make the lines parameter correspond to Eigens .cols() function + * instead of the more fitting .rows(). To make the code more obvious, we change the default. + */ +#define EIGEN_DEFAULT_TO_ROW_MAJOR + +#include <Eigen/Eigen> + +#include <cassert> +#include <cstdint> +#include <cstdlib> +#include <iostream> +#include <stdexcept> + +class Automat { +private: + /* "ADT" State */ + using cell_state_t = uint8_t; + + using Field = Eigen::Matrix<cell_state_t, Eigen::Dynamic, Eigen::Dynamic>; + +private: + /* horizontal size of the configuration */ + static constexpr Eigen::Index XSIZE = 1024; + static constexpr Eigen::Index LINE_SIZE = XSIZE + 2; + + /* annealing rule from ChoDro96 page 34 + * the table is used to map the number of nonzero + * states in the neighborhood to the new state + */ + static constexpr std::array<cell_state_t, 10> ANNEAL {0, 0, 0, 0, 1, 0, 1, 1, 1, 1}; + +private: + int m_lines; + + Field m_from {}; + Field m_to {}; + +public: + Automat(const int lines) : m_lines {lines} + { + if (lines <= 0) + throw std::invalid_argument {"There must be at least one line"}; + + m_from.resize(lines + 2, LINE_SIZE); + m_to.resize(lines + 2, LINE_SIZE); + } + + void run(const int its) + { + this->init_config(); + + for (int i = 0; i < its; i++) { + this->boundary(); + this->simulate(); + + std::swap(m_from, m_to); + } + } + + void report() + { + auto block = m_from(Eigen::seqN(1, m_lines), Eigen::all); + + for (Eigen::Index y = 0; y < block.rows(); y++) { + block(y, 0) = 0; + block(y, block.cols() - 1) = 0; + } + + const auto start = block.data(); + const auto end = start + (block.size() * sizeof(*start)); + + std::copy(start, end, std::ostreambuf_iterator(std::cout)); + } + +private: + /* random starting configuration */ + void init_config() + { + srand(424243); + + for (Eigen::Index y = 1; y <= m_to.rows() - 2; y++) { + for (Eigen::Index x = 1; x <= m_from.cols() - 2; x++) + m_from(y, x) = rand() % 2; + } + } + + /* treat torus like boundary conditions */ + void boundary() + { +#pragma omp sections + { +#pragma omp section +#pragma omp parallel for + for (Eigen::Index y = 0; y < m_from.rows(); y++) { + /* copy rightmost column to the buffer column 0 */ + m_from(y, 0) = m_from(y, m_from.cols() - 2); + + /* copy leftmost column to the buffer column XSIZE + 1 */ + m_from(y, m_from.cols() - 1) = m_from(y, 1); + } + +#pragma omp section +#pragma omp parallel for + for (Eigen::Index x = 0; x < m_from.cols(); x++) { + /* copy bottommost row to buffer row 0 */ + m_from(0, x) = m_from(m_from.rows() - 2, x); + + /* copy topmost row to buffer row lines + 1 */ + m_from(m_from.rows() - 1, x) = m_from(1, x); + } + } + } + + /* make one simulation iteration with lines lines. + * old configuration is in from, new one is written to to. + */ + void simulate() + { +#pragma omp parallel for + for (Eigen::Index y = 1; y <= m_from.rows() - 2; y++) { + for (Eigen::Index x = 1; x <= m_from.cols() - 2; x++) + m_to(y, x) = transition(m_from, x, y); + } + } + + /* a: reference to matrix; x,y: coordinates; result: n-th element of anneal, + where n is the number of neighbors */ + cell_state_t transition(const Field &a, const Eigen::Index x, const Eigen::Index y) const + { + return ANNEAL[a(Eigen::seq(y - 1, y + 1), Eigen::seq(x - 1, x + 1)).sum()]; + } +}; + +int main(int argc, char **argv) +{ + assert(argc == 3); + + int lines = atoi(argv[1]); + int its = atoi(argv[2]); + + Automat ca {lines}; + + ca.run(its); + ca.report(); + + return EXIT_SUCCESS; +} diff --git a/src/benchmarks/zellularautomat/cpp/meson.build b/src/benchmarks/zellularautomat/cpp/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..772dffb91af86180f6ac3e5cb336ab38e5808524 --- /dev/null +++ b/src/benchmarks/zellularautomat/cpp/meson.build @@ -0,0 +1,31 @@ +sources = [ + 'main.cpp', +] + +# Build wrapped dependencies as static libraries and disable warnings +dependency_options = [ + 'default_library=static', + 'warning_level=0', + 'werror=false', +] + +dependencies = [ + dependency('openmp'), + dependency( + 'eigen3', + fallback: ['eigen', 'eigen_dep'], + default_options: dependency_options, + include_type: 'system', + ), +] + +options = [ + 'cpp_std=c++17', +] + +executable( + 'zellularautomat-cpp', + sources, + dependencies: dependencies, + override_options: options, +) diff --git a/src/benchmarks/zellularautomat/meson.build b/src/benchmarks/zellularautomat/meson.build index c957e89f62d3a1a6f452b225bafc3e970cb764f0..3a509a0d52d0c0724ee0b2180e39f37c8663d6c8 100644 --- a/src/benchmarks/zellularautomat/meson.build +++ b/src/benchmarks/zellularautomat/meson.build @@ -1,2 +1,3 @@ subdir('c') +subdir('cpp') subdir('fortran') diff --git a/subprojects/eigen.wrap b/subprojects/eigen.wrap new file mode 100644 index 0000000000000000000000000000000000000000..becc4767c7603d3ae5d6a0b2ea6437ee5857fde1 --- /dev/null +++ b/subprojects/eigen.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = eigen-3.4.0 +source_url = https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.tar.bz2 +source_filename = eigen-3.4.0.tar.bz2 +source_hash = b4c198460eba6f28d34894e3a5710998818515104d6e74e5cc331ce31e46e626 +patch_filename = eigen_3.4.0-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/eigen_3.4.0-2/get_patch +patch_hash = cb764fd9fec02d94aaa2ec673d473793c0d05da4f4154c142f76ef923ea68178 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/eigen_3.4.0-2/eigen-3.4.0.tar.bz2 +wrapdb_version = 3.4.0-2 + +[provide] +eigen3 = eigen_dep