diff --git a/CMakeLists.txt b/CMakeLists.txt index 547d93a..b8b9aa4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,6 +137,8 @@ else() message(STATUS "Benchmarks will not be built (BUILD_BENCHMARK is OFF).") endif() +add_subdirectory(apps) + # --- Documentation Generation --- # Add a custom target to build Doxygen documentation find_package(Doxygen QUIET) diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt new file mode 100644 index 0000000..0ef9e8f --- /dev/null +++ b/apps/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright 2026 Raphael S. Steiner +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.20) + +# Create an empty list for the applications +set( App_list ) + +# Function to create an application +macro( _add_App name ) + # Add the application to the list + list( APPEND App_list ${name} ) + + # Create the Benchmark + add_executable(${name} ${name}.cpp) + target_link_libraries(${name} PRIVATE SPAPQueue ProjectExecutableFlags) + target_include_directories(${name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) +endmacro() + +# Adding Applications +_add_App( QNetworkDotWriter ) + +# Custom target to compile all the Benchmarks +add_custom_target( build_Apps DEPENDS ${App_list} ) \ No newline at end of file diff --git a/apps/QNetworkDotWriter.cpp b/apps/QNetworkDotWriter.cpp new file mode 100644 index 0000000..c653f29 --- /dev/null +++ b/apps/QNetworkDotWriter.cpp @@ -0,0 +1,41 @@ +/* +Copyright 2026 Raphael S. Steiner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +@author Raphael S. Steiner +*/ + +#include + +#include "ParallelPriorityQueue/Drawing/QNetworkToDot.hpp" +#include "ParallelPriorityQueue/GraphExamples/FullyConnectedGraph.hpp" +#include "ParallelPriorityQueue/GraphExamples/LineGraph.hpp" +#include "ParallelPriorityQueue/GraphExamples/PetersenGraph.hpp" + +using namespace spapq; + +int main(int argc, char *argv[]) { + if (argc < 2) { + std::cout << "Usage:\n" + << " " << argv[0] << " \n"; + return 1; + } + + std::ofstream os(argv[1]); + + constexpr auto netw = LINE_GRAPH(FULLY_CONNECTED_GRAPH<3U>()); + + QNetworkToDot(netw, os); + return 0; +} diff --git a/include/ParallelPriorityQueue/Drawing/QNetworkToDot.hpp b/include/ParallelPriorityQueue/Drawing/QNetworkToDot.hpp new file mode 100644 index 0000000..c8107c1 --- /dev/null +++ b/include/ParallelPriorityQueue/Drawing/QNetworkToDot.hpp @@ -0,0 +1,116 @@ +/* +Copyright 2026 Raphael S. Steiner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +@author Raphael S. Steiner +*/ + +#pragma once + +#include +#include +#include +#include + +#include "ParallelPriorityQueue/Drawing/WorkLoadDistribution.hpp" +#include "ParallelPriorityQueue/QNetwork.hpp" + +namespace spapq { + +template +void globalQNetworkInfo(const QNetwork &netw, std::ostream &os) { + os << " NetworkInfo [\n"; + os << " label=<\n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << "
QNetwork Information
\n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << "
Number of Workers" << netw.numWorkers_ << "
Number of Channels" << netw.numChannels_ << "
Buffer Size" << netw.channelBufferSize_ << "
Enqueue Frequency" << netw.enqueueFrequency_ << "
Max. Push Attempts" << netw.maxPushAttempts_ << "
\n"; + os << " >\n"; + os << " ];\n\n"; +} + +template +void QNetworkWorkerInfo(const QNetwork &netw, const std::array &loads, const std::size_t worker, std::ostream &os) { + os << " Worker" << worker << " [\n"; + os << " label=<\n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << "
Worker " << worker << "
Core" << netw.logicalCore_[worker] << "
Load" << loads[worker] * 100.0 << "%
\n"; + os << " >\n"; + os << " ];\n\n"; +} + +template +void QNetworkChannelInfo(const QNetwork &netw, const std::size_t edge, std::ostream &os) { + const std::size_t source = netw.source(edge); + const std::size_t target = netw.target(edge); + + os << " Worker" << source << " -> Worker" << target << " [\n"; + os << " label=<\n"; + os << " \n"; + os << " \n"; + os << " \n"; + os << "
Mult." << netw.multiplicities_[edge] << "
Batch" << netw.batchSize_[edge] << "
\n"; + os << " >\n"; + os << " ];\n"; +} + +template +void QNetworkToDot(const QNetwork &netw, std::ostream &os) { + const std::array loads = workLoadDistribution(netw); + + // Header + os << std::fixed << std::setprecision(1); + os << "digraph QNetwork{\n"; + os << " layout=\"fdp\";\n"; + os << " len=\"2.0\";\n"; + os << " sep=\"+45\";\n"; + os << " dim=3;\n\n"; + + os << " node [shape=plaintext;]\n"; + os << " edge [shape=plaintext; arrowhead=\"vee\"; fontsize=\"8\";]\n"; + os << " rankdir=LR\n\n"; + + // Global Network Info + globalQNetworkInfo(netw, os); + + // Actual Network + // Workers (Vertices) + for (std::size_t worker = 0U; worker < workers; ++worker) { + QNetworkWorkerInfo(netw, loads, worker, os); + } + + // Channels (Edges) + for (std::size_t edge = 0U; edge < channels; ++edge) { + QNetworkChannelInfo(netw, edge, os); + } + + os << "}\n"; +} + +template +void QNetworkToDot(const QNetwork &netw, const std::string &filename) { + std::ofstream os(filename); + WriteTxt(netw, os); +} + +} // namespace spapq diff --git a/include/ParallelPriorityQueue/Drawing/WorkLoadDistribution.hpp b/include/ParallelPriorityQueue/Drawing/WorkLoadDistribution.hpp new file mode 100644 index 0000000..7dfa4c6 --- /dev/null +++ b/include/ParallelPriorityQueue/Drawing/WorkLoadDistribution.hpp @@ -0,0 +1,86 @@ +/* +Copyright 2026 Raphael S. Steiner + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +@author Raphael S. Steiner +*/ + +#pragma once + +#include +#include + +#include "ParallelPriorityQueue/QNetwork.hpp" + +namespace spapq { + +template +std::array workLoadDistribution(const QNetwork &netw, const double epsilon = 1e-10) { + assert(netw.isValidQNetwork()); + assert(netw.isStronglyConnected()); + + std::array weights; + for (double &val : weights) { val = 0.0; } + for (std::size_t worker = 0U; worker < workers; ++worker) { + std::size_t totalWeight = 0U; + + std::size_t batchSizeGCD = 0U; + for (std::size_t edge = netw.vertexPointer_[worker]; edge < netw.vertexPointer_[worker + 1]; ++edge) { + batchSizeGCD = std::gcd(batchSizeGCD, netw.batchSize_[edge]); + } + + for (std::size_t edge = netw.vertexPointer_[worker]; edge < netw.vertexPointer_[worker + 1U]; ++edge) { + std::size_t weight = netw.multiplicities_[edge] * (netw.batchSize_[edge] / batchSizeGCD); + + totalWeight += weight; + weights[edge] = static_cast(weight); + } + + for (std::size_t edge = netw.vertexPointer_[worker]; edge < netw.vertexPointer_[worker + 1U]; ++edge) { + weights[edge] /= static_cast(totalWeight); + } + } + + std::array targets; + for (std::size_t edge = 0U; edge < targets.size(); ++edge) { targets[edge] = netw.target(edge); } + + std::array dist; + for (double &val : dist) { val = 1.0 / static_cast(workers); } + + std::array distAfterIteration; + bool loop = true; + while (loop) { + loop = false; + for (double &val : distAfterIteration) { val = 0.0; } + + for (std::size_t worker = 0U; worker < workers; ++worker) { + for (std::size_t edge = netw.vertexPointer_[worker]; edge < netw.vertexPointer_[worker + 1U]; ++edge) { + distAfterIteration[targets[edge]] += dist[worker] * weights[edge]; + } + } + + for (std::size_t worker = 0U; worker < workers; ++worker) { + if (std::abs(dist[worker] - distAfterIteration[worker]) > epsilon) { + loop = true; + break; + } + } + + std::swap(dist, distAfterIteration); + } + + return dist; +} + +} // end namespace spapq diff --git a/tests/QNetwork.cpp b/tests/QNetwork.cpp index c12da8f..b67379b 100644 --- a/tests/QNetwork.cpp +++ b/tests/QNetwork.cpp @@ -1,5 +1,5 @@ /* -Copyright 2025 Raphael S. Steiner +Copyright 2025, 2026 Raphael S. Steiner Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ limitations under the License. #include +#include "ParallelPriorityQueue/Drawing/WorkLoadDistribution.hpp" #include "ParallelPriorityQueue/GraphExamples/FullyConnectedGraph.hpp" #include "ParallelPriorityQueue/GraphExamples/LineGraph.hpp" #include "ParallelPriorityQueue/GraphExamples/PetersenGraph.hpp" @@ -29,8 +30,7 @@ limitations under the License. using namespace spapq; TEST(QNetworkTest, Constructors1) { - constexpr QNetwork<4, 4> netw( - {0, 1, 2, 3, 4}, {1, 2, 3, 0}, {11, 12, 13, 14}, {10, 9, 8, 7}, {2, 4, 6, 8}); + constexpr QNetwork<4, 4> netw({0, 1, 2, 3, 4}, {1, 2, 3, 0}, {11, 12, 13, 14}, {10, 9, 8, 7}, {2, 4, 6, 8}); EXPECT_EQ(netw.numWorkers_, 4); EXPECT_EQ(netw.numChannels_, 4); for (std::size_t i = 0; i < 5; ++i) { EXPECT_EQ(netw.vertexPointer_[i], i); } @@ -396,8 +396,7 @@ TEST(QNetworkTest, Connectivity) { TEST(QNetworkTest, SrcTgt) { constexpr QNetwork<10, 30> netw1 = PETERSEN_GRAPH; for (std::size_t worker = 0U; worker < netw1.numWorkers_; ++worker) { - for (std::size_t channel = netw1.vertexPointer_[worker]; channel < netw1.vertexPointer_[worker + 1U]; - ++channel) { + for (std::size_t channel = netw1.vertexPointer_[worker]; channel < netw1.vertexPointer_[worker + 1U]; ++channel) { EXPECT_EQ(netw1.source(channel), worker); EXPECT_EQ(netw1.target(channel), netw1.edgeTargets_[channel]); } @@ -406,8 +405,7 @@ TEST(QNetworkTest, SrcTgt) { constexpr auto netw2 = FULLY_CONNECTED_GRAPH<9U>(); for (std::size_t worker = 0U; worker < netw2.numWorkers_; ++worker) { - for (std::size_t channel = netw2.vertexPointer_[worker]; channel < netw2.vertexPointer_[worker + 1U]; - ++channel) { + for (std::size_t channel = netw2.vertexPointer_[worker]; channel < netw2.vertexPointer_[worker + 1U]; ++channel) { EXPECT_EQ(netw2.source(channel), worker); if (channel % 9U == 0U) { EXPECT_EQ(netw2.target(channel), worker); @@ -423,3 +421,51 @@ TEST(QNetworkTest, PrintQNetwork) { constexpr QNetwork<10, 30> netw = PETERSEN_GRAPH; netw.printQNetwork(); } + +TEST(QNetworkTest, WorkLoads) { + constexpr auto netw1 = FULLY_CONNECTED_GRAPH<1U>(); + std::array expectedLoad1; + for (double &val : expectedLoad1) { val = 1.0 / static_cast(netw1.numWorkers_); } + + const auto load1 = workLoadDistribution(netw1); + for (std::size_t worker = 0U; worker < netw1.numWorkers_; ++worker) { + EXPECT_DOUBLE_EQ(load1[worker], expectedLoad1[worker]); + } + + constexpr auto netw3 = FULLY_CONNECTED_GRAPH<3U>(); + std::array expectedLoad3; + for (double &val : expectedLoad3) { val = 1.0 / static_cast(netw3.numWorkers_); } + + const auto load3 = workLoadDistribution(netw3); + for (std::size_t worker = 0U; worker < netw3.numWorkers_; ++worker) { + EXPECT_DOUBLE_EQ(load3[worker], expectedLoad3[worker]); + } + + constexpr auto netw12 = FULLY_CONNECTED_GRAPH<12U>(); + std::array expectedLoad12; + for (double &val : expectedLoad12) { val = 1.0 / static_cast(netw12.numWorkers_); } + + const auto load12 = workLoadDistribution(netw12); + for (std::size_t worker = 0U; worker < netw12.numWorkers_; ++worker) { + EXPECT_DOUBLE_EQ(load12[worker], expectedLoad12[worker]); + } + + constexpr auto netw10 = PETERSEN_GRAPH; + std::array expectedLoad10; + for (double &val : expectedLoad10) { val = 1.0 / static_cast(netw10.numWorkers_); } + + const auto load10 = workLoadDistribution(netw10); + for (std::size_t worker = 0U; worker < netw10.numWorkers_; ++worker) { + EXPECT_DOUBLE_EQ(load10[worker], expectedLoad10[worker]); + } + + constexpr auto netw = QNetwork<4U, 10U>( + {0, 2, 4, 7, 10}, {0, 1, 2, 3, 0, 2, 3, 0, 2, 3}, {0, 1, 2, 3}, {1, 1, 1, 1, 1, 2, 2, 1, 2, 2} + ); + std::array expectedLoad = {1.0 / 4.0, 1.0 / 8.0, 5.0 / 16.0, 5.0 / 16.0}; + + const auto load = workLoadDistribution(netw); + for (std::size_t worker = 0U; worker < netw.numWorkers_; ++worker) { + EXPECT_NEAR(load[worker], expectedLoad[worker], 1e-8); + } +}