Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
35 changes: 35 additions & 0 deletions apps/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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} )
41 changes: 41 additions & 0 deletions apps/QNetworkDotWriter.cpp
Original file line number Diff line number Diff line change
@@ -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 <iostream>

#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] << " <output file>\n";
return 1;
}

std::ofstream os(argv[1]);

constexpr auto netw = LINE_GRAPH(FULLY_CONNECTED_GRAPH<3U>());

QNetworkToDot(netw, os);
return 0;
}
116 changes: 116 additions & 0 deletions include/ParallelPriorityQueue/Drawing/QNetworkToDot.hpp
Original file line number Diff line number Diff line change
@@ -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 <iomanip>
#include <ios>
#include <fstream>
#include <string>

#include "ParallelPriorityQueue/Drawing/WorkLoadDistribution.hpp"
#include "ParallelPriorityQueue/QNetwork.hpp"

namespace spapq {

template <std::size_t workers, std::size_t channels>
void globalQNetworkInfo(const QNetwork<workers, channels> &netw, std::ostream &os) {
os << " NetworkInfo [\n";
os << " label=<\n";
os << " <table border='3' cellborder='1' cellspacing='4'>\n";
os << " <tr><td align=\"center\">QNetwork Information</td></tr>\n";
os << " <tr><td align=\"center\"><table border='0' cellborder='0' cellspacing='1'>\n";
os << " <tr><td align=\"left\">Number of Workers</td><td align=\"right\">" << netw.numWorkers_ << "</td></tr>\n";
os << " <tr><td align=\"left\">Number of Channels</td><td align=\"right\">" << netw.numChannels_ << "</td></tr>\n";
os << " <tr><td align=\"left\">Buffer Size</td><td align=\"right\">" << netw.channelBufferSize_ << "</td></tr>\n";
os << " <tr><td align=\"left\">Enqueue Frequency</td><td align=\"right\">" << netw.enqueueFrequency_ << "</td></tr>\n";
os << " <tr><td align=\"left\">Max. Push Attempts</td><td align=\"right\">" << netw.maxPushAttempts_ << "</td></tr>\n";
os << " </table></td></tr>\n";
os << " </table>\n";
os << " >\n";
os << " ];\n\n";
}

template <std::size_t workers, std::size_t channels>
void QNetworkWorkerInfo(const QNetwork<workers, channels> &netw, const std::array<double, workers> &loads, const std::size_t worker, std::ostream &os) {
os << " Worker" << worker << " [\n";
os << " label=<\n";
os << " <table border='2' cellborder='1' cellspacing='2'>\n";
os << " <tr><td align=\"center\" colspan='2'><b>Worker " << worker << "</b></td></tr>\n";
os << " <tr><td align=\"left\">Core</td><td align=\"right\">" << netw.logicalCore_[worker] << "</td></tr>\n";
os << " <tr><td align=\"left\">Load</td><td align=\"right\">" << loads[worker] * 100.0 << "%</td></tr>\n";
os << " </table>\n";
os << " >\n";
os << " ];\n\n";
}

template <std::size_t workers, std::size_t channels>
void QNetworkChannelInfo(const QNetwork<workers, channels> &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 << " <table border='0' cellborder='1' cellspacing='0'>\n";
os << " <tr><td align=\"left\">Mult.</td><td align=\"right\">" << netw.multiplicities_[edge] << "</td></tr>\n";
os << " <tr><td align=\"left\">Batch</td><td align=\"right\">" << netw.batchSize_[edge] << "</td></tr>\n";
os << " </table>\n";
os << " >\n";
os << " ];\n";
}

template <std::size_t workers, std::size_t channels>
void QNetworkToDot(const QNetwork<workers, channels> &netw, std::ostream &os) {
const std::array<double, workers> 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 <std::size_t workers, std::size_t channels>
void QNetworkToDot(const QNetwork<workers, channels> &netw, const std::string &filename) {
std::ofstream os(filename);
WriteTxt(netw, os);
}

} // namespace spapq
86 changes: 86 additions & 0 deletions include/ParallelPriorityQueue/Drawing/WorkLoadDistribution.hpp
Original file line number Diff line number Diff line change
@@ -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 <cassert>
#include <cmath>

#include "ParallelPriorityQueue/QNetwork.hpp"

namespace spapq {

template <std::size_t workers, std::size_t channels>
std::array<double, workers> workLoadDistribution(const QNetwork<workers, channels> &netw, const double epsilon = 1e-10) {
assert(netw.isValidQNetwork());
assert(netw.isStronglyConnected());

std::array<double, channels> 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<double>(weight);
}

for (std::size_t edge = netw.vertexPointer_[worker]; edge < netw.vertexPointer_[worker + 1U]; ++edge) {
weights[edge] /= static_cast<double>(totalWeight);
}
}

std::array<std::size_t, channels> targets;
for (std::size_t edge = 0U; edge < targets.size(); ++edge) { targets[edge] = netw.target(edge); }

std::array<double, workers> dist;
for (double &val : dist) { val = 1.0 / static_cast<double>(workers); }

std::array<double, workers> 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
60 changes: 53 additions & 7 deletions tests/QNetwork.cpp
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -22,15 +22,15 @@ limitations under the License.

#include <initializer_list>

#include "ParallelPriorityQueue/Drawing/WorkLoadDistribution.hpp"
#include "ParallelPriorityQueue/GraphExamples/FullyConnectedGraph.hpp"
#include "ParallelPriorityQueue/GraphExamples/LineGraph.hpp"
#include "ParallelPriorityQueue/GraphExamples/PetersenGraph.hpp"

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); }
Expand Down Expand Up @@ -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]);
}
Expand All @@ -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);
Expand All @@ -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<double, netw1.numWorkers_> expectedLoad1;
for (double &val : expectedLoad1) { val = 1.0 / static_cast<double>(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<double, netw3.numWorkers_> expectedLoad3;
for (double &val : expectedLoad3) { val = 1.0 / static_cast<double>(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<double, netw12.numWorkers_> expectedLoad12;
for (double &val : expectedLoad12) { val = 1.0 / static_cast<double>(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<double, netw10.numWorkers_> expectedLoad10;
for (double &val : expectedLoad10) { val = 1.0 / static_cast<double>(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<double, netw.numWorkers_> 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);
}
}