diff --git a/NEWS.md b/NEWS.md index e4dc47bb984..ac3b7301418 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_makeMaximalPlanar + **Summary of changes by function** * pgr_edgeColoring * Fix the way it builds the graph +* pgr_makeMaximalPlanar + + * New experimental function. + ## pgRouting 4.0 @@ -272,6 +282,7 @@ Summary of changes by function * All signatures promoted to official. + * pgr_maxCardinalityMatch .. Breaking change @@ -463,6 +474,7 @@ New experimental functions. * [#2954](https://github.com/pgRouting/pgrouting/issues/2954): pgr_kingOrdering * [#2955](https://github.com/pgRouting/pgrouting/issues/2955): pgr_sloanOrdering + SQL signatures and output standardization [#2904](https://github.com/pgRouting/pgrouting/issues/2904): Standardize output columns of functions with different output diff --git a/doc/_static/page_history.js b/doc/_static/page_history.js index f158646394d..aec4cee3793 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_makeMaximalPlanar']}, + {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..010015ecf7b 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_makeMaximalPlanar.rst ) foreach (f ${LOCAL_FILES}) diff --git a/doc/planar/images/CMakeLists.txt b/doc/planar/images/CMakeLists.txt index 55c40e368f1..cbddf514685 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 + maximal_planar_sampledata.png + maximal_planar_5cycle.png + maximal_planar_5cycle_before.png ) foreach (f ${LOCAL_FILES}) diff --git a/doc/planar/images/maximal_planar_5cycle.png b/doc/planar/images/maximal_planar_5cycle.png new file mode 100644 index 00000000000..f4e0b2c4ade Binary files /dev/null and b/doc/planar/images/maximal_planar_5cycle.png differ diff --git a/doc/planar/images/maximal_planar_5cycle_before.png b/doc/planar/images/maximal_planar_5cycle_before.png new file mode 100644 index 00000000000..f5181b6bb42 Binary files /dev/null and b/doc/planar/images/maximal_planar_5cycle_before.png differ diff --git a/doc/planar/images/maximal_planar_sampledata.png b/doc/planar/images/maximal_planar_sampledata.png new file mode 100644 index 00000000000..b9eacb9e6b1 Binary files /dev/null and b/doc/planar/images/maximal_planar_sampledata.png differ diff --git a/doc/planar/pgr_makeMaximalPlanar.rst b/doc/planar/pgr_makeMaximalPlanar.rst new file mode 100644 index 00000000000..3755ba59614 --- /dev/null +++ b/doc/planar/pgr_makeMaximalPlanar.rst @@ -0,0 +1,163 @@ +: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_makeMaximalPlanar - Experimental + single: makeMaximalPlanar - Experimental on v4.1 + +| + +``pgr_makeMaximalPlanar`` - Experimental +=============================================================================== + +``pgr_makeMaximalPlanar`` — Returns the set of edges needed to make a planar graph maximal planar. + +.. include:: experimental.rst + :start-after: warning-begin + :end-before: end-warning + +.. rubric:: Availability + +.. rubric:: Version 4.1.0 + +* New experimental function. + + +Description +------------------------------------------------------------------------------- + +``pgr_makeMaximalPlanar`` identifies the missing edges that need to be added to +the connected components of a planar graph to make each eligible component maximal planar. + +A planar graph is considered **maximal planar** (or fully triangulated) if no additional +edges can be added to it without violating its planarity. In a maximal planar graph, +every face (including the outer face) is a triangle. + +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 triangulate the eligible components of the graph and make them maximal planar. +* The augmentation applies independently to eligible components (components with 3 or more vertices). Components with fewer than 3 vertices produce no added edges. +* 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_makeMaximalPlanar(`Edges SQL`_) + + | Returns set of |result-component-make| + | OR EMPTY SET + +:Example: List of edges that are needed to make the sample graph maximal planar. + +**Sample graph before:** + +.. figure:: /images/Fig6-undirected.png + :scale: 50% + + Sample graph before + +**Output:** + +.. literalinclude:: makeMaximalPlanar.queries + :start-after: -- q1 + :end-before: -- q2 + +**Sample graph after adding triangulation edges:** + +.. figure:: images/maximal_planar_sampledata.png + :scale: 75% + + Sample graph after adding triangulation edges (olive = original edges, red dashed = 17 new triangulation edges). + Note: When a graph contains multiple disconnected components, each component is processed independently. Components (2,4) and (13,14) have < 3 vertices and cannot be triangulated; the 13-vertex component is triangulated with 17 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: Triangulating a simple 5-cycle ring graph. + +.. literalinclude:: makeMaximalPlanar.queries + :start-after: -- q2 + :end-before: -- q3 + +**Sample graph before:** + +.. figure:: images/maximal_planar_5cycle_before.png + :scale: 75% + + Sample 5-cycle ring graph before triangulation. + +**Output:** + +.. literalinclude:: makeMaximalPlanar.queries + :start-after: -- q3 + :end-before: -- q4 + +**Sample graph after adding triangulation edges:** + +.. figure:: images/maximal_planar_5cycle.png + :scale: 75% + + Maximal planar triangulation of a simple 5-cycle ring graph (4 red dashed triangulation edges added). + +See Also +------------------------------------------------------------------------------- + +* `Boost: make_maximal_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..edf324c0e7e --- /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_makeMaximalPlanar` - Returns edges to add to a planar graph to make its eligible components maximal planar. + +.. experimental-end + + +.. toctree:: + :hidden: + + pgr_isPlanar + pgr_makeMaximalPlanar + +.. rubric:: Indices and tables + +* :ref:`genindex` +* :ref:`search` diff --git a/doc/src/experimental.rst b/doc/src/experimental.rst index 859a8cb19b4..d3dcb157243 100644 --- a/doc/src/experimental.rst +++ b/doc/src/experimental.rst @@ -93,6 +93,12 @@ Experimental Functions :start-after: experimental-start :end-before: experimental-end +:doc:`planar-family` + +.. include:: planar-family.rst + :start-after: experimental-start + :end-before: experimental-end + .. toctree:: :hidden: @@ -100,6 +106,7 @@ Experimental Functions transformation-family components-family ordering-family + planar-family .. rubric:: categories @@ -127,14 +134,7 @@ Experimental Functions pgr_dagShortestPath pgr_edwardMoore -.. rubric:: Planar Family - -- :doc:`pgr_isPlanar` - -.. toctree:: - :hidden: - pgr_isPlanar .. rubric:: Miscellaneous Algorithms diff --git a/doc/src/release_notes.rst b/doc/src/release_notes.rst index 40390c34c70..9cc06f56a50 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_makeMaximalPlanar + .. 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_makeMaximalPlanar + + .. include:: pgr_makeMaximalPlanar.rst + :start-after: Version 4.1.0 + :end-before: Description + pgRouting 4.0 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -358,6 +370,7 @@ Summary of changes by function :start-after: Version 4.0.0 :end-before: .. rubric + * pgr_maxCardinalityMatch .. include:: pgr_maxCardinalityMatch.rst @@ -525,6 +538,7 @@ New experimental functions. * :issue:`2954`: pgr_kingOrdering * :issue:`2955`: pgr_sloanOrdering + SQL signatures and output standardization ............................................................................... diff --git a/docqueries/planar/CMakeLists.txt b/docqueries/planar/CMakeLists.txt index 1995c7014e6..fb57b557bfd 100644 --- a/docqueries/planar/CMakeLists.txt +++ b/docqueries/planar/CMakeLists.txt @@ -5,6 +5,7 @@ set(LOCAL_FILES boyerMyrvold isPlanar + makeMaximalPlanar ) foreach (f ${LOCAL_FILES}) diff --git a/docqueries/planar/makeMaximalPlanar.pg b/docqueries/planar/makeMaximalPlanar.pg new file mode 100644 index 00000000000..5d7013da748 --- /dev/null +++ b/docqueries/planar/makeMaximalPlanar.pg @@ -0,0 +1,27 @@ +/* :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_makeMaximalPlanar( + 'SELECT id, source, target, cost, reverse_cost FROM edges' +); +/* -- q2 */ +CREATE TABLE example_edges1 ( + id SERIAL PRIMARY KEY, + source INTEGER, + target INTEGER, + cost DOUBLE PRECISION, + reverse_cost DOUBLE PRECISION +); +INSERT INTO example_edges1 (source, target, cost, reverse_cost) VALUES +(1, 2, 1, 1), +(2, 3, 1, 1), +(3, 4, 1, 1), +(4, 5, 1, 1), +(5, 1, 1, 1); + +/* -- q3 */ +SELECT * FROM pgr_makeMaximalPlanar( + 'SELECT id, source, target, cost, reverse_cost FROM example_edges1' +); +/* -- q4 */ diff --git a/docqueries/planar/makeMaximalPlanar.result b/docqueries/planar/makeMaximalPlanar.result new file mode 100644 index 00000000000..05c7aa35b7e --- /dev/null +++ b/docqueries/planar/makeMaximalPlanar.result @@ -0,0 +1,63 @@ +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_makeMaximalPlanar( + 'SELECT id, source, target, cost, reverse_cost FROM edges' +); + seq | start_vid | end_vid +-----+-----------+--------- + 1 | 1 | 5 + 2 | 1 | 6 + 3 | 1 | 7 + 4 | 1 | 10 + 5 | 1 | 11 + 6 | 3 | 11 + 7 | 5 | 10 + 8 | 6 | 17 + 9 | 7 | 9 + 10 | 7 | 17 + 11 | 8 | 17 + 12 | 9 | 11 + 13 | 9 | 12 + 14 | 10 | 17 + 15 | 11 | 15 + 16 | 12 | 16 + 17 | 15 | 17 +(17 rows) + +/* -- q2 */ +CREATE TABLE example_edges1 ( + id SERIAL PRIMARY KEY, + source INTEGER, + target INTEGER, + cost DOUBLE PRECISION, + reverse_cost DOUBLE PRECISION +); +CREATE TABLE +INSERT INTO example_edges1 (source, target, cost, reverse_cost) VALUES +(1, 2, 1, 1), +(2, 3, 1, 1), +(3, 4, 1, 1), +(4, 5, 1, 1), +(5, 1, 1, 1); +INSERT 0 5 +/* -- q3 */ +SELECT * FROM pgr_makeMaximalPlanar( + 'SELECT id, source, target, cost, reverse_cost FROM example_edges1' +); + seq | start_vid | end_vid +-----+-----------+--------- + 1 | 1 | 3 + 2 | 1 | 4 + 3 | 2 | 4 + 4 | 2 | 5 +(4 rows) + +/* -- q4 */ +ROLLBACK; +ROLLBACK diff --git a/docqueries/planar/test.conf b/docqueries/planar/test.conf index 3c33912e3db..96e526c790a 100644 --- a/docqueries/planar/test.conf +++ b/docqueries/planar/test.conf @@ -7,6 +7,7 @@ 'any' => { 'files' => [qw( isPlanar.pg + makeMaximalPlanar.pg )] }, diff --git a/include/c_common/enums.h b/include/c_common/enums.h index e2195070d13..471700a828f 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 */ + MAXIMALPLANAR, /* For components */ CONNECTEDCOMPONENTS, BICONNECTEDCOMPONENTS, STRONGCOMPONENTS, ARTICULATIONPOINTS, BRIDGES, MAKECONNECTED diff --git a/include/planar/makeMaximalPlanar.hpp b/include/planar/makeMaximalPlanar.hpp new file mode 100644 index 00000000000..a0baaa69401 --- /dev/null +++ b/include/planar/makeMaximalPlanar.hpp @@ -0,0 +1,207 @@ +/*PGR-GNU***************************************************************** +File: makeMaximalPlanar.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_MAKEMAXIMALPLANAR_HPP_ +#define INCLUDE_PLANAR_MAKEMAXIMALPLANAR_HPP_ + +#include +#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_makeMaximalPlanar : public pgrouting::Pgr_messages { + public: + typedef typename G::V V; + typedef typename G::E E; + typedef typename G::E_i E_i; + + std::vector makeMaximalPlanar(G &graph) { + if (boost::num_vertices(graph.graph) < 3) { + return std::vector(); + } + + /* 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 generateMakeMaximalPlanar(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; + G sub_graph; + sub_graph.insert_edges(comp_edges[c]); + auto sub_results = generateMakeMaximalPlanar(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 generateMakeMaximalPlanar(G &graph) { + if (boost::num_vertices(graph.graph) < 3) { + return std::vector(); + } + + 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(); + } + + + 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); + + edge_id_map.clear(); + edge_count = 0; + for (boost::tie(ei, ei_end) = edges(graph.graph); ei != ei_end; ++ei) { + edge_id_map[*ei] = edge_count++; + } + + embedding.resize(boost::num_vertices(graph.graph)); + is_planar = boost::boyer_myrvold_planarity_test( + boost::boyer_myrvold_params::graph = graph.graph, + boost::boyer_myrvold_params::embedding = &embedding[0]); + + /* abort in case of an interruption occurs (e.g. the query is being cancelled) */ + CHECK_FOR_INTERRUPTS(); + boost::make_maximal_planar(graph.graph, &embedding[0], + boost::get(boost::vertex_index, graph.graph), 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_MAKEMAXIMALPLANAR_HPP_ diff --git a/pgtap/planar/makeMaximalPlanar/edge_cases.pg b/pgtap/planar/makeMaximalPlanar/edge_cases.pg new file mode 100644 index 00000000000..e08a33cf260 --- /dev/null +++ b/pgtap/planar/makeMaximalPlanar/edge_cases.pg @@ -0,0 +1,93 @@ +/* :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(5) 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, 'pgr_makeMaximalPlanar is new on 4.1.0'); + RETURN; +END IF; + +-- 0 edge, 0 vertex test + +RETURN QUERY +SELECT is_empty( + $$SELECT * FROM pgr_makeMaximalPlanar('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_makeMaximalPlanar('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_makeMaximalPlanar( + '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'); + +-- 2 disconnected 4-vertex paths +-- Component 1: 1-2, 2-3, 3-4 (vertices 1,2,3,4) +-- Component 2: 5-6, 6-7, 7-8 (vertices 5,6,7,8) +-- Expected edges: only edges within {1,2,3,4} and within {5,6,7,8}, no cross edges. + +RETURN QUERY +SELECT results_eq( + $$SELECT seq, start_vid, end_vid FROM pgr_makeMaximalPlanar( + '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 UNION ' || + 'SELECT 6 AS id, 7 AS source, 8 AS target, 1 AS cost, 1 AS reverse_cost' + ) ORDER BY seq$$, + $$VALUES + (1::BIGINT, 1::BIGINT, 3::BIGINT), + (2::BIGINT, 1::BIGINT, 4::BIGINT), + (3::BIGINT, 2::BIGINT, 4::BIGINT), + (4::BIGINT, 5::BIGINT, 7::BIGINT), + (5::BIGINT, 5::BIGINT, 8::BIGINT), + (6::BIGINT, 6::BIGINT, 8::BIGINT) + $$, + '4: Disconnected components are augmented independently without cross-linking'); + +-- 5 vertex non-planar graph (K5) returns empty set +RETURN QUERY +SELECT is_empty( + $$SELECT * FROM pgr_makeMaximalPlanar( + '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'); + +END; +$BODY$ +LANGUAGE plpgsql; + +SELECT edge_cases(); + +SELECT * FROM finish(); +ROLLBACK; diff --git a/pgtap/planar/makeMaximalPlanar/inner_query.pg b/pgtap/planar/makeMaximalPlanar/inner_query.pg new file mode 100644 index 00000000000..6d4364e815e --- /dev/null +++ b/pgtap/planar/makeMaximalPlanar/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_makeMaximalPlanar(', ')'); + +END; +$BODY$ +LANGUAGE plpgsql; + +SELECT inner_query(); + +SELECT finish(); +ROLLBACK; diff --git a/pgtap/planar/makeMaximalPlanar/no_crash_test.pg b/pgtap/planar/makeMaximalPlanar/no_crash_test.pg new file mode 100644 index 00000000000..4cd8b022cfc --- /dev/null +++ b/pgtap/planar/makeMaximalPlanar/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_makemaximalplanar', params, subs); + +END +$BODY$ +LANGUAGE plpgsql VOLATILE; + + +SELECT * FROM test_function(); +SELECT finish(); +ROLLBACK; diff --git a/pgtap/planar/makeMaximalPlanar/types_check.pg b/pgtap/planar/makeMaximalPlanar/types_check.pg new file mode 100644 index 00000000000..45d0848113c --- /dev/null +++ b/pgtap/planar/makeMaximalPlanar/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_makemaximalplanar'); + RETURN QUERY SELECT function_returns('pgr_makemaximalplanar', ARRAY['text'], 'setof record'); + + RETURN QUERY + SELECT function_args_eq('pgr_makemaximalplanar', + $$SELECT '{"",seq,start_vid,end_vid}'::TEXT[] $$ + ); + + RETURN QUERY + SELECT function_types_eq('pgr_makemaximalplanar', + $$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..8e066285ddc 100644 --- a/sql/planar/CMakeLists.txt +++ b/sql/planar/CMakeLists.txt @@ -5,6 +5,8 @@ set(LOCAL_FILES _isPlanar.sql isPlanar.sql + _makeMaximalPlanar.sql + makeMaximalPlanar.sql ) foreach (f ${LOCAL_FILES}) diff --git a/sql/planar/_makeMaximalPlanar.sql b/sql/planar/_makeMaximalPlanar.sql new file mode 100644 index 00000000000..d25e27a56b2 --- /dev/null +++ b/sql/planar/_makeMaximalPlanar.sql @@ -0,0 +1,47 @@ +/*PGR-GNU***************************************************************** +File: _makeMaximalPlanar.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*/ +------------------------- +------------------------- +-- _makeMaximalPlanar +------------------------- +------------------------- + +--v4.1 +CREATE FUNCTION _pgr_makeMaximalPlanar( + 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_makeMaximalPlanar(TEXT) +IS 'pgRouting internal function'; diff --git a/sql/planar/makeMaximalPlanar.sql b/sql/planar/makeMaximalPlanar.sql new file mode 100644 index 00000000000..77b1beb1a2e --- /dev/null +++ b/sql/planar/makeMaximalPlanar.sql @@ -0,0 +1,57 @@ +/*PGR-GNU***************************************************************** +File: makeMaximalPlanar.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_makeMaximalPlanar +------------------ + +--v4.1 +CREATE FUNCTION pgr_makeMaximalPlanar( + 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_makeMaximalPlanar(_pgr_get_statement($1)); +$BODY$ +LANGUAGE SQL VOLATILE STRICT +COST ${COST_HIGH} ROWS ${ROWS_HIGH}; + + +COMMENT ON FUNCTION pgr_makeMaximalPlanar(TEXT) +IS 'pgr_makeMaximalPlanar +- EXPERIMENTAL +- Undirected graph +- Parameters: + - edges SQL with columns: id, source, target, cost [,reverse_cost] +- Documentation: + - ${PROJECT_DOC_LINK}/pgr_makeMaximalPlanar.html +'; diff --git a/sql/sigs/pgrouting--4.1.sig b/sql/sigs/pgrouting--4.1.sig index 2e8a3eb33bf..87efb391b96 100644 --- a/sql/sigs/pgrouting--4.1.sig +++ b/sql/sigs/pgrouting--4.1.sig @@ -192,6 +192,8 @@ _pgr_linegraph(text,boolean) pgr_linegraph(text,boolean) _pgr_makeconnected(text) pgr_makeconnected(text) +_pgr_makemaximalplanar(text) +pgr_makemaximalplanar(text) pgr_maxcardinalitymatch(text) _pgr_maxcardinalitymatch_v4(text) pgr_maxflowmincost_cost(text,anyarray,anyarray) diff --git a/src/coloring/coloring_driver.cpp b/src/coloring/coloring_driver.cpp index d70cf709912..09e28952769 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/makeMaximalPlanar.hpp" #include "coloring/bipartite.hpp" #include "coloring/edgeColoring.hpp" #include "coloring/sequentialVertexColoring.hpp" @@ -119,6 +121,14 @@ void do_coloring( undigraph.insert_edges(edges); switch (which) { + case MAXIMALPLANAR: + { + pgrouting::functions::Pgr_makeMaximalPlanar + fn_makeMaximalPlanar; + results = fn_makeMaximalPlanar.makeMaximalPlanar(undigraph); + log << fn_makeMaximalPlanar.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..e156bd66bb3 100644 --- a/src/cpp_common/utilities.cpp +++ b/src/cpp_common/utilities.cpp @@ -104,7 +104,9 @@ get_name(Which which) { case EDMONDSKARP: return "pgr_edmondsKarp"; break; - + case MAXIMALPLANAR: + return "pgr_makeMaximalPlanar"; + break; case ARTICULATIONPOINTS: return "pgr_articulationPoints"; break; diff --git a/src/planar/CMakeLists.txt b/src/planar/CMakeLists.txt index a7187b065ee..b5bc3a3e276 100644 --- a/src/planar/CMakeLists.txt +++ b/src/planar/CMakeLists.txt @@ -4,4 +4,5 @@ ADD_LIBRARY(planar OBJECT isPlanar.c isPlanar_driver.cpp - ) + makeMaximalPlanar.c +) diff --git a/src/planar/makeMaximalPlanar.c b/src/planar/makeMaximalPlanar.c new file mode 100644 index 00000000000..25b09f09271 --- /dev/null +++ b/src/planar/makeMaximalPlanar.c @@ -0,0 +1,101 @@ +/*PGR-GNU***************************************************************** +File: makeMaximalPlanar.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_makemaximalplanar(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(_pgr_makemaximalplanar); + +PGDLLEXPORT Datum _pgr_makemaximalplanar(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, + + MAXIMALPLANAR, + &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); + } +}