diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 8372cae..d1e69dd 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -101,7 +101,7 @@ jobs: make docker - name: Run Tests - timeout-minutes: 5 + timeout-minutes: 15 run: | CONTAINER_CMD="make run-tests" \ IMAGE_NAME="${{ env.IMAGE_NAME }}" \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 70349fa..aaad89e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,10 @@ set(BUILD_TESTING "${BUILD_TESTING_SAVED}") set(PHMAP_DIRECTORY ${Galois_SOURCE_DIR}/external/parallel-hashmap) set(PCG_DIRECTORY ${Galois_SOURCE_DIR}/external/pcg-cpp/include) +# wf4 target +add_library(wf4-galois) +add_library(wf4-galois::wf4-galois ALIAS wf4-galois) + add_subdirectory(microbench) add_subdirectory(scripts) add_subdirectory(wf4) diff --git a/cmake/GaloisCompilerOptions.cmake b/cmake/GaloisCompilerOptions.cmake index 3f6d7e5..4b96143 100644 --- a/cmake/GaloisCompilerOptions.cmake +++ b/cmake/GaloisCompilerOptions.cmake @@ -3,8 +3,6 @@ include_guard() -option(GALOIS_WERROR "Make all warnings into errors." ON) - # Default compiler options for targets function(galois_compiler_options TARGET) set_target_properties(${TARGET} @@ -25,12 +23,12 @@ endfunction() function(galois_compiler_warnings TARGET) target_compile_options(${TARGET} PRIVATE $<$:/W4 /WX> - $<$>:-Wall -Wextra -Wpedantic $<$:-Werror>> + $<$>:-Wall -Wextra -Wpedantic> ) endfunction() function(galois_add_executable TARGET) add_executable(${TARGET} ${ARGN}) - #galois_compiler_warnings(${TARGET}) + galois_compiler_warnings(${TARGET}) galois_compiler_options(${TARGET}) endfunction() diff --git a/cmake/GaloisTesting.cmake b/cmake/GaloisTesting.cmake index 161c907..5eb3177 100644 --- a/cmake/GaloisTesting.cmake +++ b/cmake/GaloisTesting.cmake @@ -15,7 +15,7 @@ FetchContent_MakeAvailable(googletest) include(GoogleTest) -include(${PROJECT_SOURCE_DIR}/cmake/GaloisCompilerOptions.cmake) +include(${graph-log-sketch_SOURCE_DIR}/cmake/GaloisCompilerOptions.cmake) # # options @@ -25,17 +25,19 @@ option(GALOIS_TEST_DISCOVERY "Enable test discovery for use with ctest." ON) if (${GALOIS_TEST_DISCOVERY}) set(GALOIS_TEST_DISCOVERY_TIMEOUT "" CACHE STRING "GoogleTest test discover timeout in seconds") endif () +set(GALOIS_TEST_DISCOVERY_TIMEOUT "120" CACHE STRING "Test timeout (secs)") # # functions # # Adds a source file as a GoogleTest based test -function(galois_add_test TARGET SOURCEFILE) - add_executable(${TARGET} ${SOURCEFILE}) +function(galois_add_test TARGET LIBRARY SOURCEFILE) + galois_add_executable(${TARGET} ${SOURCEFILE}) target_link_libraries(${TARGET} PRIVATE GTest::gtest_main + ${LIBRARY} ) galois_compiler_options(${TARGET}) galois_compiler_warnings(${TARGET}) @@ -51,3 +53,29 @@ function(galois_add_test TARGET SOURCEFILE) endif () endif () endfunction() + +set(NUM_PROCS 2) + +# Adds a source file as a GoogleTest based test using mpirun to launch it +function(galois_add_driver_test TARGET LIBRARY SOURCEFILE) + galois_add_executable(${TARGET} ${graph-log-sketch_SOURCE_DIR}/test/test_driver.cpp ${SOURCEFILE}) + target_link_libraries(${TARGET} + PRIVATE + GTest::gtest + ${LIBRARY} + ) + galois_compiler_options(${TARGET}) + galois_compiler_warnings(${TARGET}) + set_property(TARGET ${TARGET} PROPERTY CROSSCOMPILING_EMULATOR 'mpirun -np ${NUM_PROCS}') + + if (${GALOIS_TEST_DISCOVERY}) + if (NOT DEFINED ${GALOIS_TEST_DISCOVERY_TIMEOUT}) + # use default test discovery timeout + gtest_discover_tests(${TARGET}) + else () + gtest_discover_tests(${TARGET} + DISCOVERY_TIMEOUT ${GALOIS_TEST_DISCOVERY_TIMEOUT} + ) + endif () + endif () +endfunction() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0cdc24b..506e5d3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,14 +1,13 @@ # SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2023. University of Texas at Austin. All rights reserved. -galois_add_test(bfs-test test-algo-bfs.cpp) -target_link_libraries(bfs-test PRIVATE Galois::shmem) +galois_add_test(bfs-test Galois::shmem test-algo-bfs.cpp) target_include_directories(bfs-test PRIVATE ${graph-log-sketch_SOURCE_DIR}/include) -galois_add_test(tc-test test-algo-tc.cpp) -target_link_libraries(tc-test PRIVATE Galois::shmem) +galois_add_test(tc-test Galois::shmem test-algo-tc.cpp) target_include_directories(tc-test PRIVATE ${graph-log-sketch_SOURCE_DIR}/include) -galois_add_test(bc-test test-algo-bc.cpp) -target_link_libraries(bc-test PRIVATE Galois::shmem) +galois_add_test(bc-test Galois::shmem test-algo-bc.cpp) target_include_directories(bc-test PRIVATE ${graph-log-sketch_SOURCE_DIR}/include) + +add_subdirectory(wf4) diff --git a/test/test_driver.cpp b/test/test_driver.cpp new file mode 100644 index 0000000..1948d2c --- /dev/null +++ b/test/test_driver.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: BSD-2-Clause +// Copyright (c) 2023. University of Texas at Austin. All rights reserved. + +#include + +#include +#include + +#include "galois/DistGalois.h" + +namespace { + +bool isDiscoveringTests(int argc, char** argv) { + for (int64_t i = 0; i < argc; i++) { + if (std::string(argv[i]) == "--gtest_list_tests") { + return true; + } + } + return false; +} + +} // namespace + +int main(int argc, char** argv) { + bool discoveringTests = isDiscoveringTests(argc, argv); + ::testing::InitGoogleTest(&argc, argv); + galois::DistMemSys G; + auto& net = galois::runtime::getSystemNetworkInterface(); + int result = 0; + + if (net.ID == 0 || !discoveringTests) { + result = RUN_ALL_TESTS(); + } + return result; +} diff --git a/test/wf4/CMakeLists.txt b/test/wf4/CMakeLists.txt index 7f74a0e..e3a6766 100644 --- a/test/wf4/CMakeLists.txt +++ b/test/wf4/CMakeLists.txt @@ -1,10 +1,6 @@ # SPDX-License-Identifier: BSD-2-Clause # Copyright (c) 2023. University of Texas at Austin. All rights reserved. -enable_testing() - -add_executable(influencer_test influencer_test.cpp) -target_link_libraries(influencer_test GTest::gtest_main) - -include(GoogleTest) -gtest_discover_tests(influencer_test) +galois_add_driver_test(wf4_import_test wf4-galois::wf4-galois test_import.cpp) +galois_add_driver_test(wf4_influence_maximization_test wf4-galois::wf4-galois test_influence_maximization.cpp) +galois_add_driver_test(wf4_quiesce_test wf4-galois::wf4-galois test_quiesce.cpp) diff --git a/test/wf4/influencer_test.cpp b/test/wf4/influencer_test.cpp deleted file mode 100644 index 4dd156b..0000000 --- a/test/wf4/influencer_test.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: BSD-2-Clause -// Copyright (c) 2023. University of Texas at Austin. All rights reserved. - -#include - -// Demonstrate some basic assertions. -TEST(HelloTest, BasicAssertions) { - // Expect two strings not to be equal. - EXPECT_STRNE("hello", "world"); - // Expect equality. - EXPECT_EQ(7 * 6, 42); -} diff --git a/test/wf4/test_import.cpp b/test/wf4/test_import.cpp new file mode 100644 index 0000000..1d08475 --- /dev/null +++ b/test/wf4/test_import.cpp @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: BSD-2-Clause +// Copyright (c) 2023. University of Texas at Austin. All rights reserved. + +// SPDX-License-Identifier: MIT +// Copyright (c) 2023. University of Texas at Austin. All rights reserved. + +#include + +#include + +namespace { + +const char* someFile = "some_file.csv"; + +void checkParsedEdge(galois::graphs::ParsedGraphStructure + result, + wf4::FullNetworkEdge expected, + agile::workflow1::TYPES expected_inverse, + uint64_t expected_num_edges) { + EXPECT_EQ(result.isEdge, true); + EXPECT_EQ(result.edges.size(), expected_num_edges); + + wf4::FullNetworkEdge edge0 = result.edges[0]; + + EXPECT_EQ(edge0.src, expected.src); + EXPECT_EQ(edge0.dst, expected.dst); + EXPECT_EQ(edge0.type, expected.type); + EXPECT_EQ(edge0.src_type, expected.src_type); + EXPECT_EQ(edge0.dst_type, expected.dst_type); + EXPECT_FLOAT_EQ(edge0.amount_, expected.amount_); + EXPECT_EQ(edge0.topic, expected.topic); + + if (result.edges.size() >= 2) { + wf4::FullNetworkEdge edge1 = result.edges[1]; + EXPECT_EQ(edge1.type, expected_inverse); + EXPECT_NE(edge0.type, edge1.type); + EXPECT_EQ(edge0.src, edge1.dst); + EXPECT_EQ(edge0.dst, edge1.src); + EXPECT_EQ(edge0.src_type, edge1.dst_type); + EXPECT_EQ(edge0.dst_type, edge1.src_type); + EXPECT_FLOAT_EQ(edge0.amount_, edge1.amount_); + EXPECT_EQ(edge0.topic, edge1.topic); + } +} + +const uint64_t num_nodes = 19; +const uint64_t num_projected_nodes = 17; +const uint64_t num_projected_edges = + (num_projected_nodes) * (num_projected_nodes - 1); +const uint64_t num_edges = num_projected_edges + 4 * num_projected_nodes + 1; +const uint64_t projected_node_offset = num_projected_nodes; +const uint64_t projected_edge_offset = + num_projected_edges + 4 * num_projected_nodes; + +std::unique_ptr generateTestFullGraph() { + std::vector vertices(num_nodes); + std::vector> edges(num_edges); + + // Data that will be projected out + { + wf4::FullNetworkNode node(projected_node_offset + 0, + agile::workflow1::TYPES::DEVICE); + vertices[projected_node_offset + 0] = node; + wf4::FullNetworkNode node2(projected_node_offset + 1, + agile::workflow1::TYPES::PERSON); + vertices[projected_node_offset + 1] = node2; + + edges[projected_edge_offset] = galois::GenericEdge( + projected_node_offset + 1, projected_node_offset + 0, + wf4::FullNetworkEdge( + projected_node_offset + 1, projected_node_offset + 0, + agile::workflow1::TYPES::SALE, agile::workflow1::TYPES::PERSON, + agile::workflow1::TYPES::DEVICE, 1, 8486)); + } + + for (uint64_t i = 0; i < num_projected_nodes; i++) { + wf4::FullNetworkNode node(i, agile::workflow1::TYPES::PERSON); + node.sold_ = i * i; + node.bought_ = + (num_projected_nodes + i) * (num_projected_nodes - (i + 1)) / 2; + vertices[i] = node; + } + // Every node sells to every node with a global ID less than itself + // And so every node buys from every node with a global ID more than itself + // We set the edge weight to be equal to the global ID of the seller + // Vertex 0 does not sell anything + uint64_t edge_count = 0; + for (uint64_t src = 0; src < num_projected_nodes; src++) { + // will be projected out + edges[edge_count++] = galois::GenericEdge( + src, 1, + wf4::FullNetworkEdge(src, 2, agile::workflow1::TYPES::AUTHOR, + agile::workflow1::TYPES::PERSON, + agile::workflow1::TYPES::PERSON, 1, 8486)); + // will be projected out + edges[edge_count++] = galois::GenericEdge( + src, 1, + wf4::FullNetworkEdge(src, 0, agile::workflow1::TYPES::AUTHOR, + agile::workflow1::TYPES::PERSON, + agile::workflow1::TYPES::DEVICE, 1, 8486)); + for (uint64_t dst = 0; dst < src; dst++) { + edges[edge_count++] = galois::GenericEdge( + src, dst, + wf4::FullNetworkEdge(src, dst, agile::workflow1::TYPES::SALE, + agile::workflow1::TYPES::PERSON, + agile::workflow1::TYPES::PERSON, src, 8486)); + } + // will be projected out + edges[edge_count++] = galois::GenericEdge( + src, 1, + wf4::FullNetworkEdge(src, 1, agile::workflow1::TYPES::SALE, + agile::workflow1::TYPES::PERSON, + agile::workflow1::TYPES::PERSON, 1, 8487)); + for (uint64_t dst = src + 1; dst < num_projected_nodes; dst++) { + edges[edge_count++] = galois::GenericEdge( + src, dst, + wf4::FullNetworkEdge(src, dst, agile::workflow1::TYPES::PURCHASE, + agile::workflow1::TYPES::PERSON, + agile::workflow1::TYPES::PERSON, dst, 8486)); + } + // will be projected out + edges[edge_count++] = galois::GenericEdge( + src, 3, + wf4::FullNetworkEdge(src, 3, agile::workflow1::TYPES::SALE, + agile::workflow1::TYPES::PERSON, + agile::workflow1::TYPES::PERSON, 0, 8486)); + } + auto& net = galois::runtime::getSystemNetworkInterface(); + return std::unique_ptr( + new wf4::FullNetworkGraph(net.ID, net.Num, vertices, edges)); +} + +std::unique_ptr generateTestGraph(bool singleHost = false) { + std::vector vertices(num_projected_nodes); + std::vector> edges(num_projected_edges); + + for (uint64_t i = 0; i < num_projected_nodes; i++) { + wf4::NetworkNode node(i); + node.sold_ = i * i; + node.bought_ = + (num_projected_nodes + i) * (num_projected_nodes - (i + 1)) / 2; + vertices[i] = node; + } + // Every node sells to every node with a global ID less than itself + // And so every node buys from every node with a global ID more than itself + // We set the edge weight to be equal to the global ID of the seller + // Vertex 0 does not sell anything + uint64_t edge_count = 0; + for (uint64_t src = 0; src < num_projected_nodes; src++) { + for (uint64_t dst = 0; dst < src; dst++) { + edges[edge_count++] = galois::GenericEdge( + src, dst, + wf4::NetworkEdge(src, dst, src, agile::workflow1::TYPES::SALE)); + } + for (uint64_t dst = src + 1; dst < num_projected_nodes; dst++) { + edges[edge_count++] = galois::GenericEdge( + src, dst, + wf4::NetworkEdge(src, dst, dst, agile::workflow1::TYPES::PURCHASE)); + } + } + auto& net = galois::runtime::getSystemNetworkInterface(); + uint32_t host = net.ID; + uint32_t hosts = net.Num; + if (singleHost) { + host = 0; + hosts = 1; + } + return std::unique_ptr( + new wf4::NetworkGraph(host, hosts, vertices, edges)); +} + +void graphsEqual(wf4::NetworkGraph& actual, wf4::NetworkGraph& expected) { + EXPECT_EQ(actual.size(), expected.size()); + galois::DGAccumulator vertex_count; + vertex_count.reset(); + for (uint64_t i = 0; i < num_projected_nodes; i++) { + if (!actual.isOwned(i)) { + continue; + } + vertex_count += 1; + wf4::NetworkNode actual_node = actual.getData(actual.getLID(i)); + bool found = false; + for (uint64_t j = 0; j < num_projected_nodes; j++) { + wf4::NetworkNode expected_node = expected.getData(expected.getLID(j)); + if (actual_node.id == expected_node.id) { + found = true; + EXPECT_FLOAT_EQ(actual_node.bought_, expected_node.bought_); + EXPECT_FLOAT_EQ(actual_node.sold_, expected_node.sold_); + EXPECT_FLOAT_EQ(actual_node.desired_, expected_node.desired_); + std::printf( + "node token id: %lu, node global id: %lu, node local id: %u\n", i, + actual_node.id, actual.getLID(i)); + auto actualEdges = actual.edges(actual.getLID(i)).begin(); + auto expectedEdges = expected.edges(expected.getLID(j)).begin(); + EXPECT_EQ( + std::distance(actualEdges, actual.edges(actual.getLID(i)).end()), + std::distance(expectedEdges, + expected.edges(expected.getLID(j)).end())); + for (; actualEdges != actual.edges(actual.getLID(i)).end(); + actualEdges++) { + wf4::NetworkEdge actual_edge = actual.getEdgeData(*actualEdges); + wf4::NetworkEdge expected_edge = + expected.getEdgeData(*(expectedEdges++)); + EXPECT_FLOAT_EQ(actual_edge.amount_, expected_edge.amount_); + EXPECT_FLOAT_EQ(actual_edge.weight_, expected_edge.weight_); + EXPECT_EQ(actual_edge.type, expected_edge.type); + } + } + } + EXPECT_EQ(found, true); + } + EXPECT_EQ(vertex_count.reduce(), num_projected_nodes); +} + +} // namespace + +TEST(Import, Parse) { + std::vector files; + files.emplace_back(std::string(someFile)); + wf4::internal::CyberParser cyber_parser(files); + wf4::internal::SocialParser social_parser(files); + wf4::internal::UsesParser uses_parser(files); + galois::graphs::WMDParser + commercial_parser(8, files); + uint64_t half_max = std::numeric_limits::max() / 2; + + // TODO(Patrick) add node parser test + const char* invalid = + "invalid,,,1615340315424362057,1116314936447312244,,,2/11/2018,,"; + + const char* sale = "Sale,1552474,1928788,,8/21/2018,,,"; + const char* weighted_sale = "Sale,299156,458364,8486,,,,3.0366367403882406"; + const char* communication = "0,217661,172800,0,6,26890,94857,6,5,1379,1770"; + const char* friend_edge = "5,679697"; + const char* uses = "12,311784"; + + galois::graphs::ParsedGraphStructure + result; + result = commercial_parser.ParseLine(const_cast(invalid), + std::strlen(invalid)); + EXPECT_EQ(result.isEdge, false); + + result = + commercial_parser.ParseLine(const_cast(sale), std::strlen(sale)); + checkParsedEdge(result, + wf4::FullNetworkEdge(1552474UL, 1928788UL, + agile::workflow1::TYPES::SALE, + agile::workflow1::TYPES::PERSON, + agile::workflow1::TYPES::PERSON, 0, 0), + agile::workflow1::TYPES::PURCHASE, 2); + result = commercial_parser.ParseLine(const_cast(weighted_sale), + std::strlen(weighted_sale)); + checkParsedEdge(result, + wf4::FullNetworkEdge(299156UL, 458364UL, + agile::workflow1::TYPES::SALE, + agile::workflow1::TYPES::PERSON, + agile::workflow1::TYPES::PERSON, + 3.0366367403882406, 8486), + agile::workflow1::TYPES::PURCHASE, 2); + result = cyber_parser.ParseLine(const_cast(communication), + std::strlen(communication)); + checkParsedEdge(result, + wf4::FullNetworkEdge(half_max + 0UL, half_max + 217661UL, + agile::workflow1::TYPES::COMMUNICATION, + agile::workflow1::TYPES::DEVICE, + agile::workflow1::TYPES::DEVICE, 0, 0), + agile::workflow1::TYPES::NONE, 1); + result = social_parser.ParseLine(const_cast(friend_edge), + std::strlen(friend_edge)); + checkParsedEdge(result, + wf4::FullNetworkEdge(5UL, 679697UL, + agile::workflow1::TYPES::FRIEND, + agile::workflow1::TYPES::PERSON, + agile::workflow1::TYPES::PERSON, 0, 0), + agile::workflow1::TYPES::NONE, 1); + result = uses_parser.ParseLine(const_cast(uses), std::strlen(uses)); + checkParsedEdge(result, + wf4::FullNetworkEdge(12UL, half_max + 311784UL, + agile::workflow1::TYPES::USES, + agile::workflow1::TYPES::PERSON, + agile::workflow1::TYPES::DEVICE, 0, 0), + agile::workflow1::TYPES::NONE, 1); +} + +TEST(Import, GeneratedGraph) { + std::unique_ptr test_graph_ptr = generateTestGraph(); + wf4::NetworkGraph& test_graph = *test_graph_ptr; + galois::DGAccumulator vertex_count; + galois::DGAccumulator edge_count; + vertex_count.reset(); + edge_count.reset(); + for (wf4::NetworkGraph::GraphNode node : test_graph.masterNodesRange()) { + vertex_count += 1; + wf4::NetworkNode node_data = test_graph.getData(node); + double bought = node_data.bought_; + EXPECT_EQ(test_graph.getGID(node), node_data.id); + EXPECT_EQ(bought, (num_projected_nodes + node_data.id) * + (num_projected_nodes - (node_data.id + 1)) / 2); + for (const auto& edge : test_graph.edges(node)) { + edge_count += 1; + wf4::NetworkEdge edge_data = test_graph.getEdgeData(edge); + uint64_t dst_id = test_graph.getGID(test_graph.getEdgeDst(edge)); + EXPECT_NE(dst_id, node_data.id); + EXPECT_EQ(dst_id < node_data.id, + edge_data.type == agile::workflow1::TYPES::SALE); + EXPECT_EQ(dst_id > node_data.id, + edge_data.type == agile::workflow1::TYPES::PURCHASE); + if (edge_data.type == agile::workflow1::TYPES::SALE) { + EXPECT_EQ(edge_data.amount_, node_data.id); + } else { + EXPECT_EQ(edge_data.amount_, dst_id); + } + } + } + EXPECT_EQ(vertex_count.reduce(), num_projected_nodes); + EXPECT_EQ(edge_count.reduce(), num_projected_edges); +} + +TEST(Import, Projection) { + std::unique_ptr full_graph = generateTestFullGraph(); + std::unique_ptr projected_graph = + wf4::ProjectGraph(std::move(full_graph)); + std::unique_ptr expected_graph = generateTestGraph(true); + graphsEqual(*projected_graph, *expected_graph); +} diff --git a/test/wf4/test_influence_maximization.cpp b/test/wf4/test_influence_maximization.cpp new file mode 100644 index 0000000..d877b78 --- /dev/null +++ b/test/wf4/test_influence_maximization.cpp @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: BSD-2-Clause +// Copyright (c) 2023. University of Texas at Austin. All rights reserved. + +// SPDX-License-Identifier: MIT +// Copyright (c) 2023. University of Texas at Austin. All rights reserved. + +#include + +#include + +galois::DynamicBitSet bitset_bought_; +galois::DynamicBitSet bitset_sold_; + +namespace { + +const uint64_t num_nodes = 16; +const uint64_t num_edges = 240; +const uint64_t seed = 9801; +const uint64_t epochs = 10; + +std::unique_ptr +generateTestGraph(bool set_node_properties = false, bool single_host = false) { + std::vector vertices(num_nodes); + std::vector> edges(num_edges); + + for (uint64_t i = 0; i < num_nodes; i++) { + wf4::NetworkNode node(i); + if (set_node_properties) { + node.sold_ = i * i; + node.bought_ = (num_nodes + i) * (num_nodes - (i + 1)) / 2; + node.desired_ = node.bought_; + node.frequency_ = i; + } + vertices[i] = node; + } + // Every node sells to every node with a global ID less than itself + // And so every node buys from every node with a global ID more than itself + // We set the edge weight to be equal to the global ID of the seller + // Vertex 0 does not sell anything + uint64_t edge_count = 0; + for (uint64_t src = 0; src < num_nodes; src++) { + for (uint64_t dst = 0; dst < src; dst++) { + edges[edge_count++] = galois::GenericEdge( + src, dst, + wf4::NetworkEdge(src, dst, src, agile::workflow1::TYPES::SALE)); + } + for (uint64_t dst = src + 1; dst < num_nodes; dst++) { + edges[edge_count++] = galois::GenericEdge( + src, dst, + wf4::NetworkEdge(src, dst, dst, agile::workflow1::TYPES::PURCHASE)); + } + } + auto& net = galois::runtime::getSystemNetworkInterface(); + uint32_t host = net.ID; + uint32_t hosts = net.Num; + if (single_host) { + host = 0; + hosts = 1; + } + return std::unique_ptr( + new wf4::NetworkGraph(host, hosts, vertices, edges)); +} + +double AmountSold(uint64_t num_nodes) { + double sold = 0; + for (uint64_t i = 0; i < num_nodes; i++) { + sold += i * i; + } + return sold; +} + +uint64_t getHostSets(uint64_t global_sets) { + auto& net = galois::runtime::getSystemNetworkInterface(); + uint32_t host = net.ID; + uint32_t num_hosts = net.Num; + uint64_t host_sets = global_sets / num_hosts; + if (host == num_hosts - 1) { + host_sets = global_sets - ((num_hosts - 1) * (global_sets / num_hosts)); + } + return host_sets; +} + +std::vector getRootCounts(wf4::NetworkGraph& graph, + uint64_t host_sets) { + auto& net = galois::runtime::getSystemNetworkInterface(); + std::vector root_counts(graph.globalSize()); + for (uint64_t i = 0; i < host_sets; i++) { + galois::gstl::Vector seed_nodes( + host_sets); + wf4::internal::GenerateSeedNodes(seed_nodes, graph, seed, i, net.ID); + root_counts[*(seed_nodes[i].partial_reachable_set.begin())]++; + } + return root_counts; +} + +} // namespace + +TEST(IF, INIT) { + std::unique_ptr graph_ptr = generateTestGraph(); + wf4::NetworkGraph& graph = *graph_ptr; + EXPECT_EQ(graph.size(), num_nodes); + galois::DGAccumulator global_nodes; + galois::DGAccumulator global_edges; + global_nodes.reset(); + global_edges.reset(); + + galois::do_all(galois::iterate(graph.masterNodesRange()), + [&](wf4::NetworkGraph::GraphNode node) { + bool local = graph.isLocal(graph.getGID(node)); + EXPECT_EQ(local, true); + if (!local) { + return; + } + global_nodes += 1; + auto actualEdges = graph.edges(node).begin(); + uint64_t node_edges = + std::distance(actualEdges, graph.edges(node).end()); + EXPECT_EQ(node_edges, num_nodes - 1); + + for (auto& edge : graph.edges(node)) { + wf4::NetworkEdge edge_value = graph.getEdgeData(edge); + wf4::GlobalNodeID token_id = graph.getGID(node); + EXPECT_EQ(edge_value.type == agile::workflow1::TYPES::SALE, + graph.getGID(graph.getEdgeDst(edge)) < token_id); + EXPECT_EQ(edge_value.type == + agile::workflow1::TYPES::PURCHASE, + graph.getGID(graph.getEdgeDst(edge)) > token_id); + global_edges += 1; + } + }); + EXPECT_EQ(global_nodes.reduce(), num_nodes); + EXPECT_EQ(global_edges.reduce(), num_edges); +} + +TEST(IF, FillNodeValues) { + std::unique_ptr graph_ptr = generateTestGraph(); + wf4::NetworkGraph& graph = *graph_ptr; + bitset_sold_.resize(graph.size()); + bitset_sold_.reset(); + galois::do_all(galois::iterate(graph.masterNodesRange()), + [&](wf4::NetworkGraph::GraphNode node_lid) { + wf4::internal::FillNodeValues(graph, node_lid); + wf4::NetworkNode node_data = graph.getData(node_lid); + wf4::GlobalNodeID node_gid = graph.getGID(node_lid); + EXPECT_EQ(node_data.sold_, node_gid * node_gid); + EXPECT_EQ(node_data.bought_, + (num_nodes + node_gid) * + (num_nodes - (node_gid + 1)) / 2); + EXPECT_EQ(node_data.bought_, node_data.desired_); + }); +} + +TEST(IF, EdgeProbability) { + const bool set_node_properties = true; + std::unique_ptr graph_ptr = + generateTestGraph(set_node_properties); + wf4::NetworkGraph& graph = *graph_ptr; + galois::DGAccumulator total_edge_weights; + galois::DGAccumulator total_sales; + total_edge_weights.reset(); + total_sales.reset(); + galois::do_all( + galois::iterate(graph.masterNodesRange()), + [&](wf4::NetworkGraph::GraphNode node_lid) { + wf4::internal::CalculateEdgeProbability( + graph, node_lid, total_edge_weights, total_sales); + for (auto& edge : graph.edges(node_lid)) { + wf4::NetworkEdge edge_value = graph.getEdgeData(edge); + if (edge_value.type == agile::workflow1::TYPES::SALE) { + EXPECT_FLOAT_EQ(edge_value.weight_, 1.0 / graph.getGID(node_lid)); + } else { + EXPECT_FLOAT_EQ(edge_value.weight_, + 1.0 / graph.getGID(graph.getEdgeDst(edge))); + } + } + }); + EXPECT_FLOAT_EQ(total_edge_weights.reduce(), (num_nodes - 1) * 2); + EXPECT_FLOAT_EQ(total_sales.reduce(), AmountSold(num_nodes) * 2); +} + +TEST(IF, EdgeProbabilities) { + std::unique_ptr graph_ptr = generateTestGraph(); + wf4::NetworkGraph& graph = *graph_ptr; + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> + sync_substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), + graph.cartesianGrid()); + wf4::CalculateEdgeProbabilities(graph, *sync_substrate); + galois::do_all(galois::iterate(graph.masterNodesRange()), + [&](wf4::NetworkGraph::GraphNode node_lid) { + wf4::NetworkNode node_data = graph.getData(node_lid); + wf4::GlobalNodeID token_id = graph.getGID(node_lid); + EXPECT_EQ(node_data.sold_, token_id * token_id); + EXPECT_EQ(node_data.bought_, + (num_nodes + token_id) * + (num_nodes - (token_id + 1)) / 2); + EXPECT_EQ(node_data.bought_, node_data.desired_); + }); + galois::do_all( + galois::iterate(graph.masterNodesRange()), + [&](wf4::NetworkGraph::GraphNode node_lid) { + for (auto& edge : graph.edges(node_lid)) { + wf4::NetworkEdge edge_value = graph.getEdgeData(edge); + if (edge_value.type == agile::workflow1::TYPES::SALE) { + EXPECT_FLOAT_EQ(edge_value.weight_, 1.0 / graph.getGID(node_lid)); + } else { + EXPECT_FLOAT_EQ(edge_value.weight_, + 1.0 / graph.getGID(graph.getEdgeDst(edge))); + } + } + }); +} + +TEST(IF, GenerateRRRSet) { + std::unique_ptr graph_ptr = generateTestGraph(); + wf4::NetworkGraph& graph = *graph_ptr; + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> + sync_substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), + graph.cartesianGrid()); + wf4::CalculateEdgeProbabilities(graph, *sync_substrate); + wf4::ReverseReachableSet rrr_sets = + wf4::GetRandomReverseReachableSets(graph, net.Num, seed, epochs); + + galois::gstl::Vector seed_nodes(1); + wf4::internal::GenerateSeedNodes(seed_nodes, graph, seed, 0, net.ID); + wf4::GlobalNodeID root = *(seed_nodes[0].partial_reachable_set.begin()); + + EXPECT_EQ(rrr_sets.size(), 1); + for (phmap::flat_hash_set rrr_set : rrr_sets) { + EXPECT_GT(rrr_set.size(), 0); + } + wf4::NetworkNode root_data = graph.getData(graph.getLID(root)); + uint64_t root_influence = root_data.frequency_; + EXPECT_GE(root_influence, 1); + EXPECT_LE(root_influence, net.Num); +} + +TEST(IF, GenerateRRRSets) { + const uint64_t num_sets = 100; + const uint64_t host_sets = getHostSets(num_sets); + std::unique_ptr graph_ptr = generateTestGraph(); + wf4::NetworkGraph& graph = *graph_ptr; + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> + sync_substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), + graph.cartesianGrid()); + wf4::CalculateEdgeProbabilities(graph, *sync_substrate); + wf4::ReverseReachableSet rrr_sets = + wf4::GetRandomReverseReachableSets(graph, num_sets, seed, epochs); + std::vector root_counts = getRootCounts(graph, host_sets); + + EXPECT_EQ(rrr_sets.size(), host_sets); + for (phmap::flat_hash_set rrr_set : rrr_sets) { + EXPECT_GT(rrr_set.size(), 0); + } + + galois::DGAccumulator has_greater; + has_greater.reset(); + for (uint64_t i = 0; i < num_nodes; i++) { + if (!graph.isOwned(i)) { + continue; + } + wf4::NetworkNode node_data = graph.getData(graph.getLID(i)); + uint64_t influence = node_data.frequency_; + uint64_t root_occurrences = root_counts[i]; + EXPECT_GE(influence, root_occurrences); + if (influence > root_occurrences) { + has_greater += 1; + } + } + EXPECT_GT(has_greater.reduce(), 0); +} + +TEST(IF, FindLocalMax) { + std::unique_ptr graph_ptr = generateTestGraph(true); + wf4::NetworkGraph& graph = *graph_ptr; + galois::PerThreadVector max_array; + galois::DGAccumulator total_influence; + total_influence.reset(); + + galois::do_all(galois::iterate(graph.masterNodesRange()), + [&](wf4::NetworkGraph::GraphNode node) { + wf4::internal::FindLocalMaxNode(graph, node, max_array.get(), + total_influence); + }); + EXPECT_EQ(total_influence.reduce(), num_edges / 2); + EXPECT_GT(max_array.size_all(), 0); +} + +TEST(IF, GetMaxNode) { + std::unique_ptr graph_ptr = generateTestGraph(true); + wf4::NetworkGraph& graph = *graph_ptr; + EXPECT_EQ(wf4::internal::GetMostInfluentialNode(graph), num_nodes - 1); +} + +TEST(IF, GetInfluential) { + const uint64_t num_sets = 100; + std::unique_ptr graph_ptr = generateTestGraph(); + wf4::NetworkGraph& graph = *graph_ptr; + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> + sync_substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), + graph.cartesianGrid()); + wf4::CalculateEdgeProbabilities(graph, *sync_substrate); + wf4::ReverseReachableSet rrr_sets = + wf4::GetRandomReverseReachableSets(graph, num_sets, seed, epochs); + std::vector influential_nodes = + wf4::GetInfluentialNodes(graph, std::move(rrr_sets), 1, 0); + EXPECT_EQ(influential_nodes.size(), 1); + + wf4::GlobalNodeID influential = influential_nodes[0]; + galois::DGAccumulator most_influence_accum; + most_influence_accum.reset(); + if (graph.isOwned(influential)) { + wf4::NetworkNode most_influential = + graph.getData(graph.getLID(influential)); + uint64_t most_influence = most_influential.frequency_; + EXPECT_GT(most_influence, 0); + most_influence_accum += most_influence; + } + uint64_t most_influence = most_influence_accum.reduce(); + for (uint64_t node = 0; node < num_nodes; node++) { + wf4::NetworkNode node_data = graph.getData(graph.getLID(node)); + uint64_t influence = node_data.frequency_; + EXPECT_GE(most_influence, influence); + } +} + +TEST(IF, GetInfluentials2) { + const uint64_t num_sets = 100; + std::unique_ptr graph_ptr = generateTestGraph(); + wf4::NetworkGraph& graph = *graph_ptr; + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> + sync_substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), + graph.cartesianGrid()); + wf4::CalculateEdgeProbabilities(graph, *sync_substrate); + wf4::ReverseReachableSet rrr_sets = + wf4::GetRandomReverseReachableSets(graph, num_sets, seed, epochs); + std::vector influential_nodes = + wf4::GetInfluentialNodes(graph, std::move(rrr_sets), 2, 0); + EXPECT_EQ(influential_nodes.size(), 2); + wf4::GlobalNodeID influential = influential_nodes[0]; + if (graph.isOwned(influential)) { + wf4::NetworkNode most_influential = + graph.getData(graph.getLID(influential)); + uint64_t most_influence = most_influential.frequency_; + EXPECT_EQ(most_influence, 0); + } +} + +TEST(IF, GetInfluentials3) { + const uint64_t num_sets = 100; + std::unique_ptr graph_ptr = generateTestGraph(); + wf4::NetworkGraph& graph = *graph_ptr; + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> + sync_substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), + graph.cartesianGrid()); + wf4::CalculateEdgeProbabilities(graph, *sync_substrate); + wf4::ReverseReachableSet rrr_sets = + wf4::GetRandomReverseReachableSets(graph, num_sets, seed, epochs); + std::vector influential_nodes = + wf4::GetInfluentialNodes(graph, std::move(rrr_sets), 3, 0); + EXPECT_EQ(influential_nodes.size(), 3); + wf4::GlobalNodeID influential = influential_nodes[0]; + if (graph.isOwned(influential)) { + wf4::NetworkNode most_influential = + graph.getData(graph.getLID(influential)); + uint64_t most_influence = most_influential.frequency_; + EXPECT_EQ(most_influence, 0); + } + for (uint64_t node = 0; node < num_nodes; node++) { + if (!graph.isOwned(node)) { + continue; + } + wf4::NetworkNode node_data = graph.getData(graph.getLID(node)); + uint64_t influence = node_data.frequency_; + EXPECT_LE(influence, 100); + } +} diff --git a/test/wf4/test_quiesce.cpp b/test/wf4/test_quiesce.cpp new file mode 100644 index 0000000..113c176 --- /dev/null +++ b/test/wf4/test_quiesce.cpp @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: BSD-2-Clause +// Copyright (c) 2023. University of Texas at Austin. All rights reserved. + +// SPDX-License-Identifier: MIT +// Copyright (c) 2023. University of Texas at Austin. All rights reserved. + +#include + +#include + +galois::DynamicBitSet bitset_bought_; +galois::DynamicBitSet bitset_sold_; + +namespace { + +const uint64_t num_nodes = 16; +const uint64_t num_edges = 240; +const bool async = false; + +std::unique_ptr +generateTestGraph(bool remove_high = false, uint64_t nodes_to_set = num_nodes, + bool single_host = false) { + std::vector vertices(num_nodes); + std::vector> edges(num_edges); + + for (uint64_t i = 0; i < num_nodes; i++) { + wf4::NetworkNode node(i); + if (remove_high) { + if (i < nodes_to_set) { + node.sold_ = i * i; + node.bought_ = (nodes_to_set + i) * (nodes_to_set - (i + 1)) / 2; + node.desired_ = (num_nodes + i) * (num_nodes - (i + 1)) / 2; + } else { + node.sold_ = 0; + node.bought_ = 0; + node.desired_ = 0; + } + } else { + if (i <= nodes_to_set) { + node.sold_ = 0; + node.bought_ = 0; + node.desired_ = 0; + } else { + node.sold_ = i * (i - nodes_to_set - 1); + node.bought_ = (num_nodes + i) * (num_nodes - (i + 1)) / 2; + node.desired_ = (num_nodes + i) * (num_nodes - (i + 1)) / 2; + } + } + vertices[i] = node; + } + // Every node sells to every node with a global ID less than itself + // And so every node buys from every node with a global ID more than itself + // We set the edge weight to be equal to the global ID of the seller + // Vertex 0 does not sell anything + uint64_t edge_count = 0; + for (uint64_t src = 0; src < num_nodes; src++) { + for (uint64_t dst = 0; dst < src; dst++) { + edges[edge_count++] = galois::GenericEdge( + src, dst, + wf4::NetworkEdge(src, dst, src, agile::workflow1::TYPES::SALE)); + } + for (uint64_t dst = src + 1; dst < num_nodes; dst++) { + edges[edge_count++] = galois::GenericEdge( + src, dst, + wf4::NetworkEdge(src, dst, dst, agile::workflow1::TYPES::PURCHASE)); + } + } + auto& net = galois::runtime::getSystemNetworkInterface(); + uint32_t host = net.ID; + uint32_t hosts = net.Num; + if (single_host) { + host = 0; + hosts = 1; + } + return std::unique_ptr( + new wf4::NetworkGraph(host, hosts, vertices, edges)); +} + +std::pair, + std::unique_ptr> +generateTestGraphs(std::vector remove_nodes) { + std::unique_ptr graph1 = generateTestGraph(true); + std::unique_ptr graph2 = + generateTestGraph(true, num_nodes, true); + std::unique_ptr> + substrate2 = + std::make_unique>( + *graph2, 0, 1, graph2->isTransposed(), graph2->cartesianGrid()); + wf4::CancelNodes(*graph2, *substrate2, remove_nodes); + + return std::pair(std::move(graph1), std::move(graph2)); +} + +void nodePropertiesGreater(wf4::NetworkGraph& actual, + wf4::NetworkGraph& expected) { + for (uint64_t i = 0; i < num_nodes; i++) { + if (!actual.isOwned(i)) { + continue; + } + wf4::NetworkNode actual_node = actual.getData(actual.getLID(i)); + wf4::NetworkNode expected_node = expected.getData(expected.getLID(i)); + EXPECT_EQ(actual_node.id, expected_node.id); + EXPECT_GE(actual_node.bought_, expected_node.bought_); + EXPECT_GE(actual_node.sold_, expected_node.sold_); + EXPECT_EQ(actual_node.desired_, expected_node.desired_); + } +} + +void nodePropertiesEqual(wf4::NetworkGraph& actual, + wf4::NetworkGraph& expected) { + for (uint64_t i = 0; i < num_nodes; i++) { + if (!actual.isOwned(i)) { + continue; + } + wf4::NetworkNode actual_node = actual.getData(actual.getLID(i)); + wf4::NetworkNode expected_node = expected.getData(expected.getLID(i)); + EXPECT_EQ(actual_node.id, expected_node.id); + EXPECT_FLOAT_EQ(actual_node.bought_, expected_node.bought_); + EXPECT_FLOAT_EQ(actual_node.sold_, expected_node.sold_); + EXPECT_FLOAT_EQ(actual_node.desired_, expected_node.desired_); + } +} + +void nodePropertiesEqual(wf4::NetworkGraph& actual, + std::vector& removed_nodes) { + std::unique_ptr expected_ptr = + generateTestGraph(true, num_nodes, true); + wf4::NetworkGraph& expected = *expected_ptr; + for (uint64_t i = 0; i < num_nodes; i++) { + if (!actual.isOwned(i)) { + continue; + } + wf4::NetworkNode actual_node = actual.getData(actual.getLID(i)); + wf4::NetworkNode expected_node = expected.getData(expected.getLID(i)); + EXPECT_EQ(actual_node.id, expected_node.id); + bool cancelled = false; + for (wf4::GlobalNodeID node : removed_nodes) { + if (i == node) { + EXPECT_FLOAT_EQ(actual_node.bought_, 0); + EXPECT_FLOAT_EQ(actual_node.sold_, 0); + EXPECT_FLOAT_EQ(actual_node.desired_, 0); + cancelled = true; + } + } + if (!cancelled) { + double sold_delta = 0; + double bought_delta = 0; + for (wf4::GlobalNodeID node : removed_nodes) { + if (node < i) { + sold_delta -= i; + } else { + bought_delta -= node; + } + } + EXPECT_FLOAT_EQ(actual_node.bought_, + expected_node.bought_ + bought_delta); + EXPECT_FLOAT_EQ(actual_node.sold_, expected_node.sold_ + sold_delta); + EXPECT_FLOAT_EQ(actual_node.desired_, expected_node.desired_); + } + } +} + +} // namespace + +TEST(Quiesce, Cancel) { + const bool remove_high = true; + { + std::unique_ptr graph_ptr = + generateTestGraph(remove_high); + wf4::NetworkGraph& graph = *graph_ptr; + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> + substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), + graph.cartesianGrid()); + + for (uint64_t i = 0; i <= num_nodes; i++) { + bitset_bought_.resize(graph.size()); + bitset_bought_.reset(); + bitset_sold_.resize(graph.size()); + bitset_sold_.reset(); + uint64_t node_id = num_nodes - i; + galois::do_all(galois::iterate(graph.allNodesRange()), + [&](wf4::NetworkGraph::GraphNode node_lid) { + if (node_id == graph.getGID(node_lid)) { + wf4::internal::CancelNode(graph, node_id); + } + }); + // update mirrors + substrate->sync( + "Update bought values after cancellation"); + substrate->sync( + "Update sold values after cancellation"); + galois::runtime::getHostBarrier().wait(); + for (uint64_t j = node_id; j < num_nodes; j++) { + if (graph.isLocal(j)) { + graph.getData(graph.getLID(j)).Cancel(); + } + } + + std::unique_ptr expected_graph = + generateTestGraph(remove_high, num_nodes - i, true); + nodePropertiesEqual(graph, *expected_graph); + } + } + { + std::unique_ptr graph_ptr = + generateTestGraph(remove_high); + wf4::NetworkGraph& graph = *graph_ptr; + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> + substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), + graph.cartesianGrid()); + + for (uint64_t i = 0; i < num_nodes; i++) { + bitset_bought_.resize(graph.size()); + bitset_bought_.reset(); + bitset_sold_.resize(graph.size()); + bitset_sold_.reset(); + uint64_t node_id = i; + galois::do_all(galois::iterate(graph.allNodesRange()), + [&](wf4::NetworkGraph::GraphNode node_lid) { + if (node_id == graph.getGID(node_lid)) { + wf4::internal::CancelNode(graph, node_id); + } + }); + // update mirrors + substrate->sync( + "Update bought values after cancellation"); + substrate->sync( + "Update sold values after cancellation"); + galois::runtime::getHostBarrier().wait(); + for (uint64_t j = 0; j <= node_id; j++) { + if (graph.isLocal(j)) { + graph.getData(graph.getLID(j)).Cancel(); + } + } + + std::unique_ptr expected_graph = + generateTestGraph(!remove_high, i, true); + nodePropertiesEqual(graph, *expected_graph); + } + } +} + +TEST(Quiesce, CancelNodes) { + const bool remove_high = true; + std::unique_ptr graph_ptr = generateTestGraph(remove_high); + wf4::NetworkGraph& graph = *graph_ptr; + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), graph.cartesianGrid()); + std::vector influential_vertices; + influential_vertices.emplace_back(num_nodes - 1); + influential_vertices.emplace_back(num_nodes / 2); + + wf4::CancelNodes(graph, *substrate, influential_vertices); + nodePropertiesEqual(graph, influential_vertices); +} + +TEST(Quiesce, TryQuiesceNode) { + const bool remove_high = true; + const uint64_t removed_node = num_nodes - 1; + std::unique_ptr graph_ptr = generateTestGraph(remove_high); + wf4::NetworkGraph& graph = *graph_ptr; + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), graph.cartesianGrid()); + if (net.Num != 2 && net.Num != 4) { + return; + } + const uint64_t test_node = num_nodes / net.Num - 1; + + galois::gstl::Vector< + phmap::parallel_flat_hash_set_m> + proposed_edges(net.Num); + std::vector influential_vertices; + influential_vertices.emplace_back(removed_node); + + wf4::CancelNodes(graph, *substrate, influential_vertices); + galois::do_all(galois::iterate(graph.masterNodesRange()), + [&](wf4::NetworkGraph::GraphNode node) { + if (graph.getGID(node) == test_node) { + wf4::internal::TryQuiesceNode(graph, proposed_edges, node); + } + }); + if (graph.isOwned(test_node)) { + bool proposing_edge = false; + for (phmap::parallel_flat_hash_set_m& edges : + proposed_edges) { + for (wf4::internal::ProposedEdge res : edges) { + EXPECT_EQ(res.src, test_node); + EXPECT_GT(res.dst, test_node); + EXPECT_LE(res.weight, removed_node); + proposing_edge = true; + } + } + EXPECT_TRUE(proposing_edge); + } +} + +// TODO(Patrick) implement +// TEST(Quiesce, TryAddEdges) + +TEST(Quiesce, QuiesceOne) { + const uint64_t removed_node = 1; + std::vector influential_vertices; + influential_vertices.emplace_back(removed_node); + auto graphs = generateTestGraphs(influential_vertices); + wf4::NetworkGraph& graph = *(graphs.first); + wf4::NetworkGraph& expected = *(graphs.second); + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), graph.cartesianGrid()); + wf4::CancelNodes(graph, *substrate, influential_vertices); + + wf4::QuiesceGraph(graph, *substrate); + + double delta = 1.0; + wf4::NetworkNode& buyer_node = expected.getData(expected.getLID(0)); + wf4::NetworkNode& seller_node = expected.getData(expected.getLID(2)); + galois::atomicAdd(buyer_node.bought_, delta); + galois::atomicAdd(seller_node.sold_, delta); + + nodePropertiesEqual(graph, expected); +} + +TEST(Quiesce, Quiesce) { + std::vector influential_vertices; + influential_vertices.emplace_back(0); + influential_vertices.emplace_back(2); + influential_vertices.emplace_back(num_nodes / 2); + influential_vertices.emplace_back(num_nodes - 1); + auto graphs = generateTestGraphs(influential_vertices); + wf4::NetworkGraph& graph = *(graphs.first); + wf4::NetworkGraph& expected = *(graphs.second); + auto& net = galois::runtime::getSystemNetworkInterface(); + std::unique_ptr> substrate = + std::make_unique>( + graph, net.ID, net.Num, graph.isTransposed(), graph.cartesianGrid()); + wf4::CancelNodes(graph, *substrate, influential_vertices); + + wf4::QuiesceGraph(graph, *substrate); + + nodePropertiesGreater(graph, expected); +} diff --git a/wf4/CMakeLists.txt b/wf4/CMakeLists.txt index 9abc1ea..79119da 100644 --- a/wf4/CMakeLists.txt +++ b/wf4/CMakeLists.txt @@ -3,12 +3,16 @@ set(sources src/import.cpp - src/influencer.cpp - src/main.cpp + src/influence_maximization.cpp src/quiesce.cpp ) -add_executable(wf4) -target_sources(wf4 PRIVATE ${sources}) -target_include_directories(wf4 PRIVATE ${CMAKE_CURRENT_LIST_DIR}/include ${PHMAP_DIRECTORY} ${PCG_DIRECTORY} ${graph-log-sketch_SOURCE_DIR}/include) -target_link_libraries(wf4 PRIVATE Galois::cusp Galois::dist_async Galois::gluon Galois::wmd) +target_sources(wf4-galois PRIVATE ${sources}) +target_include_directories(wf4-galois PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include ${PHMAP_DIRECTORY} ${PCG_DIRECTORY} ${graph-log-sketch_SOURCE_DIR}/include) +target_link_libraries(wf4-galois PUBLIC Galois::cusp Galois::dist_async Galois::gluon Galois::wmd) + +galois_add_executable(wf4 src/main.cpp) +target_link_libraries(wf4 + PUBLIC + wf4-galois::wf4-galois +) diff --git a/wf4/include/full_graph.hpp b/wf4/include/wf4/full_graph.hpp similarity index 88% rename from wf4/include/full_graph.hpp rename to wf4/include/wf4/full_graph.hpp index c7f6cef..55fb3b5 100644 --- a/wf4/include/full_graph.hpp +++ b/wf4/include/wf4/full_graph.hpp @@ -25,6 +25,12 @@ class FullNetworkEdge { FullNetworkEdge() = default; FullNetworkEdge(time_t date, double amount) : date_(date), amount_(amount), weight_(0) {} + FullNetworkEdge(uint64_t src_, uint64_t dst_, agile::workflow1::TYPES type_, + agile::workflow1::TYPES src_type_, + agile::workflow1::TYPES dst_type_, double amount, + uint64_t topic_) + : amount_(amount), type(type_), src_type(src_type_), dst_type(dst_type_), + src(src_), dst(dst_), src_glbid(src_), dst_glbid(dst_), topic(topic_) {} explicit FullNetworkEdge(const std::vector& tokens) { double amount_sold = 0; if (tokens[7].size() > 0) { @@ -88,7 +94,7 @@ class FullNetworkEdge { uint64_t src_glbid = std::numeric_limits::max(); uint64_t dst_glbid = std::numeric_limits::max(); - uint64_t topic; + uint64_t topic = 0; uint64_t epoch_time; uint64_t duration; @@ -106,6 +112,8 @@ struct FullNetworkNode { FullNetworkNode() = default; FullNetworkNode(uint64_t id_, uint64_t, agile::workflow1::TYPES type) : id(id_), glbid(id_), sold_(0), bought_(0), desired_(0), type_(type) {} + FullNetworkNode(uint64_t id_, agile::workflow1::TYPES type) + : id(id_), glbid(id_), sold_(0), bought_(0), desired_(0), type_(type) {} explicit FullNetworkNode(uint64_t id_) : id(id_), glbid(id_), sold_(0), bought_(0), desired_(0) {} diff --git a/wf4/include/graph.hpp b/wf4/include/wf4/graph.hpp similarity index 96% rename from wf4/include/graph.hpp rename to wf4/include/wf4/graph.hpp index cec2f03..2ae56ce 100644 --- a/wf4/include/graph.hpp +++ b/wf4/include/wf4/graph.hpp @@ -35,6 +35,10 @@ class NetworkEdge { NetworkEdge() = default; NetworkEdge(time_t date, double amount) : date_(date), amount_(amount), weight_(0) {} + NetworkEdge(uint64_t src_, uint64_t dst_, double amount, + agile::workflow1::TYPES type_) + : amount_(amount), weight_(0), type(type_), src(src_), dst(dst_), + src_glbid(src_), dst_glbid(dst_) {} explicit NetworkEdge(const std::vector& tokens) { double amount_sold = 0; if (tokens[7].size() > 0) { diff --git a/wf4/include/import.hpp b/wf4/include/wf4/import.hpp similarity index 100% rename from wf4/include/import.hpp rename to wf4/include/wf4/import.hpp diff --git a/wf4/include/influencer.hpp b/wf4/include/wf4/influence_maximization.hpp similarity index 100% rename from wf4/include/influencer.hpp rename to wf4/include/wf4/influence_maximization.hpp diff --git a/wf4/include/quiesce.hpp b/wf4/include/wf4/quiesce.hpp similarity index 100% rename from wf4/include/quiesce.hpp rename to wf4/include/wf4/quiesce.hpp diff --git a/wf4/src/import.cpp b/wf4/src/import.cpp index cd4726b..f2b3b9e 100644 --- a/wf4/src/import.cpp +++ b/wf4/src/import.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-2-Clause // Copyright (c) 2023. University of Texas at Austin. All rights reserved. -#include "import.hpp" +#include "wf4/import.hpp" #include #include diff --git a/wf4/src/influencer.cpp b/wf4/src/influence_maximization.cpp similarity index 99% rename from wf4/src/influencer.cpp rename to wf4/src/influence_maximization.cpp index 5d25a01..83a9aff 100644 --- a/wf4/src/influencer.cpp +++ b/wf4/src/influence_maximization.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-2-Clause // Copyright (c) 2023. University of Texas at Austin. All rights reserved. -#include "influencer.hpp" +#include "wf4/influence_maximization.hpp" #include #include @@ -500,7 +500,7 @@ void wf4::internal::RemoveReachableSetsWithInfluentialNode( } else { remote_updates[graph.getHostID(reachable_node_gid)].try_emplace_l( reachable_node_gid, [&](map::value_type& p) { p.second++; }, - (uint64_t)0); // may need to change to 1 + (uint64_t)1); } } reachability_set.clear(); diff --git a/wf4/src/main.cpp b/wf4/src/main.cpp index 1b04856..89e5470 100644 --- a/wf4/src/main.cpp +++ b/wf4/src/main.cpp @@ -5,11 +5,9 @@ #include "galois/DistGalois.h" #include "galois/runtime/DataCommMode.h" -#include "import.hpp" -#include "influencer.hpp" -#include "quiesce.hpp" - -static const char* name = "Network of Networks"; +#include "wf4/import.hpp" +#include "wf4/influence_maximization.hpp" +#include "wf4/quiesce.hpp" galois::DynamicBitSet bitset_bought_; galois::DynamicBitSet bitset_sold_; @@ -161,7 +159,7 @@ uint64_t countEdges(T& graph) { auto dst_node = graph.getData(dst_lid); if (data.type == agile::workflow1::TYPES::SALE || data.type == agile::workflow1::TYPES::PURCHASE) { - if (data.topic = 8486 && data.amount_ > 0) { + if (data.topic == 8486 && data.amount_ > 0) { trading_edges++; if (node.type_ == agile::workflow1::TYPES::PERSON) { diff --git a/wf4/src/quiesce.cpp b/wf4/src/quiesce.cpp index ebfc11f..01da289 100644 --- a/wf4/src/quiesce.cpp +++ b/wf4/src/quiesce.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-2-Clause // Copyright (c) 2023. University of Texas at Austin. All rights reserved. -#include "quiesce.hpp" +#include "wf4/quiesce.hpp" #include "galois/AtomicHelpers.h" @@ -76,6 +76,15 @@ void wf4::CancelNodes( Reduce_add_sold_, Bitset_sold_, async>( "Update sold values after cancellation"); galois::runtime::getHostBarrier().wait(); + + // Required since repeated cancellations can give other cancelled nodes + // negative values + for (wf4::GlobalNodeID node_gid : nodes) { + if (!graph.isLocal(node_gid)) { + continue; + } + graph.getData(graph.getLID(node_gid)).Cancel(); + } } void wf4::QuiesceGraph( @@ -201,8 +210,8 @@ void wf4::internal::TryQuiesceNode( wf4::NetworkNode& seller_data = graph.getData(seller_node); // TODO(Patrick) explore negative case more closely - double seller_surplus = seller_data.bought_ - seller_data.sold_; - double trade_delta = std::min(needed, seller_surplus); + const double seller_surplus = seller_data.bought_ - seller_data.sold_; + const double trade_delta = std::min(needed, seller_surplus); if (trade_delta <= 0) { continue; }