diff --git a/NEWS.md b/NEWS.md index e4dc47bb984..8bd874dce11 100644 --- a/NEWS.md +++ b/NEWS.md @@ -34,9 +34,18 @@ 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.** + +* Flow + + * :issue:3136: pgr_maxWeightedMatching **Summary of changes by function** +* pgr_maxWeightedMatching + + * New experimental function. + * pgr_edgeColoring * Fix the way it builds the graph diff --git a/doc/_static/page_history.js b/doc/_static/page_history.js index f158646394d..65d40450bac 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_maxWeightedMatching']}, + {v: '4.0', pages: ['pgr_bandwidth', 'pgr_kingOrdering', 'pgr_sloanOrdering']}, {v: '3.8', pages: ['pgr_contractionDeadEnd', 'pgr_contractionLinear', 'pgr_separateCrossing', diff --git a/doc/max_flow/CMakeLists.txt b/doc/max_flow/CMakeLists.txt index d8ab23cfaf6..c4697462157 100644 --- a/doc/max_flow/CMakeLists.txt +++ b/doc/max_flow/CMakeLists.txt @@ -11,6 +11,7 @@ set(LOCAL_FILES pgr_maxCardinalityMatch.rst pgr_pushRelabel.rst pgr_maxFlowMinCost.rst + pgr_maxWeightedMatching.rst # TODO rename pgr_maxFlowMinCost_Cost.rst ) @@ -22,3 +23,7 @@ endforeach() set(PROJECT_DOC_FILES ${PROJECT_DOC_FILES} ${LOCAL_DOC_FILES} PARENT_SCOPE) +add_subdirectory("images") +set(PROJECT_IMG_FILES ${PROJECT_IMG_FILES} PARENT_SCOPE) + + diff --git a/doc/max_flow/flow-family.rst b/doc/max_flow/flow-family.rst index dd7f9bf1c95..df7e37d77bd 100644 --- a/doc/max_flow/flow-family.rst +++ b/doc/max_flow/flow-family.rst @@ -36,6 +36,8 @@ Flow - Family of functions * :doc:`pgr_maxFlowMinCost` - Details of flow and cost on edges. * :doc:`pgr_maxFlowMinCost_Cost` - Only the Min Cost calculation. +* :doc:`pgr_maxWeightedMatching` - Calculates a maximum weighted matching in a + graph. .. experimental-end @@ -50,6 +52,7 @@ Flow - Family of functions pgr_maxCardinalityMatch pgr_maxFlowMinCost pgr_maxFlowMinCost_Cost + pgr_maxWeightedMatching Flow Functions General Information diff --git a/doc/max_flow/images/CMakeLists.txt b/doc/max_flow/images/CMakeLists.txt new file mode 100644 index 00000000000..0cda8ecfb43 --- /dev/null +++ b/doc/max_flow/images/CMakeLists.txt @@ -0,0 +1,15 @@ +# This file is part of the pgRouting project. +# Copyright (c) 2017-2026 pgRouting developers +# License: GPL-2 See https://github.com/pgRouting/pgrouting/blob/main/LICENSE + +set(LOCAL_FILES + mwm_graph.png + mwm_result.png + ) + +foreach (f ${LOCAL_FILES}) + configure_file(${f} "${PGR_DOCUMENTATION_SOURCE_DIR}/images/${f}" COPYONLY) + list(APPEND LOCAL_IMG_FILES "${PGR_DOCUMENTATION_SOURCE_DIR}/images/${f}") +endforeach() + +set(PROJECT_IMG_FILES ${PROJECT_IMG_FILES} ${LOCAL_IMG_FILES} PARENT_SCOPE) diff --git a/doc/max_flow/images/mwm_graph.png b/doc/max_flow/images/mwm_graph.png new file mode 100644 index 00000000000..b409a258b8e Binary files /dev/null and b/doc/max_flow/images/mwm_graph.png differ diff --git a/doc/max_flow/images/mwm_result.png b/doc/max_flow/images/mwm_result.png new file mode 100644 index 00000000000..dfdb0718dc0 Binary files /dev/null and b/doc/max_flow/images/mwm_result.png differ diff --git a/doc/max_flow/pgr_maxWeightedMatching.rst b/doc/max_flow/pgr_maxWeightedMatching.rst new file mode 100644 index 00000000000..8a84f3ff476 --- /dev/null +++ b/doc/max_flow/pgr_maxWeightedMatching.rst @@ -0,0 +1,179 @@ +: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: Flow Family ; pgr_maxWeightedMatching - Experimental + single: maxWeightedMatching - Experimental on v4.1 + +| + +``pgr_maxWeightedMatching`` - Experimental +=============================================================================== + +``pgr_maxWeightedMatching`` — Calculates a maximum weighted matching in a graph. + +.. include:: experimental.rst + :start-after: warning-begin + :end-before: end-warning + +.. rubric:: Availability + +.. rubric:: Version 4.1.0 + +* New experimental function. + + +Description +------------------------------------------------------------------------------- + +A **maximum weighted matching** in a graph is a matching where the sum of the +weights of selected edges is maximized. + +A matching or independent edge set in a graph is a set of edges without common +vertices. + +The main characteristics are: + +- Works for **undirected** graphs. +- Each vertex is matched with at most one other vertex. +- Maximizes the total edge weight sum. +- There may be many maximum weighted matchings. + + - Calculates one possible maximum weighted matching in a graph. + +- Returns the matched pairs of vertices in the form of a set of + `(start_vid, end_vid, agg_cost)`. + + - `start_vid` and `end_vid` are the endpoints of the matched edge. + - `agg_cost` is the weight of the matched edge. + +- For the undirected graph, the results are symmetric. + + - The `agg_cost` of `(u, v)` is the same as for `(v, u)`. + +- Running time: :math:`O(n^3)` where :math:`n` is the number of vertices. + +|Boost| Boost Graph Inside + +Signatures +------------------------------------------------------------------------------- + +.. rubric:: Summary + +.. admonition:: \ \ + :class: signatures + + | pgr_maxWeightedMatching(`Edges SQL`_, ``directed``) + + | Returns set of |matrix-result| + | OR EMPTY SET + +:Example: Using all edges. + +.. literalinclude:: maxWeightedMatching.queries + :start-after: -- q1 + :end-before: -- q2 + +Parameters +------------------------------------------------------------------------------- + +.. include:: allpairs-family.rst + :start-after: edges_start + :end-before: edges_end + +Optional parameters +............................................................................... + +.. list-table:: + :width: 81 + :widths: auto + :header-rows: 1 + + * - Column + - Type + - Default + - Description + * - ``directed`` + - ``BOOLEAN`` + - ``false`` + - Ignored. The matching algorithm always works on **undirected** graphs. + +Inner Queries +------------------------------------------------------------------------------- + +Edges SQL +............................................................................... + +.. include:: pgRouting-concepts.rst + :start-after: basic_edges_sql_start + :end-before: basic_edges_sql_end + +Result columns +------------------------------------------------------------------------------- + +Set of |matrix-result| + +.. list-table:: + :width: 81 + :widths: 12 14 60 + :header-rows: 1 + + * - Column + - Type + - Description + * - ``start_vid`` + - ``BIGINT`` + - Identifier of the first end point vertex of the matched edge. + * - ``end_vid`` + - ``BIGINT`` + - Identifier of the second end point vertex of the matched edge. + * - ``agg_cost`` + - ``FLOAT`` + - Weight of the matched edge. + +Additional Examples +------------------------------------------------------------------------------- + +.. raw:: html + + + + + + +
+ Before Matching
+ Sample graph before maximum weighted matching +

Sample graph with 5 vertices and 6 weighted edges before matching.

+
+ After Matching
+ Sample graph after maximum weighted matching +

Graph after maximum weighted matching: selected edges are highlighted.

+
+ +.. image:: images/mwm_graph.png + :width: 0 + +.. image:: images/mwm_result.png + :width: 0 + +:Example: Maximum weighted matching on a custom 5-vertex graph. + +.. literalinclude:: maxWeightedMatching.queries + :start-after: -- q2 + :end-before: -- q4 + + +See Also +------------------------------------------------------------------------------- + +* :doc:`flow-family` +* :doc:`sampledata` +* `Boost: maximum_weighted_matching + `__ + +.. rubric:: Indices and tables + +* :ref:`genindex` +* :ref:`search` diff --git a/doc/src/pgRouting-introduction.rst b/doc/src/pgRouting-introduction.rst index a81f0c6ed60..88acfdd7cd2 100644 --- a/doc/src/pgRouting-introduction.rst +++ b/doc/src/pgRouting-introduction.rst @@ -139,6 +139,7 @@ Jinfu Leng, Kai Behncke, Kishore Kumar, Ko Nagase, +Mayur Galhate, Mahmoud Sakr, Manikata Kondeti, Mario Basa, diff --git a/doc/src/release_notes.rst b/doc/src/release_notes.rst index 40390c34c70..6efa7784f20 100644 --- a/doc/src/release_notes.rst +++ b/doc/src/release_notes.rst @@ -59,9 +59,20 @@ To see all issues & pull requests closed by this release see the * :issue:`3101`: pgr_edgeColoring not building graph correctly +.. rubric:: New experimental functions. + +* Flow + + * :issue:3136: pgr_maxWeightedMatching .. rubric:: Summary of changes by function +* pgr_maxWeightedMatching + + .. include:: pgr_maxWeightedMatching.rst + :start-after: Version 4.1.0 + :end-before: Description + * pgr_edgeColoring .. include:: pgr_edgeColoring.rst diff --git a/docqueries/max_flow/CMakeLists.txt b/docqueries/max_flow/CMakeLists.txt index 0691d4c3158..01419f131b0 100644 --- a/docqueries/max_flow/CMakeLists.txt +++ b/docqueries/max_flow/CMakeLists.txt @@ -11,6 +11,7 @@ set(LOCAL_FILES pushRelabel maxFlowMinCost maxFlowMinCost_Cost + maxWeightedMatching ) diff --git a/docqueries/max_flow/maxWeightedMatching.pg b/docqueries/max_flow/maxWeightedMatching.pg new file mode 100644 index 00000000000..9b001efd92d --- /dev/null +++ b/docqueries/max_flow/maxWeightedMatching.pg @@ -0,0 +1,31 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2016-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ +/* -- q1 */ +SELECT * FROM pgr_maxWeightedMatching( + 'SELECT id, source, target, cost FROM edges', + false +); + +/* -- q2 */ +CREATE TABLE additional_sample_1 ( + id SERIAL PRIMARY KEY, + source INTEGER, + target INTEGER, + cost DOUBLE PRECISION, + reverse_cost DOUBLE PRECISION +); + +INSERT INTO additional_sample_1 (source, target, cost, reverse_cost) VALUES +(1, 2, 5, 5), +(2, 3, 1, 1), +(1, 3, 2, 2), +(2, 4, 4, 4), +(4, 5, 3, 3), +(3, 5, 6, 6); +SELECT * FROM pgr_maxWeightedMatching( + 'SELECT id, source, target, cost, reverse_cost FROM additional_sample_1', + false +); + +/* -- q4 */ diff --git a/docqueries/max_flow/maxWeightedMatching.result b/docqueries/max_flow/maxWeightedMatching.result new file mode 100644 index 00000000000..f5d532270e5 --- /dev/null +++ b/docqueries/max_flow/maxWeightedMatching.result @@ -0,0 +1,54 @@ +BEGIN; +BEGIN +SET client_min_messages TO NOTICE; +SET +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2016-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ +/* -- q1 */ +SELECT * FROM pgr_maxWeightedMatching( + 'SELECT id, source, target, cost FROM edges', + false +); + start_vid | end_vid | agg_cost +-----------+---------+---------- + 2 | 4 | 1 + 5 | 6 | 2 + 3 | 7 | 3 + 8 | 9 | 4 + 10 | 11 | 5 + 13 | 14 | 6 + 15 | 16 | 7 + 12 | 17 | 8 +(8 rows) + +/* -- q2 */ +CREATE TABLE additional_sample_1 ( + id SERIAL PRIMARY KEY, + source INTEGER, + target INTEGER, + cost DOUBLE PRECISION, + reverse_cost DOUBLE PRECISION +); +CREATE TABLE +INSERT INTO additional_sample_1 (source, target, cost, reverse_cost) VALUES +(1, 2, 5, 5), +(2, 3, 1, 1), +(1, 3, 2, 2), +(2, 4, 4, 4), +(4, 5, 3, 3), +(3, 5, 6, 6); +INSERT 0 6 +SELECT * FROM pgr_maxWeightedMatching( + 'SELECT id, source, target, cost, reverse_cost FROM additional_sample_1', + false +); + start_vid | end_vid | agg_cost +-----------+---------+---------- + 1 | 2 | 5 + 3 | 5 | 11 +(2 rows) + +/* -- q4 */ +ROLLBACK; +ROLLBACK diff --git a/docqueries/max_flow/test.conf b/docqueries/max_flow/test.conf index 768a404ba27..4bffe520e43 100644 --- a/docqueries/max_flow/test.conf +++ b/docqueries/max_flow/test.conf @@ -14,6 +14,7 @@ edgeDisjointPaths.pg maxFlowMinCost.pg maxFlowMinCost_Cost.pg + maxWeightedMatching.pg )] }, ); diff --git a/include/c_common/enums.h b/include/c_common/enums.h index e2195070d13..a6c5e6f8111 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 Matching */ + MAXWEIGHTEDMATCHING, /* For components */ CONNECTEDCOMPONENTS, BICONNECTEDCOMPONENTS, STRONGCOMPONENTS, ARTICULATIONPOINTS, BRIDGES, MAKECONNECTED diff --git a/include/drivers/maxWeightedMatching_driver.hpp b/include/drivers/maxWeightedMatching_driver.hpp new file mode 100644 index 00000000000..95816b62abc --- /dev/null +++ b/include/drivers/maxWeightedMatching_driver.hpp @@ -0,0 +1,50 @@ +/*PGR-GNU***************************************************************** +File: maxWeightedMatching_driver.hpp + +Copyright (c) 2025-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Mayur Galhate +Mail: galhatemayur 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_DRIVERS_MAXWEIGHTEDMATCHING_DRIVER_HPP_ +#define INCLUDE_DRIVERS_MAXWEIGHTEDMATCHING_DRIVER_HPP_ +#pragma once + +#include +#include +#include + +using IID_t_rt = struct IID_t_rt; + +namespace pgrouting { +namespace drivers { + +void do_maxWeightedMatching( + const std::string&, + IID_t_rt*&, size_t&, + std::ostringstream&, std::ostringstream&, std::ostringstream&); + +} // namespace drivers +} // namespace pgrouting + +#endif // INCLUDE_DRIVERS_MAXWEIGHTEDMATCHING_DRIVER_HPP_ diff --git a/include/drivers/max_flow/maxWeightedMatching_driver.h b/include/drivers/max_flow/maxWeightedMatching_driver.h new file mode 100644 index 00000000000..a2cce8386b8 --- /dev/null +++ b/include/drivers/max_flow/maxWeightedMatching_driver.h @@ -0,0 +1,57 @@ +/*PGR-GNU***************************************************************** +File: maxWeightedMatching_driver.h + +Generated with Template by: +Copyright (c) 2007-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Mayur Galhate +Mail: galhatemayur 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_DRIVERS_MAX_FLOW_MAXWEIGHTEDMATCHING_DRIVER_H_ +#define INCLUDE_DRIVERS_MAX_FLOW_MAXWEIGHTEDMATCHING_DRIVER_H_ +#pragma once + +#ifdef __cplusplus +# include +using IID_t_rt = struct IID_t_rt; +#else +# include +typedef struct IID_t_rt IID_t_rt; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void pgr_do_maxWeightedMatching( + const char*, + + IID_t_rt**, size_t*, + + char**, char**, char**); + +#ifdef __cplusplus +} +#endif + +#endif // INCLUDE_DRIVERS_MAX_FLOW_MAXWEIGHTEDMATCHING_DRIVER_H_ diff --git a/include/max_flow/maxWeightedMatching.hpp b/include/max_flow/maxWeightedMatching.hpp new file mode 100644 index 00000000000..73e7316b892 --- /dev/null +++ b/include/max_flow/maxWeightedMatching.hpp @@ -0,0 +1,104 @@ +/*PGR-GNU***************************************************************** +File: maxWeightedMatching.hpp + +Copyright (c) 2025-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Mayur Galhate +Mail: galhatemayur 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_MAX_FLOW_MAXWEIGHTEDMATCHING_HPP_ +#define INCLUDE_MAX_FLOW_MAXWEIGHTEDMATCHING_HPP_ +#pragma once + +#include +#include +#include + +#include + +#include "c_types/iid_t_rt.h" +#include "cpp_common/undirectedHasCostBG.hpp" +#include "cpp_common/interruption.hpp" + + +namespace pgrouting { +namespace flow { + +inline std::vector +maximumWeightedMatch(pgrouting::graph::UndirectedHasCostBG &graph) { + using G = pgrouting::graph::UndirectedHasCostBG::TSP_Graph; + using V = pgrouting::graph::UndirectedHasCostBG::V; + using E = pgrouting::graph::UndirectedHasCostBG::E; + + std::vector mate_map(boost::num_vertices(graph.graph())); + + CHECK_FOR_INTERRUPTS(); + try { + boost::maximum_weighted_matching(graph.graph(), &mate_map[0]); + } catch (boost::exception const &ex) { + (void)ex; + throw; + } catch (std::exception &e) { + (void)e; + throw; + } catch (...) { + throw; + } + + std::vector results; + + for (const auto &v2 : mate_map) { + auto v1 = static_cast(&v2 - &mate_map[0]); + + if (v2 == boost::graph_traits::null_vertex()) continue; + if (v1 >= v2) continue; + + E e; + bool exists = false; + boost::tie(e, exists) = boost::edge(v1, v2, graph.graph()); + if (!exists) throw; + + int64_t src = graph.get_vertex_id(v1); + int64_t tgt = graph.get_vertex_id(v2); + + if (src > tgt) std::swap(src, tgt); + + IID_t_rt row; + row.from_vid = src; + row.to_vid = tgt; + row.cost = boost::get(boost::edge_weight_t(), graph.graph(), e); + results.push_back(row); + } + + std::sort(results.begin(), results.end(), + [](const IID_t_rt &a, const IID_t_rt &b) { + return a.to_vid < b.to_vid; + }); + + return results; +} + +} // namespace flow +} // namespace pgrouting + +#endif // INCLUDE_MAX_FLOW_MAXWEIGHTEDMATCHING_HPP_ diff --git a/include/process/maxWeightedMatching_process.h b/include/process/maxWeightedMatching_process.h new file mode 100644 index 00000000000..52ca2b8e4e4 --- /dev/null +++ b/include/process/maxWeightedMatching_process.h @@ -0,0 +1,56 @@ +/*PGR-GNU***************************************************************** +File: maxWeightedMatching_process.h + +Copyright (c) 2007-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Mayur Galhate +Mail: galhatemayur 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_PROCESS_MAXWEIGHTEDMATCHING_PROCESS_H_ +#define INCLUDE_PROCESS_MAXWEIGHTEDMATCHING_PROCESS_H_ +#pragma once + +#ifdef __cplusplus +#include +#include +using IID_t_rt = struct IID_t_rt; +#else +#include +#include +#include +typedef struct IID_t_rt IID_t_rt; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void pgr_process_maxWeightedMatching( + const char*, + IID_t_rt**, size_t*); + +#ifdef __cplusplus +} +#endif + +#endif // INCLUDE_PROCESS_MAXWEIGHTEDMATCHING_PROCESS_H_ diff --git a/locale/en/LC_MESSAGES/pgrouting_doc_strings.po b/locale/en/LC_MESSAGES/pgrouting_doc_strings.po index e8a867aa925..d9123dc56f8 100644 --- a/locale/en/LC_MESSAGES/pgrouting_doc_strings.po +++ b/locale/en/LC_MESSAGES/pgrouting_doc_strings.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: pgRouting v4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-08-08 21:44+0000\n" +"POT-Creation-Date: 2026-08-22 09:09+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -3364,6 +3364,11 @@ msgstr "" msgid ":doc:`pgr_maxFlowMinCost_Cost` - Only the Min Cost calculation." msgstr "" +msgid "" +":doc:`pgr_maxWeightedMatching` - Calculates a maximum weighted matching in a " +"graph." +msgstr "" + msgid ":doc:`chinesePostmanProblem-family`" msgstr "" @@ -3992,6 +3997,11 @@ msgid "" "breadthFirstSearch: Reorganize into traversal" msgstr "" +msgid "" +"`#3131 `__: " +"binaryBreadthFirstSearch: Integrate into existing process/driver pair" +msgstr "" + msgid "Bug Fixes" msgstr "" @@ -4000,9 +4010,25 @@ msgid "" "pgr_edgeColoring not building graph correctly" msgstr "" +msgid "New experimental functions." +msgstr "" + +msgid "Flow" +msgstr "" + +msgid "issue:3136" +msgstr "" + +#, fuzzy +msgid "pgr_maxWeightedMatching" +msgstr "pgr_maximumcardinalitymatching" + msgid "Summary of changes by function" msgstr "" +msgid "New experimental function." +msgstr "" + msgid "pgr_edgeColoring" msgstr "pgr_edgeColoring" @@ -6495,9 +6521,6 @@ msgstr "``y2``" msgid "Y coordinate of ``target`` vertex." msgstr "" -msgid "Flow" -msgstr "" - msgid "Edges SQL for :doc:`flow-family`" msgstr "" @@ -7148,12 +7171,12 @@ msgid "" "Denis Rykov, Ema Miyawaki, Esteban Zimanyi, Fan Wu, Florian Thurkow, " "Frederic Junod, Gerald Fenoy, Gudesa Venkata Sai Akhil, Hang Wu, Himanshu " "Raj, Imre Samu, Jay Mahadeokar, Jinfu Leng, Kai Behncke, Kishore Kumar, Ko " -"Nagase, Mahmoud Sakr, Manikata Kondeti, Mario Basa, Martin Wiesenhaan, Maxim " -"Dubinin, Maoguang Wang, Mohamed Bakli, Mohamed Zia, Mohit Rawat, Mukul " -"Priya, Nitish Chauhan, Rajat Shinde, Razequl Islam, Regina Obe, Rohith " -"Reddy, Saloni Kumari, Sarthak Agarwal, Shobhit Chaurasia, Sourabh Garg, " -"Stephen Woodbridge, Swapnil Joshi, Sylvain Housseman, Sylvain Pasche, Veenit " -"Kumar, Vidhan Jain, Virginia Vergara, Yige Huang" +"Nagase, Mayur Galhate, Mahmoud Sakr, Manikata Kondeti, Mario Basa, Martin " +"Wiesenhaan, Maxim Dubinin, Maoguang Wang, Mohamed Bakli, Mohamed Zia, Mohit " +"Rawat, Mukul Priya, Nitish Chauhan, Rajat Shinde, Razequl Islam, Regina Obe, " +"Rohith Reddy, Saloni Kumari, Sarthak Agarwal, Shobhit Chaurasia, Sourabh " +"Garg, Stephen Woodbridge, Swapnil Joshi, Sylvain Housseman, Sylvain Pasche, " +"Veenit Kumar, Vidhan Jain, Virginia Vergara, Yige Huang" msgstr "" msgid "Corporate Sponsors (in alphabetical order)" @@ -7995,9 +8018,6 @@ msgstr "" msgid "Version 2.5.0" msgstr "" -msgid "New experimental function." -msgstr "" - msgid "" "Those vertices that belong to more than one biconnected component are called " "articulation points or, equivalently, cut vertices. Articulation points are " @@ -13154,6 +13174,68 @@ msgstr "" msgid "Minimum Cost Maximum Flow possible from the source(s) to the target(s)" msgstr "" +msgid "``pgr_maxWeightedMatching`` - Experimental" +msgstr "" + +msgid "" +"``pgr_maxWeightedMatching`` — Calculates a maximum weighted matching in a " +"graph." +msgstr "" + +msgid "" +"A **maximum weighted matching** in a graph is a matching where the sum of " +"the weights of selected edges is maximized." +msgstr "" + +msgid "Each vertex is matched with at most one other vertex." +msgstr "" + +msgid "Maximizes the total edge weight sum." +msgstr "" + +msgid "There may be many maximum weighted matchings." +msgstr "" + +msgid "Calculates one possible maximum weighted matching in a graph." +msgstr "" + +msgid "" +"Returns the matched pairs of vertices in the form of a set of `(start_vid, " +"end_vid, agg_cost)`." +msgstr "" + +msgid "`start_vid` and `end_vid` are the endpoints of the matched edge." +msgstr "" + +msgid "`agg_cost` is the weight of the matched edge." +msgstr "" + +msgid "Running time: :math:`O(n^3)` where :math:`n` is the number of vertices." +msgstr "" + +msgid "pgr_maxWeightedMatching(`Edges SQL`_, ``directed``)" +msgstr "" + +msgid "Ignored. The matching algorithm always works on **undirected** graphs." +msgstr "" + +msgid "Identifier of the first end point vertex of the matched edge." +msgstr "" + +msgid "Identifier of the second end point vertex of the matched edge." +msgstr "" + +msgid "Weight of the matched edge." +msgstr "" + +msgid "Maximum weighted matching on a custom 5-vertex graph." +msgstr "" + +msgid "" +"`Boost: maximum_weighted_matching `__" +msgstr "" + msgid "``pgr_pickDeliver`` - Experimental" msgstr "" @@ -15557,9 +15639,6 @@ msgid "" "pgr_pushRelabel(Combinations)" msgstr "" -msgid "New experimental functions." -msgstr "" - msgid "Metrics" msgstr "" diff --git a/locale/pot/pgrouting_doc_strings.pot b/locale/pot/pgrouting_doc_strings.pot index 251bca6dd9b..17263898e22 100644 --- a/locale/pot/pgrouting_doc_strings.pot +++ b/locale/pot/pgrouting_doc_strings.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: pgRouting v4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-08-08 21:44+0000\n" +"POT-Creation-Date: 2026-08-22 09:09+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -3052,6 +3052,9 @@ msgstr "" msgid ":doc:`pgr_maxFlowMinCost_Cost` - Only the Min Cost calculation." msgstr "" +msgid ":doc:`pgr_maxWeightedMatching` - Calculates a maximum weighted matching in a graph." +msgstr "" + msgid ":doc:`chinesePostmanProblem-family`" msgstr "" @@ -3571,15 +3574,33 @@ msgstr "" msgid "`#3129 `__: breadthFirstSearch: Reorganize into traversal" msgstr "" +msgid "`#3131 `__: binaryBreadthFirstSearch: Integrate into existing process/driver pair" +msgstr "" + msgid "Bug Fixes" msgstr "" msgid "`#3101 `__: pgr_edgeColoring not building graph correctly" msgstr "" +msgid "New experimental functions." +msgstr "" + +msgid "Flow" +msgstr "" + +msgid "issue:3136" +msgstr "" + +msgid "pgr_maxWeightedMatching" +msgstr "" + msgid "Summary of changes by function" msgstr "" +msgid "New experimental function." +msgstr "" + msgid "pgr_edgeColoring" msgstr "" @@ -5734,9 +5755,6 @@ msgstr "" msgid "Y coordinate of ``target`` vertex." msgstr "" -msgid "Flow" -msgstr "" - msgid "Edges SQL for :doc:`flow-family`" msgstr "" @@ -6280,7 +6298,7 @@ msgstr "" msgid "Individuals (in alphabetical order)" msgstr "" -msgid "Aasheesh Tiwari, Abhinav Jain, Aditya Pratap Singh, Adrien Berchet, Akio Takubo, Andrea Nardelli, Anthony Tasca, Anton Patrushev, Aryan Gupta, Ashraf Hossain, Ashish Kumar, Aurélie Bousquet, Bipasha Gayary, Cayetano Benavent, Christian Gonzalez, Daniel Kastl, Dapeng Wang, Dave Potts, David Techer, Denis Rykov, Ema Miyawaki, Esteban Zimanyi, Fan Wu, Florian Thurkow, Frederic Junod, Gerald Fenoy, Gudesa Venkata Sai Akhil, Hang Wu, Himanshu Raj, Imre Samu, Jay Mahadeokar, Jinfu Leng, Kai Behncke, Kishore Kumar, Ko Nagase, Mahmoud Sakr, Manikata Kondeti, Mario Basa, Martin Wiesenhaan, Maxim Dubinin, Maoguang Wang, Mohamed Bakli, Mohamed Zia, Mohit Rawat, Mukul Priya, Nitish Chauhan, Rajat Shinde, Razequl Islam, Regina Obe, Rohith Reddy, Saloni Kumari, Sarthak Agarwal, Shobhit Chaurasia, Sourabh Garg, Stephen Woodbridge, Swapnil Joshi, Sylvain Housseman, Sylvain Pasche, Veenit Kumar, Vidhan Jain, Virginia Vergara, Yige Huang" +msgid "Aasheesh Tiwari, Abhinav Jain, Aditya Pratap Singh, Adrien Berchet, Akio Takubo, Andrea Nardelli, Anthony Tasca, Anton Patrushev, Aryan Gupta, Ashraf Hossain, Ashish Kumar, Aurélie Bousquet, Bipasha Gayary, Cayetano Benavent, Christian Gonzalez, Daniel Kastl, Dapeng Wang, Dave Potts, David Techer, Denis Rykov, Ema Miyawaki, Esteban Zimanyi, Fan Wu, Florian Thurkow, Frederic Junod, Gerald Fenoy, Gudesa Venkata Sai Akhil, Hang Wu, Himanshu Raj, Imre Samu, Jay Mahadeokar, Jinfu Leng, Kai Behncke, Kishore Kumar, Ko Nagase, Mayur Galhate, Mahmoud Sakr, Manikata Kondeti, Mario Basa, Martin Wiesenhaan, Maxim Dubinin, Maoguang Wang, Mohamed Bakli, Mohamed Zia, Mohit Rawat, Mukul Priya, Nitish Chauhan, Rajat Shinde, Razequl Islam, Regina Obe, Rohith Reddy, Saloni Kumari, Sarthak Agarwal, Shobhit Chaurasia, Sourabh Garg, Stephen Woodbridge, Swapnil Joshi, Sylvain Housseman, Sylvain Pasche, Veenit Kumar, Vidhan Jain, Virginia Vergara, Yige Huang" msgstr "" msgid "Corporate Sponsors (in alphabetical order)" @@ -7003,9 +7021,6 @@ msgstr "" msgid "Version 2.5.0" msgstr "" -msgid "New experimental function." -msgstr "" - msgid "Those vertices that belong to more than one biconnected component are called articulation points or, equivalently, cut vertices. Articulation points are vertices whose removal would increase the number of connected components in the graph. This implementation can only be used with an undirected graph." msgstr "" @@ -11218,6 +11233,60 @@ msgstr "" msgid "Minimum Cost Maximum Flow possible from the source(s) to the target(s)" msgstr "" +msgid "``pgr_maxWeightedMatching`` - Experimental" +msgstr "" + +msgid "``pgr_maxWeightedMatching`` — Calculates a maximum weighted matching in a graph." +msgstr "" + +msgid "A **maximum weighted matching** in a graph is a matching where the sum of the weights of selected edges is maximized." +msgstr "" + +msgid "Each vertex is matched with at most one other vertex." +msgstr "" + +msgid "Maximizes the total edge weight sum." +msgstr "" + +msgid "There may be many maximum weighted matchings." +msgstr "" + +msgid "Calculates one possible maximum weighted matching in a graph." +msgstr "" + +msgid "Returns the matched pairs of vertices in the form of a set of `(start_vid, end_vid, agg_cost)`." +msgstr "" + +msgid "`start_vid` and `end_vid` are the endpoints of the matched edge." +msgstr "" + +msgid "`agg_cost` is the weight of the matched edge." +msgstr "" + +msgid "Running time: :math:`O(n^3)` where :math:`n` is the number of vertices." +msgstr "" + +msgid "pgr_maxWeightedMatching(`Edges SQL`_, ``directed``)" +msgstr "" + +msgid "Ignored. The matching algorithm always works on **undirected** graphs." +msgstr "" + +msgid "Identifier of the first end point vertex of the matched edge." +msgstr "" + +msgid "Identifier of the second end point vertex of the matched edge." +msgstr "" + +msgid "Weight of the matched edge." +msgstr "" + +msgid "Maximum weighted matching on a custom 5-vertex graph." +msgstr "" + +msgid "`Boost: maximum_weighted_matching `__" +msgstr "" + msgid "``pgr_pickDeliver`` - Experimental" msgstr "" @@ -13120,9 +13189,6 @@ msgstr "" msgid "`#2718 `__: pgr_pushRelabel(Combinations)" msgstr "" -msgid "New experimental functions." -msgstr "" - msgid "Metrics" msgstr "" diff --git a/pgtap/max_flow/maxWeightedMatching/edge_cases.pg b/pgtap/max_flow/maxWeightedMatching/edge_cases.pg new file mode 100644 index 00000000000..1571ad89eae --- /dev/null +++ b/pgtap/max_flow/maxWeightedMatching/edge_cases.pg @@ -0,0 +1,58 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2021-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(3) 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_maxWeightedMatching('SELECT id, source, target, cost, reverse_cost FROM edges WHERE id > 18', false)$$, + '1: Graph with 0 edges and 0 vertices is empty'); + +-- 1 edge, 2 vertices test + +RETURN QUERY +SELECT set_eq( + $$SELECT * FROM pgr_maxWeightedMatching('SELECT 1::BIGINT AS id, 5::BIGINT AS source, 6::BIGINT AS target, 1.0::FLOAT AS cost, 1.0::FLOAT AS reverse_cost', false)$$, + $$VALUES (5::BIGINT, 6::BIGINT, 1.0::FLOAT)$$, + '2: Single edge (2 vertices) -> 1 matched pair'); + +-- Triangle (3-cycle) test + +RETURN QUERY +SELECT set_eq( + $$SELECT * FROM pgr_maxWeightedMatching( + 'SELECT * FROM (VALUES + (1::BIGINT, 1::BIGINT, 2::BIGINT, 5.0::FLOAT, 5.0::FLOAT), + (2::BIGINT, 2::BIGINT, 3::BIGINT, 6.0::FLOAT, 6.0::FLOAT), + (3::BIGINT, 3::BIGINT, 1::BIGINT, 7.0::FLOAT, 7.0::FLOAT) + ) AS t(id, source, target, cost, reverse_cost)', + false + )$$, + $$VALUES (1::BIGINT, 3::BIGINT, 7.0::FLOAT)$$, + '3: Triangle (3-cycle) -> maximum weighted edge selected'); + +END; +$BODY$ +LANGUAGE plpgsql; + +SELECT edge_cases(); + +SELECT * FROM finish(); +ROLLBACK; diff --git a/pgtap/max_flow/maxWeightedMatching/inner_query.pg b/pgtap/max_flow/maxWeightedMatching/inner_query.pg new file mode 100644 index 00000000000..6acd8ed6c6d --- /dev/null +++ b/pgtap/max_flow/maxWeightedMatching/inner_query.pg @@ -0,0 +1,32 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 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_lib_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_lib_version('4.1.0') THEN + RETURN QUERY SELECT skip(1, 'pgr_maxWeightedMatching is new on 4.1.0'); + RETURN; +END IF; + +RETURN QUERY +SELECT style_dijkstra('pgr_maxWeightedMatching(', ', false)'); + +END; +$BODY$ +LANGUAGE plpgsql; + +SELECT inner_query(); + +SELECT finish(); +ROLLBACK; diff --git a/pgtap/max_flow/maxWeightedMatching/no_crash_test.pg b/pgtap/max_flow/maxWeightedMatching/no_crash_test.pg new file mode 100644 index 00000000000..dcca49fa684 --- /dev/null +++ b/pgtap/max_flow/maxWeightedMatching/no_crash_test.pg @@ -0,0 +1,61 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 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_lib_version('4.1.0') THEN plan(7) ELSE plan(1) END; + +PREPARE edges AS +SELECT id, source, target, cost, reverse_cost FROM edges; + +PREPARE null_ret AS +SELECT id FROM vertices WHERE id IN (-1); + +PREPARE null_ret_arr AS +SELECT array_agg(id) FROM vertices WHERE id IN (-1); + +CREATE OR REPLACE FUNCTION no_crash() +RETURNS SETOF TEXT AS +$BODY$ +DECLARE + params TEXT[]; + subs TEXT[]; +BEGIN + IF NOT min_lib_version('4.1.0') THEN + RETURN QUERY + SELECT skip(1, 'pgr_maxWeightedMatching is new on 4.1.0'); + RETURN; + END IF; + + params := ARRAY[ + '$$SELECT id, source, target, cost, reverse_cost FROM edges$$', + 'false' + ]::TEXT[]; + + subs := ARRAY[ + 'NULL', + 'NULL' + ]::TEXT[]; + + RETURN QUERY + SELECT * + FROM no_crash_test( + 'pgr_maxWeightedMatching', + params, + subs); + + RETURN QUERY + SELECT throw_on_empty_edges_sql( + 'pgr_maxWeightedMatching', + ', false'); + +END +$BODY$ +LANGUAGE plpgsql VOLATILE; + +SELECT * FROM no_crash(); + +SELECT finish(); +ROLLBACK; diff --git a/pgtap/max_flow/maxWeightedMatching/types_check.pg b/pgtap/max_flow/maxWeightedMatching/types_check.pg new file mode 100644 index 00000000000..0d8e4709035 --- /dev/null +++ b/pgtap/max_flow/maxWeightedMatching/types_check.pg @@ -0,0 +1,54 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ + + +BEGIN; + +SELECT CASE WHEN min_lib_version('4.1.0') THEN plan(5) ELSE plan(1) END; + +CREATE OR REPLACE FUNCTION types_check() +RETURNS SETOF TEXT AS +$BODY$ +BEGIN + + IF NOT min_lib_version('4.1.0') THEN + RETURN QUERY SELECT skip(1, 'pgr_maxWeightedMatching: Function is new on 4.1.0'); + RETURN; + END IF; + + RETURN QUERY SELECT has_function('pgr_maxweightedmatching'); + + RETURN QUERY + SELECT has_function( + 'pgr_maxweightedmatching', + ARRAY['text', 'boolean']); + + RETURN QUERY + SELECT function_returns( + 'pgr_maxweightedmatching', + ARRAY['text', 'boolean'], + 'setof record'); + + RETURN QUERY +SELECT function_args_eq( + 'pgr_maxweightedmatching', + $$SELECT '{"", "", start_vid, end_vid, agg_cost}'::TEXT[]$$ + ); + + RETURN QUERY + SELECT function_types_eq( + 'pgr_maxweightedmatching', + $$VALUES + ('{text,bool,int8,int8,float8}'::TEXT[]) + $$ + ); + +END; +$BODY$ +LANGUAGE plpgsql; + +SELECT types_check(); + +SELECT * FROM finish(); +ROLLBACK; \ No newline at end of file diff --git a/sql/max_flow/CMakeLists.txt b/sql/max_flow/CMakeLists.txt index a11f19bb401..b785eb398a1 100644 --- a/sql/max_flow/CMakeLists.txt +++ b/sql/max_flow/CMakeLists.txt @@ -15,6 +15,8 @@ set(LOCAL_FILES maxFlowMinCost.sql maxFlowMinCost_Cost.sql maxFlow.sql + _maxWeightedMatching.sql + maxWeightedMatching.sql ) foreach (f ${LOCAL_FILES}) diff --git a/sql/max_flow/_maxWeightedMatching.sql b/sql/max_flow/_maxWeightedMatching.sql new file mode 100644 index 00000000000..956dca247b4 --- /dev/null +++ b/sql/max_flow/_maxWeightedMatching.sql @@ -0,0 +1,42 @@ +/*PGR-GNU***************************************************************** +FILE: _maxWeightedMatching.sql + +Copyright (c) 2007-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Mayur Galhate +Mail: galhatemayur 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*/ + +--v4.1 +CREATE FUNCTION _pgr_maxWeightedMatching( + edges_sql TEXT, + + OUT start_vid BIGINT, + OUT end_vid BIGINT, + OUT agg_cost FLOAT) +RETURNS SETOF RECORD AS +'MODULE_PATHNAME' +LANGUAGE C VOLATILE STRICT +COST ${COST_HIGH} ROWS ${ROWS_HIGH}; + +COMMENT ON FUNCTION _pgr_maxWeightedMatching(TEXT) +IS 'pgRouting internal function'; diff --git a/sql/max_flow/maxWeightedMatching.sql b/sql/max_flow/maxWeightedMatching.sql new file mode 100644 index 00000000000..8866d346ed6 --- /dev/null +++ b/sql/max_flow/maxWeightedMatching.sql @@ -0,0 +1,53 @@ +/*PGR-GNU***************************************************************** +FILE: maxWeightedMatching.sql + +Copyright (c) 2007-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Mayur Galhate +Mail: galhatemayur 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*/ + +--v4.1 +CREATE FUNCTION pgr_maxWeightedMatching( + TEXT, -- edges_sql (required) + BOOLEAN, -- directed (ignored, always undirected) + + OUT start_vid BIGINT, + OUT end_vid BIGINT, + OUT agg_cost FLOAT) +RETURNS SETOF RECORD AS +$BODY$ + SELECT start_vid, end_vid, agg_cost + FROM _pgr_maxWeightedMatching(_pgr_get_statement($1)); +$BODY$ +LANGUAGE SQL VOLATILE STRICT +COST ${COST_HIGH} ROWS ${ROWS_HIGH}; + +COMMENT ON FUNCTION pgr_maxWeightedMatching(TEXT, BOOLEAN) +IS 'pgr_maxWeightedMatching +- EXPERIMENTAL +- Undirected graph +- Parameters: + - Edges SQL with columns: id, source, target, cost [,reverse_cost] +- Documentation: + - ${PROJECT_DOC_LINK}/pgr_maxWeightedMatching.html +'; diff --git a/sql/sigs/pgrouting--4.1.sig b/sql/sigs/pgrouting--4.1.sig index 2e8a3eb33bf..27702b8f89c 100644 --- a/sql/sigs/pgrouting--4.1.sig +++ b/sql/sigs/pgrouting--4.1.sig @@ -213,6 +213,8 @@ pgr_maxflow(text,bigint,anyarray) pgr_maxflow(text,bigint,bigint) pgr_maxflow(text,text) _pgr_maxflow(text,text,integer,boolean) +_pgr_maxweightedmatching(text) +pgr_maxweightedmatching(text,boolean) _pgr_operating_system() _pgr_parameter_check(text,text,boolean) _pgr_pgsql_version() diff --git a/src/cpp_common/utilities.cpp b/src/cpp_common/utilities.cpp index 71e366c8555..be99c07fb84 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 MAXWEIGHTEDMATCHING: + return "pgr_maxWeightedMatching"; case ARTICULATIONPOINTS: return "pgr_articulationPoints"; diff --git a/src/max_flow/CMakeLists.txt b/src/max_flow/CMakeLists.txt index 57e1dd27edf..febdbf5e1bc 100644 --- a/src/max_flow/CMakeLists.txt +++ b/src/max_flow/CMakeLists.txt @@ -6,15 +6,18 @@ ADD_LIBRARY(max_flow OBJECT minCostMaxFlow.c maximum_cardinality_matching.c edge_disjoint_paths.c + maxWeightedMatching.c maxFlow_driver.cpp maxFlow_process.cpp maximum_cardinality_matching_driver.cpp minCostMaxFlow_driver.cpp + maxWeightedMatching_driver.cpp maximumcardinalitymatching.cpp maxflow.cpp flowgraph.cpp minCostMaxFlow.cpp + maxWeightedMatching_process.cpp ) diff --git a/src/max_flow/maxWeightedMatching.c b/src/max_flow/maxWeightedMatching.c new file mode 100644 index 00000000000..8914ba571cc --- /dev/null +++ b/src/max_flow/maxWeightedMatching.c @@ -0,0 +1,102 @@ +/*PGR-GNU***************************************************************** +File: maxWeightedMatching.c + +Generated with Template by: +Copyright (c) 2015-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Mayur Galhate +Mail: galhatemayur 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/iid_t_rt.h" +#include "c_common/debug_macro.h" + +#include "process/maxWeightedMatching_process.h" + +PGDLLEXPORT Datum _pgr_maxweightedmatching(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(_pgr_maxweightedmatching); + +PGDLLEXPORT Datum _pgr_maxweightedmatching(PG_FUNCTION_ARGS) { + FuncCallContext *funcctx; + TupleDesc tuple_desc; + + IID_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_DBG("Calling process"); + pgr_process_maxWeightedMatching( + text_to_cstring(PG_GETARG_TEXT_P(0)), + &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 = (IID_t_rt *) funcctx->user_fctx; + + if (funcctx->call_cntr < funcctx->max_calls) { + HeapTuple tuple; + Datum result; + Datum *values; + bool *nulls; + + size_t numb = 3; + values = (Datum *) palloc(numb * sizeof(Datum)); + nulls = (bool *) palloc(numb * sizeof(bool)); + + size_t i; + for (i = 0; i < numb; ++i) { + nulls[i] = false; + } + + values[0] = Int64GetDatum(result_tuples[funcctx->call_cntr].from_vid); + values[1] = Int64GetDatum(result_tuples[funcctx->call_cntr].to_vid); + values[2] = Float8GetDatum(result_tuples[funcctx->call_cntr].cost); + + tuple = heap_form_tuple(tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + SRF_RETURN_NEXT(funcctx, result); + } else { + SRF_RETURN_DONE(funcctx); + } +} diff --git a/src/max_flow/maxWeightedMatching_driver.cpp b/src/max_flow/maxWeightedMatching_driver.cpp new file mode 100644 index 00000000000..d105ff7c7df --- /dev/null +++ b/src/max_flow/maxWeightedMatching_driver.cpp @@ -0,0 +1,121 @@ +/*PGR-GNU***************************************************************** +File: maxWeightedMatching_driver.cpp + +Copyright (c) 2025-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Mayur Galhate +Mail: galhatemayur 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 "drivers/maxWeightedMatching_driver.hpp" + +#include +#include +#include +#include + +#include "c_types/iid_t_rt.h" +#include "cpp_common/pgdata_getters.hpp" +#include "cpp_common/alloc.hpp" +#include "cpp_common/assert.hpp" + +#include "max_flow/maxWeightedMatching.hpp" + + +namespace pgrouting { +namespace drivers { + +void do_maxWeightedMatching( + const std::string &edges_sql, + IID_t_rt* &return_tuples, + size_t &return_count, + std::ostringstream &log, + std::ostringstream ¬ice, + std::ostringstream &err) { + using pgrouting::pgr_alloc; + + std::string hint = ""; + return_tuples = nullptr; + return_count = 0; + + try { + if (edges_sql.empty()) { + err << "Empty edges SQL"; + return; + } + + hint = edges_sql; + auto edges = pgrouting::pgget::get_edges(edges_sql, false, false); + + if (edges.empty()) { + notice << "No edges found"; + log << edges_sql; + return; + } + hint = ""; + + std::vector matrix; + matrix.reserve(edges.size()); + for (const auto &e : edges) { + IID_t_rt row; + row.from_vid = e.source; + row.to_vid = e.target; + row.cost = e.cost; + matrix.push_back(row); + } + + pgrouting::graph::UndirectedHasCostBG graph(matrix); + + auto matched_pairs = pgrouting::flow::maximumWeightedMatch(graph); + + auto count = matched_pairs.size(); + + if (count == 0) { + log << "No matching found"; + return; + } + + double agg = 0.0; + return_tuples = pgr_alloc(count, return_tuples); + for (size_t i = 0; i < count; i++) { + agg += matched_pairs[i].cost; + matched_pairs[i].cost = agg; + return_tuples[i] = matched_pairs[i]; + } + return_count = count; + } catch (AssertFailedException &except) { + err << except.what(); + } catch (const std::pair &ex) { + err << ex.first; + log << ex.second; + } catch (const std::string &ex) { + err << ex; + log << hint; + } catch (std::exception &except) { + err << except.what(); + } catch (...) { + err << "Caught unknown exception!"; + } +} + +} // namespace drivers +} // namespace pgrouting diff --git a/src/max_flow/maxWeightedMatching_process.cpp b/src/max_flow/maxWeightedMatching_process.cpp new file mode 100644 index 00000000000..6c4d020ab86 --- /dev/null +++ b/src/max_flow/maxWeightedMatching_process.cpp @@ -0,0 +1,77 @@ +/*PGR-GNU***************************************************************** +File: maxWeightedMatching_process.cpp + +Copyright (c) 2025-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Mayur Galhate +Mail: galhatemayur 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 "process/maxWeightedMatching_process.h" + +#include +#include + +extern "C" { +#include "c_common/postgres_connection.h" +#include "c_common/e_report.h" +#include "c_common/time_msg.h" +} + +#include "c_types/iid_t_rt.h" + +#include "cpp_common/report_messages.hpp" +#include "cpp_common/assert.hpp" +#include "cpp_common/alloc.hpp" + +#include "drivers/maxWeightedMatching_driver.hpp" + + +void pgr_process_maxWeightedMatching( + const char* edges_sql, + IID_t_rt **result_tuples, + size_t *result_count) { + pgassert(!(*result_tuples)); + pgassert(*result_count == 0); + pgr_SPI_connect(); + + std::ostringstream log; + std::ostringstream err; + std::ostringstream notice; + + clock_t start_t = clock(); + pgrouting::drivers::do_maxWeightedMatching( + edges_sql ? edges_sql : "", + (*result_tuples), (*result_count), + log, notice, err); + + time_msg(" processing pgr_maxWeightedMatching", start_t, clock()); + + if (!err.str().empty() && (*result_tuples)) { + pfree(*result_tuples); + (*result_tuples) = nullptr; + (*result_count) = 0; + } + + pgrouting::report_messages(log, notice, err); + pgr_SPI_finish(); +}