diff --git a/NEWS.md b/NEWS.md index e4dc47bb984..c2711596355 100644 --- a/NEWS.md +++ b/NEWS.md @@ -35,12 +35,22 @@ To see all issues & pull requests closed by this release see the * [#3101](https://github.com/pgRouting/pgrouting/issues/3101): pgr_edgeColoring not building graph correctly +**New experimental functions.** + +* Planar + + * pgr_makeBiconnectedPlanar + **Summary of changes by function** * pgr_edgeColoring * Fix the way it builds the graph +* pgr_makeBiconnectedPlanar + + * New experimental function. + ## pgRouting 4.0 diff --git a/doc/_static/page_history.js b/doc/_static/page_history.js index f158646394d..aaab168099e 100644 --- a/doc/_static/page_history.js +++ b/doc/_static/page_history.js @@ -16,6 +16,8 @@ var titles = [ var newpages = [ + {v: '4.1', pages: ['pgr_makeBiconnectedPlanar']}, + {v: '4.0', pages: ['pgr_bandwidth', 'pgr_kingOrdering', 'pgr_sloanOrdering']}, {v: '3.8', pages: ['pgr_contractionDeadEnd', 'pgr_contractionLinear', 'pgr_separateCrossing', diff --git a/doc/planar/CMakeLists.txt b/doc/planar/CMakeLists.txt index d9b910d35ea..9fe1c6c797b 100644 --- a/doc/planar/CMakeLists.txt +++ b/doc/planar/CMakeLists.txt @@ -3,7 +3,9 @@ # License: GPL-2 See https://github.com/pgRouting/pgrouting/blob/main/LICENSE set(LOCAL_FILES + planar-family.rst pgr_isPlanar.rst + pgr_makeBiconnectedPlanar.rst ) foreach (f ${LOCAL_FILES}) diff --git a/doc/planar/images/CMakeLists.txt b/doc/planar/images/CMakeLists.txt index 55c40e368f1..28cda8de07b 100644 --- a/doc/planar/images/CMakeLists.txt +++ b/doc/planar/images/CMakeLists.txt @@ -3,6 +3,9 @@ # License: GPL-2 See https://github.com/pgRouting/pgrouting/blob/main/LICENSE set(LOCAL_FILES nonPlanar.png + biconnected_planar_sampledata.png + biconnected_line_before.png + biconnected_line_after.png ) foreach (f ${LOCAL_FILES}) diff --git a/doc/planar/images/biconnected_line_after.png b/doc/planar/images/biconnected_line_after.png new file mode 100644 index 00000000000..b8a8a77a3c6 Binary files /dev/null and b/doc/planar/images/biconnected_line_after.png differ diff --git a/doc/planar/images/biconnected_line_before.png b/doc/planar/images/biconnected_line_before.png new file mode 100644 index 00000000000..ad03c26b293 Binary files /dev/null and b/doc/planar/images/biconnected_line_before.png differ diff --git a/doc/planar/images/biconnected_planar_sampledata.png b/doc/planar/images/biconnected_planar_sampledata.png new file mode 100644 index 00000000000..937f56a7862 Binary files /dev/null and b/doc/planar/images/biconnected_planar_sampledata.png differ diff --git a/doc/planar/pgr_makeBiconnectedPlanar.rst b/doc/planar/pgr_makeBiconnectedPlanar.rst new file mode 100644 index 00000000000..55ded3ae781 --- /dev/null +++ b/doc/planar/pgr_makeBiconnectedPlanar.rst @@ -0,0 +1,164 @@ +:file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2020-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 + +.. index:: + single: Planar Family ; pgr_makeBiconnectedPlanar - Experimental + single: makeBiconnectedPlanar - Experimental on v4.1 + +| + +``pgr_makeBiconnectedPlanar`` - Experimental +=============================================================================== + +``pgr_makeBiconnectedPlanar`` — Returns the set of edges needed to make each connected component of a planar graph biconnected. + +.. include:: experimental.rst + :start-after: warning-begin + :end-before: end-warning + +.. rubric:: Availability + +.. rubric:: Version 4.1.0 + +* New experimental function. + + +Description +------------------------------------------------------------------------------- + +``pgr_makeBiconnectedPlanar`` identifies the missing edges that need to be added to each +connected component of an existing planar graph to make those components biconnected, +while ensuring the graph remains planar. + +A graph is considered **biconnected** if it is connected and cannot be broken into +disconnected pieces by deleting any single vertex (it has no articulation points). +A planar graph is one that can be drawn in two-dimensional space with no two of +its edges crossing. + +The main characteristics are: + +* Works for **undirected** graphs. +* Works for **planar** graphs only. +* If any component of the input graph is not planar, no added edges are returned for that component. +* Returns a list of all new edges needed to make each connected component of the graph biconnected. +* The algorithm does not consider traversal costs in the calculations. +* The algorithm does not consider geometric topology in the calculations. +* Running time: :math:`O(|V_G| + |E_G| + R \log R)` where :math:`G(V_G, E_G)` is the input graph and :math:`R` is the number of returned edges. + +|Boost| Boost Graph Inside + +Signatures +------------------------------------------------------------------------------- + +.. admonition:: \ \ + :class: signatures + + | pgr_makeBiconnectedPlanar(`Edges SQL`_) + + | Returns set of |result-component-make| + | OR EMPTY SET + +:Example: List of edges that are needed to make the graph biconnected planar. + +**Sample graph before:** + +.. figure:: /images/Fig6-undirected.png + :scale: 50% + + Sample graph before + +**Output:** + +.. literalinclude:: makeBiconnectedPlanar.queries + :start-after: -- q1 + :end-before: -- q2 + +**Sample graph after adding biconnecting edges:** + +.. figure:: images/biconnected_planar_sampledata.png + :scale: 75% + + Sample graph after adding biconnecting edges (olive = original edges, red dashed = 4 new biconnecting edges). + Note: When a graph contains multiple disconnected components, each component is processed independently. Components (2,4) and (13,14) are already biconnected (0 articulation points) and require no new edges. + +Parameters +------------------------------------------------------------------------------- + +.. include:: pgRouting-concepts.rst + :start-after: only_edge_param_start + :end-before: only_edge_param_end + +Inner Queries +------------------------------------------------------------------------------- + +Edges SQL +............................................................................... + +.. include:: pgRouting-concepts.rst + :start-after: basic_edges_sql_start + :end-before: basic_edges_sql_end + +Result columns +------------------------------------------------------------------------------- + +Returns set of |result-component-make| + +.. list-table:: + :width: 81 + :widths: auto + :header-rows: 1 + + * - Column + - Type + - Description + * - ``seq`` + - ``BIGINT`` + - Sequential value starting from **1**. + * - ``start_vid`` + - ``BIGINT`` + - Identifier of the first end point vertex of the edge. + * - ``end_vid`` + - ``BIGINT`` + - Identifier of the second end point vertex of the edge. + +Additional Examples +------------------------------------------------------------------------------- + +:Example: Biconnecting a simple 4-vertex line graph (path graph). + +.. literalinclude:: makeBiconnectedPlanar.queries + :start-after: -- q2 + :end-before: -- q3 + +**Sample graph before:** + +.. figure:: images/biconnected_line_before.png + :scale: 75% + + Sample 4-vertex line graph before biconnecting (vertices 2 and 3 are articulation points). + +**Output:** + +.. literalinclude:: makeBiconnectedPlanar.queries + :start-after: -- q3 + :end-before: -- q4 + +**Sample graph after adding biconnecting edges:** + +.. figure:: images/biconnected_line_after.png + :scale: 75% + + Biconnected planar graph after adding new edges to eliminate articulation points without crossing. + +See Also +------------------------------------------------------------------------------- + +* `Boost: make_biconnected_planar + `__ +* :doc:`sampledata` + +.. rubric:: Indices and tables + +* :ref:`genindex` +* :ref:`search` diff --git a/doc/planar/planar-family.rst b/doc/planar/planar-family.rst new file mode 100644 index 00000000000..35cca502c41 --- /dev/null +++ b/doc/planar/planar-family.rst @@ -0,0 +1,33 @@ +:file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2020-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 + +.. index:: Planar Family + +| + +Planar - Family of functions +=============================================================================== + +.. include:: experimental.rst + :start-after: warning-begin + :end-before: end-warning + +.. experimental-start + +* :doc:`pgr_isPlanar` - Returns a boolean depending upon the planarity of the graph. +* :doc:`pgr_makeBiconnectedPlanar` - Returns edges to add to a planar graph to make each connected component biconnected. + +.. experimental-end + + +.. toctree:: + :hidden: + + pgr_isPlanar + pgr_makeBiconnectedPlanar + +.. rubric:: Indices and tables + +* :ref:`genindex` +* :ref:`search` diff --git a/doc/src/experimental.rst b/doc/src/experimental.rst index 859a8cb19b4..878a56ece1a 100644 --- a/doc/src/experimental.rst +++ b/doc/src/experimental.rst @@ -129,12 +129,12 @@ Experimental Functions .. rubric:: Planar Family -- :doc:`pgr_isPlanar` +- :doc:`planar-family` .. toctree:: :hidden: - pgr_isPlanar + planar-family .. rubric:: Miscellaneous Algorithms diff --git a/doc/src/release_notes.rst b/doc/src/release_notes.rst index 40390c34c70..4ec0d45062f 100644 --- a/doc/src/release_notes.rst +++ b/doc/src/release_notes.rst @@ -60,6 +60,12 @@ To see all issues & pull requests closed by this release see the * :issue:`3101`: pgr_edgeColoring not building graph correctly +.. rubric:: New experimental functions. + +* Planar + + * pgr_makeBiconnectedPlanar + .. rubric:: Summary of changes by function * pgr_edgeColoring @@ -68,6 +74,12 @@ To see all issues & pull requests closed by this release see the :start-after: Version 4.1.0 :end-before: .. rubric +* pgr_makeBiconnectedPlanar + + .. include:: pgr_makeBiconnectedPlanar.rst + :start-after: Version 4.1.0 + :end-before: Description + pgRouting 4.0 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/docqueries/planar/CMakeLists.txt b/docqueries/planar/CMakeLists.txt index 1995c7014e6..a906ad7ed3d 100644 --- a/docqueries/planar/CMakeLists.txt +++ b/docqueries/planar/CMakeLists.txt @@ -5,6 +5,7 @@ set(LOCAL_FILES boyerMyrvold isPlanar + makeBiconnectedPlanar ) foreach (f ${LOCAL_FILES}) diff --git a/docqueries/planar/makeBiconnectedPlanar.pg b/docqueries/planar/makeBiconnectedPlanar.pg new file mode 100644 index 00000000000..3ec79a6aa45 --- /dev/null +++ b/docqueries/planar/makeBiconnectedPlanar.pg @@ -0,0 +1,25 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2020-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ +/* -- q1 */ +SELECT * FROM pgr_makeBiconnectedPlanar( + 'SELECT id, source, target, cost, reverse_cost FROM edges' +); +/* -- q2 */ +CREATE TABLE example_edges_bi ( + id SERIAL PRIMARY KEY, + source INTEGER, + target INTEGER, + cost DOUBLE PRECISION, + reverse_cost DOUBLE PRECISION +); +INSERT INTO example_edges_bi (source, target, cost, reverse_cost) VALUES +(1, 2, 1, 1), +(2, 3, 1, 1), +(3, 4, 1, 1); + +/* -- q3 */ +SELECT * FROM pgr_makeBiconnectedPlanar( + 'SELECT id, source, target, cost, reverse_cost FROM example_edges_bi' +); +/* -- q4 */ diff --git a/docqueries/planar/makeBiconnectedPlanar.result b/docqueries/planar/makeBiconnectedPlanar.result new file mode 100644 index 00000000000..65e10a890d3 --- /dev/null +++ b/docqueries/planar/makeBiconnectedPlanar.result @@ -0,0 +1,46 @@ +BEGIN; +BEGIN +SET client_min_messages TO NOTICE; +SET +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2020-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ +/* -- q1 */ +SELECT * FROM pgr_makeBiconnectedPlanar( + 'SELECT id, source, target, cost, reverse_cost FROM edges' +); + seq | start_vid | end_vid +-----+-----------+--------- + 1 | 1 | 7 + 2 | 3 | 11 + 3 | 5 | 10 + 4 | 7 | 9 +(4 rows) + +/* -- q2 */ +CREATE TABLE example_edges_bi ( + id SERIAL PRIMARY KEY, + source INTEGER, + target INTEGER, + cost DOUBLE PRECISION, + reverse_cost DOUBLE PRECISION +); +CREATE TABLE +INSERT INTO example_edges_bi (source, target, cost, reverse_cost) VALUES +(1, 2, 1, 1), +(2, 3, 1, 1), +(3, 4, 1, 1); +INSERT 0 3 +/* -- q3 */ +SELECT * FROM pgr_makeBiconnectedPlanar( + 'SELECT id, source, target, cost, reverse_cost FROM example_edges_bi' +); + seq | start_vid | end_vid +-----+-----------+--------- + 1 | 1 | 3 + 2 | 2 | 4 +(2 rows) + +/* -- q4 */ +ROLLBACK; +ROLLBACK diff --git a/docqueries/planar/test.conf b/docqueries/planar/test.conf index 3c33912e3db..359fad8eb21 100644 --- a/docqueries/planar/test.conf +++ b/docqueries/planar/test.conf @@ -7,6 +7,7 @@ 'any' => { 'files' => [qw( isPlanar.pg + makeBiconnectedPlanar.pg )] }, diff --git a/include/c_common/enums.h b/include/c_common/enums.h index e2195070d13..5b9f30771e8 100644 --- a/include/c_common/enums.h +++ b/include/c_common/enums.h @@ -52,6 +52,8 @@ enum Which { // NOLINT(cppcoreguidelines-use-enum-class) MAXFLOW, PUSHRELABEL, BOYKOV, EDMONDSKARP, /* For coloring */ EDGECOLORING, BIPARTITE, SEQUENTIAL, + /* For planar */ + BICONNECTEDPLANAR, /* For components */ CONNECTEDCOMPONENTS, BICONNECTEDCOMPONENTS, STRONGCOMPONENTS, ARTICULATIONPOINTS, BRIDGES, MAKECONNECTED diff --git a/include/planar/makeBiconnectedPlanar.hpp b/include/planar/makeBiconnectedPlanar.hpp new file mode 100644 index 00000000000..634ebc3227f --- /dev/null +++ b/include/planar/makeBiconnectedPlanar.hpp @@ -0,0 +1,184 @@ +/*PGR-GNU***************************************************************** +File: makeBiconnectedPlanar.hpp + +Copyright (c) 2020-2026 pgRouting developers +Mail: project@pgrouting.org + +Copyright (c) 2026 Mohit Rawat +Mail: mohit25rawat at gmail.com + +------ +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + ********************************************************************PGR-GNU*/ + +#ifndef INCLUDE_PLANAR_MAKEBICONNECTEDPLANAR_HPP_ +#define INCLUDE_PLANAR_MAKEBICONNECTEDPLANAR_HPP_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "c_types/ii_t_rt.h" +#include "cpp_common/edge_t.hpp" +#include "cpp_common/messages.hpp" +#include "cpp_common/base_graph.hpp" +#include "cpp_common/interruption.hpp" + +namespace pgrouting { +namespace functions { + +template +class Pgr_makeBiconnectedPlanar : public pgrouting::Pgr_messages { + public: + typedef typename G::V V; + typedef typename G::E E; + typedef typename G::E_i E_i; + + std::vector makeBiconnectedPlanar(G &graph) { + /* Process based on connected components */ + std::vector component(boost::num_vertices(graph.graph)); + auto num_components = boost::connected_components( + graph.graph, &component[0]); + + if (num_components == 1) { + /* Single connected component */ + return generateMakeBiconnectedPlanar(graph); + } + + /* Multiple connected components */ + log << "Graph has " << num_components + << " connected components. Processing each independently.\n"; + + /* Group edges by component */ + std::vector> comp_edges(num_components); + E_i ei, ei_end; + for (boost::tie(ei, ei_end) = edges(graph.graph); + ei != ei_end; ++ei) { + V src_v = boost::source(*ei, graph.graph); + size_t c = component[src_v]; + Edge_t e; + e.id = graph[*ei].id; + e.source = graph[src_v].id; + e.target = graph[boost::target(*ei, graph.graph)].id; + e.cost = graph[*ei].cost; + e.reverse_cost = -1; + comp_edges[c].push_back(e); + } + + std::vector all_results; + for (size_t c = 0; c < num_components; ++c) { + if (comp_edges[c].empty()) continue; + CHECK_FOR_INTERRUPTS(); + G sub_graph; + sub_graph.insert_edges(comp_edges[c]); + auto sub_results = generateMakeBiconnectedPlanar(sub_graph); + all_results.insert( + all_results.end(), + sub_results.begin(), sub_results.end()); + } + std::sort(all_results.begin(), all_results.end(), [](const II_t_rt &a, const II_t_rt &b) { + if (a.d1 != b.d1) return a.d1 < b.d1; + return a.d2 < b.d2; + }); + return all_results; + } + + private: + struct planar_visitor { + std::vector& m_results; + G& m_graph; + + planar_visitor(std::vector& results, G& graph) + : m_results(results), m_graph(graph) {} + + template + void visit_vertex_pair(Vertex u, Vertex v, BGraph& g) { + boost::add_edge(u, v, g); + m_results.push_back({m_graph[u].id, m_graph[v].id}); + } + }; + + std::vector generateMakeBiconnectedPlanar(G &graph) { + log << "Number of edges before: " << boost::num_edges(graph.graph) << "\n"; + + E_i ei, ei_end; + std::map edge_id_map; + size_t edge_count = 0; + for (boost::tie(ei, ei_end) = edges(graph.graph); ei != ei_end; ++ei) { + edge_id_map[*ei] = edge_count++; + } + boost::associative_property_map> + e_index(edge_id_map); + + typedef std::vector::edge_descriptor> vec_t; + std::vector embedding(boost::num_vertices(graph.graph)); + + /* abort in case of an interruption occurs (e.g. the query is being cancelled) */ + CHECK_FOR_INTERRUPTS(); + + bool is_planar = boost::boyer_myrvold_planarity_test( + boost::boyer_myrvold_params::graph = graph.graph, + boost::boyer_myrvold_params::embedding = &embedding[0]); + + if (!is_planar) { + notice << "Graph is not planar\n"; + return std::vector(); + } + + /* Sub-graphs are guaranteed to be connected at this point */ + + std::vector results; + planar_visitor vis(results, graph); + + /* abort in case of an interruption occurs (e.g. the query is being cancelled) */ + CHECK_FOR_INTERRUPTS(); + boost::make_biconnected_planar(graph.graph, &embedding[0], e_index, vis); + + log << "Number of edges after: " << boost::num_edges(graph.graph) << "\n"; + for (const auto& r : results) { + log << "src:" << r.d1 << " tgt:" << r.d2 << "\n"; + } + + for (auto &edge : results) { + if (edge.d1 > edge.d2) { + std::swap(edge.d1, edge.d2); + } + } + std::sort(results.begin(), results.end(), [](const II_t_rt &a, const II_t_rt &b) { + if (a.d1 != b.d1) return a.d1 < b.d1; + return a.d2 < b.d2; + }); + + return results; + } +}; + +} // namespace functions +} // namespace pgrouting + +#endif // INCLUDE_PLANAR_MAKEBICONNECTEDPLANAR_HPP_ diff --git a/pgtap/planar/makeBiconnectedPlanar/edge_cases.pg b/pgtap/planar/makeBiconnectedPlanar/edge_cases.pg new file mode 100644 index 00000000000..e0de126d500 --- /dev/null +++ b/pgtap/planar/makeBiconnectedPlanar/edge_cases.pg @@ -0,0 +1,90 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2018-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ + +BEGIN; + +SELECT CASE WHEN min_version('4.1.0') THEN plan(6) ELSE plan(1) END; + +CREATE OR REPLACE FUNCTION edge_cases() +RETURNS SETOF TEXT AS +$BODY$ +BEGIN + +IF NOT min_version('4.1.0') THEN + RETURN QUERY + SELECT skip(1, 'Function is new on 4.1.0'); + RETURN; +END IF; + +-- 0 edge, 0 vertex test + +RETURN QUERY +SELECT is_empty( + $$SELECT * FROM pgr_makeBiconnectedPlanar('SELECT id, source, target, cost, reverse_cost FROM edges WHERE false')$$, + '1: Graph with 0 edges and 0 vertices is empty'); + +-- 1 vertex test + +--6 -- 6 +RETURN QUERY +SELECT is_empty( + $$SELECT * FROM pgr_makeBiconnectedPlanar('SELECT 2 AS id, 6 AS source, 6 AS target, 1 AS cost, 1 AS reverse_cost')$$, + '2: Graph with 1 vertex and 1 edge returns empty set of edges'); + +-- 2 vertex test(already biconnected) + +--5 -- 6 +RETURN QUERY +SELECT is_empty( + $$SELECT * FROM pgr_makeBiconnectedPlanar( + 'SELECT 1 AS id,5 AS source,6 AS target, 1 AS cost,1 AS reverse_cost')$$, + '3: Graph with 2 vertices returns empty set of edges'); +-- 4 vertex test (path graph 1-2-3-4, needs 1 edge to become biconnected) +RETURN QUERY +SELECT isnt_empty( + $$SELECT * FROM pgr_makeBiconnectedPlanar( + 'SELECT 1 AS id, 1 AS source, 2 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 2 AS id, 2 AS source, 3 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 3 AS id, 3 AS source, 4 AS target, 1 AS cost, 1 AS reverse_cost' + )$$, + '4: Graph with 4 vertices in a path returns new edges to make it biconnected'); + +-- 5 vertex non-planar graph (K5) +RETURN QUERY +SELECT is_empty( + $$SELECT * FROM pgr_makeBiconnectedPlanar( + 'SELECT 1 AS id, 1 AS source, 2 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 2 AS id, 1 AS source, 3 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 3 AS id, 1 AS source, 4 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 4 AS id, 1 AS source, 5 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 5 AS id, 2 AS source, 3 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 6 AS id, 2 AS source, 4 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 7 AS id, 2 AS source, 5 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 8 AS id, 3 AS source, 4 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 9 AS id, 3 AS source, 5 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 10 AS id, 4 AS source, 5 AS target, 1 AS cost, 1 AS reverse_cost' + )$$, + '5: Non-planar graph (K5) returns empty set'); + +-- Disconnected graph: 4-vertex path (1-2-3-4) and 3-vertex path (5-6-7) +-- Each component needs at least 1 added edge to become biconnected +RETURN QUERY +SELECT isnt_empty( + $$SELECT * FROM pgr_makeBiconnectedPlanar( + 'SELECT 1 AS id, 1 AS source, 2 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 2 AS id, 2 AS source, 3 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 3 AS id, 3 AS source, 4 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 4 AS id, 5 AS source, 6 AS target, 1 AS cost, 1 AS reverse_cost UNION + SELECT 5 AS id, 6 AS source, 7 AS target, 1 AS cost, 1 AS reverse_cost' + )$$, + '6: Disconnected graph processes components independently and returns new edges for components that need them'); + +END; +$BODY$ +LANGUAGE plpgsql; + +SELECT edge_cases(); + +SELECT * FROM finish(); +ROLLBACK; diff --git a/pgtap/planar/makeBiconnectedPlanar/inner_query.pg b/pgtap/planar/makeBiconnectedPlanar/inner_query.pg new file mode 100644 index 00000000000..d456b80b482 --- /dev/null +++ b/pgtap/planar/makeBiconnectedPlanar/inner_query.pg @@ -0,0 +1,31 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2020-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ + + +BEGIN; + +UPDATE edges SET cost = sign(cost), reverse_cost = sign(reverse_cost); +SELECT CASE WHEN min_version('4.1.0') THEN plan (54) ELSE plan(1) END; + +CREATE OR REPLACE FUNCTION inner_query() +RETURNS SETOF TEXT AS +$BODY$ +BEGIN + +IF NOT min_version('4.1.0') THEN + RETURN QUERY + SELECT skip(1, 'Function is new on 4.1.0'); + RETURN; +END IF; + +RETURN QUERY SELECT style_dijkstra('pgr_makeBiconnectedPlanar(', ')'); + +END; +$BODY$ +LANGUAGE plpgsql; + +SELECT inner_query(); + +SELECT finish(); +ROLLBACK; diff --git a/pgtap/planar/makeBiconnectedPlanar/no_crash_test.pg b/pgtap/planar/makeBiconnectedPlanar/no_crash_test.pg new file mode 100644 index 00000000000..cc74e3e8d4d --- /dev/null +++ b/pgtap/planar/makeBiconnectedPlanar/no_crash_test.pg @@ -0,0 +1,44 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2018-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ + +BEGIN; + +UPDATE edges SET cost = sign(cost), reverse_cost = sign(reverse_cost); +SELECT CASE WHEN min_version('4.1.0') THEN plan (5) ELSE plan(1) END; + +PREPARE edges AS +SELECT id, source, target, cost, reverse_cost FROM edges; + +CREATE OR REPLACE FUNCTION test_function() +RETURNS SETOF TEXT AS +$BODY$ +DECLARE +params TEXT[]; +subs TEXT[]; +BEGIN + IF NOT min_version('4.1.0') THEN + RETURN QUERY + SELECT skip(1, 'Function is new on 4.1.0'); + RETURN; + END IF; + + RETURN QUERY + SELECT isnt_empty('edges', 'Should not be empty true to tests be meaningful'); + + params = ARRAY['$$SELECT id, source, target, cost, reverse_cost FROM edges$$']::TEXT[]; + subs = ARRAY[ + 'NULL' + ]::TEXT[]; + + RETURN QUERY + SELECT * FROM no_crash_test('pgr_makebiconnectedplanar', params, subs); + +END +$BODY$ +LANGUAGE plpgsql VOLATILE; + + +SELECT * FROM test_function(); +SELECT finish(); +ROLLBACK; diff --git a/pgtap/planar/makeBiconnectedPlanar/types_check.pg b/pgtap/planar/makeBiconnectedPlanar/types_check.pg new file mode 100644 index 00000000000..48bad4c1905 --- /dev/null +++ b/pgtap/planar/makeBiconnectedPlanar/types_check.pg @@ -0,0 +1,41 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2018-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ + +BEGIN; + +SELECT CASE WHEN NOT min_version('4.1.0') THEN plan(1) ELSE plan(4) END; + +CREATE OR REPLACE FUNCTION types_check() +RETURNS SETOF TEXT AS +$BODY$ +BEGIN + + IF NOT min_version('4.1.0') THEN + RETURN QUERY + SELECT skip(1, 'Function is new on 4.1.0'); + RETURN; + END IF; + + RETURN QUERY SELECT has_function('pgr_makebiconnectedplanar'); + RETURN QUERY SELECT function_returns('pgr_makebiconnectedplanar', ARRAY['text'], 'setof record'); + + RETURN QUERY + SELECT function_args_eq('pgr_makebiconnectedplanar', + $$SELECT '{"",seq,start_vid,end_vid}'::TEXT[] $$ + ); + + RETURN QUERY + SELECT function_types_eq('pgr_makebiconnectedplanar', + $$VALUES + ('{text,int8,int8,int8}'::TEXT[]) + $$ + ); +END; +$BODY$ +LANGUAGE plpgsql; + +SELECT types_check(); + +SELECT * FROM finish(); +ROLLBACK; diff --git a/sql/planar/CMakeLists.txt b/sql/planar/CMakeLists.txt index 44a7574d299..fc93e621af4 100644 --- a/sql/planar/CMakeLists.txt +++ b/sql/planar/CMakeLists.txt @@ -5,6 +5,8 @@ set(LOCAL_FILES _isPlanar.sql isPlanar.sql + _makeBiconnectedPlanar.sql + makeBiconnectedPlanar.sql ) foreach (f ${LOCAL_FILES}) diff --git a/sql/planar/_makeBiconnectedPlanar.sql b/sql/planar/_makeBiconnectedPlanar.sql new file mode 100644 index 00000000000..3d026540ca0 --- /dev/null +++ b/sql/planar/_makeBiconnectedPlanar.sql @@ -0,0 +1,47 @@ +/*PGR-GNU***************************************************************** +File: _makeBiconnectedPlanar.sql + +Copyright (c) 2015-2026 pgRouting developers +Mail: project@pgrouting.org + +Copyright (c) 2026 Mohit Rawat +Mail: mohit25rawat at gmail.com + +------ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + ********************************************************************PGR-GNU*/ +------------------------- +------------------------- +-- _makeBiconnectedPlanar +------------------------- +------------------------- + +--v4.1 +CREATE FUNCTION _pgr_makeBiconnectedPlanar( + TEXT, -- edges_sql (required) + + OUT seq BIGINT, + OUT start_vid BIGINT, + OUT end_vid BIGINT) + +RETURNS SETOF RECORD AS +'MODULE_PATHNAME' +LANGUAGE c VOLATILE STRICT; + + +COMMENT ON FUNCTION _pgr_makeBiconnectedPlanar(TEXT) +IS 'pgRouting internal function'; diff --git a/sql/planar/makeBiconnectedPlanar.sql b/sql/planar/makeBiconnectedPlanar.sql new file mode 100644 index 00000000000..e55fdb28d06 --- /dev/null +++ b/sql/planar/makeBiconnectedPlanar.sql @@ -0,0 +1,57 @@ +/*PGR-GNU***************************************************************** +File: makeBiconnectedPlanar.sql + +Copyright (c) 2007-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Mohit Rawat +Mail: mohit25rawat at gmail.com + +------ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + ********************************************************************PGR-GNU*/ + +------------------ +-- pgr_makeBiconnectedPlanar +------------------ + +--v4.1 +CREATE FUNCTION pgr_makeBiconnectedPlanar( + TEXT, -- edges_sql (required) + OUT seq BIGINT, + OUT start_vid BIGINT, + OUT end_vid BIGINT) + +RETURNS SETOF RECORD AS +$BODY$ + SELECT seq, start_vid, end_vid + FROM _pgr_makeBiconnectedPlanar(_pgr_get_statement($1)); +$BODY$ +LANGUAGE SQL VOLATILE STRICT +COST ${COST_HIGH} ROWS ${ROWS_HIGH}; + + +COMMENT ON FUNCTION pgr_makeBiconnectedPlanar(TEXT) +IS 'pgr_makeBiconnectedPlanar +- EXPERIMENTAL +- Undirected graph +- Parameters: + - edges SQL with columns: id, source, target, cost [,reverse_cost] +- Documentation: + - ${PROJECT_DOC_LINK}/pgr_makeBiconnectedPlanar.html +'; diff --git a/sql/sigs/pgrouting--4.1.sig b/sql/sigs/pgrouting--4.1.sig index 2e8a3eb33bf..774cfca0bdd 100644 --- a/sql/sigs/pgrouting--4.1.sig +++ b/sql/sigs/pgrouting--4.1.sig @@ -190,6 +190,8 @@ _pgr_linegraphfull(text) pgr_linegraphfull(text) _pgr_linegraph(text,boolean) pgr_linegraph(text,boolean) +_pgr_makebiconnectedplanar(text) +pgr_makebiconnectedplanar(text) _pgr_makeconnected(text) pgr_makeconnected(text) pgr_maxcardinalitymatch(text) diff --git a/src/coloring/coloring_driver.cpp b/src/coloring/coloring_driver.cpp index d70cf709912..e9d2aa49cdf 100644 --- a/src/coloring/coloring_driver.cpp +++ b/src/coloring/coloring_driver.cpp @@ -34,6 +34,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #include #include #include +#include #include "c_types/ii_t_rt.h" #include "cpp_common/base_graph.hpp" @@ -43,6 +44,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #include "cpp_common/alloc.hpp" #include "cpp_common/assert.hpp" +#include "planar/makeBiconnectedPlanar.hpp" #include "coloring/bipartite.hpp" #include "coloring/edgeColoring.hpp" #include "coloring/sequentialVertexColoring.hpp" @@ -119,6 +121,13 @@ void do_coloring( undigraph.insert_edges(edges); switch (which) { + case BICONNECTEDPLANAR: + { + pgrouting::functions::Pgr_makeBiconnectedPlanar fn; + results = fn.makeBiconnectedPlanar(undigraph); + log << fn.get_log(); + } + break; case EDGECOLORING: results = edgeColoring(undigraph); break; diff --git a/src/cpp_common/utilities.cpp b/src/cpp_common/utilities.cpp index 71e366c8555..10e2a57b94b 100644 --- a/src/cpp_common/utilities.cpp +++ b/src/cpp_common/utilities.cpp @@ -104,6 +104,8 @@ get_name(Which which) { case EDMONDSKARP: return "pgr_edmondsKarp"; break; + case BICONNECTEDPLANAR: + return "pgr_makeBiconnectedPlanar"; case ARTICULATIONPOINTS: return "pgr_articulationPoints"; diff --git a/src/planar/CMakeLists.txt b/src/planar/CMakeLists.txt index a7187b065ee..9551eb95101 100644 --- a/src/planar/CMakeLists.txt +++ b/src/planar/CMakeLists.txt @@ -4,4 +4,5 @@ ADD_LIBRARY(planar OBJECT isPlanar.c isPlanar_driver.cpp - ) + makeBiconnectedPlanar.c +) diff --git a/src/planar/makeBiconnectedPlanar.c b/src/planar/makeBiconnectedPlanar.c new file mode 100644 index 00000000000..6b4a5ab1f4c --- /dev/null +++ b/src/planar/makeBiconnectedPlanar.c @@ -0,0 +1,101 @@ +/*PGR-GNU***************************************************************** +File: makeBiconnectedPlanar.c + +Generated with Template by: +Copyright (c) 2015-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Mohit Rawat +Mail: mohit25rawat at gmail.com + +------ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + ********************************************************************PGR-GNU*/ + +#include +#include "c_common/postgres_connection.h" +#include "c_types/ii_t_rt.h" +#include "process/coloring_process.h" + +PGDLLEXPORT Datum _pgr_makebiconnectedplanar(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(_pgr_makebiconnectedplanar); + +PGDLLEXPORT Datum _pgr_makebiconnectedplanar(PG_FUNCTION_ARGS) { + FuncCallContext *funcctx; + TupleDesc tuple_desc; + + II_t_rt *result_tuples = NULL; + size_t result_count = 0; + + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + pgr_process_coloring( + text_to_cstring(PG_GETARG_TEXT_P(0)), + false, + + BICONNECTEDPLANAR, + &result_tuples, + &result_count); + + funcctx->max_calls = result_count; + funcctx->user_fctx = result_tuples; + if (get_call_result_type(fcinfo, NULL, &tuple_desc) + != TYPEFUNC_COMPOSITE) { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + } + + funcctx->tuple_desc = tuple_desc; + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + tuple_desc = funcctx->tuple_desc; + result_tuples = (II_t_rt*) funcctx->user_fctx; + uint64_t call_cntr = funcctx->call_cntr; + + if (call_cntr < funcctx->max_calls) { + HeapTuple tuple; + Datum result; + Datum *values; + bool *nulls; + + size_t num = 3; + values = palloc(num * sizeof(Datum)); + nulls = palloc(num * sizeof(bool)); + size_t i; + for (i = 0; i < num; ++i) { + nulls[i] = false; + } + + values[0] = Int64GetDatum(call_cntr + 1); + values[1] = Int64GetDatum(result_tuples[call_cntr].d1); + values[2] = Int64GetDatum(result_tuples[call_cntr].d2); + + tuple = heap_form_tuple(tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + SRF_RETURN_NEXT(funcctx, result); + } else { + SRF_RETURN_DONE(funcctx); + } +}