From 57d84a00af5ef063b356736cb8b9e82a67ad71b0 Mon Sep 17 00:00:00 2001 From: Mohit Rawat Date: Fri, 21 Aug 2026 12:39:35 +0530 Subject: [PATCH 1/6] [makeBiconnectedPlanar/sql] Adding SQL code for pgr_makeBiconnectedPlanar --- sql/planar/CMakeLists.txt | 2 + sql/planar/_makeBiconnectedPlanar.sql | 47 ++++++++++++++++++++++ sql/planar/makeBiconnectedPlanar.sql | 57 +++++++++++++++++++++++++++ sql/sigs/pgrouting--4.1.sig | 2 + 4 files changed, 108 insertions(+) create mode 100644 sql/planar/_makeBiconnectedPlanar.sql create mode 100644 sql/planar/makeBiconnectedPlanar.sql 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) From da249a2d67c053f79dd2834828b6cd0eb91264a6 Mon Sep 17 00:00:00 2001 From: Mohit Rawat Date: Fri, 21 Aug 2026 12:39:43 +0530 Subject: [PATCH 2/6] [makeBiconnectedPlanar/C/C++] Adding C/C++ code for pgr_makeBiconnectedPlanar --- include/c_common/enums.h | 2 + include/planar/makeBiconnectedPlanar.hpp | 184 +++++++++++++++++++++++ src/coloring/coloring_driver.cpp | 9 ++ src/cpp_common/utilities.cpp | 2 + src/planar/CMakeLists.txt | 3 +- src/planar/makeBiconnectedPlanar.c | 101 +++++++++++++ 6 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 include/planar/makeBiconnectedPlanar.hpp create mode 100644 src/planar/makeBiconnectedPlanar.c 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/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); + } +} From 639530d4045362f64635967f0b69e77a16a3ef6c Mon Sep 17 00:00:00 2001 From: Mohit Rawat Date: Fri, 21 Aug 2026 12:39:51 +0530 Subject: [PATCH 3/6] [makeBiconnectedPlanar/pgtap] Adding test files for pgr_makeBiconnectedPlanar --- .../makeBiconnectedPlanar/edge_cases.pg | 90 +++++++++++++++++++ .../makeBiconnectedPlanar/inner_query.pg | 31 +++++++ .../makeBiconnectedPlanar/no_crash_test.pg | 44 +++++++++ .../makeBiconnectedPlanar/types_check.pg | 41 +++++++++ 4 files changed, 206 insertions(+) create mode 100644 pgtap/planar/makeBiconnectedPlanar/edge_cases.pg create mode 100644 pgtap/planar/makeBiconnectedPlanar/inner_query.pg create mode 100644 pgtap/planar/makeBiconnectedPlanar/no_crash_test.pg create mode 100644 pgtap/planar/makeBiconnectedPlanar/types_check.pg 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; From 1ddb5271dc7cf361668874780bb7b6c75ae8dfd8 Mon Sep 17 00:00:00 2001 From: Mohit Rawat Date: Fri, 21 Aug 2026 12:40:03 +0530 Subject: [PATCH 4/6] [makeBiconnectedPlanar/docqueries] Adding test documentation examples code for pgr_makeBiconnectedPlanar --- docqueries/planar/CMakeLists.txt | 1 + docqueries/planar/makeBiconnectedPlanar.pg | 25 ++++++++++ .../planar/makeBiconnectedPlanar.result | 46 +++++++++++++++++++ docqueries/planar/test.conf | 1 + 4 files changed, 73 insertions(+) create mode 100644 docqueries/planar/makeBiconnectedPlanar.pg create mode 100644 docqueries/planar/makeBiconnectedPlanar.result 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 )] }, From 528bf209ee743d8557cf710adc0345eeda6e670d Mon Sep 17 00:00:00 2001 From: Mohit Rawat Date: Fri, 21 Aug 2026 12:40:45 +0530 Subject: [PATCH 5/6] [makeBiconnectedPlanar/doc] Adding documentation for pgr_makeBiconnectedPlanar --- doc/_static/page_history.js | 2 + doc/planar/CMakeLists.txt | 2 + doc/planar/images/CMakeLists.txt | 3 + doc/planar/images/biconnected_line_after.png | Bin 0 -> 16661 bytes doc/planar/images/biconnected_line_before.png | Bin 0 -> 4011 bytes .../images/biconnected_planar_sampledata.png | Bin 0 -> 40580 bytes doc/planar/pgr_makeBiconnectedPlanar.rst | 164 ++++++++++++++++++ doc/planar/planar-family.rst | 33 ++++ doc/src/experimental.rst | 4 +- 9 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 doc/planar/images/biconnected_line_after.png create mode 100644 doc/planar/images/biconnected_line_before.png create mode 100644 doc/planar/images/biconnected_planar_sampledata.png create mode 100644 doc/planar/pgr_makeBiconnectedPlanar.rst create mode 100644 doc/planar/planar-family.rst 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 0000000000000000000000000000000000000000..b8a8a77a3c6e3bd7f71998223882a874f3c39a00 GIT binary patch literal 16661 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 G74$#EMZ1px literal 0 HcmV?d00001 diff --git a/doc/planar/images/biconnected_planar_sampledata.png b/doc/planar/images/biconnected_planar_sampledata.png new file mode 100644 index 0000000000000000000000000000000000000000..937f56a7862e0584a53c19af5c2523cd8954a72c GIT binary patch literal 40580 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

&mUEX0Col-6i#yP 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 P17x44tDnm{r-UW|nfD`__ +* :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 From e66e04d3ac6389ea28119ce0a459914552bdb9c1 Mon Sep 17 00:00:00 2001 From: Mohit Rawat Date: Fri, 21 Aug 2026 12:41:21 +0530 Subject: [PATCH 6/6] Updating release notes and NEWS about the new function pgr_makeBiconnectedPlanar --- NEWS.md | 10 ++++++++++ doc/src/release_notes.rst | 12 ++++++++++++ 2 files changed, 22 insertions(+) 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/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 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++