diff --git a/meson.build b/meson.build index 86aa0c2cb..1e500bbf4 100644 --- a/meson.build +++ b/meson.build @@ -5,4 +5,10 @@ project('pydatastructs', 'cpp', python = import('python').find_installation(pure: false) +if host_machine.system() == 'darwin' + arch = run_command('uname', '-m').stdout().strip() + add_project_arguments('-arch', arch, language: 'cpp') + add_project_link_arguments('-arch', arch, language: 'cpp') +endif + subdir('pydatastructs') diff --git a/pydatastructs/graphs/_backend/cpp/Algorithms.hpp b/pydatastructs/graphs/_backend/cpp/Algorithms.hpp index 2bb362c34..0c168b4c0 100644 --- a/pydatastructs/graphs/_backend/cpp/Algorithms.hpp +++ b/pydatastructs/graphs/_backend/cpp/Algorithms.hpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "GraphEdge.hpp" #include "AdjacencyList.hpp" #include "AdjacencyMatrix.hpp" @@ -457,3 +459,1062 @@ static PyObject* shortest_paths_dijkstra_adjacency_list(PyObject* self, PyObject PyTuple_SetItem(result, 1, pred_dict); return result; } + +static void _visit_kosaraju(AdjacencyListGraph* graph, const std::string& u, + std::unordered_map& visited, + std::unordered_map>& incoming, + std::vector& L) { + visited[u] = true; + AdjacencyListGraphNode* node = graph->node_map[u]; + for (const auto& [v_name, _] : node->adjacent) { + incoming[v_name].push_back(u); + if (!visited[v_name]) { + _visit_kosaraju(graph, v_name, visited, incoming, L); + } + } + L.push_back(u); +} + +static void _assign_kosaraju(AdjacencyListGraph* graph, const std::string& u, + std::unordered_map>& incoming, + std::unordered_map& assigned, + std::unordered_set& comp) { + assigned[u] = true; + comp.insert(u); + if (incoming.count(u)) { + for (const auto& v : incoming[u]) { + if (!assigned[v]) { + _assign_kosaraju(graph, v, incoming, assigned, comp); + } + } + } +} + +static PyObject* strongly_connected_components_kosaraju_adjacency_list(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + static const char* kwlist[] = {"graph", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", const_cast(kwlist), + &AdjacencyListGraphType, &graph_obj)) { + return nullptr; + } + + AdjacencyListGraph* graph = reinterpret_cast(graph_obj); + + std::unordered_map visited; + std::unordered_map> incoming; + std::vector L; + + for (const auto& [name, _] : graph->node_map) { + visited[name] = false; + } + + for (const auto& [name, _] : graph->node_map) { + if (!visited[name]) { + _visit_kosaraju(graph, name, visited, incoming, L); + } + } + + std::unordered_map assigned; + for (const auto& [name, _] : graph->node_map) { + assigned[name] = false; + } + + PyObject* components = PyList_New(0); + if (!components) return nullptr; + + for (int i = L.size() - 1; i >= 0; i--) { + std::unordered_set comp; + if (!assigned[L[i]]) { + _assign_kosaraju(graph, L[i], incoming, assigned, comp); + if (!comp.empty()) { + PyObject* py_comp = PySet_New(nullptr); + if (!py_comp) { + Py_DECREF(components); + return nullptr; + } + for (const auto& node_name : comp) { + PyObject* py_name = PyUnicode_FromString(node_name.c_str()); + if (!py_name || PySet_Add(py_comp, py_name) < 0) { + Py_XDECREF(py_name); + Py_DECREF(py_comp); + Py_DECREF(components); + return nullptr; + } + Py_DECREF(py_name); + } + if (PyList_Append(components, py_comp) < 0) { + Py_DECREF(py_comp); + Py_DECREF(components); + return nullptr; + } + Py_DECREF(py_comp); + } + } + } + + return components; +} + +static PyObject* strongly_connected_components_kosaraju_adjacency_matrix(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + static const char* kwlist[] = {"graph", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", const_cast(kwlist), + &AdjacencyMatrixGraphType, &graph_obj)) { + return nullptr; + } + + AdjacencyMatrixGraph* graph = reinterpret_cast(graph_obj); + + std::unordered_map visited; + std::unordered_map> incoming; + std::vector L; + + for (const auto& [name, _] : graph->node_map) { + visited[name] = false; + } + + std::function visit = [&](const std::string& u) { + visited[u] = true; + if (graph->matrix.count(u)) { + for (const auto& [v, connected] : graph->matrix[u]) { + if (connected) { + incoming[v].push_back(u); + if (!visited[v]) { + visit(v); + } + } + } + } + L.push_back(u); + }; + + for (const auto& [name, _] : graph->node_map) { + if (!visited[name]) { + visit(name); + } + } + + std::unordered_map assigned; + for (const auto& [name, _] : graph->node_map) { + assigned[name] = false; + } + + std::function&)> assign = + [&](const std::string& u, std::unordered_set& comp) { + assigned[u] = true; + comp.insert(u); + if (incoming.count(u)) { + for (const auto& v : incoming[u]) { + if (!assigned[v]) { + assign(v, comp); + } + } + } + }; + + PyObject* components = PyList_New(0); + if (!components) return nullptr; + + for (int i = L.size() - 1; i >= 0; i--) { + std::unordered_set comp; + if (!assigned[L[i]]) { + assign(L[i], comp); + if (!comp.empty()) { + PyObject* py_comp = PySet_New(nullptr); + if (!py_comp) { + Py_DECREF(components); + return nullptr; + } + for (const auto& node_name : comp) { + PyObject* py_name = PyUnicode_FromString(node_name.c_str()); + if (!py_name || PySet_Add(py_comp, py_name) < 0) { + Py_XDECREF(py_name); + Py_DECREF(py_comp); + Py_DECREF(components); + return nullptr; + } + Py_DECREF(py_name); + } + if (PyList_Append(components, py_comp) < 0) { + Py_DECREF(py_comp); + Py_DECREF(components); + return nullptr; + } + Py_DECREF(py_comp); + } + } + } + + return components; +} + +static void _tarjan_dfs(const std::string& u, AdjacencyListGraph* graph, + int& index, std::vector& stack, + std::unordered_map& indices, + std::unordered_map& low_links, + std::unordered_map& on_stacks, + PyObject* components) { + indices[u] = index; + low_links[u] = index; + index++; + stack.push_back(u); + on_stacks[u] = true; + + AdjacencyListGraphNode* node = graph->node_map[u]; + for (const auto& [v, _] : node->adjacent) { + if (indices[v] == -1) { + _tarjan_dfs(v, graph, index, stack, indices, low_links, on_stacks, components); + low_links[u] = std::min(low_links[u], low_links[v]); + } else if (on_stacks[v]) { + low_links[u] = std::min(low_links[u], low_links[v]); + } + } + + if (low_links[u] == indices[u]) { + PyObject* component = PySet_New(nullptr); + while (!stack.empty()) { + std::string w = stack.back(); + stack.pop_back(); + on_stacks[w] = false; + PyObject* py_name = PyUnicode_FromString(w.c_str()); + PySet_Add(component, py_name); + Py_DECREF(py_name); + if (w == u) break; + } + PyList_Append(components, component); + Py_DECREF(component); + } +} + +static PyObject* strongly_connected_components_tarjan_adjacency_list(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + static const char* kwlist[] = {"graph", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", const_cast(kwlist), + &AdjacencyListGraphType, &graph_obj)) { + return nullptr; + } + + AdjacencyListGraph* graph = reinterpret_cast(graph_obj); + + int index = 0; + std::vector stack; + std::unordered_map indices; + std::unordered_map low_links; + std::unordered_map on_stacks; + + for (const auto& [name, _] : graph->node_map) { + indices[name] = -1; + low_links[name] = -1; + on_stacks[name] = false; + } + + PyObject* components = PyList_New(0); + if (!components) return nullptr; + + for (const auto& [name, _] : graph->node_map) { + if (indices[name] == -1) { + _tarjan_dfs(name, graph, index, stack, indices, low_links, on_stacks, components); + } + } + + return components; +} + +static PyObject* strongly_connected_components_tarjan_adjacency_matrix(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + static const char* kwlist[] = {"graph", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", const_cast(kwlist), + &AdjacencyMatrixGraphType, &graph_obj)) { + return nullptr; + } + + AdjacencyMatrixGraph* graph = reinterpret_cast(graph_obj); + + int index = 0; + std::vector stack; + std::unordered_map indices; + std::unordered_map low_links; + std::unordered_map on_stacks; + + for (const auto& [name, _] : graph->node_map) { + indices[name] = -1; + low_links[name] = -1; + on_stacks[name] = false; + } + + PyObject* components = PyList_New(0); + if (!components) return nullptr; + + std::function tarjan_dfs = [&](const std::string& u) { + indices[u] = index; + low_links[u] = index; + index++; + stack.push_back(u); + on_stacks[u] = true; + + if (graph->matrix.count(u)) { + for (const auto& [v, connected] : graph->matrix[u]) { + if (!connected) continue; + if (indices[v] == -1) { + tarjan_dfs(v); + low_links[u] = std::min(low_links[u], low_links[v]); + } else if (on_stacks[v]) { + low_links[u] = std::min(low_links[u], low_links[v]); + } + } + } + + if (low_links[u] == indices[u]) { + PyObject* component = PySet_New(nullptr); + while (!stack.empty()) { + std::string w = stack.back(); + stack.pop_back(); + on_stacks[w] = false; + PyObject* py_name = PyUnicode_FromString(w.c_str()); + PySet_Add(component, py_name); + Py_DECREF(py_name); + if (w == u) break; + } + PyList_Append(components, component); + Py_DECREF(component); + } + }; + + for (const auto& [name, _] : graph->node_map) { + if (indices[name] == -1) { + tarjan_dfs(name); + } + } + + return components; +} + +static PyObject* all_pair_shortest_paths_floyd_warshall_adjacency_list(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + static const char* kwlist[] = {"graph", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", const_cast(kwlist), + &AdjacencyListGraphType, &graph_obj)) { + return nullptr; + } + + AdjacencyListGraph* graph = reinterpret_cast(graph_obj); + + PyObject* dist_dict = PyDict_New(); + PyObject* next_dict = PyDict_New(); + if (!dist_dict || !next_dict) { + Py_XDECREF(dist_dict); + Py_XDECREF(next_dict); + return nullptr; + } + + for (const auto& [v_name, _] : graph->node_map) { + PyObject* v_dist = PyDict_New(); + PyObject* v_next = PyDict_New(); + if (!v_dist || !v_next) { + Py_XDECREF(v_dist); + Py_XDECREF(v_next); + Py_DECREF(dist_dict); + Py_DECREF(next_dict); + return nullptr; + } + PyDict_SetItemString(dist_dict, v_name.c_str(), v_dist); + PyDict_SetItemString(next_dict, v_name.c_str(), v_next); + Py_DECREF(v_dist); + Py_DECREF(v_next); + } + + for (const auto& [edge_key, edge] : graph->edges) { + size_t delim = edge_key.find('_'); + std::string u_name = edge_key.substr(0, delim); + std::string v_name = edge_key.substr(delim + 1); + + double weight = 0.0; + if (edge->value_type == DataType::Int) + weight = static_cast(std::get(edge->value)); + else if (edge->value_type == DataType::Double) + weight = std::get(edge->value); + else + continue; + + PyObject* u_dist = PyDict_GetItemString(dist_dict, u_name.c_str()); + PyObject* u_next = PyDict_GetItemString(next_dict, u_name.c_str()); + + PyObject* weight_obj = PyFloat_FromDouble(weight); + PyObject* source_str = PyUnicode_FromString(u_name.c_str()); + + PyDict_SetItemString(u_dist, v_name.c_str(), weight_obj); + PyDict_SetItemString(u_next, v_name.c_str(), source_str); + + Py_DECREF(weight_obj); + Py_DECREF(source_str); + } + + for (const auto& [v_name, _] : graph->node_map) { + PyObject* v_dist = PyDict_GetItemString(dist_dict, v_name.c_str()); + PyObject* v_next = PyDict_GetItemString(next_dict, v_name.c_str()); + + PyObject* zero = PyFloat_FromDouble(0.0); + PyObject* v_str = PyUnicode_FromString(v_name.c_str()); + + PyDict_SetItemString(v_dist, v_name.c_str(), zero); + PyDict_SetItemString(v_next, v_name.c_str(), v_str); + + Py_DECREF(zero); + Py_DECREF(v_str); + } + + for (const auto& [k_name, _] : graph->node_map) { + for (const auto& [i_name, __] : graph->node_map) { + PyObject* i_dist = PyDict_GetItemString(dist_dict, i_name.c_str()); + PyObject* i_next = PyDict_GetItemString(next_dict, i_name.c_str()); + + for (const auto& [j_name, ___] : graph->node_map) { + PyObject* k_dist = PyDict_GetItemString(dist_dict, k_name.c_str()); + + PyObject* dist_i_j_obj = PyDict_GetItemString(i_dist, j_name.c_str()); + PyObject* dist_i_k_obj = PyDict_GetItemString(i_dist, k_name.c_str()); + PyObject* dist_k_j_obj = PyDict_GetItemString(k_dist, j_name.c_str()); + + double dist_i_j = dist_i_j_obj ? PyFloat_AsDouble(dist_i_j_obj) : INFINITY; + double dist_i_k = dist_i_k_obj ? PyFloat_AsDouble(dist_i_k_obj) : INFINITY; + double dist_k_j = dist_k_j_obj ? PyFloat_AsDouble(dist_k_j_obj) : INFINITY; + + if (dist_i_j > dist_i_k + dist_k_j) { + PyObject* new_dist = PyFloat_FromDouble(dist_i_k + dist_k_j); + PyDict_SetItemString(i_dist, j_name.c_str(), new_dist); + Py_DECREF(new_dist); + + PyObject* next_i_k = PyDict_GetItemString(i_next, k_name.c_str()); + if (next_i_k) { + PyDict_SetItemString(i_next, j_name.c_str(), next_i_k); + } + } + } + } + } + + for (const auto& [i_name, _] : graph->node_map) { + PyObject* i_next = PyDict_GetItemString(next_dict, i_name.c_str()); + PyObject* i_dist = PyDict_GetItemString(dist_dict, i_name.c_str()); + for (const auto& [j_name, __] : graph->node_map) { + if (i_name == j_name) continue; + + PyObject* dist_val = PyDict_GetItemString(i_dist, j_name.c_str()); + double dist = dist_val ? PyFloat_AsDouble(dist_val) : INFINITY; + + if (dist == INFINITY) { + PyDict_SetItemString(i_next, j_name.c_str(), Py_None); + } else { + PyObject* next_val = PyDict_GetItemString(i_next, j_name.c_str()); + if (!next_val) { + PyDict_SetItemString(i_next, j_name.c_str(), Py_None); + } + } + } + } + + PyObject* result = PyTuple_New(2); + if (!result) { + Py_DECREF(dist_dict); + Py_DECREF(next_dict); + return nullptr; + } + + PyTuple_SetItem(result, 0, dist_dict); + PyTuple_SetItem(result, 1, next_dict); + return result; +} + +static PyObject* all_pair_shortest_paths_floyd_warshall_adjacency_matrix(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + static const char* kwlist[] = {"graph", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", const_cast(kwlist), + &AdjacencyMatrixGraphType, &graph_obj)) { + return nullptr; + } + + AdjacencyMatrixGraph* graph = reinterpret_cast(graph_obj); + + PyObject* dist_dict = PyDict_New(); + PyObject* next_dict = PyDict_New(); + if (!dist_dict || !next_dict) { + Py_XDECREF(dist_dict); + Py_XDECREF(next_dict); + return nullptr; + } + + for (const auto& [v_name, _] : graph->node_map) { + PyObject* v_dist = PyDict_New(); + PyObject* v_next = PyDict_New(); + if (!v_dist || !v_next) { + Py_XDECREF(v_dist); + Py_XDECREF(v_next); + Py_DECREF(dist_dict); + Py_DECREF(next_dict); + return nullptr; + } + PyDict_SetItemString(dist_dict, v_name.c_str(), v_dist); + PyDict_SetItemString(next_dict, v_name.c_str(), v_next); + Py_DECREF(v_dist); + Py_DECREF(v_next); + } + + for (const auto& [u_name, neighbors] : graph->matrix) { + for (const auto& [v_name, connected] : neighbors) { + if (!connected) continue; + + std::string edge_key = make_edge_key(u_name, v_name); + if (graph->edge_weights.count(edge_key) == 0) continue; + + GraphEdge* edge = graph->edge_weights[edge_key]; + + double weight = 0.0; + if (edge->value_type == DataType::Int) + weight = static_cast(std::get(edge->value)); + else if (edge->value_type == DataType::Double) + weight = std::get(edge->value); + else + continue; + + PyObject* u_dist = PyDict_GetItemString(dist_dict, u_name.c_str()); + PyObject* u_next = PyDict_GetItemString(next_dict, u_name.c_str()); + + PyObject* weight_obj = PyFloat_FromDouble(weight); + PyObject* source_str = PyUnicode_FromString(u_name.c_str()); + + PyDict_SetItemString(u_dist, v_name.c_str(), weight_obj); + PyDict_SetItemString(u_next, v_name.c_str(), source_str); + + Py_DECREF(weight_obj); + Py_DECREF(source_str); + } + } + + for (const auto& [v_name, _] : graph->node_map) { + PyObject* v_dist = PyDict_GetItemString(dist_dict, v_name.c_str()); + PyObject* v_next = PyDict_GetItemString(next_dict, v_name.c_str()); + + PyObject* zero = PyFloat_FromDouble(0.0); + PyObject* v_str = PyUnicode_FromString(v_name.c_str()); + + PyDict_SetItemString(v_dist, v_name.c_str(), zero); + PyDict_SetItemString(v_next, v_name.c_str(), v_str); + + Py_DECREF(zero); + Py_DECREF(v_str); + } + + for (const auto& [k_name, _] : graph->node_map) { + for (const auto& [i_name, __] : graph->node_map) { + PyObject* i_dist = PyDict_GetItemString(dist_dict, i_name.c_str()); + PyObject* i_next = PyDict_GetItemString(next_dict, i_name.c_str()); + + for (const auto& [j_name, ___] : graph->node_map) { + PyObject* k_dist = PyDict_GetItemString(dist_dict, k_name.c_str()); + + PyObject* dist_i_j_obj = PyDict_GetItemString(i_dist, j_name.c_str()); + PyObject* dist_i_k_obj = PyDict_GetItemString(i_dist, k_name.c_str()); + PyObject* dist_k_j_obj = PyDict_GetItemString(k_dist, j_name.c_str()); + + double dist_i_j = dist_i_j_obj ? PyFloat_AsDouble(dist_i_j_obj) : INFINITY; + double dist_i_k = dist_i_k_obj ? PyFloat_AsDouble(dist_i_k_obj) : INFINITY; + double dist_k_j = dist_k_j_obj ? PyFloat_AsDouble(dist_k_j_obj) : INFINITY; + + if (dist_i_j > dist_i_k + dist_k_j) { + PyObject* new_dist = PyFloat_FromDouble(dist_i_k + dist_k_j); + PyDict_SetItemString(i_dist, j_name.c_str(), new_dist); + Py_DECREF(new_dist); + + PyObject* next_i_k = PyDict_GetItemString(i_next, k_name.c_str()); + if (next_i_k) { + PyDict_SetItemString(i_next, j_name.c_str(), next_i_k); + } + } + } + } + } + + for (const auto& [i_name, _] : graph->node_map) { + PyObject* i_next = PyDict_GetItemString(next_dict, i_name.c_str()); + PyObject* i_dist = PyDict_GetItemString(dist_dict, i_name.c_str()); + for (const auto& [j_name, __] : graph->node_map) { + if (i_name == j_name) continue; + + PyObject* dist_val = PyDict_GetItemString(i_dist, j_name.c_str()); + double dist = dist_val ? PyFloat_AsDouble(dist_val) : INFINITY; + + if (dist == INFINITY) { + PyDict_SetItemString(i_next, j_name.c_str(), Py_None); + } else { + PyObject* next_val = PyDict_GetItemString(i_next, j_name.c_str()); + if (!next_val) { + PyDict_SetItemString(i_next, j_name.c_str(), Py_None); + } + } + } + } + + PyObject* result = PyTuple_New(2); + if (!result) { + Py_DECREF(dist_dict); + Py_DECREF(next_dict); + return nullptr; + } + + PyTuple_SetItem(result, 0, dist_dict); + PyTuple_SetItem(result, 1, next_dict); + return result; +} + +static PyObject* topological_sort_kahn_adjacency_list(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + static const char* kwlist[] = {"graph", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", const_cast(kwlist), + &AdjacencyListGraphType, &graph_obj)) { + return nullptr; + } + + AdjacencyListGraph* graph = reinterpret_cast(graph_obj); + + std::deque S; + std::unordered_map in_degree; + + for (const auto& [name, _] : graph->node_map) { + in_degree[name] = 0; + } + + for (const auto& [u_name, u_node] : graph->node_map) { + for (const auto& [v_name, _] : u_node->adjacent) { + in_degree[v_name]++; + } + } + + for (const auto& [name, degree] : in_degree) { + if (degree == 0) { + S.push_back(name); + } + } + + PyObject* L = PyList_New(0); + if (!L) return nullptr; + + while (!S.empty()) { + std::string n = S.front(); + S.pop_front(); + + PyObject* py_name = PyUnicode_FromString(n.c_str()); + if (!py_name || PyList_Append(L, py_name) < 0) { + Py_XDECREF(py_name); + Py_DECREF(L); + return nullptr; + } + Py_DECREF(py_name); + + AdjacencyListGraphNode* node = graph->node_map[n]; + std::vector neighbors_to_process; + for (const auto& [m_name, _] : node->adjacent) { + neighbors_to_process.push_back(m_name); + } + + for (const auto& m_name : neighbors_to_process) { + std::string key_nm = make_edge_key(n, m_name); + if (graph->edges.count(key_nm)) { + GraphEdge* edge = graph->edges[key_nm]; + Py_DECREF(edge); + graph->edges.erase(key_nm); + } + + node->adjacent.erase(m_name); + + in_degree[m_name]--; + if (in_degree[m_name] == 0) { + S.push_back(m_name); + } + } + } + + for (const auto& [name, degree] : in_degree) { + if (degree > 0) { + Py_DECREF(L); + PyErr_SetString(PyExc_ValueError, "Graph is not acyclic."); + return nullptr; + } + } + + return L; +} + +static PyObject* topological_sort_kahn_adjacency_matrix(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + static const char* kwlist[] = {"graph", nullptr}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!", const_cast(kwlist), + &AdjacencyMatrixGraphType, &graph_obj)) { + return nullptr; + } + + AdjacencyMatrixGraph* graph = reinterpret_cast(graph_obj); + + std::deque S; + std::unordered_map in_degree; + + for (const auto& [name, _] : graph->node_map) { + in_degree[name] = 0; + } + + for (const auto& [u_name, neighbors] : graph->matrix) { + for (const auto& [v_name, connected] : neighbors) { + if (connected) { + in_degree[v_name]++; + } + } + } + + for (const auto& [name, degree] : in_degree) { + if (degree == 0) { + S.push_back(name); + } + } + + PyObject* L = PyList_New(0); + if (!L) return nullptr; + + while (!S.empty()) { + std::string n = S.front(); + S.pop_front(); + + PyObject* py_name = PyUnicode_FromString(n.c_str()); + if (!py_name || PyList_Append(L, py_name) < 0) { + Py_XDECREF(py_name); + Py_DECREF(L); + return nullptr; + } + Py_DECREF(py_name); + + if (graph->matrix.count(n)) { + std::vector neighbors_to_process; + for (const auto& [m_name, connected] : graph->matrix[n]) { + if (connected) { + neighbors_to_process.push_back(m_name); + } + } + + for (const auto& m_name : neighbors_to_process) { + graph->matrix[n][m_name] = false; + + std::string key_nm = make_edge_key(n, m_name); + if (graph->edge_weights.count(key_nm)) { + GraphEdge* edge = graph->edge_weights[key_nm]; + Py_DECREF(edge); + graph->edge_weights.erase(key_nm); + } + + in_degree[m_name]--; + if (in_degree[m_name] == 0) { + S.push_back(m_name); + } + } + } + } + + for (const auto& [name, degree] : in_degree) { + if (degree > 0) { + Py_DECREF(L); + PyErr_SetString(PyExc_ValueError, "Graph is not acyclic."); + return nullptr; + } + } + + return L; +} + +static PyObject* breadth_first_search_max_flow_adjacency_list(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + const char* source_name; + const char* sink_name; + PyObject* flow_passed_dict; + int for_dinic = 0; + + static const char* kwlist[] = {"graph", "source_node", "sink_node", "flow_passed", "for_dinic", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!ssO!|p", const_cast(kwlist), + &AdjacencyListGraphType, &graph_obj, + &source_name, &sink_name, + &PyDict_Type, &flow_passed_dict, + &for_dinic)) { + return nullptr; + } + + AdjacencyListGraph* graph = reinterpret_cast(graph_obj); + + std::deque bfs_queue; + std::unordered_map parent; + std::unordered_map currentPathC; + + currentPathC[source_name] = INFINITY; + bfs_queue.push_back(source_name); + + while (!bfs_queue.empty()) { + std::string curr_node = bfs_queue.front(); + bfs_queue.pop_front(); + + AdjacencyListGraphNode* node = graph->node_map[curr_node]; + if (node->adjacent.empty()) continue; + + for (const auto& [next_name, _] : node->adjacent) { + std::string edge_key = make_edge_key(curr_node, next_name); + if (!graph->edges.count(edge_key)) continue; + + GraphEdge* edge = graph->edges[edge_key]; + double capacity = 0.0; + if (edge->value_type == DataType::Int) + capacity = static_cast(std::get(edge->value)); + else if (edge->value_type == DataType::Double) + capacity = std::get(edge->value); + else + continue; + + PyObject* key_tuple = PyTuple_Pack(2, + PyUnicode_FromString(curr_node.c_str()), + PyUnicode_FromString(next_name.c_str())); + PyObject* fp_obj = PyDict_GetItem(flow_passed_dict, key_tuple); + double fp = fp_obj ? PyFloat_AsDouble(fp_obj) : 0.0; + Py_DECREF(key_tuple); + + if (capacity && parent.find(next_name) == parent.end() && capacity - fp > 0) { + parent[next_name] = curr_node; + double next_flow = std::min(currentPathC[curr_node], capacity - fp); + currentPathC[next_name] = next_flow; + + if (next_name == sink_name && !for_dinic) { + PyObject* parent_dict = PyDict_New(); + for (const auto& [k, v] : parent) { + PyDict_SetItemString(parent_dict, k.c_str(), PyUnicode_FromString(v.c_str())); + } + PyObject* result = PyTuple_Pack(2, PyFloat_FromDouble(next_flow), parent_dict); + Py_DECREF(parent_dict); + return result; + } + bfs_queue.push_back(next_name); + } + } + } + + PyObject* parent_dict = PyDict_New(); + for (const auto& [k, v] : parent) { + PyDict_SetItemString(parent_dict, k.c_str(), PyUnicode_FromString(v.c_str())); + } + PyObject* result = PyTuple_Pack(2, PyFloat_FromDouble(0.0), parent_dict); + Py_DECREF(parent_dict); + return result; +} + +static PyObject* max_flow_edmonds_karp_adjacency_list(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + const char* source_name; + const char* sink_name; + + static const char* kwlist[] = {"graph", "source", "sink", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!ss", const_cast(kwlist), + &AdjacencyListGraphType, &graph_obj, + &source_name, &sink_name)) { + return nullptr; + } + + AdjacencyListGraph* graph = reinterpret_cast(graph_obj); + + double m_flow = 0.0; + PyObject* flow_passed_dict = PyDict_New(); + if (!flow_passed_dict) return nullptr; + + PyObject* bfs_args = Py_BuildValue("(OssOi)", graph_obj, source_name, sink_name, flow_passed_dict, 0); + PyObject* bfs_result = breadth_first_search_max_flow_adjacency_list(self, bfs_args, nullptr); + Py_DECREF(bfs_args); + if (!bfs_result) { + Py_DECREF(flow_passed_dict); + return nullptr; + } + + PyObject* new_flow_obj = PyTuple_GetItem(bfs_result, 0); + PyObject* parent_dict = PyTuple_GetItem(bfs_result, 1); + double new_flow = PyFloat_AsDouble(new_flow_obj); + + while (new_flow != 0.0) { + m_flow += new_flow; + std::string current = sink_name; + + while (current != source_name) { + PyObject* prev_obj = PyDict_GetItemString(parent_dict, current.c_str()); + if (!prev_obj) break; + + const char* prev_cstr = PyUnicode_AsUTF8(prev_obj); + std::string prev = prev_cstr; + + PyObject* key_tuple_forward = PyTuple_Pack(2, + PyUnicode_FromString(prev.c_str()), + PyUnicode_FromString(current.c_str())); + PyObject* fp_forward = PyDict_GetItem(flow_passed_dict, key_tuple_forward); + double fp_forward_val = fp_forward ? PyFloat_AsDouble(fp_forward) : 0.0; + PyDict_SetItem(flow_passed_dict, key_tuple_forward, PyFloat_FromDouble(fp_forward_val + new_flow)); + Py_DECREF(key_tuple_forward); + + PyObject* key_tuple_backward = PyTuple_Pack(2, + PyUnicode_FromString(current.c_str()), + PyUnicode_FromString(prev.c_str())); + PyObject* fp_backward = PyDict_GetItem(flow_passed_dict, key_tuple_backward); + double fp_backward_val = fp_backward ? PyFloat_AsDouble(fp_backward) : 0.0; + PyDict_SetItem(flow_passed_dict, key_tuple_backward, PyFloat_FromDouble(fp_backward_val - new_flow)); + Py_DECREF(key_tuple_backward); + + current = prev; + } + + Py_DECREF(bfs_result); + bfs_args = Py_BuildValue("(OssOi)", graph_obj, source_name, sink_name, flow_passed_dict, 0); + bfs_result = breadth_first_search_max_flow_adjacency_list(self, bfs_args, nullptr); + Py_DECREF(bfs_args); + if (!bfs_result) { + Py_DECREF(flow_passed_dict); + return nullptr; + } + + new_flow_obj = PyTuple_GetItem(bfs_result, 0); + parent_dict = PyTuple_GetItem(bfs_result, 1); + new_flow = PyFloat_AsDouble(new_flow_obj); + } + + Py_DECREF(bfs_result); + Py_DECREF(flow_passed_dict); + + return PyFloat_FromDouble(m_flow); +} + +static double depth_first_search_max_flow_dinic_adjacency_list( + AdjacencyListGraph* graph, + const std::string& u, + std::unordered_map& parent, + const std::string& sink_node, + double flow, + PyObject* flow_passed_dict) { + + if (u == sink_node) { + return flow; + } + + AdjacencyListGraphNode* node = graph->node_map[u]; + if (node->adjacent.empty()) return 0.0; + + for (const auto& [next_name, _] : node->adjacent) { + std::string edge_key = make_edge_key(u, next_name); + if (!graph->edges.count(edge_key)) continue; + + GraphEdge* edge = graph->edges[edge_key]; + double capacity = 0.0; + if (edge->value_type == DataType::Int) + capacity = static_cast(std::get(edge->value)); + else if (edge->value_type == DataType::Double) + capacity = std::get(edge->value); + else + continue; + + PyObject* key_tuple = PyTuple_Pack(2, + PyUnicode_FromString(u.c_str()), + PyUnicode_FromString(next_name.c_str())); + PyObject* fp_obj = PyDict_GetItem(flow_passed_dict, key_tuple); + double fp = fp_obj ? PyFloat_AsDouble(fp_obj) : 0.0; + Py_DECREF(key_tuple); + + if (parent.count(next_name) && parent[next_name] == u && capacity - fp > 0) { + double path_flow = depth_first_search_max_flow_dinic_adjacency_list( + graph, next_name, parent, sink_node, std::min(flow, capacity - fp), flow_passed_dict); + + if (path_flow > 0) { + PyObject* key_tuple_forward = PyTuple_Pack(2, + PyUnicode_FromString(u.c_str()), + PyUnicode_FromString(next_name.c_str())); + PyObject* fp_forward = PyDict_GetItem(flow_passed_dict, key_tuple_forward); + double fp_forward_val = fp_forward ? PyFloat_AsDouble(fp_forward) : 0.0; + PyDict_SetItem(flow_passed_dict, key_tuple_forward, PyFloat_FromDouble(fp_forward_val + path_flow)); + Py_DECREF(key_tuple_forward); + + PyObject* key_tuple_backward = PyTuple_Pack(2, + PyUnicode_FromString(next_name.c_str()), + PyUnicode_FromString(u.c_str())); + PyObject* fp_backward = PyDict_GetItem(flow_passed_dict, key_tuple_backward); + double fp_backward_val = fp_backward ? PyFloat_AsDouble(fp_backward) : 0.0; + PyDict_SetItem(flow_passed_dict, key_tuple_backward, PyFloat_FromDouble(fp_backward_val - path_flow)); + Py_DECREF(key_tuple_backward); + + return path_flow; + } + } + } + + return 0.0; +} + +static PyObject* max_flow_dinic_adjacency_list(PyObject* self, PyObject* args, PyObject* kwargs) { + PyObject* graph_obj; + const char* source_name; + const char* sink_name; + + static const char* kwlist[] = {"graph", "source", "sink", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!ss", const_cast(kwlist), + &AdjacencyListGraphType, &graph_obj, + &source_name, &sink_name)) { + return nullptr; + } + + AdjacencyListGraph* graph = reinterpret_cast(graph_obj); + + double max_flow_val = 0.0; + PyObject* flow_passed_dict = PyDict_New(); + if (!flow_passed_dict) return nullptr; + + while (true) { + PyObject* bfs_args = Py_BuildValue("(OssOi)", graph_obj, source_name, sink_name, flow_passed_dict, 1); + PyObject* bfs_result = breadth_first_search_max_flow_adjacency_list(self, bfs_args, nullptr); + Py_DECREF(bfs_args); + if (!bfs_result) { + Py_DECREF(flow_passed_dict); + return nullptr; + } + + PyObject* parent_dict = PyTuple_GetItem(bfs_result, 1); + PyObject* sink_parent = PyDict_GetItemString(parent_dict, sink_name); + + if (!sink_parent) { + Py_DECREF(bfs_result); + break; + } + + std::unordered_map parent_map; + PyObject *key, *value; + Py_ssize_t pos = 0; + while (PyDict_Next(parent_dict, &pos, &key, &value)) { + parent_map[PyUnicode_AsUTF8(key)] = PyUnicode_AsUTF8(value); + } + + while (true) { + double path_flow = depth_first_search_max_flow_dinic_adjacency_list( + graph, source_name, parent_map, sink_name, INFINITY, flow_passed_dict); + + if (path_flow <= 0) { + break; + } + max_flow_val += path_flow; + } + + Py_DECREF(bfs_result); + } + + Py_DECREF(flow_passed_dict); + + return PyFloat_FromDouble(max_flow_val); +} diff --git a/pydatastructs/graphs/_backend/cpp/graph.cpp b/pydatastructs/graphs/_backend/cpp/graph.cpp index 67b1b4572..9c9a24f7d 100644 --- a/pydatastructs/graphs/_backend/cpp/graph.cpp +++ b/pydatastructs/graphs/_backend/cpp/graph.cpp @@ -14,6 +14,16 @@ static PyMethodDef GraphMethods[] = { {"bfs_adjacency_matrix", (PyCFunction)breadth_first_search_adjacency_matrix, METH_VARARGS | METH_KEYWORDS, "Run BFS on adjacency matrix with callback"}, {"minimum_spanning_tree_prim_adjacency_list", (PyCFunction)minimum_spanning_tree_prim_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Run Prim's algorithm on adjacency list"}, {"shortest_paths_dijkstra_adjacency_list", (PyCFunction)shortest_paths_dijkstra_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Dijkstra's algorithm for adjacency list graphs"}, + {"strongly_connected_components_kosaraju_adjacency_list", (PyCFunction)strongly_connected_components_kosaraju_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Kosaraju's algorithm for adjacency list"}, + {"strongly_connected_components_kosaraju_adjacency_matrix", (PyCFunction)strongly_connected_components_kosaraju_adjacency_matrix, METH_VARARGS | METH_KEYWORDS, "Kosaraju's algorithm for adjacency matrix"}, + {"strongly_connected_components_tarjan_adjacency_list", (PyCFunction)strongly_connected_components_tarjan_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Tarjan's algorithm for adjacency list"}, + {"strongly_connected_components_tarjan_adjacency_matrix", (PyCFunction)strongly_connected_components_tarjan_adjacency_matrix, METH_VARARGS | METH_KEYWORDS, "Tarjan's algorithm for adjacency matrix"}, + {"all_pair_shortest_paths_floyd_warshall_adjacency_list", (PyCFunction)all_pair_shortest_paths_floyd_warshall_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Floyd Warshall for adjacency list"}, + {"all_pair_shortest_paths_floyd_warshall_adjacency_matrix", (PyCFunction)all_pair_shortest_paths_floyd_warshall_adjacency_matrix, METH_VARARGS | METH_KEYWORDS, "Floyd Warshall for adjacency matrix"}, + {"topological_sort_kahn_adjacency_list", (PyCFunction)topological_sort_kahn_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Kahns for adjacency list"}, + {"topological_sort_kahn_adjacency_matrix", (PyCFunction)topological_sort_kahn_adjacency_matrix, METH_VARARGS | METH_KEYWORDS, "Kahns for adjacency matrix"}, + {"max_flow_edmonds_karp_adjacency_list", (PyCFunction)max_flow_edmonds_karp_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Max Flow Edmonds Karp"}, + {"max_flow_dinic_adjacency_list", (PyCFunction)max_flow_dinic_adjacency_list, METH_VARARGS | METH_KEYWORDS, "Max Flow Dinic"}, {NULL, NULL, 0, NULL} }; diff --git a/pydatastructs/graphs/algorithms.py b/pydatastructs/graphs/algorithms.py index 6c2182fed..75a586b58 100644 --- a/pydatastructs/graphs/algorithms.py +++ b/pydatastructs/graphs/algorithms.py @@ -640,16 +640,26 @@ def strongly_connected_components(graph, algorithm, **kwargs): .. [2] https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm """ - raise_if_backend_is_not_python( - strongly_connected_components, kwargs.get('backend', Backend.PYTHON)) - import pydatastructs.graphs.algorithms as algorithms - func = "_strongly_connected_components_" + algorithm + "_" + graph._impl - if not hasattr(algorithms, func): - raise NotImplementedError( - "Currently %s algoithm for %s implementation of graphs " - "isn't implemented for finding strongly connected components." - %(algorithm, graph._impl)) - return getattr(algorithms, func)(graph) + backend = kwargs.get('backend', Backend.PYTHON) + if backend == backend.PYTHON: + import pydatastructs.graphs.algorithms as algorithms + func = "_strongly_connected_components_" + algorithm + "_" + graph._impl + if not hasattr(algorithms, func): + raise NotImplementedError( + "Currently %s algoithm for %s implementation of graphs " + "isn't implemented for finding strongly connected components." + %(algorithm, graph._impl)) + return getattr(algorithms, func)(graph) + elif backend == backend.CPP: + from pydatastructs.graphs._backend.cpp._graph import strongly_connected_components_kosaraju_adjacency_list, strongly_connected_components_kosaraju_adjacency_matrix, strongly_connected_components_tarjan_adjacency_list, strongly_connected_components_tarjan_adjacency_matrix + if (graph._impl == "adjacency_list") and algorithm == 'kosaraju': + return strongly_connected_components_kosaraju_adjacency_list(graph) + if (graph._impl == "adjacency_matrix") and algorithm == 'kosaraju': + return strongly_connected_components_kosaraju_adjacency_matrix(graph) + if (graph._impl == "adjacency_list") and algorithm == 'tarjan': + return strongly_connected_components_tarjan_adjacency_list(graph) + if (graph._impl == "adjacency_matrix") and algorithm == 'tarjan': + return strongly_connected_components_tarjan_adjacency_matrix(graph) def depth_first_search( graph, source_node, operation, *args, **kwargs): @@ -933,15 +943,21 @@ def all_pair_shortest_paths(graph: Graph, algorithm: str, .. [1] https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm .. [2] https://en.wikipedia.org/wiki/Johnson's_algorithm """ - raise_if_backend_is_not_python( - all_pair_shortest_paths, kwargs.get('backend', Backend.PYTHON)) - import pydatastructs.graphs.algorithms as algorithms - func = "_" + algorithm + "_" + graph._impl - if not hasattr(algorithms, func): - raise NotImplementedError( - "Currently %s algorithm isn't implemented for " - "finding shortest paths in graphs."%(algorithm)) - return getattr(algorithms, func)(graph) + backend = kwargs.get('backend', Backend.PYTHON) + if (backend == Backend.PYTHON): + import pydatastructs.graphs.algorithms as algorithms + func = "_" + algorithm + "_" + graph._impl + if not hasattr(algorithms, func): + raise NotImplementedError( + "Currently %s algorithm isn't implemented for " + "finding shortest paths in graphs."%(algorithm)) + return getattr(algorithms, func)(graph) + elif (backend == Backend.CPP): + from pydatastructs.graphs._backend.cpp._graph import all_pair_shortest_paths_floyd_warshall_adjacency_list, all_pair_shortest_paths_floyd_warshall_adjacency_matrix + if (graph._impl == 'adjacency_list' and algorithm == 'floyd_warshall'): + return all_pair_shortest_paths_floyd_warshall_adjacency_list(graph) + if (graph._impl == 'adjacency_matrix' and algorithm == 'floyd_warshall'): + return all_pair_shortest_paths_floyd_warshall_adjacency_matrix(graph) def _floyd_warshall_adjacency_list(graph: Graph): dist, next_vertex = {}, {} @@ -1061,15 +1077,21 @@ def topological_sort(graph: Graph, algorithm: str, .. [1] https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm """ - raise_if_backend_is_not_python( - topological_sort, kwargs.get('backend', Backend.PYTHON)) - import pydatastructs.graphs.algorithms as algorithms - func = "_" + algorithm + "_" + graph._impl - if not hasattr(algorithms, func): - raise NotImplementedError( - "Currently %s algorithm isn't implemented for " - "performing topological sort on %s graphs."%(algorithm, graph._impl)) - return getattr(algorithms, func)(graph) + backend = kwargs.get('backend', Backend.PYTHON) + if backend == Backend.PYTHON: + import pydatastructs.graphs.algorithms as algorithms + func = "_" + algorithm + "_" + graph._impl + if not hasattr(algorithms, func): + raise NotImplementedError( + "Currently %s algorithm isn't implemented for " + "performing topological sort on %s graphs."%(algorithm, graph._impl)) + return getattr(algorithms, func)(graph) + elif (backend == Backend.CPP): + from pydatastructs.graphs._backend.cpp._graph import topological_sort_kahn_adjacency_list, topological_sort_kahn_adjacency_matrix + if (graph._impl == 'adjacency_list'): + return topological_sort_kahn_adjacency_list(graph) + if (graph._impl == 'adjacency_matrix'): + return topological_sort_kahn_adjacency_matrix(graph) def _kahn_adjacency_list(graph: Graph) -> list: S = Queue() @@ -1271,16 +1293,21 @@ def _max_flow_dinic_(graph: Graph, source, sink): def max_flow(graph, source, sink, algorithm='edmonds_karp', **kwargs): - raise_if_backend_is_not_python( - max_flow, kwargs.get('backend', Backend.PYTHON)) - - import pydatastructs.graphs.algorithms as algorithms - func = "_max_flow_" + algorithm + "_" - if not hasattr(algorithms, func): - raise NotImplementedError( - f"Currently {algorithm} algorithm isn't implemented for " - "performing max flow on graphs.") - return getattr(algorithms, func)(graph, source, sink) + backend = kwargs.get('backend', Backend.PYTHON) + if backend == Backend.PYTHON: + import pydatastructs.graphs.algorithms as algorithms + func = "_max_flow_" + algorithm + "_" + if not hasattr(algorithms, func): + raise NotImplementedError( + f"Currently {algorithm} algorithm isn't implemented for " + "performing max flow on graphs.") + return getattr(algorithms, func)(graph, source, sink) + elif (backend == Backend.CPP): + from pydatastructs.graphs._backend.cpp._graph import max_flow_edmonds_karp_adjacency_list, max_flow_dinic_adjacency_list + if (graph._impl == 'adjacency_list' and algorithm=='edmonds_karp'): + return max_flow_edmonds_karp_adjacency_list(graph, source, sink) + if (graph._impl == 'adjacency_list' and algorithm=='dinic'): + return max_flow_dinic_adjacency_list(graph, source, sink) def find_bridges(graph): diff --git a/pydatastructs/graphs/tests/test_algorithms.py b/pydatastructs/graphs/tests/test_algorithms.py index 7dd2e1b78..6e732e66d 100644 --- a/pydatastructs/graphs/tests/test_algorithms.py +++ b/pydatastructs/graphs/tests/test_algorithms.py @@ -260,11 +260,77 @@ def _test_strongly_connected_components(func, ds, algorithm, *args): expected_comps = [{'e', 'a', 'b'}, {'d', 'c', 'h'}, {'g', 'f'}] assert comps.sort() == expected_comps.sort() + def _test_strongly_connected_components_cpp(algorithm): + a = AdjacencyListGraphNode('a', 0, backend=Backend.CPP) + b = AdjacencyListGraphNode('b', 0, backend=Backend.CPP) + c = AdjacencyListGraphNode('c', 0, backend=Backend.CPP) + d = AdjacencyListGraphNode('d', 0, backend=Backend.CPP) + e = AdjacencyListGraphNode('e', 0, backend=Backend.CPP) + f = AdjacencyListGraphNode('f', 0, backend=Backend.CPP) + g = AdjacencyListGraphNode('g', 0, backend=Backend.CPP) + h = AdjacencyListGraphNode('h', 0, backend=Backend.CPP) + + graph = Graph(a, b, c, d, e, f, g, h, backend=Backend.CPP) + graph.add_edge('a', 'b') + graph.add_edge('b', 'c') + graph.add_edge('b', 'f') + graph.add_edge('b', 'e') + graph.add_edge('c', 'd') + graph.add_edge('c', 'g') + graph.add_edge('d', 'h') + graph.add_edge('d', 'c') + graph.add_edge('e', 'f') + graph.add_edge('e', 'a') + graph.add_edge('f', 'g') + graph.add_edge('g', 'f') + graph.add_edge('h', 'd') + graph.add_edge('h', 'g') + + comps = strongly_connected_components(graph, algorithm, backend=Backend.CPP) + expected_comps = [{'e', 'a', 'b'}, {'d', 'c', 'h'}, {'g', 'f'}] + comps_sorted = sorted([tuple(sorted(comp)) for comp in comps]) + expected_sorted = sorted([tuple(sorted(comp)) for comp in expected_comps]) + assert comps_sorted == expected_sorted + + a2 = AdjacencyMatrixGraphNode('a', 0, backend=Backend.CPP) + b2 = AdjacencyMatrixGraphNode('b', 0, backend=Backend.CPP) + c2 = AdjacencyMatrixGraphNode('c', 0, backend=Backend.CPP) + d2 = AdjacencyMatrixGraphNode('d', 0, backend=Backend.CPP) + e2 = AdjacencyMatrixGraphNode('e', 0, backend=Backend.CPP) + f2 = AdjacencyMatrixGraphNode('f', 0, backend=Backend.CPP) + g2 = AdjacencyMatrixGraphNode('g', 0, backend=Backend.CPP) + h2 = AdjacencyMatrixGraphNode('h', 0, backend=Backend.CPP) + + graph2 = Graph(a2, b2, c2, d2, e2, f2, g2, h2, implementation='adjacency_matrix', backend=Backend.CPP) + graph2.add_edge('a', 'b') + graph2.add_edge('b', 'c') + graph2.add_edge('b', 'f') + graph2.add_edge('b', 'e') + graph2.add_edge('c', 'd') + graph2.add_edge('c', 'g') + graph2.add_edge('d', 'h') + graph2.add_edge('d', 'c') + graph2.add_edge('e', 'f') + graph2.add_edge('e', 'a') + graph2.add_edge('f', 'g') + graph2.add_edge('g', 'f') + graph2.add_edge('h', 'd') + graph2.add_edge('h', 'g') + + comps2 = strongly_connected_components(graph2, algorithm, backend=Backend.CPP) + expected_comps2 = [{'e', 'a', 'b'}, {'d', 'c', 'h'}, {'g', 'f'}] + + comps2_sorted = sorted([tuple(sorted(comp)) for comp in comps2]) + expected2_sorted = sorted([tuple(sorted(comp)) for comp in expected_comps2]) + assert comps2_sorted == expected2_sorted + scc = strongly_connected_components _test_strongly_connected_components(scc, "List", "kosaraju") _test_strongly_connected_components(scc, "Matrix", "kosaraju") _test_strongly_connected_components(scc, "List", "tarjan") _test_strongly_connected_components(scc, "Matrix", "tarjan") + _test_strongly_connected_components_cpp('kosaraju') + _test_strongly_connected_components_cpp('tarjan') def test_depth_first_search(): @@ -413,34 +479,6 @@ def _test_shortest_paths_negative_edges(ds, algorithm): _test_shortest_paths_positive_edges("List", 'dijkstra') _test_shortest_paths_positive_edges("Matrix", 'dijkstra') -def test_all_pair_shortest_paths(): - - def _test_shortest_paths_negative_edges(ds, algorithm): - import pydatastructs.utils.misc_util as utils - GraphNode = getattr(utils, "Adjacency" + ds + "GraphNode") - vertices = [GraphNode('1'), GraphNode('2'), - GraphNode('3'), GraphNode('4')] - - graph = Graph(*vertices) - graph.add_edge('1', '3', -2) - graph.add_edge('2', '1', 4) - graph.add_edge('2', '3', 3) - graph.add_edge('3', '4', 2) - graph.add_edge('4', '2', -1) - dist, next_v = all_pair_shortest_paths(graph, algorithm) - assert dist == {'1': {'3': -2, '1': 0, '4': 0, '2': -1}, - '2': {'1': 4, '3': 2, '2': 0, '4': 4}, - '3': {'4': 2, '3': 0, '1': 5, '2': 1}, - '4': {'2': -1, '4': 0, '1': 3, '3': 1}} - assert next_v == {'1': {'3': '1', '1': '1', '4': None, '2': None}, - '2': {'1': '2', '3': None, '2': '2', '4': None}, - '3': {'4': '3', '3': '3', '1': None, '2': None}, - '4': {'2': '4', '4': '4', '1': None, '3': None}} - - _test_shortest_paths_negative_edges("List", 'floyd_warshall') - _test_shortest_paths_negative_edges("Matrix", 'floyd_warshall') - _test_shortest_paths_negative_edges("List", 'johnson') - def test_topological_sort(): def _test_topological_sort(func, ds, algorithm, threads=None): @@ -468,6 +506,58 @@ def _test_topological_sort(func, ds, algorithm, threads=None): [(l2 in l[3:5]) for l2 in ('8', '11')] + [(l3 in l[5:]) for l3 in ('10', '9', '2')]) + if ds == 'List' and algorithm == 'kahn' and threads is None: + vertices2 = [AdjacencyListGraphNode('2', backend=Backend.CPP), + AdjacencyListGraphNode('3', backend=Backend.CPP), + AdjacencyListGraphNode('5', backend=Backend.CPP), + AdjacencyListGraphNode('7', backend=Backend.CPP), + AdjacencyListGraphNode('8', backend=Backend.CPP), + AdjacencyListGraphNode('10', backend=Backend.CPP), + AdjacencyListGraphNode('11', backend=Backend.CPP), + AdjacencyListGraphNode('9', backend=Backend.CPP)] + + graph2 = Graph(*vertices2, backend=Backend.CPP) + graph2.add_edge('5', '11') + graph2.add_edge('7', '11') + graph2.add_edge('7', '8') + graph2.add_edge('3', '8') + graph2.add_edge('3', '10') + graph2.add_edge('11', '2') + graph2.add_edge('11', '9') + graph2.add_edge('11', '10') + graph2.add_edge('8', '9') + + l2 = func(graph2, algorithm, backend=Backend.CPP) + assert all([(l1 in l2[0:3]) for l1 in ('3', '5', '7')] + + [(l2_item in l2[3:5]) for l2_item in ('8', '11')] + + [(l3 in l2[5:]) for l3 in ('10', '9', '2')]) + + if ds == 'Matrix' and algorithm == 'kahn': + vertices3 = [AdjacencyMatrixGraphNode('2', backend=Backend.CPP), + AdjacencyMatrixGraphNode('3', backend=Backend.CPP), + AdjacencyMatrixGraphNode('5', backend=Backend.CPP), + AdjacencyMatrixGraphNode('7', backend=Backend.CPP), + AdjacencyMatrixGraphNode('8', backend=Backend.CPP), + AdjacencyMatrixGraphNode('10', backend=Backend.CPP), + AdjacencyMatrixGraphNode('11', backend=Backend.CPP), + AdjacencyMatrixGraphNode('9', backend=Backend.CPP)] + + graph3 = Graph(*vertices3, implementation='adjacency_matrix', backend=Backend.CPP) + graph3.add_edge('5', '11') + graph3.add_edge('7', '11') + graph3.add_edge('7', '8') + graph3.add_edge('3', '8') + graph3.add_edge('3', '10') + graph3.add_edge('11', '2') + graph3.add_edge('11', '9') + graph3.add_edge('11', '10') + graph3.add_edge('8', '9') + + l3 = func(graph3, algorithm, backend=Backend.CPP) + assert all([(l1 in l3[0:3]) for l1 in ('3', '5', '7')] + + [(l2_item in l3[3:5]) for l2_item in ('8', '11')] + + [(l3_item in l3[5:]) for l3_item in ('10', '9', '2')]) + _test_topological_sort(topological_sort, "List", "kahn") _test_topological_sort(topological_sort_parallel, "List", "kahn", 3) @@ -534,6 +624,70 @@ def _test_max_flow(ds, algorithm): assert max_flow(G3, 'a', 'd', algorithm) == 5 assert max_flow(G3, 'a', 'b', algorithm) == 3 + if ds == 'List' and algorithm == 'edmonds_karp': + a = AdjacencyListGraphNode('a', backend=Backend.CPP) + b = AdjacencyListGraphNode('b', backend=Backend.CPP) + c = AdjacencyListGraphNode('c', backend=Backend.CPP) + d = AdjacencyListGraphNode('d', backend=Backend.CPP) + e = AdjacencyListGraphNode('e', backend=Backend.CPP) + G_cpp = Graph(a, b, c, d, e, backend=Backend.CPP) + G_cpp.add_edge('a', 'b', 3) + G_cpp.add_edge('a', 'c', 4) + G_cpp.add_edge('b', 'c', 2) + G_cpp.add_edge('b', 'd', 3) + G_cpp.add_edge('c', 'd', 1) + G_cpp.add_edge('d', 'e', 6) + assert max_flow(G_cpp, 'a', 'e', algorithm, backend=Backend.CPP) == 4 + assert max_flow(G_cpp, 'a', 'c', algorithm, backend=Backend.CPP) == 6 + + a = AdjacencyListGraphNode('a', backend=Backend.CPP) + b = AdjacencyListGraphNode('b', backend=Backend.CPP) + c = AdjacencyListGraphNode('c', backend=Backend.CPP) + d = AdjacencyListGraphNode('d', backend=Backend.CPP) + e = AdjacencyListGraphNode('e', backend=Backend.CPP) + f = AdjacencyListGraphNode('f', backend=Backend.CPP) + G2_cpp = Graph(a, b, c, d, e, f, backend=Backend.CPP) + G2_cpp.add_edge('a', 'b', 16) + G2_cpp.add_edge('a', 'c', 13) + G2_cpp.add_edge('b', 'c', 10) + G2_cpp.add_edge('b', 'd', 12) + G2_cpp.add_edge('c', 'b', 4) + G2_cpp.add_edge('c', 'e', 14) + G2_cpp.add_edge('d', 'c', 9) + G2_cpp.add_edge('d', 'f', 20) + G2_cpp.add_edge('e', 'd', 7) + G2_cpp.add_edge('e', 'f', 4) + assert max_flow(G2_cpp, 'a', 'f', algorithm, backend=Backend.CPP) == 23 + assert max_flow(G2_cpp, 'a', 'd', algorithm, backend=Backend.CPP) == 19 + + if ds == 'List' and algorithm == 'dinic': + a = AdjacencyListGraphNode('a', backend=Backend.CPP) + b = AdjacencyListGraphNode('b', backend=Backend.CPP) + c = AdjacencyListGraphNode('c', backend=Backend.CPP) + d = AdjacencyListGraphNode('d', backend=Backend.CPP) + e = AdjacencyListGraphNode('e', backend=Backend.CPP) + G_cpp = Graph(a, b, c, d, e, backend=Backend.CPP) + G_cpp.add_edge('a', 'b', 3) + G_cpp.add_edge('a', 'c', 4) + G_cpp.add_edge('b', 'c', 2) + G_cpp.add_edge('b', 'd', 3) + G_cpp.add_edge('c', 'd', 1) + G_cpp.add_edge('d', 'e', 6) + assert max_flow(G_cpp, 'a', 'e', algorithm, backend=Backend.CPP) == 4 + assert max_flow(G_cpp, 'a', 'c', algorithm, backend=Backend.CPP) == 6 + + a = AdjacencyListGraphNode('a', backend=Backend.CPP) + b = AdjacencyListGraphNode('b', backend=Backend.CPP) + c = AdjacencyListGraphNode('c', backend=Backend.CPP) + d = AdjacencyListGraphNode('d', backend=Backend.CPP) + G3_cpp = Graph(a, b, c, d, backend=Backend.CPP) + G3_cpp.add_edge('a', 'b', 3) + G3_cpp.add_edge('a', 'c', 2) + G3_cpp.add_edge('b', 'c', 2) + G3_cpp.add_edge('b', 'd', 3) + G3_cpp.add_edge('c', 'd', 2) + assert max_flow(G3_cpp, 'a', 'd', algorithm, backend=Backend.CPP) == 5 + assert max_flow(G3_cpp, 'a', 'b', algorithm, backend=Backend.CPP) == 3 _test_max_flow("List", "edmonds_karp") _test_max_flow("Matrix", "edmonds_karp")