From d37adc666cef55fcd4ccdb0e23bcd3ada0ea4ed6 Mon Sep 17 00:00:00 2001 From: RANDOMFNP Date: Fri, 28 Aug 2026 16:27:25 -0400 Subject: [PATCH] Add files via upload --- README.md | 3 +- detail/add_edges.tpp | 19 + detail/add_edges_weighted.tpp | 20 + detail/bfs.tpp | 32 ++ detail/dfs.tpp | 33 ++ detail/dijkstra.tpp | 42 ++ include/graphlib.hpp | 15 + tests/test_core.cpp | 890 ++++++++++++++++----------------- tests/test_d_and_g_n.cpp | 1 - tests/test_weighted_create.cpp | 5 + 10 files changed, 608 insertions(+), 452 deletions(-) create mode 100644 tests/test_weighted_create.cpp diff --git a/README.md b/README.md index e547578..c39f658 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -# Graphlib, the one stop, C++ 23/20, header-only library for directional (and soon undirectional) graphs! - +# Graphlib 🇬 Graphlib is a C++ library that empowers users to create and edit graphs! If you want to contribute to GRAPHLIB, check out the CONTRIBUTING.md file! diff --git a/detail/add_edges.tpp b/detail/add_edges.tpp index b33c6ff..1b68355 100644 --- a/detail/add_edges.tpp +++ b/detail/add_edges.tpp @@ -20,4 +20,23 @@ void add_edge(const std::vector& new_value, const node& key, const std::st } create_graph(graph, input_file); } + +// Added in-memory abilities +template + +void add_edge(const std::vector& new_value, const node& key, std::unordered_map>& graph, std::string& input_file) { + auto g_it = graph.find(key); + + if (g_it == graph.end()) { + std::cout << "Key doesnt exist" << "\n"; + return; + } + + for (const auto& neighbor : new_value) { + if (std::find(g_it->second.begin(), g_it->second.end(), neighbor) == g_it->second.end()) { + g_it->second.push_back(neighbor); + } + } + create_graph(graph, input_file); +} } \ No newline at end of file diff --git a/detail/add_edges_weighted.tpp b/detail/add_edges_weighted.tpp index 6f16fdd..f5b08f7 100644 --- a/detail/add_edges_weighted.tpp +++ b/detail/add_edges_weighted.tpp @@ -21,4 +21,24 @@ void add_edge(const std::vector>& new_value, const node } create_graph(graph, input_file); } + +template + +requires Number +void add_edge(const std::vector>& new_value, const node& key, std::unordered_map>> graph, std::string& input_file) { + auto g_it = graph.find(key); + + if (g_it == graph.end()) { + std::cout << "Key doesnt exist" << "\n"; + return; + } + + for (const auto &[neighbor, weight] : new_value) { + auto ex = std::find_if(g_it->second.begin(), g_it->second.end(), [neighbor](auto edge) {return edge.first == neighbor;}); + if (ex == g_it->second.end()) { + g_it->second.push_back({neighbor, weight}); + } + } + create_graph(graph, input_file); +} } \ No newline at end of file diff --git a/detail/bfs.tpp b/detail/bfs.tpp index d7cbc4a..bfbc517 100644 --- a/detail/bfs.tpp +++ b/detail/bfs.tpp @@ -34,4 +34,36 @@ std::vector bfs_algorithm(const node& starting_node, const std::string& in } return return_graph; } + +// Added in-memory abilities +template + +std::vector bfs_algorithm(const node& starting_node, std::unordered_map>& graph) { + + std::queue q; + std::unordered_set visited; + visited.insert(starting_node); + q.push(starting_node); + + std::vector return_graph; + + while (!q.empty()) { + node node2 = q.front(); + q.pop(); + return_graph.push_back(node2); + + auto g_it = graph.find(node2); + if (g_it == graph.end()) { + continue; + } + + for (const auto& neighbor : g_it->second) { + if (visited.find(neighbor) == visited.end()) { + visited.insert(neighbor); + q.push(neighbor); + } + } + } + return return_graph; +} } \ No newline at end of file diff --git a/detail/dfs.tpp b/detail/dfs.tpp index 8c9d936..7fa2f62 100644 --- a/detail/dfs.tpp +++ b/detail/dfs.tpp @@ -35,4 +35,37 @@ std::vector dfs_algorithm(const node& starting_value, const std::string& i } return return_vector; } + +// Added in-memory abilities +template + +std::vector dfs_algorithm(const node& starting_value, const std::unordered_map>& graph) { + std::unordered_set visited; + std::stack stack_of_numbers; + + stack_of_numbers.push(starting_value); + + std::vector return_vector; + + while (!stack_of_numbers.empty()) { + node current = stack_of_numbers.top(); + stack_of_numbers.pop(); + + if (visited.contains(current)) { + continue; + } + + visited.insert(current); + return_vector.push_back(current); + + auto it = graph.find(current); + if (it == graph.end()) { + continue; + } + for (const auto& neighbor : it->second) { + stack_of_numbers.push(neighbor); + } + } + return return_vector; +} } \ No newline at end of file diff --git a/detail/dijkstra.tpp b/detail/dijkstra.tpp index 427bf61..9d66aaa 100644 --- a/detail/dijkstra.tpp +++ b/detail/dijkstra.tpp @@ -45,4 +45,46 @@ std::vector dijkstras_algorithm(const node starting_node, const std::strin } return return_graph; } + +// Added in-memory abilities +template + +requires Number +std::vector dijkstras_algorithm(const node starting_node, std::unordered_map>>& graph) { + std::priority_queue, std::vector>, std::greater>> q; + std::unordered_map visited; + visited[starting_node] = 0; + q.push(std::make_pair(0, starting_node)); + + std::vector return_graph; + + while (!q.empty()) { + auto [dist, current] = q.top(); + q.pop(); + + auto v_it = visited.find(current); + if (v_it != visited.end() && dist > v_it->second) { + continue; + } + + return_graph.push_back(current); + + auto g_it = graph.find(current); + if (g_it == graph.end()) { + continue; + } + + for (const auto& [n, w] : g_it->second) { + weights new_distance = dist + w; + + auto n_it = visited.find(n); + + if (n_it == visited.end() || new_distance < n_it->second) { + visited[n] = new_distance; + q.push({new_distance, n}); + } + } + } + return return_graph; +} } \ No newline at end of file diff --git a/include/graphlib.hpp b/include/graphlib.hpp index 3c5e3d1..eb9063f 100644 --- a/include/graphlib.hpp +++ b/include/graphlib.hpp @@ -70,6 +70,21 @@ template requires Number std::vector dijkstras_algorithm(const node starting_node, const std::string& input_file); + +// In memory +template +requires Number +void add_edge(const std::vector>& new_value, const node& key, std::unordered_map>>& graph); +template +void add_edge(const std::vector& new_value, const node& key, std::unordered_map>& graph); + +template +std::vector bfs_algorithm(const node& starting_node, std::unordered_map>& graph); +template +std::vector dfs_algorithm(const node& starting_value, const std::unordered_map>& graph); +template +requires Number +std::vector dijkstras_algorithm(const node starting_node, std::unordered_map>>& graph); } #include "../detail/txt_to_un_map.tpp" diff --git a/tests/test_core.cpp b/tests/test_core.cpp index 600cc82..bb213f9 100644 --- a/tests/test_core.cpp +++ b/tests/test_core.cpp @@ -1,449 +1,441 @@ -// Generated with assistance from artificial intelligence - -// CONTRIBUTORS: @TrueFurina - -#include "graphlib.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -// forward declaration for tests in test_d_and_g_n.cpp (merged runner) -extern int run_d_and_g_n_tests(); - -namespace { - -std::atomic g_checks_done{0}; -std::atomic g_ops_done{0}; - -static inline uint64_t get_rss_bytes() { - // Read resident set size from /proc/self/statm (field 2 = resident pages) - std::ifstream f("/proc/self/statm"); - if (!f) return 0; - uint64_t size_pages = 0, resident_pages = 0; - f >> size_pages >> resident_pages; - const long page_size = sysconf(_SC_PAGESIZE); - return resident_pages * static_cast(page_size); -} - -std::atomic g_monitor_running{false}; - -void start_monitor(std::chrono::steady_clock::time_point start_time) { - g_monitor_running = true; - std::thread([start_time]() { - uint64_t last_checks = 0; - uint64_t last_ops = 0; - auto last_time = std::chrono::steady_clock::now(); - while (g_monitor_running.load(std::memory_order_relaxed)) { - std::this_thread::sleep_for(std::chrono::seconds(1)); - auto now = std::chrono::steady_clock::now(); - double elapsed = std::chrono::duration(now - start_time).count(); - double delta_t = std::chrono::duration(now - last_time).count(); - uint64_t checks = g_checks_done.load(std::memory_order_relaxed); - uint64_t ops = g_ops_done.load(std::memory_order_relaxed); - double checks_per_sec = delta_t > 0 ? (checks - last_checks) / delta_t : 0.0; - double ops_per_sec = delta_t > 0 ? (ops - last_ops) / delta_t : 0.0; - uint64_t rss = get_rss_bytes(); - std::cerr << "[STATS] elapsed=" << (uint64_t)elapsed << "s" - << " checks/s=" << (uint64_t)checks_per_sec - << " total_checks=" << checks - << " ops/s=" << (uint64_t)ops_per_sec - << " total_ops=" << ops - << " rss_mb=" << (rss / 1024 / 1024) - << std::endl; - last_checks = checks; - last_ops = ops; - last_time = now; - } - }).detach(); -} - -std::string unique_temp_path(const std::string& name) { - const auto now = std::chrono::steady_clock::now().time_since_epoch().count(); - static std::atomic counter{0}; - const auto id = counter.fetch_add(1); - const char* tmp = std::getenv("TEMP"); - if (tmp == nullptr || *tmp == '\0') tmp = std::getenv("TMP"); - if (tmp == nullptr || *tmp == '\0') tmp = "/tmp"; - return std::string(tmp) + "/graphlib_" + name + "_" + std::to_string(now) + "_" + std::to_string(id) + ".txt"; -} - -template -bool same_vector_contents(const std::vector& actual, const std::vector& expected) { - if (actual.size() != expected.size()) return false; - std::vector a = actual; - std::vector e = expected; - std::sort(a.begin(), a.end()); - std::sort(e.begin(), e.end()); - return a == e; -} - -template -bool same_graph(const std::unordered_map>& actual, - const std::unordered_map>& expected) { - if (actual.size() != expected.size()) return false; - - for (const auto& [key, value] : expected) { - const auto it = actual.find(key); - if (it == actual.end()) return false; - if (!same_vector_contents(it->second, value)) return false; - } - return true; -} - -template -bool same_graph(const std::unordered_map>>& actual, - const std::unordered_map>>& expected) { - if (actual.size() != expected.size()) return false; - - for (const auto& [key, value] : expected) { - const auto it = actual.find(key); - if (it == actual.end()) return false; - - if (it->second.size() != value.size()) return false; - std::vector> a = it->second; - std::vector> e = value; - std::sort(a.begin(), a.end()); - std::sort(e.begin(), e.end()); - if (a != e) return false; - } - return true; -} - -void check_true(bool condition, const std::string& message) { - g_checks_done.fetch_add(1, std::memory_order_relaxed); - if (!condition) { - throw std::runtime_error("ASSERTION FAILED: " + message); - } -} - -std::string capture_output(std::function fn) { - std::ostringstream buffer; - auto* old = std::cout.rdbuf(buffer.rdbuf()); - try { - fn(); - } catch (...) { - std::cout.rdbuf(old); - throw; - } - std::cout.rdbuf(old); - return buffer.str(); -} - -// small helper: ensure node not present anywhere -template -bool node_absent(const std::unordered_map>& g, const node& n) { - if (g.find(n) != g.end()) return false; - for (const auto& [k, v] : g) { - for (const auto& x : v) if (x == n) return false; - } - return true; -} - -template -bool node_absent_weighted(const std::unordered_map>>& g, const node& n) { - if (g.find(n) != g.end()) return false; - for (const auto& [k, v] : g) { - for (const auto& p : v) if (p.first == n) return false; - } - return true; -} - -struct StressConfig { - int iterations = 200; - int max_nodes = 1000; - int max_edges_per_node = 20; - int thread_count = 4; -}; - -void run_unweighted_stress(int thread_id, const StressConfig& cfg, uint32_t seed) { - std::mt19937 rng(seed); - std::uniform_int_distribution node_dist(0, cfg.max_nodes); - std::uniform_int_distribution edges_dist(0, cfg.max_edges_per_node); - - for (int it = 0; it < cfg.iterations; ++it) { - const int N = std::max(0, node_dist(rng)); - std::unordered_map> graph; - graph.reserve(N); - for (int i = 0; i < N; ++i) { - int m = edges_dist(rng); - std::vector adj; - adj.reserve(m); - for (int e = 0; e < m; ++e) { - adj.push_back(rng() % (N + 1)); - } - graph[i] = adj; - } - - const std::string file = unique_temp_path("uw_create"); - graphlib::create_graph(graph, file); - const auto parsed = graphlib::parse(file); - check_true(same_graph(parsed, graph), "roundtrip create->parse mismatch (unweighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - - // random add_nodes - std::unordered_map> new_nodes; - int add_count = std::min(10, cfg.max_nodes / 10 + 1); - for (int a = 0; a < add_count; ++a) { - int key = N + a + 1; - int m = edges_dist(rng); - std::vector adj; - for (int e = 0; e < m; ++e) adj.push_back(rng() % (N + add_count + 1)); - new_nodes[key] = adj; - } - graphlib::add_nodes(new_nodes, file); - for (auto &p : new_nodes) graph[p.first] = p.second; - { - const auto p2 = graphlib::parse(file); - check_true(same_graph(p2, graph), "add_nodes mismatch (unweighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - } - - // random edge replacement - if (!graph.empty()) { - int key = rng() % (N + add_count + 1); - std::vector new_adj; - int m = edges_dist(rng); - for (int e = 0; e < m; ++e) new_adj.push_back(rng() % (N + add_count + 1)); - - // determine whether key exists before calling (current add_edge prints/returns if missing) - bool key_exists = (graph.find(key) != graph.end()); - graphlib::add_edge(new_adj, key, file); - - if (key_exists) { - // add_edge appends neighbors (no duplicates) — merge into local model - auto &vec = graph[key]; - for (auto n : new_adj) { - if (std::find(vec.begin(), vec.end(), n) == vec.end()) vec.push_back(n); - } - } else { - // add_edge does nothing when key missing (implementation prints and returns) - // local graph remains unchanged - } - - const auto p3 = graphlib::parse(file); - check_true(same_graph(p3, graph), "add_edge mismatch (unweighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - } - - // random delete - if (!graph.empty()) { - int candidate = rng() % (N + add_count + 1); - graphlib::delete_instances(std::to_string(candidate), file); - // remove candidate locally - graph.erase(candidate); - for (auto &kv : graph) { - auto &vec = kv.second; - vec.erase(std::remove(vec.begin(), vec.end(), candidate), vec.end()); - } - const auto p4 = graphlib::parse(file); - check_true(same_graph(p4, graph), "delete_instances mismatch (unweighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - check_true(node_absent(p4, candidate), "deleted node still present (unweighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - } - - // traversal sanity - if (!graph.empty()) { - int start = rng() % (graph.size()); - // pick an existing key - auto it = graph.begin(); - std::advance(it, start); - const int start_key = it->first; - auto bfs = graphlib::bfs_algorithm(start_key, file); - auto dfs = graphlib::dfs_algorithm(start_key, file); - check_true(!bfs.empty(), "bfs returned empty unexpectedly (unweighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - check_true(!dfs.empty(), "dfs returned empty unexpectedly (unweighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - } - - std::remove(file.c_str()); - } -} - -void run_weighted_stress(int thread_id, const StressConfig& cfg, uint32_t seed) { - std::mt19937 rng(seed); - std::uniform_int_distribution node_dist(0, cfg.max_nodes); - std::uniform_int_distribution edges_dist(0, cfg.max_edges_per_node); - std::uniform_int_distribution weight_dist(1, 1000); - - for (int it = 0; it < cfg.iterations; ++it) { - const int N = std::max(0, node_dist(rng)); - std::unordered_map>> graph; - graph.reserve(N); - for (int i = 0; i < N; ++i) { - int m = edges_dist(rng); - std::vector> adj; - adj.reserve(m); - for (int e = 0; e < m; ++e) { - adj.emplace_back(rng() % (N + 1), weight_dist(rng)); - } - graph[i] = adj; - } - - const std::string file = unique_temp_path("w_create"); - graphlib::create_graph(graph, file); - const auto parsed = graphlib::parse_weighted(file); - check_true(same_graph(parsed, graph), "roundtrip create->parse mismatch (weighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - - // random add_nodes - std::unordered_map>> new_nodes; - int add_count = std::min(10, cfg.max_nodes / 10 + 1); - for (int a = 0; a < add_count; ++a) { - int key = N + a + 1; - int m = edges_dist(rng); - std::vector> adj; - for (int e = 0; e < m; ++e) adj.emplace_back(rng() % (N + add_count + 1), weight_dist(rng)); - new_nodes[key] = adj; - } - graphlib::add_nodes(new_nodes, file); - for (auto &p : new_nodes) graph[p.first] = p.second; - { - const auto p2 = graphlib::parse_weighted(file); - check_true(same_graph(p2, graph), "add_nodes mismatch (weighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - } - - // random edge replacement - if (!graph.empty()) { - int key = rng() % (N + add_count + 1); - std::vector> new_adj; - int m = edges_dist(rng); - for (int e = 0; e < m; ++e) new_adj.emplace_back(rng() % (N + add_count + 1), weight_dist(rng)); - - bool key_exists = (graph.find(key) != graph.end()); - graphlib::add_edge(new_adj, key, file); - - if (key_exists) { - // add_edge appends weighted neighbors (no duplicate neighbor keys) — merge into local model - auto &vec = graph[key]; - for (auto &p : new_adj) { - auto ex = std::find_if(vec.begin(), vec.end(), [&](const auto &e) { return e.first == p.first; }); - if (ex == vec.end()) vec.push_back(p); - } - } else { - // add_edge does nothing when key missing - } - - const auto p3 = graphlib::parse_weighted(file); - if (!same_graph(p3, graph)) { - std::cerr << "DEBUG: add_edge weighted mismatch\n"; - std::cerr << "file: " << file << " key: " << key << " key_exists: " << key_exists << "\n"; - std::cerr << "new_adj:\n"; - for (auto &pp : new_adj) std::cerr << " (" << pp.first << "," << pp.second << ")\n"; - std::cerr << "expected adjacency for key:\n"; - if (graph.find(key) != graph.end()) { - for (auto &pp : graph[key]) std::cerr << " (" << pp.first << "," << pp.second << ")\n"; - } else { - std::cerr << " \n"; - } - std::cerr << "actual adjacency for key in file:\n"; - auto itf = p3.find(key); - if (itf != p3.end()) { - for (auto &pp : itf->second) std::cerr << " (" << pp.first << "," << pp.second << ")\n"; - } else { - std::cerr << " \n"; - } - check_true(false, "add_edge mismatch (weighted)"); - } else { - g_ops_done.fetch_add(1, std::memory_order_relaxed); - } - } - - // random delete - if (!graph.empty()) { - int candidate = rng() % (N + add_count + 1); - graphlib::delete_instances_weighted(std::to_string(candidate), file); - graph.erase(candidate); - for (auto &kv : graph) { - auto &vec = kv.second; - vec.erase(std::remove_if(vec.begin(), vec.end(), [&](const auto &p) { return p.first == candidate; }), vec.end()); - } - const auto p4 = graphlib::parse_weighted(file); - check_true(same_graph(p4, graph), "delete_instances mismatch (weighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - check_true(node_absent_weighted(p4, candidate), "deleted node still present (weighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - } - - // dijkstra traversal sanity - if (!graph.empty()) { - int start = rng() % (graph.size()); - auto it = graph.begin(); - std::advance(it, start); - const int start_key = it->first; - auto dres = graphlib::dijkstras_algorithm(start_key, file); - check_true(!dres.empty(), "dijkstra returned empty unexpectedly (weighted)"); - g_ops_done.fetch_add(1, std::memory_order_relaxed); - } - - std::remove(file.c_str()); - } -} - -} // namespace - -int main() { - try { - StressConfig cfg; - cfg.iterations = 50; - cfg.max_nodes = 1000; - cfg.max_edges_per_node = 20; - cfg.thread_count = 10; - - std::vector threads; - - // start monitor - auto start_time = std::chrono::steady_clock::now(); - start_monitor(start_time); - - // launch unweighted stressers - for (int t = 0; t < cfg.thread_count; ++t) { - threads.emplace_back(run_unweighted_stress, t, cfg, static_cast(12345 + t)); - } - - // launch weighted stressers - for (int t = 0; t < cfg.thread_count; ++t) { - threads.emplace_back(run_weighted_stress, t, cfg, static_cast(54321 + t)); - } - - for (auto &th : threads) th.join(); - - // stop monitor and print final stats - g_monitor_running = false; - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - - std::cerr << "FINAL: total_checks=" << g_checks_done.load() << " total_ops=" << g_ops_done.load() - << " rss_mb=" << (get_rss_bytes() / 1024 / 1024) << std::endl; - - std::cout << "Stress tests completed successfully." << std::endl; - - // run additional tests from tests/test_d_and_g_n.cpp - int rc = run_d_and_g_n_tests(); - if (rc != 0) return rc; - - return 0; - } catch (const std::exception& ex) { - std::cerr << ex.what() << std::endl; - return 1; - } -} +#include "graphlib.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// forward declaration for tests in test_d_and_g_n.cpp (merged runner) +extern int run_d_and_g_n_tests(); + +namespace { + +std::atomic g_checks_done{0}; +std::atomic g_ops_done{0}; + +static inline uint64_t get_rss_bytes() { + // Read resident set size from /proc/self/statm (field 2 = resident pages) + std::ifstream f("/proc/self/statm"); + if (!f) return 0; + uint64_t size_pages = 0, resident_pages = 0; + f >> size_pages >> resident_pages; + const long page_size = sysconf(_SC_PAGESIZE); + return resident_pages * static_cast(page_size); +} + +std::atomic g_monitor_running{false}; + +void start_monitor(std::chrono::steady_clock::time_point start_time) { + g_monitor_running = true; + std::thread([start_time]() { + uint64_t last_checks = 0; + uint64_t last_ops = 0; + auto last_time = std::chrono::steady_clock::now(); + while (g_monitor_running.load(std::memory_order_relaxed)) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + auto now = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration(now - start_time).count(); + double delta_t = std::chrono::duration(now - last_time).count(); + uint64_t checks = g_checks_done.load(std::memory_order_relaxed); + uint64_t ops = g_ops_done.load(std::memory_order_relaxed); + double checks_per_sec = delta_t > 0 ? (checks - last_checks) / delta_t : 0.0; + double ops_per_sec = delta_t > 0 ? (ops - last_ops) / delta_t : 0.0; + uint64_t rss = get_rss_bytes(); + std::cerr << "[STATS] elapsed=" << (uint64_t)elapsed << "s" + << " checks/s=" << (uint64_t)checks_per_sec + << " total_checks=" << checks + << " ops/s=" << (uint64_t)ops_per_sec + << " total_ops=" << ops + << " rss_mb=" << (rss / 1024 / 1024) + << std::endl; + last_checks = checks; + last_ops = ops; + last_time = now; + } + }).detach(); +} + +std::string unique_temp_path(const std::string& name) { + const auto now = std::chrono::steady_clock::now().time_since_epoch().count(); + static std::atomic counter{0}; + const auto id = counter.fetch_add(1); + return "/tmp/graphlib_" + name + "_" + std::to_string(now) + "_" + std::to_string(id) + ".txt"; +} + +template +bool same_vector_contents(const std::vector& actual, const std::vector& expected) { + if (actual.size() != expected.size()) return false; + std::vector a = actual; + std::vector e = expected; + std::sort(a.begin(), a.end()); + std::sort(e.begin(), e.end()); + return a == e; +} + +template +bool same_graph(const std::unordered_map>& actual, + const std::unordered_map>& expected) { + if (actual.size() != expected.size()) return false; + + for (const auto& [key, value] : expected) { + const auto it = actual.find(key); + if (it == actual.end()) return false; + if (!same_vector_contents(it->second, value)) return false; + } + return true; +} + +template +bool same_graph(const std::unordered_map>>& actual, + const std::unordered_map>>& expected) { + if (actual.size() != expected.size()) return false; + + for (const auto& [key, value] : expected) { + const auto it = actual.find(key); + if (it == actual.end()) return false; + + if (it->second.size() != value.size()) return false; + std::vector> a = it->second; + std::vector> e = value; + std::sort(a.begin(), a.end()); + std::sort(e.begin(), e.end()); + if (a != e) return false; + } + return true; +} + +void check_true(bool condition, const std::string& message) { + g_checks_done.fetch_add(1, std::memory_order_relaxed); + if (!condition) { + throw std::runtime_error("ASSERTION FAILED: " + message); + } +} + +std::string capture_output(std::function fn) { + std::ostringstream buffer; + auto* old = std::cout.rdbuf(buffer.rdbuf()); + try { + fn(); + } catch (...) { + std::cout.rdbuf(old); + throw; + } + std::cout.rdbuf(old); + return buffer.str(); +} + +// small helper: ensure node not present anywhere +template +bool node_absent(const std::unordered_map>& g, const node& n) { + if (g.find(n) != g.end()) return false; + for (const auto& [k, v] : g) { + for (const auto& x : v) if (x == n) return false; + } + return true; +} + +template +bool node_absent_weighted(const std::unordered_map>>& g, const node& n) { + if (g.find(n) != g.end()) return false; + for (const auto& [k, v] : g) { + for (const auto& p : v) if (p.first == n) return false; + } + return true; +} + +struct StressConfig { + int iterations = 200; + int max_nodes = 1000; + int max_edges_per_node = 20; + int thread_count = 4; +}; + +void run_unweighted_stress(int thread_id, const StressConfig& cfg, uint32_t seed) { + std::mt19937 rng(seed); + std::uniform_int_distribution node_dist(0, cfg.max_nodes); + std::uniform_int_distribution edges_dist(0, cfg.max_edges_per_node); + + for (int it = 0; it < cfg.iterations; ++it) { + const int N = std::max(0, node_dist(rng)); + std::unordered_map> graph; + graph.reserve(N); + for (int i = 0; i < N; ++i) { + int m = edges_dist(rng); + std::vector adj; + adj.reserve(m); + for (int e = 0; e < m; ++e) { + adj.push_back(rng() % (N + 1)); + } + graph[i] = adj; + } + + const std::string file = unique_temp_path("uw_create"); + graphlib::create_graph(graph, file); + const auto parsed = graphlib::parse(file); + check_true(same_graph(parsed, graph), "roundtrip create->parse mismatch (unweighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + + // random add_nodes + std::unordered_map> new_nodes; + int add_count = std::min(10, cfg.max_nodes / 10 + 1); + for (int a = 0; a < add_count; ++a) { + int key = N + a + 1; + int m = edges_dist(rng); + std::vector adj; + for (int e = 0; e < m; ++e) adj.push_back(rng() % (N + add_count + 1)); + new_nodes[key] = adj; + } + graphlib::add_nodes(new_nodes, file); + for (auto &p : new_nodes) graph[p.first] = p.second; + { + const auto p2 = graphlib::parse(file); + check_true(same_graph(p2, graph), "add_nodes mismatch (unweighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + } + + // random edge replacement + if (!graph.empty()) { + int key = rng() % (N + add_count + 1); + std::vector new_adj; + int m = edges_dist(rng); + for (int e = 0; e < m; ++e) new_adj.push_back(rng() % (N + add_count + 1)); + + // determine whether key exists before calling (current add_edge prints/returns if missing) + bool key_exists = (graph.find(key) != graph.end()); + graphlib::add_edge(new_adj, key, file); + + if (key_exists) { + // add_edge appends neighbors (no duplicates) — merge into local model + auto &vec = graph[key]; + for (auto n : new_adj) { + if (std::find(vec.begin(), vec.end(), n) == vec.end()) vec.push_back(n); + } + } else { + // add_edge does nothing when key missing (implementation prints and returns) + // local graph remains unchanged + } + + const auto p3 = graphlib::parse(file); + check_true(same_graph(p3, graph), "add_edge mismatch (unweighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + } + + // random delete + if (!graph.empty()) { + int candidate = rng() % (N + add_count + 1); + graphlib::delete_instances(std::to_string(candidate), file); + // remove candidate locally + graph.erase(candidate); + for (auto &kv : graph) { + auto &vec = kv.second; + vec.erase(std::remove(vec.begin(), vec.end(), candidate), vec.end()); + } + const auto p4 = graphlib::parse(file); + check_true(same_graph(p4, graph), "delete_instances mismatch (unweighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + check_true(node_absent(p4, candidate), "deleted node still present (unweighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + } + + // traversal sanity + if (!graph.empty()) { + int start = rng() % (graph.size()); + // pick an existing key + auto it = graph.begin(); + std::advance(it, start); + const int start_key = it->first; + auto bfs = graphlib::bfs_algorithm(start_key, file); + auto dfs = graphlib::dfs_algorithm(start_key, file); + check_true(!bfs.empty(), "bfs returned empty unexpectedly (unweighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + check_true(!dfs.empty(), "dfs returned empty unexpectedly (unweighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + } + + std::remove(file.c_str()); + } +} + +void run_weighted_stress(int thread_id, const StressConfig& cfg, uint32_t seed) { + std::mt19937 rng(seed); + std::uniform_int_distribution node_dist(0, cfg.max_nodes); + std::uniform_int_distribution edges_dist(0, cfg.max_edges_per_node); + std::uniform_int_distribution weight_dist(1, 1000); + + for (int it = 0; it < cfg.iterations; ++it) { + const int N = std::max(0, node_dist(rng)); + std::unordered_map>> graph; + graph.reserve(N); + for (int i = 0; i < N; ++i) { + int m = edges_dist(rng); + std::vector> adj; + adj.reserve(m); + for (int e = 0; e < m; ++e) { + adj.emplace_back(rng() % (N + 1), weight_dist(rng)); + } + graph[i] = adj; + } + + const std::string file = unique_temp_path("w_create"); + graphlib::create_graph(graph, file); + const auto parsed = graphlib::parse_weighted(file); + check_true(same_graph(parsed, graph), "roundtrip create->parse mismatch (weighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + + // random add_nodes + std::unordered_map>> new_nodes; + int add_count = std::min(10, cfg.max_nodes / 10 + 1); + for (int a = 0; a < add_count; ++a) { + int key = N + a + 1; + int m = edges_dist(rng); + std::vector> adj; + for (int e = 0; e < m; ++e) adj.emplace_back(rng() % (N + add_count + 1), weight_dist(rng)); + new_nodes[key] = adj; + } + graphlib::add_nodes(new_nodes, file); + for (auto &p : new_nodes) graph[p.first] = p.second; + { + const auto p2 = graphlib::parse_weighted(file); + check_true(same_graph(p2, graph), "add_nodes mismatch (weighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + } + + // random edge replacement + if (!graph.empty()) { + int key = rng() % (N + add_count + 1); + std::vector> new_adj; + int m = edges_dist(rng); + for (int e = 0; e < m; ++e) new_adj.emplace_back(rng() % (N + add_count + 1), weight_dist(rng)); + + bool key_exists = (graph.find(key) != graph.end()); + graphlib::add_edge(new_adj, key, file); + + if (key_exists) { + // add_edge appends weighted neighbors (no duplicate neighbor keys) — merge into local model + auto &vec = graph[key]; + for (auto &p : new_adj) { + auto ex = std::find_if(vec.begin(), vec.end(), [&](const auto &e) { return e.first == p.first; }); + if (ex == vec.end()) vec.push_back(p); + } + } else { + // add_edge does nothing when key missing + } + + const auto p3 = graphlib::parse_weighted(file); + if (!same_graph(p3, graph)) { + std::cerr << "DEBUG: add_edge weighted mismatch\n"; + std::cerr << "file: " << file << " key: " << key << " key_exists: " << key_exists << "\n"; + std::cerr << "new_adj:\n"; + for (auto &pp : new_adj) std::cerr << " (" << pp.first << "," << pp.second << ")\n"; + std::cerr << "expected adjacency for key:\n"; + if (graph.find(key) != graph.end()) { + for (auto &pp : graph[key]) std::cerr << " (" << pp.first << "," << pp.second << ")\n"; + } else { + std::cerr << " \n"; + } + std::cerr << "actual adjacency for key in file:\n"; + auto itf = p3.find(key); + if (itf != p3.end()) { + for (auto &pp : itf->second) std::cerr << " (" << pp.first << "," << pp.second << ")\n"; + } else { + std::cerr << " \n"; + } + check_true(false, "add_edge mismatch (weighted)"); + } else { + g_ops_done.fetch_add(1, std::memory_order_relaxed); + } + } + + // random delete + if (!graph.empty()) { + int candidate = rng() % (N + add_count + 1); + graphlib::delete_instances_weighted(std::to_string(candidate), file); + graph.erase(candidate); + for (auto &kv : graph) { + auto &vec = kv.second; + vec.erase(std::remove_if(vec.begin(), vec.end(), [&](const auto &p) { return p.first == candidate; }), vec.end()); + } + const auto p4 = graphlib::parse_weighted(file); + check_true(same_graph(p4, graph), "delete_instances mismatch (weighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + check_true(node_absent_weighted(p4, candidate), "deleted node still present (weighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + } + + // dijkstra traversal sanity + if (!graph.empty()) { + int start = rng() % (graph.size()); + auto it = graph.begin(); + std::advance(it, start); + const int start_key = it->first; + auto dres = graphlib::dijkstras_algorithm(start_key, file); + check_true(!dres.empty(), "dijkstra returned empty unexpectedly (weighted)"); + g_ops_done.fetch_add(1, std::memory_order_relaxed); + } + + std::remove(file.c_str()); + } +} + +} // namespace + +int main() { + try { + StressConfig cfg; + cfg.iterations = 50; + cfg.max_nodes = 1000; + cfg.max_edges_per_node = 20; + cfg.thread_count = 10; + + std::vector threads; + + // start monitor + auto start_time = std::chrono::steady_clock::now(); + start_monitor(start_time); + + // launch unweighted stressers + for (int t = 0; t < cfg.thread_count; ++t) { + threads.emplace_back(run_unweighted_stress, t, cfg, static_cast(12345 + t)); + } + + // launch weighted stressers + for (int t = 0; t < cfg.thread_count; ++t) { + threads.emplace_back(run_weighted_stress, t, cfg, static_cast(54321 + t)); + } + + for (auto &th : threads) th.join(); + + // stop monitor and print final stats + g_monitor_running = false; + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + std::cerr << "FINAL: total_checks=" << g_checks_done.load() << " total_ops=" << g_ops_done.load() + << " rss_mb=" << (get_rss_bytes() / 1024 / 1024) << std::endl; + + std::cout << "Stress tests completed successfully." << std::endl; + + // run additional tests from tests/test_d_and_g_n.cpp + int rc = run_d_and_g_n_tests(); + if (rc != 0) return rc; + + return 0; + } catch (const std::exception& ex) { + std::cerr << ex.what() << std::endl; + return 1; + } +} \ No newline at end of file diff --git a/tests/test_d_and_g_n.cpp b/tests/test_d_and_g_n.cpp index b591e5f..fd15e0f 100644 --- a/tests/test_d_and_g_n.cpp +++ b/tests/test_d_and_g_n.cpp @@ -1,4 +1,3 @@ -// Generated with the assistance of Artificial intelligence #include #include #include diff --git a/tests/test_weighted_create.cpp b/tests/test_weighted_create.cpp new file mode 100644 index 0000000..30033db --- /dev/null +++ b/tests/test_weighted_create.cpp @@ -0,0 +1,5 @@ +#include "graphlib.hpp" +#include + +using namespace std; +