From bf976aeffd52bd64d4aed60456f0abf1558264c7 Mon Sep 17 00:00:00 2001 From: mayurgalhate Date: Sat, 22 Aug 2026 08:25:20 +0000 Subject: [PATCH 1/7] [maxWeightedMatching/sql] Adding SQL code for pgr_maxWeightedMatching --- sql/max_flow/CMakeLists.txt | 2 + sql/max_flow/_maxWeightedMatching.sql | 42 +++++++++++++++++++++ sql/max_flow/maxWeightedMatching.sql | 53 +++++++++++++++++++++++++++ sql/sigs/pgrouting--4.1.sig | 2 + 4 files changed, 99 insertions(+) create mode 100644 sql/max_flow/_maxWeightedMatching.sql create mode 100644 sql/max_flow/maxWeightedMatching.sql 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() From 6bb0f96a5697ab88fa816dc8e7490461cb518d4b Mon Sep 17 00:00:00 2001 From: mayurgalhate Date: Sat, 22 Aug 2026 08:28:37 +0000 Subject: [PATCH 2/7] [maxWeightedMatching/C/C++] Adding C/C++ code for pgr_maxWeightedMatching --- include/c_common/enums.h | 2 + .../drivers/maxWeightedMatching_driver.hpp | 50 ++++++++ .../max_flow/maxWeightedMatching_driver.h | 57 +++++++++ include/max_flow/maxWeightedMatching.hpp | 104 +++++++++++++++ include/process/maxWeightedMatching_process.h | 56 ++++++++ src/cpp_common/utilities.cpp | 2 + src/max_flow/CMakeLists.txt | 3 + src/max_flow/maxWeightedMatching.c | 102 +++++++++++++++ src/max_flow/maxWeightedMatching_driver.cpp | 121 ++++++++++++++++++ src/max_flow/maxWeightedMatching_process.cpp | 77 +++++++++++ 10 files changed, 574 insertions(+) create mode 100644 include/drivers/maxWeightedMatching_driver.hpp create mode 100644 include/drivers/max_flow/maxWeightedMatching_driver.h create mode 100644 include/max_flow/maxWeightedMatching.hpp create mode 100644 include/process/maxWeightedMatching_process.h create mode 100644 src/max_flow/maxWeightedMatching.c create mode 100644 src/max_flow/maxWeightedMatching_driver.cpp create mode 100644 src/max_flow/maxWeightedMatching_process.cpp 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/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(); +} From 45aa8810f89d437f4be4acc53a8e4b6c76818fc3 Mon Sep 17 00:00:00 2001 From: mayurgalhate Date: Sat, 22 Aug 2026 08:29:00 +0000 Subject: [PATCH 3/7] [maxWeightedMatching/pgtap] Adding test files for pgr_maxWeightedMatching --- .../maxWeightedMatching/edge_cases.pg | 58 ++++++++++++++++++ .../maxWeightedMatching/inner_query.pg | 32 ++++++++++ .../maxWeightedMatching/no_crash_test.pg | 61 +++++++++++++++++++ .../maxWeightedMatching/types_check.pg | 54 ++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 pgtap/max_flow/maxWeightedMatching/edge_cases.pg create mode 100644 pgtap/max_flow/maxWeightedMatching/inner_query.pg create mode 100644 pgtap/max_flow/maxWeightedMatching/no_crash_test.pg create mode 100644 pgtap/max_flow/maxWeightedMatching/types_check.pg 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 From e615c43d1ffd56939b1724f3688da56a5e337246 Mon Sep 17 00:00:00 2001 From: mayurgalhate Date: Sat, 22 Aug 2026 08:30:09 +0000 Subject: [PATCH 4/7] [maxWeightedMatching/docqueries] Adding test documentation examples code for pgr_maxWeightedMatching --- docqueries/max_flow/CMakeLists.txt | 1 + docqueries/max_flow/maxWeightedMatching.pg | 31 +++++++++++ .../max_flow/maxWeightedMatching.result | 54 +++++++++++++++++++ docqueries/max_flow/test.conf | 1 + 4 files changed, 87 insertions(+) create mode 100644 docqueries/max_flow/maxWeightedMatching.pg create mode 100644 docqueries/max_flow/maxWeightedMatching.result 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 )] }, ); From 164fd0f153abba58ec5b00f4d0f9572a36b1b586 Mon Sep 17 00:00:00 2001 From: mayurgalhate Date: Sat, 22 Aug 2026 08:35:49 +0000 Subject: [PATCH 5/7] [maxWeightedMatching/doc] Adding documentation for pgr_maxWeightedMatching --- doc/_static/page_history.js | 2 + doc/max_flow/CMakeLists.txt | 5 + doc/max_flow/flow-family.rst | 3 + doc/max_flow/images/CMakeLists.txt | 15 ++ doc/max_flow/images/mwm_graph.png | Bin 0 -> 24369 bytes doc/max_flow/images/mwm_result.png | Bin 0 -> 24545 bytes doc/max_flow/pgr_maxWeightedMatching.rst | 179 +++++++++++++++++++++++ doc/src/pgRouting-introduction.rst | 1 + 8 files changed, 205 insertions(+) create mode 100644 doc/max_flow/images/CMakeLists.txt create mode 100644 doc/max_flow/images/mwm_graph.png create mode 100644 doc/max_flow/images/mwm_result.png create mode 100644 doc/max_flow/pgr_maxWeightedMatching.rst 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 0000000000000000000000000000000000000000..b409a258b8eb3f8be1eed718ee3eda9ed5ce3b75 GIT binary patch literal 24369 zcmX_nb6954+IO~X+xBEnZYI0QlWm(*Q+nxSYufM!YzCmv+uHDJ+W3i#4(qDn@%d{o+c5dB-VWO?Dty6zV7s`O{W+No&)|7j zvkl>W*o(fM6#DQ|#(L5xBSDRWk%piELjzqE-ug;0?285(?k6t%pU;;tzzR{%xgf(& zqyFy`X^3)sP};SMmnWHXO5lI~^QV{aU|MMBT`bhd|Gm(EJ~R*jQ)$MJO=%YcE>(>R zk@>|l#A>l}d#%Nh$Mc?{Ql}+ADwb$j`r-#S85>w5^&0v0>~HTsz*_(#mM_XrRv55&J3 zlvsaPtfFvgIr~`^W!jyr!8fcpNe)2CD_6@&kZ3vmTO$8qKLIS1Kh!TOdmt**McMBsB3lh+(7KB7b!ecRN1?5}J_B0qHyLnU% z*3NRXxZeogrtL>_Wg1UaYYWX;EMhjSx`@0nbxa#=+1;k|A@9C#`0ty6v$&jbahx26 zxBoS%ULJy;NaL0QGa~X}>c7r3Luw8>Jn+xpMoDGTS?4OL!w=y2kU28J7mCQsaARrLO;jHC^_XTpc zkr-{oZRdB1{GiyaMoloS@6ya2PP+l*brMaq`^`bm^fYRoWHg&5e|(%cNjr@6Hjy60 z{@dri89;HnIY3NpZp-s|dv-&8R7sv2dhZ~lEL~K~C?b$ZpR&CtyG_{+=18f_wcYHb zDSW)cbw++tBr`As-fPn(I7_y>dpNKyQ4qkSVhBVwyq>l?>`2o~=TLl{p!y&kOG-np z|IigZ?l(mmv=EXOQlVAYXK!zl7*8(=@6cJdNT7~Fzu7v&mwMkBe zD=s&o{n;twC{5rM)VOZI<9)bz(0jFEAH!(uPXQxxl3-79_xIW?G*EmIzY=rjlLd)% zx2xdIRkRfkX{7@fx^TlWE|RaE0XRH%Bp&ytxHARU&Emq}fm^C$>dEK;w`8FN+t}D3 z!(_B5ox|B{K)dx!i|?d0U#6CaLJn^7`fI{sv&QpF+ZL`hK19452Z9XOv8J7?mz)GN z4n_k3R4v?UCLb1!@~eMm9Tur`$T-$6Wqkt|5$bZ*Ye9kxZ ze*GF!f&N)lZdzn2LBmBLgtz*`(Jyar#Sa9YB-UN6&K!QheIE$oNaM2U2I@ye1Ae?M zy{IspE4_zC{P%ESaWR9b?4H~)#MV1>Z7V&_f4m^~GVw;;-6C!u?;V`364y46z=~`j zk!5O&$-v_4O;Ld@kIrR#kJ_-ten?-eDTrvoU|D}uyk3bJ@oY4~{Z+Bb0o2IV+ug*B1Q-o!Fg-oJ z0DCys8e_3Cl?I((B+5qP`qkYb9+Yjl|G$=r>p=XNgapeH^zMx>_$uLj&!JfxZrs@D zP^yBC{q`mTA6#)Ke=mg(th_S%kfi0axW9`T^o1ZkK5m1%P*ga1YU0C!OZvdS?wF zx5UFF!vOD39wMKD9CeBfY$%2h{G?vhk()eAlG}NI6w_WYqW*YbfuJm#IjhF!UG8_u z8I;1cQ4H6zB;1hiOYsGJ?1%)J0UdD2xiRD8W5k8?VA{Q&(2R#-27qpc%J1?s+Tn>= z+x2`6l|Ni1A4A)*Jm%+S0F81Usc(tHf%&;MzOQ67T${Lz-OK%%?a*F~c=xx@O-n~X zNf_=2g__Ol(;Ea^tk=O;YdTRXg5>+-_-c1(M_AzHVtjWejIS)T3Ea+Z%wx`c9<){Z z;DcbWrmipVbEQ-313W*nw}_#60^l zuxK*B41U+0={%`o^{UM8*;mhZR^kx^xXO7$ek&h>-j9Bmvx9k(k@sPQ%$+L}kY9zg z#x>mT{(Y}LWN?pLAi~DRK3#9aK0iMnPNc^^-|UKgzB@7A9gI%;A>U|C@`(swez(h! z8Cb)x3|J0E;U29vF~7gwD3%#~+#F6?UOj*PXkyfc35`nm#UypE|2O5Nn|RdmDff0+ z=L~n{Zz`wNROxs6bOCoX$AfWH?LtMMd3>Mi|Hwv&LMWiDszU&7p$+A_T>4pWX(*mp zZI-G@Rf@Q5JKfy)ur#Mv0y`msLxV&f^uBn)ssByKm>v1w3yqjAks}EGrP`vy; zxtp;+%rsi{G?t5%jN?}n`LoCU7Zft_Tf0NCFmNg#xHY;hL4?3|>5Iu|S0&5VgwW_s z42bV^nP9_3Fs?Bg=>5Z-b)|_)(v!|LlH*#XZ`c>0S-f0IZqy%ROdyZ0|F*rZT|G%f zh0o;ROA{C!h0E@Pe(GU_@ekt_@hJNmP3OA<%@pm&2XN4mYc8_1VQm@1UV z|5mI7%Ik0-PA9=HUv7>m6Hg_PT5s`-Szqv5nVLKV#6$)^wj4NoX6_lGNlj{rYLU_P z-binM1@I7|T1B(4a!b2{-p^sibLUUm^pPDM9R~aRH)cjmV7ea0@llol}^^f<`$#jgTDtsgk z<2=m|kF<@}n@mukw}dK7L*e-9$yguHC(JAc!xZBdaWI@!qlxr+CSyrm48RXeg2Rb4 z_4Kh{->J(zS8lV$&L91aiGX3bP>#DZ6dOw=pSra7I}N}bVw0I)BuFp< zb2|0;Tux~+?K1eB`=25(=_59^Gz-4|QK)wKIB>4{Zk$Nvs8)YP@G>@Iw8fGy_XqXr z6g@}gLd1NjL&#`QdGe?4#USS|wIo4r56)H#v{c~KfkXQ_-K@1b!$+lNaapHstx_OL zla-PJ8_RM^w%1U?@1W~6LQ)2_-g;FHqwF+bV;0Ncxa8!By$_y=afQ?~$0*F|p$hiF z^^8I;3G7KWDOmeZ@A6hJFxcnaocpJM1BMIODy?37Gmh}-O2dikk3C*?5<8$A=(M{j z*E`+pxYso7fA21U&-Glc4X&B1Y}Q`?ezDPUbv#Q=z+(sUbaM!ux8LS^5koE&Q@mQ8 zPh|6n%eaH${Yp@lnrw+9czI!?p9d#EK2h?`eU|9r6}wWl9Ap9qI=zj+@5-|f5%?tI zwUsj()gnP2KN{6S1RPBw6cmGa7m8n-9R)0Pv1Zz69u)kwV~X5-k(m>G{rN10qV)guQ=^UGGW9V z@o+SrVsn`Jw5Gn(FGE`^CDc3Aa^-mX7PUXbR&#b)+-LO$_>>60CNYQDthgQ4Iz)Xc28|L32k6VwQ%*VCRIA51hhB%9 zjRF=bqokZ0T_s_sr)8(>vw|*<3(iPF5lSH58!P-zM4NcgP5@mS$U&Da;(Fz^Pmz5# z3e+W&JP!ya-PRy7iO2xrw{(g0Ag=Z1Ly)>cngZ!RA~i_k;#DO#pSS`wcvE~@i6-vW zYqY{1hNaA3bhWP!9N2AGA{xQ)*ig`zMG6^U-fs-mj_6;KWcO4pZbzGgA>si9jV4&2qkq=4{F11vcE` z8hJPlg7VjrZzX6OzHYIZst381WNQsmjtd+Y93F6ZE+?VJQn9@)KV+0<=fG^WcKp%X z?a+SRpRGokTbdGASAwWwX`Tjg!l+efLF&0aU7L)jaU)0Lb4#XjScslU0)1OKPZE(} zvku&{us?V-ECQQg>6RNHuyBfsiobfUPM6uIJVc^sgteP&5G_ZXzSro1LW|SY-hf+N z_j=s}8_)3l^(h-R6BD^`gOTxnS^ze)9s|Qqg8ZvBWbP5-R51WVO?*LHH46*KKv!C# zQ~-+&E>kMjF_~~~l`KO#VsRilU+av9!=&FHPhn5polZy4ZF4~^N1m#;P-W4L4j7uB z{|;lVK>pRSEjAVz3>DaJZ&x^K^y(E;lcL&&KH&PD|G8HxZw6=W!oFaf0oB;&HWMW5y9*O z1z__qJWi{fzZv{kJN@kwP}n1Xb$%{DXLBi>rQuPN7ao(FV%!b?A{Y6J0NFv?>}+o@ zvhZ`c+PVZ=u+!HJE*{`8xQONk%B}b!Pj1yk~c% z^(MUx#S(uUpI?6b*jl^X?vwZ?unEaTur|Ff+C%(~MFHtZ0<4x-DajfJBDwGMK#f=# zld<6Nx4YeN{#!P|!4f<3`$05*@n&|;QD{X44v)Ds#f$QQyvQbB!>B(vbno&q0IDRv zBgWY1w(U%m{dx2}cbZ>e;Wo59GebHx!8jqqmNO_K%vLB%t?-r`6dCZy|j;FR-PS*Ftb zfkf#3Hy?(>YJ-4TA1Zft$wE4dJyyn|OeV_xDhigT+Lw5x!K%nq)ih@UIYBVK<6DV} z0m>i2_>A5>qk)L*g-1sUen?pAJXZJwS@L>}y^%ERGBbtVzSTx$UxT|SSlLMs%O0bl zVNCCBA|j$sfu~rWeJqF*>juuh-ZGTDL{bwi3C1fFWGsh> z*9)hT&@)?US#~p{CT87(uob@3>~A`(q=z>2;)jEb<2e94CGhZsB+Dli>zIYq=GgE! zEg_hhnR9Sw?z-QDF-G2*C2lIdBqGyI^hOTFvgqg0HPoBlW9abvszFTS<~D>5LXENA zT>PuPc6I)E_m;~N)P>-_)^xA|9ps;alvIZc+!4*Ap8W6wdF1ZvRjV-?!a7bT_-4J{ zNT|Hi560V(T%$>`5_)>bkV;4a-rAOL2MP9eN#v)*Gsep7GhMK8x~ zY@z$?bez>tR~Hx=8A*dCW*rl3{Mci*wU9`!&SJH|pp404y;48OU3g81p@Q(w=-*P~ z{svuBA<@%Vk)pwIzw&T}xr>uUax10fX+7Pv!1$posG|J!Mc0SqdaJXctt~Uyy<2+V z7u6u{x77#?LS5`tWZatZ?+3q`|GJ; zGGTOF<`G&I{5hH*Ik|FNhwYbw9-X)% z{&3;0_qP_2yiD+c1ugY-rF_F!f3j{HK-fz?f-*bHr4p`V5Zd9GR>|AV=ZR{MAr|Un z0Zn6zUtc54;;|>2;o~!kks{V<=&h~K67uQSiQNEeq)niXtVpF7bQJWp%p6ZFLNT=5 z1|DS^sCX2zzoq9$vI`ZX_2u=-!?|<`K3gnhoRAkR8YY|Bxd7Otgq3pL_aPR@kHIK( zX!nee4`5mir46!$i@ z9_MKOuhUXzCWzioF0sbz*uQ-2-L^Fkp!7{GwFFP+{)w;eMy+$e$xKN+OJ+3=n7=r( zbidw%UejfWGAYgaBCxbdJ_9rwei&(GI3)WGh zwrpwNK!)8s{0hmnQhy+bsp4w?=Fp&OcBO|`>^Xo5GIhMo>DWm5K36=P49eZ{@b5rh zD6)ur=KB@^$zXa0L23Y9A=3OcOl8r3GRToor^5l0qfYVq3KRWn$y@f$G9^ZEttnk# zm`X%%H6U^QdTuf33D`uO8bn(A2ns|JvwqB1>^J2SWk*)pP|^pDK2A+|4AjC# z0P#eSaHw8;9nVlI=%)Z&Z-Frhn^rGdqpf}cKo`nI%c#OANGi|YNdl2*Bg zROCQQUMNehGr8C6<;IjYqr>{EA$Ya*vgn+$*9M2bc2GD9UNi+Q)RvRq{TKDBzKPPS+0(_JVMAy^=u7>P+@Xo^KQFQdjAbCwZTR{JtGLL9rR*=Yim z*b6H9wC4&~f4D8Q!gyaYj_M^TAm}Om<_M55*l2g(;Q?3Zk3p;$ZQ(-e$XOLR4Kf^v z&m3niIyl?e0h8pe>S%8d8j1OJ#!+QS8UF=A7wLg9%Guxw>mUNz zT1&BR-=gULIA9_YySqRVv0SHyZ0$`g4MdlpL8ng(L(ZRo&xz*fI>$L;wxgDe6k2_? z(ONYPbN<-T@)$sz;k=B=9kZ@U-0}GiN*(K)oS#5FO0x|ycvKCdNTgDy0PZN@W&KM| z#*0LAs7s&#Gb#%NsHEX6w@$kjWB>P8jgU2C`Ncg)rpP@Df*SKHqrl}t>1$+l~W%h%58%2y7~zjJ<8>9W_Ds+KHGpc+YQEqtw=c3{mhh|y(s&Kc(Vf$ z`GIe#&8A>j_|fxBtD@SJg*a*zhs6K{r&y*U6EvE@>2Ky2d`!A;C>M;X{pOrT6A*#0 zR$INmtBNq_rH&;I4|8QSx#*9i56Bm)a%=o_6@ z2_83B3kID&WD{mWVO5oF^_wvT+sTom111)+AqM-`dKDH{ZeBq8>0KNlDFO_Fgt;Hc zNvQY&0LRg1S-)7`4ZZw%5b>RzN~`_P3s<>Fl@_Mm`LGDf`o!0oW{va`JGMBw?{uG_ z>YKjtD>S4R0ExS00(CH~5;8If0a6L8K-ZUyZ2m|LKmARaV@LWIx$#uMKAF014*~(6KC4;_lJ{Nk=Kd0Mc6diOh_JjWY;dkairP%LBU@4w$J!q ztdfR~?&a;B{v3mMGi3?-YX*AcxYy`t2>C+6VKR|j58*kc567*Ii1Xb=RkO2_ti91Mnn78faIi0)>Bf07JGZsN-O)O$iOEtr(~YV&Eb zJad_C8w{OmBQcb%J@WRytlA1T-!#pk9%Cd=QEuk^{Iu!X~6c8#)+dq8cozhHHhKz?wKQ^)uCCU-O^4RCNEtjTyMd& z)$d?SEvga`;kj@{`AQW9P``qy$%fKvA4bV5G)iIg*KILqiTCE|AnVHz^XXhzVu7*7 z87akyOgzB`S1t9&D7GKNB&FTcxdmJhQIN?(zMVVzRWbi2R!i2?>26ouVno0s+>e)P z(0L%MbE3sTwryxIuK83+yPfc6WD`lzxl0+CvxI`ZXZGwYmWh;qBg5f`WAL)c&O1mz zHc`EXj}^s)SEQExK4yozj{1qs?_gpNk=!~UEUETt^uznQKfK1egG3|bJBj%d7 zQ}ErufHnz^v?a~#FN6*yJ!(`Ij89(&sDsEgJA>@E zSb0^EW`9s_rH-9iiO=Vcorb@UYZ62fu_rB+w#de_HIPgSMiREnAb?rSf^Cgswd_PheHXKlm|GQ9iCGC3ZD|w90%tzFTJs>K4EMEutgoE5K=W@nHAD` z44z5AZ*Wp3A7Ry6@}X>gVp}vA+86(Pf=?+(FRytG-hcKW^axv(qyd+&K_8Wcdr$(2 zFN&5TVPQdGH5y`lmqcH;-@k?ZF`^EWa#Kn?8CaY9KP_MwOZAE57+C%!m63itD>xWH zFCpRUSP}tmQ_DaZtI$z7`!~J1!gv=Uvz`FE9a-8|w#DgEO)eby;n(bjV2k`HGlT3* zpq=pCF?%0IjdMe=CME7W&zH|lDs1@b#TVF%7lceyPcCB13B5kOPIeX8ec+;g_mx7} z;V5~-r7V@;djZ7N>M&^lz>lj+0Bx@9GE~?Q{=?(;6t*4o8>|{9w-UtiGrLCUwvAxMp4dy(>NKnr~=~ z05%nb6CWYY1W{pHpFR5OTD=_-TvA1t)3PJrEZH~J>SmlvZFbpX?9Bt7UH2pm%SjOG zJ-`e>sXeym1^|qVI)BYGU=OGoJt=6KS64uR+q5b23`XDjU9!5h%BsC=i0Jjaney`P zofUYJg>l}KlY3>XWyUNSg;U)hrR&4rL?W_7QM^#k>BM&fqg^QD{eBC3TDOoDxd(XD z6)-$}d=V-WK(qn!WZh8|A8W)gSTyx2MQ^AF#H>;Kr~{@vMl;ug(CX~m_UXWju-)&t zX)UB4aNp3_uHBMRrQ=9IH#!jzZr%)AuAZORn_YMD=(sIofQ}v-ES`Msj`^swHuQPG z;sIyxn}pxpd7JY|$lJ?9q(xi`WY1DK;9W$Pha^zP&@Z&fn&NMjtt=aa9RjGz&E@{l zbG_Bz@B2p_#`(t(S-@Gz;`-i5{PuDl^gqr^bQ3t2E<)|CnRZ69kE=sPv-!7?>!zQi zDKlm^4b4;5-NSYYm(%5%?2NTE?(r}|u%ORdz#Yq>4`zVSfX>v&iH_FKc|Bc0Z?L=C zBCLWc&G*!=XX7LGmLEjl)Bf&%gQJcimXfxI{;31!noEM6Kqid7GW2lKv&EHwh7_`7 zvL726(4jYUE(GHOS8v?bRHou{>&Zm~v4$ZuTPRQ8sHLsj?uIJIzVp*5y<~DOShRjY ze?+lg!f&C@^D4&pBwgYwiPd|rC0F!ma0J{lV6CCA zZUIaiIy3?*w#E-?*42qGgM}M7I4t_VZ_xV!J_4*#JDG{PoR_nJ{)_JEYGfYyywu?W zxsu2jwLp2}t-S_s+LKP|y%on=XL*)zc}9=jB9g?Q*{^0rU+6h4Gy*G7KbW>BMP^_L z5kqhlC|1Z2hDlvnBJ&}7vC_i6vjk*7st;z~Y{$CYzjl3}z`!o99{o+-mF;n=G5npU zzz!S5!gF9)vCzj02)sZ`ro?IktcWMv|yKw`7e zo&)nxUP$twjW@UQV673T(y;aod#{JJx7b|`#Dq%3?$BKhcvIO|5<+}=BjE?8Z=w}n z05>OfH}jYl&^rbPjHM9vCLYIlRA($d+BPM}He=K8UI-->Fw~HCOYmKwvjRV^$nEIV z#;fre+`hj)UgZq94g|yv7g{5LdRyW|b`-BS_ohYPJ$Vvbqvv^8XG?58+{NX--TfPIEKlbG%@Xw5qAX>4)?~hWdv5`_v9F}KSeG*&VWE6l2p}=;0w8Y- zBi#4>iTWt;FnzxKZ$7CDBt5|wPY(|=lQDa1^-T2d}7DB<~Xm1Z9)rhC0_vPWDZ_^K!rdk>f zUK-GCU;?3$vGz5})T@|iY&Ea{EUnB~zSIVX;d2vxdU-(g^{-Q+R7l4G@&TV3e79T( zr`1Bx5po?R!w-E5FfC!f2;`^7=Y4o?Q~e{P;^kIyLEl&Nr0Z3YkGq@LkD%8{!kF6| z&?oIxn0)D9VClZ8Eqef07Zn!v8^9@CH2~@njczN)@MHiayi&WHeYtiM(x*ZTNX5!E zMwF*|bp%cCW6(`FbyPo--(Z`)P<30KpsbKtCZY;Ic}|!NBhY!E0rhsSQWrmcEuf+5 zVolkX!nT)5Rs{{TOw3OfC8bazTKg-*2*C`Q%PT|}fu{FKkLQ5Hu;9O!iMI=E+O;mJ zKH2*c!iTd}Y+{Tqq_?XB(=_YaOm_K;?`3L`Wfb#O`oz?VnHas%cx?&~whGQwdS4(t z%gp4^w6!%VGQQwmB?@hwueW{75F*(WPi(fiA_HM9G%SR_T8VtlmMkPHYT+-CZ0^Ej z251ncXCH|9o=>MGGDpF|!?U3ezj|4}(Cqa&bg@3Pd%er)R9-YaUabR|<)XW~Q1@>@ ze<)tOjAV2t%Y43imELFOfY0d&&IFp(EIVYZKA}qxg+)uZx-%7 zuYyqB>|U@01fa-WPM1h)C(UNtTy0su?|-^S6%(ihbIDQ}>m2S%kFRm@h*09glxZH1(ySp#GU)TTPdpta2x>RUItXT7< z6;w~mzC6UqJz<4XfCF@12$z0Ug{8WlCb)g2yzZ6}e2{>EfW_rhV`+IA_Cu&a^*LEA zjU)KL9joDr=q$XcO0Q(1((-olg|L6qu`nlD)gG19R$7cNL&5y?1VeoQ2wX@A1Sk}t zGr{kSq+S3~o6ywb===%4ao})4M=iU(zqXK;k6BTGZK7=Pi*sj}mC>6+EgU;70p5Aj zjn$Tqq2p6bMGBT%5?;M-|1q&1y6cL@?uVx5Re~Qieh&V{;jDu|=r1)Tq3Y;=0RfEzRGclHCESy;bPE7%&xzs2jx0fRvU zMIr(NwM0*@N4w31Q3E#O*GtG>!@oTKGU(JhBY~dDXVWL4of>1Y$QK(%nPx&g=lS92 z{R>N&&?11$pB+aX!{F0L7s$lKw8fDGhy`HKa)UE(>vw<$2x1J@>U=`Q#BaCdhm&rv zM1?e*_?f#S67-^BP#B-u?v6{MF}<9k#2&3M{W3upg@j~bcM{)KdS*JJjxta--&-ez z%v$5AeOvyW=#K)C*rq1@uf=5n$O@tjBoxSAAN6}JKsMG7yhJ?=&2nBT$x)q}VNl5F z25AI*xxSGRS}#NhNA8KHTTJ$y)rkrD$7x=zA|Y>pkQ4*ZI9axpExr?o69B~(TulL= zSZ-`ZkfXvDd#%Oidp^|xkhatrmy99M+!}6$T&&dnwCQ`?!9$P;>4F5TaFytD8!;+F zey5GlLx;|-bm1p(e4a8ra2Kby|$7$oBvOt#ikrSaqR z46Ds&W9pr!eFB<5$)1&xJe|&EJehR|msztGVlKwZ%S)KJBX9>Qr%-WX0vG8hD9M^085@*7wj1+fBj45Mxd z!^!%-D*dj~fq;B^)W;AI-GF+UV_iE0h&LCNKK^oMk$avC@TSJr^o=fj0Pka#G?Lya z!#%{s)okw)1GWIr;Uo+R-dU&iB)j+`E2rdu56nh-&92vz8{j&FT*YuvBE zI8#|+KslzsWnH z_pRx2X+Z`KqR>DY-*&zf(>%hg2Q#V8BpXxe@E2LhhwPMWwf8F5kC9Z&n@~i*hb6;Q zu;=v?b)4Cpl&K}F`BFsUEP1T$*q(Sst$GlldM`;}{|?oeO^Ww_$sz^60&H-Ek_qj- zt0kvAjy`rI3g7qF?6|{hm7-aHtinp&ws*9>GD?=UHJvN9C;W{zo5Z9gHeBWxu1eJU zBQ{ii(sHt3I~}4%QV1OT&0=M8)bfFPj`!(W5cs!aggJn5WG#D+VF&^YSh>>6y*x_@ zyiaQZnp}9|@310bCT~5m_rGren|~|F)~)X>t-VMdqmoHQtbG6$Hk{0w_jts1AK>!> z?DTCw3KY59)9UU8AM9-}#-&9|;+&AHh&nfnAb%T=Krd3Nz>{vi?43q1Ur zr≻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 + + + 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, From 5913d656c54ede71dfdb7f30b706352b8ae7b76d Mon Sep 17 00:00:00 2001 From: mayurgalhate Date: Sat, 22 Aug 2026 08:36:58 +0000 Subject: [PATCH 6/7] Updating release notes and NEWS about the new function pgr_maxWeightedMatching --- NEWS.md | 9 +++++++++ doc/src/release_notes.rst | 11 +++++++++++ locale/en/LC_MESSAGES/pgrouting_doc_strings.po | 1 + locale/pot/pgrouting_doc_strings.pot | 1 + 4 files changed, 22 insertions(+) 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/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/locale/en/LC_MESSAGES/pgrouting_doc_strings.po b/locale/en/LC_MESSAGES/pgrouting_doc_strings.po index e8a867aa925..571367b6553 100644 --- a/locale/en/LC_MESSAGES/pgrouting_doc_strings.po +++ b/locale/en/LC_MESSAGES/pgrouting_doc_strings.po @@ -8,6 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: pgRouting v4.1\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-06-09 11:50+0000\n" "POT-Creation-Date: 2026-08-08 21:44+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" diff --git a/locale/pot/pgrouting_doc_strings.pot b/locale/pot/pgrouting_doc_strings.pot index 251bca6dd9b..dabf763126c 100644 --- a/locale/pot/pgrouting_doc_strings.pot +++ b/locale/pot/pgrouting_doc_strings.pot @@ -8,6 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: pgRouting v4.1\n" "Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-06-09 11:50+0000\n" "POT-Creation-Date: 2026-08-08 21:44+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" From 148c7c5cff630c8db83ad68320d1eb0f27a3bb6a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 22 Aug 2026 09:10:02 +0000 Subject: [PATCH 7/7] Update locale: commit 5913d656c --- .../en/LC_MESSAGES/pgrouting_doc_strings.po | 112 +++++++++++++++--- locale/pot/pgrouting_doc_strings.pot | 89 ++++++++++++-- 2 files changed, 172 insertions(+), 29 deletions(-) diff --git a/locale/en/LC_MESSAGES/pgrouting_doc_strings.po b/locale/en/LC_MESSAGES/pgrouting_doc_strings.po index 571367b6553..d9123dc56f8 100644 --- a/locale/en/LC_MESSAGES/pgrouting_doc_strings.po +++ b/locale/en/LC_MESSAGES/pgrouting_doc_strings.po @@ -8,8 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: pgRouting v4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-09 11:50+0000\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" @@ -3365,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 "" @@ -3993,6 +3997,11 @@ msgid "" "breadthFirstSearch: Reorganize into traversal" msgstr "" +msgid "" +"`#3131 `__: " +"binaryBreadthFirstSearch: Integrate into existing process/driver pair" +msgstr "" + msgid "Bug Fixes" msgstr "" @@ -4001,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" @@ -6496,9 +6521,6 @@ msgstr "``y2``" msgid "Y coordinate of ``target`` vertex." msgstr "" -msgid "Flow" -msgstr "" - msgid "Edges SQL for :doc:`flow-family`" msgstr "" @@ -7149,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)" @@ -7996,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 " @@ -13155,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 "" @@ -15558,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 dabf763126c..17263898e22 100644 --- a/locale/pot/pgrouting_doc_strings.pot +++ b/locale/pot/pgrouting_doc_strings.pot @@ -8,8 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: pgRouting v4.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-09 11:50+0000\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" @@ -3053,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 "" @@ -3572,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 "" @@ -5735,9 +5755,6 @@ msgstr "" msgid "Y coordinate of ``target`` vertex." msgstr "" -msgid "Flow" -msgstr "" - msgid "Edges SQL for :doc:`flow-family`" msgstr "" @@ -6281,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)" @@ -7004,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 "" @@ -11219,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 "" @@ -13121,9 +13189,6 @@ msgstr "" msgid "`#2718 `__: pgr_pushRelabel(Combinations)" msgstr "" -msgid "New experimental functions." -msgstr "" - msgid "Metrics" msgstr ""