From 3a59265b9048f6909ebb3d462d967334c5e3bad1 Mon Sep 17 00:00:00 2001 From: sakirr05 Date: Sat, 22 Aug 2026 04:04:34 +0530 Subject: [PATCH 1/6] [coreNumbers/sql] Adding SQL code for pgr_coreNumbers --- sql/metrics/CMakeLists.txt | 2 ++ sql/metrics/_coreNumbers.sql | 42 ++++++++++++++++++++++++++++ sql/metrics/coreNumbers.sql | 53 ++++++++++++++++++++++++++++++++++++ sql/sigs/pgrouting--4.1.sig | 2 ++ 4 files changed, 99 insertions(+) create mode 100644 sql/metrics/_coreNumbers.sql create mode 100644 sql/metrics/coreNumbers.sql diff --git a/sql/metrics/CMakeLists.txt b/sql/metrics/CMakeLists.txt index a387c9bad58..501b127bc5a 100644 --- a/sql/metrics/CMakeLists.txt +++ b/sql/metrics/CMakeLists.txt @@ -8,6 +8,8 @@ set(LOCAL_FILES degree.sql _bandwidth.sql bandwidth.sql + _coreNumbers.sql + coreNumbers.sql ) foreach (f ${LOCAL_FILES}) diff --git a/sql/metrics/_coreNumbers.sql b/sql/metrics/_coreNumbers.sql new file mode 100644 index 00000000000..b08c3bcd726 --- /dev/null +++ b/sql/metrics/_coreNumbers.sql @@ -0,0 +1,42 @@ +/*PGR-GNU***************************************************************** +File: _coreNumbers.sql + +Copyright (c) 2015-2026 pgRouting developers +Mail: project@pgrouting.org + +Copyright (c) 2026 Sakir Ahmed +Mail: sakirahmed75531 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_coreNumbers( + TEXT, -- edges_sql (required) + + OUT seq BIGINT, + OUT node BIGINT, + OUT core BIGINT) + +RETURNS SETOF RECORD AS +'MODULE_PATHNAME' +LANGUAGE C VOLATILE STRICT +COST ${COST_HIGH} ROWS ${ROWS_HIGH}; + +COMMENT ON FUNCTION _pgr_coreNumbers(TEXT) +IS 'pgRouting internal function'; diff --git a/sql/metrics/coreNumbers.sql b/sql/metrics/coreNumbers.sql new file mode 100644 index 00000000000..21830b0aaec --- /dev/null +++ b/sql/metrics/coreNumbers.sql @@ -0,0 +1,53 @@ +/*PGR-GNU***************************************************************** +File: coreNumbers.sql + +Copyright (c) 2007-2026 pgRouting developers +Mail: project@pgrouting.org + +Copyright (c) 2026 Sakir Ahmed +Mail: sakirahmed75531 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_coreNumbers( + TEXT, -- edges_sql (required) + + OUT seq BIGINT, + OUT node BIGINT, + OUT core BIGINT) + +RETURNS SETOF RECORD AS +$BODY$ + SELECT seq, node, core + FROM _pgr_coreNumbers(_pgr_get_statement($1)) AS a; +$BODY$ +LANGUAGE SQL VOLATILE STRICT +COST ${COST_HIGH} ROWS ${ROWS_HIGH}; + + +COMMENT ON FUNCTION pgr_coreNumbers(TEXT) +IS 'pgr_coreNumbers +- EXPERIMENTAL +- Undirected graph +- Parameters: + - edges SQL with columns: id, source, target, cost [,reverse_cost] +- Documentation: + - ${PROJECT_DOC_LINK}/pgr_coreNumbers.html +'; diff --git a/sql/sigs/pgrouting--4.1.sig b/sql/sigs/pgrouting--4.1.sig index 2e8a3eb33bf..99d23d7dd9b 100644 --- a/sql/sigs/pgrouting--4.1.sig +++ b/sql/sigs/pgrouting--4.1.sig @@ -89,6 +89,8 @@ pgr_contractionhierarchies(text,boolean,bigint[]) pgr_contractionlinear(text,boolean,bigint[]) _pgr_contraction(text,bigint[],integer,bigint[],boolean) pgr_contraction(text,boolean,integer[],integer,bigint[]) +_pgr_corenumbers(text) +pgr_corenumbers(text) _pgr_cuthillmckeeordering(text) pgr_cuthillmckeeordering(text) pgr_dagshortestpath(text,anyarray,anyarray) From 5a957429ed56d5bd6159c48c94a9410fc7292e46 Mon Sep 17 00:00:00 2001 From: sakirr05 Date: Sat, 22 Aug 2026 04:05:18 +0530 Subject: [PATCH 2/6] [coreNumbers/C/C++] Adding C/C++ code for pgr_coreNumbers --- include/c_common/enums.h | 4 +- include/metrics/coreNumbers.hpp | 45 +++++++++ src/coloring/coloring_driver.cpp | 22 ++++- src/cpp_common/utilities.cpp | 3 + src/metrics/CMakeLists.txt | 2 + src/metrics/coreNumbers.c | 101 +++++++++++++++++++ src/metrics/coreNumbers.cpp | 162 +++++++++++++++++++++++++++++++ 7 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 include/metrics/coreNumbers.hpp create mode 100644 src/metrics/coreNumbers.c create mode 100644 src/metrics/coreNumbers.cpp diff --git a/include/c_common/enums.h b/include/c_common/enums.h index e2195070d13..fda60297998 100644 --- a/include/c_common/enums.h +++ b/include/c_common/enums.h @@ -54,7 +54,9 @@ enum Which { // NOLINT(cppcoreguidelines-use-enum-class) EDGECOLORING, BIPARTITE, SEQUENTIAL, /* For components */ CONNECTEDCOMPONENTS, BICONNECTEDCOMPONENTS, STRONGCOMPONENTS, ARTICULATIONPOINTS, - BRIDGES, MAKECONNECTED + BRIDGES, MAKECONNECTED, + /* For metrics */ + CORENUMBERS }; #endif // INCLUDE_C_COMMON_ENUMS_H_ diff --git a/include/metrics/coreNumbers.hpp b/include/metrics/coreNumbers.hpp new file mode 100644 index 00000000000..ba01bba1be4 --- /dev/null +++ b/include/metrics/coreNumbers.hpp @@ -0,0 +1,45 @@ +/*PGR-GNU***************************************************************** +File: coreNumbers.hpp + +Copyright (c) 2026-2026 pgRouting developers +Mail: project@pgrouting.org + +Copyright (c) 2026 Sakir Ahmed +Mail: sakirahmed75531 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_METRICS_CORENUMBERS_HPP_ +#define INCLUDE_METRICS_CORENUMBERS_HPP_ +#pragma once + +#include + +#include "c_types/ii_t_rt.h" +#include "cpp_common/base_graph.hpp" + +namespace pgrouting { +namespace metrics { + +std::vector coreNumbers(const pgrouting::UndirectedGraph &graph); + +} // namespace metrics +} // namespace pgrouting + +#endif // INCLUDE_METRICS_CORENUMBERS_HPP_ diff --git a/src/coloring/coloring_driver.cpp b/src/coloring/coloring_driver.cpp index d70cf709912..9b67fbfaf6b 100644 --- a/src/coloring/coloring_driver.cpp +++ b/src/coloring/coloring_driver.cpp @@ -28,6 +28,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #include "drivers/coloring_driver.hpp" +#include #include #include #include @@ -48,6 +49,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #include "coloring/sequentialVertexColoring.hpp" #include "components/components.hpp" #include "components/makeConnected.hpp" +#include "metrics/coreNumbers.hpp" namespace pgrouting { namespace drivers { @@ -86,10 +88,21 @@ void do_coloring( using pgrouting::algorithms::connectedComponents; using pgrouting::algorithms::strongComponents; using pgrouting::functions::makeConnected; + using pgrouting::metrics::coreNumbers; hint = edges_sql; auto edges = get_edges(edges_sql, true, false); + if (which == CORENUMBERS) { + /* remove self loops */ + edges.erase( + std::remove_if(edges.begin(), edges.end(), + [](const Edge_t &edge) { + return edge.source == edge.target; + }), + edges.end()); + } + if (edges.empty()) { notice << "No edges found"; log << edges_sql; @@ -116,7 +129,11 @@ void do_coloring( return; } } else { - undigraph.insert_edges(edges); + if (which == CORENUMBERS) { + undigraph.insert_min_edges_no_parallel(edges); + } else { + undigraph.insert_edges(edges); + } switch (which) { case EDGECOLORING: @@ -139,6 +156,9 @@ void do_coloring( case CONNECTEDCOMPONENTS: component_results = connectedComponents(undigraph); break; + case CORENUMBERS: + results = coreNumbers(undigraph); + break; default: err << "coloring_driver.cpp: Unknown function with name '" << get_name(which) << "' for undirected graph"; diff --git a/src/cpp_common/utilities.cpp b/src/cpp_common/utilities.cpp index 71e366c8555..bdb7dc406e1 100644 --- a/src/cpp_common/utilities.cpp +++ b/src/cpp_common/utilities.cpp @@ -123,6 +123,9 @@ get_name(Which which) { case STRONGCOMPONENTS: return "pgr_strongComponents"; break; + case CORENUMBERS: + return "pgr_coreNumbers"; + break; default: return "unknown"; break; diff --git a/src/metrics/CMakeLists.txt b/src/metrics/CMakeLists.txt index 99e5a7e342f..907cb69ded8 100644 --- a/src/metrics/CMakeLists.txt +++ b/src/metrics/CMakeLists.txt @@ -5,6 +5,8 @@ ADD_LIBRARY(metrics OBJECT betweennessCentrality.c betweennessCentrality_driver.cpp bandwidth.c + coreNumbers.c + coreNumbers.cpp metrics_driver.cpp metrics_process.cpp ) diff --git a/src/metrics/coreNumbers.c b/src/metrics/coreNumbers.c new file mode 100644 index 00000000000..b911ca1d47f --- /dev/null +++ b/src/metrics/coreNumbers.c @@ -0,0 +1,101 @@ +/*PGR-GNU***************************************************************** +File: coreNumbers.c + +Generated with Template by: +Copyright (c) 2015-2026 pgRouting developers +Mail: project@pgrouting.org + +Function's developer: +Copyright (c) 2026 Sakir Ahmed +Mail: sakirahmed75531 at gmail.com + +------ + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + ********************************************************************PGR-GNU*/ + +#include +#include "c_common/postgres_connection.h" +#include "c_types/ii_t_rt.h" +#include "process/coloring_process.h" + +PGDLLEXPORT Datum _pgr_corenumbers(PG_FUNCTION_ARGS); +PG_FUNCTION_INFO_V1(_pgr_corenumbers); + +PGDLLEXPORT Datum _pgr_corenumbers(PG_FUNCTION_ARGS) { + FuncCallContext *funcctx; + TupleDesc tuple_desc; + + II_t_rt *result_tuples = NULL; + size_t result_count = 0; + + if (SRF_IS_FIRSTCALL()) { + MemoryContext oldcontext; + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + pgr_process_coloring( + text_to_cstring(PG_GETARG_TEXT_P(0)), + false, + + CORENUMBERS, + &result_tuples, + &result_count); + + funcctx->max_calls = result_count; + funcctx->user_fctx = result_tuples; + if (get_call_result_type(fcinfo, NULL, &tuple_desc) + != TYPEFUNC_COMPOSITE) { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + } + + funcctx->tuple_desc = tuple_desc; + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + tuple_desc = funcctx->tuple_desc; + result_tuples = (II_t_rt*) funcctx->user_fctx; + uint64_t call_cntr = funcctx->call_cntr; + + if (call_cntr < funcctx->max_calls) { + HeapTuple tuple; + Datum result; + Datum *values; + bool *nulls; + + size_t num = 3; + values = palloc(num * sizeof(Datum)); + nulls = palloc(num * sizeof(bool)); + size_t i; + for (i = 0; i < num; ++i) { + nulls[i] = false; + } + + values[0] = UInt64GetDatum(call_cntr + 1); + values[1] = Int64GetDatum(result_tuples[call_cntr].d1); + values[2] = Int64GetDatum(result_tuples[call_cntr].d2); + + tuple = heap_form_tuple(tuple_desc, values, nulls); + result = HeapTupleGetDatum(tuple); + SRF_RETURN_NEXT(funcctx, result); + } else { + SRF_RETURN_DONE(funcctx); + } +} diff --git a/src/metrics/coreNumbers.cpp b/src/metrics/coreNumbers.cpp new file mode 100644 index 00000000000..69de1fa9ef4 --- /dev/null +++ b/src/metrics/coreNumbers.cpp @@ -0,0 +1,162 @@ +/*PGR-GNU***************************************************************** +File: coreNumbers.cpp + +Copyright (c) 2026-2026 pgRouting developers +Mail: project@pgrouting.org + +Copyright (c) 2026 Sakir Ahmed +Mail: sakirahmed75531 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 "metrics/coreNumbers.hpp" + +#include +#include +#include + +#include "cpp_common/base_graph.hpp" +#include "cpp_common/interruption.hpp" + +namespace { + +using V = pgrouting::UndirectedGraph::V; + +/** + * Sort vertices by degree using bin-sort. + * + * bin[d] = starting position of vertices with degree d + * vert[i] = vertex at position i in the sorted order + * pos[v] = position of vertex v in the vert array + */ +void bin_sort_vertices( + const pgrouting::UndirectedGraph &graph, + const std::vector °ree, + size_t max_degree, + std::vector &vert, + std::vector &pos, + std::vector &bin) { + pgrouting::UndirectedGraph::V_i vi, vi_end; + + bin.assign(max_degree + 1, 0); + for (boost::tie(vi, vi_end) = vertices(graph.graph); vi != vi_end; ++vi) { + ++bin[degree[*vi]]; + } + + size_t start = 0; + for (size_t d = 0; d <= max_degree; ++d) { + size_t num = bin[d]; + bin[d] = start; + start += num; + } + + for (boost::tie(vi, vi_end) = vertices(graph.graph); vi != vi_end; ++vi) { + pos[*vi] = bin[degree[*vi]]; + vert[pos[*vi]] = *vi; + ++bin[degree[*vi]]; + } + + /* Restore bin after the sort pass */ + for (size_t d = max_degree; d > 0; --d) { + bin[d] = bin[d - 1]; + } + bin[0] = 0; +} + +std::vector compute_core_numbers( + const pgrouting::UndirectedGraph &graph) { + size_t n = boost::num_vertices(graph.graph); + if (n == 0) return {}; + + /** degree - current degree of each graph vertex */ + std::vector degree(n); + + pgrouting::UndirectedGraph::V_i vi, vi_end; + /** + * Get the degree of all vertices in the graph. + */ + for (boost::tie(vi, vi_end) = vertices(graph.graph); vi != vi_end; ++vi) { + degree[*vi] = boost::out_degree(*vi, graph.graph); + } + + size_t max_degree = *std::max_element(degree.begin(), degree.end()); + + std::vector vert(n); + std::vector pos(n); + std::vector bin; + bin_sort_vertices(graph, degree, max_degree, vert, pos, bin); + + /** + * Process vertices in order of increasing degree. + * For each vertex v, its current degree[v] becomes its core number. + * Then for each neighbor u with degree[u] > degree[v], decrement degree[u] + * and re-sort by swapping u with the first vertex of its bin. + */ + for (size_t i = 0; i < n; ++i) { + V v = vert[i]; + pgrouting::UndirectedGraph::EO_i ei, ei_end; + for (boost::tie(ei, ei_end) = out_edges(v, graph.graph); + ei != ei_end; ++ei) { + V u = target(*ei, graph.graph); + if (degree[u] > degree[v]) { + size_t du = degree[u]; + size_t pu = pos[u]; + size_t pw = bin[du]; + V w = vert[pw]; + if (u != w) { + pos[u] = pw; + pos[w] = pu; + vert[pu] = w; + vert[pw] = u; + } + ++bin[du]; + --degree[u]; + } + } + } + + /* Build results: d1 = node id, d2 = core number */ + std::vector results; + for (boost::tie(vi, vi_end) = vertices(graph.graph); vi != vi_end; ++vi) { + int64_t node = graph[*vi].id; + int64_t core = static_cast(degree[*vi]); + results.push_back({node, core}); + } + + std::sort(results.begin(), results.end(), + [](const II_t_rt &row1, const II_t_rt &row2) { + return row1.d1 < row2.d1; + }); + + return results; +} + +} // namespace + +namespace pgrouting { +namespace metrics { + +std::vector +coreNumbers(const pgrouting::UndirectedGraph &graph) { + CHECK_FOR_INTERRUPTS(); + return compute_core_numbers(graph); +} + +} // namespace metrics +} // namespace pgrouting From 394e8539c3a2ee118df2a14cdc5776dfc3e5da90 Mon Sep 17 00:00:00 2001 From: sakirr05 Date: Sat, 22 Aug 2026 04:05:44 +0530 Subject: [PATCH 3/6] [coreNumbers/pgtap] Adding test files for pgr_coreNumbers --- pgtap/metrics/coreNumbers/edge_cases.pg | 400 +++++++++++++++++++++ pgtap/metrics/coreNumbers/inner_query.pg | 32 ++ pgtap/metrics/coreNumbers/no_crash_test.pg | 58 +++ pgtap/metrics/coreNumbers/types_check.pg | 46 +++ 4 files changed, 536 insertions(+) create mode 100644 pgtap/metrics/coreNumbers/edge_cases.pg create mode 100644 pgtap/metrics/coreNumbers/inner_query.pg create mode 100644 pgtap/metrics/coreNumbers/no_crash_test.pg create mode 100644 pgtap/metrics/coreNumbers/types_check.pg diff --git a/pgtap/metrics/coreNumbers/edge_cases.pg b/pgtap/metrics/coreNumbers/edge_cases.pg new file mode 100644 index 00000000000..4fe5c62d4f0 --- /dev/null +++ b/pgtap/metrics/coreNumbers/edge_cases.pg @@ -0,0 +1,400 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2026-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 NOT min_version('4.1.0') THEN plan(1) ELSE plan(27) 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; + +PREPARE idless2_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT id, source, target, cost, reverse_cost + FROM edges WHERE id < 2' + ) ORDER BY node; + +PREPARE idless2_r AS + SELECT * FROM (VALUES + (5::BIGINT, 1::BIGINT), + (6, 1)) + AS t(node, core); + +RETURN QUERY +SELECT lives_ok('idless2_q'); + +RETURN QUERY +SELECT results_eq('idless2_q', 'idless2_r'); + + +PREPARE idless4_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT id, source, target, cost, reverse_cost + FROM edges WHERE id < 4' + ) ORDER BY node; + +PREPARE idless4_r AS + SELECT * FROM (VALUES + (5::BIGINT, 1::BIGINT), + (6, 1), + (10, 1), + (15, 1)) + AS t(node, core); + +RETURN QUERY +SELECT lives_ok('idless4_q'); + +RETURN QUERY +SELECT results_eq('idless4_q', 'idless4_r', 'test 4'); + + +PREPARE idless3_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT id, source, target, cost, reverse_cost + FROM edges WHERE id < 3' + ) ORDER BY node; + +PREPARE idless3_r AS + SELECT * FROM (VALUES + (5::BIGINT, 1::BIGINT), + (6, 1), + (10, 1)) + AS t(node, core); + +RETURN QUERY +SELECT lives_ok('idless3_q'); + +RETURN QUERY +SELECT results_eq('idless3_q', 'idless3_r', 'test 5'); + + +PREPARE idless5_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT id, source, target, cost, reverse_cost + FROM edges WHERE id < 5' + ) ORDER BY node; + +PREPARE idless5_r AS + SELECT * FROM (VALUES + (5::BIGINT, 1::BIGINT), + (6, 1), + (7, 1), + (10, 1), + (15, 1)) + AS t(node, core); + +RETURN QUERY +SELECT lives_ok('idless5_q'); + +RETURN QUERY +SELECT results_eq('idless5_q', 'idless5_r', 'test 6'); + + +PREPARE idless8_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT id, source, target, cost, reverse_cost + FROM edges WHERE id < 8' + ) ORDER BY node; + +PREPARE idless8_r AS + SELECT * FROM (VALUES + (1::BIGINT, 1::BIGINT), + (3, 1), + (5, 1), + (6, 1), + (7, 1), + (10, 1), + (11, 1), + (15, 1)) + AS t(node, core); + +RETURN QUERY +SELECT lives_ok('idless8_q'); + +RETURN QUERY +SELECT results_eq('idless8_q', 'idless8_r', 'test 7'); + + +PREPARE idless10_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT id, source, target, cost, reverse_cost + FROM edges WHERE id < 10' + ) ORDER BY node; + +PREPARE idless10_r AS + SELECT * FROM (VALUES + (1::BIGINT, 1::BIGINT), + (3, 1), + (5, 1), + (6, 2), + (7, 2), + (10, 2), + (11, 2), + (15, 1), + (16, 1)) + AS t(node, core); + +RETURN QUERY +SELECT lives_ok('idless10_q'); + +RETURN QUERY +SELECT results_eq('idless10_q', 'idless10_r', 'test 8'); + + +PREPARE empty_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT id, source, target, cost, reverse_cost + FROM edges WHERE id < 0' + ) ORDER BY node; + +RETURN QUERY +SELECT is_empty('empty_q', 'Empty graph returns no rows'); + + +/* Higher core numbers: a K4 clique puts every vertex in the 3-core */ + +PREPARE k4_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT * FROM (VALUES + (1,1,2,1.0,1.0),(2,1,3,1.0,1.0),(3,1,4,1.0,1.0), + (4,2,3,1.0,1.0),(5,2,4,1.0,1.0),(6,3,4,1.0,1.0)) + AS t(id, source, target, cost, reverse_cost)' + ) ORDER BY node; + +PREPARE k4_r AS + SELECT * FROM (VALUES + (1::BIGINT, 3::BIGINT), + (2, 3), + (3, 3), + (4, 3)) + AS t(node, core); + +RETURN QUERY +SELECT lives_ok('k4_q'); + +RETURN QUERY +SELECT results_eq('k4_q', 'k4_r', 'K4 clique: every vertex has core 3'); + + +/* A K5 clique puts every vertex in the 4-core */ + +PREPARE k5_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT * FROM (VALUES + (1,1,2,1.0,1.0),(2,1,3,1.0,1.0),(3,1,4,1.0,1.0),(4,1,5,1.0,1.0), + (5,2,3,1.0,1.0),(6,2,4,1.0,1.0),(7,2,5,1.0,1.0), + (8,3,4,1.0,1.0),(9,3,5,1.0,1.0),(10,4,5,1.0,1.0)) + AS t(id, source, target, cost, reverse_cost)' + ) ORDER BY node; + +PREPARE k5_r AS + SELECT * FROM (VALUES + (1::BIGINT, 4::BIGINT), + (2, 4), + (3, 4), + (4, 4), + (5, 4)) + AS t(node, core); + +RETURN QUERY +SELECT lives_ok('k5_q'); + +RETURN QUERY +SELECT results_eq('k5_q', 'k5_r', 'K5 clique: every vertex has core 4'); + + +/* Mixed cores: a K4 clique with one pendant vertex peels in two levels */ + +PREPARE pendant_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT * FROM (VALUES + (1,1,2,1.0,1.0),(2,1,3,1.0,1.0),(3,1,4,1.0,1.0), + (4,2,3,1.0,1.0),(5,2,4,1.0,1.0),(6,3,4,1.0,1.0), + (7,1,5,1.0,1.0)) + AS t(id, source, target, cost, reverse_cost)' + ) ORDER BY node; + +PREPARE pendant_r AS + SELECT * FROM (VALUES + (1::BIGINT, 3::BIGINT), + (2, 3), + (3, 3), + (4, 3), + (5, 1)) + AS t(node, core); + +RETURN QUERY +SELECT lives_ok('pendant_q'); + +RETURN QUERY +SELECT results_eq('pendant_q', 'pendant_r', + 'K4 with a pendant vertex: clique is core 3, pendant is core 1'); + + +/* cost and reverse_cost are ignored: a one way triangle (reverse_cost = -1) + gives the same core numbers as the two way triangle */ + +PREPARE oneway_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT * FROM (VALUES + (1,1,2,1.0,-1.0),(2,2,3,1.0,-1.0),(3,3,1,1.0,-1.0)) + AS t(id, source, target, cost, reverse_cost)' + ) ORDER BY node; + +PREPARE triangle_r AS + SELECT * FROM (VALUES + (1::BIGINT, 2::BIGINT), + (2, 2), + (3, 2)) + AS t(node, core); + +RETURN QUERY +SELECT results_eq('oneway_q', 'triangle_r', + 'reverse_cost = -1 is ignored: triangle is still core 2'); + + +/* the magnitude of cost does not change the result */ + +PREPARE bigcost_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT * FROM (VALUES + (1,1,2,999.0,999.0),(2,2,3,999.0,999.0),(3,3,1,999.0,999.0)) + AS t(id, source, target, cost, reverse_cost)' + ) ORDER BY node; + +RETURN QUERY +SELECT results_eq('bigcost_q', 'triangle_r', + 'cost magnitude is ignored: triangle is still core 2'); + + +/* results are ordered by node ascending without an explicit ORDER BY */ + +PREPARE ordering_q AS + SELECT node FROM pgr_coreNumbers( + 'SELECT id, source, target, cost, reverse_cost + FROM edges WHERE id < 10' + ); + +PREPARE ordering_r AS + SELECT * FROM (VALUES + (1::BIGINT), (3), (5), (6), (7), (10), (11), (15), (16)) + AS t(node); + +RETURN QUERY +SELECT results_eq('ordering_q', 'ordering_r', + 'Results are ordered by node ascending without an explicit ORDER BY'); + + +/* parallel edges are collapsed to a single edge, keeping the lowest cost, + so they do not inflate the degree */ + +PREPARE parallel_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT * FROM (VALUES + (1,1,2,1.0,1.0),(2,1,2,1.0,1.0)) + AS t(id, source, target, cost, reverse_cost)' + ) ORDER BY node; + +PREPARE parallel_r AS + SELECT * FROM (VALUES + (1::BIGINT, 1::BIGINT), + (2, 1)) + AS t(node, core); + +RETURN QUERY +SELECT results_eq('parallel_q', 'parallel_r', + 'Two parallel edges are collapsed: both vertices are core 1'); + + +/* three parallel edges also collapse to a single edge */ + +PREPARE parallel3_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT * FROM (VALUES + (1,1,2,1.0,1.0),(2,1,2,1.0,1.0),(3,1,2,1.0,1.0)) + AS t(id, source, target, cost, reverse_cost)' + ) ORDER BY node; + +RETURN QUERY +SELECT results_eq('parallel3_q', 'parallel_r', + 'Three parallel edges are collapsed: both vertices are core 1'); + + +/* a self loop is not a neighbor of its own vertex, so it is dropped + before peeling and does not inflate that vertex's core number */ + +PREPARE selfloop_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT * FROM (VALUES + (1,1,1,1.0,1.0),(2,1,2,1.0,1.0)) + AS t(id, source, target, cost, reverse_cost)' + ) ORDER BY node; + +PREPARE selfloop_r AS + SELECT * FROM (VALUES + (1::BIGINT, 1::BIGINT), + (2, 1)) + AS t(node, core); + +RETURN QUERY +SELECT results_eq('selfloop_q', 'selfloop_r', + 'A self loop is dropped: it does not add to the degree of its own vertex'); + +/* a vertex whose only edge is a self loop has no real neighbors left + after the self loop is dropped, so it does not appear in the result */ + +PREPARE selfloop_only_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT * FROM (VALUES + (1,1,1,1.0,1.0)) + AS t(id, source, target, cost, reverse_cost)' + ); + +RETURN QUERY +SELECT is_empty('selfloop_only_q', + 'A vertex whose only edge is a self loop has no results'); + + +/* disconnected components are peeled independently */ + +PREPARE disjoint_q AS + SELECT node, core FROM pgr_coreNumbers( + 'SELECT * FROM (VALUES + (1,1,2,1.0,1.0),(2,2,3,1.0,1.0),(3,3,1,1.0,1.0), + (4,4,5,1.0,1.0),(5,5,6,1.0,1.0),(6,6,4,1.0,1.0)) + AS t(id, source, target, cost, reverse_cost)' + ) ORDER BY node; + +PREPARE disjoint_r AS + SELECT * FROM (VALUES + (1::BIGINT, 2::BIGINT), + (2, 2), + (3, 2), + (4, 2), + (5, 2), + (6, 2)) + AS t(node, core); + +RETURN QUERY +SELECT results_eq('disjoint_q', 'disjoint_r', + 'Two disjoint triangles: every vertex is core 2'); + +END; +$BODY$ +LANGUAGE plpgsql; + +SELECT edge_cases(); + +SELECT finish(); +ROLLBACK; diff --git a/pgtap/metrics/coreNumbers/inner_query.pg b/pgtap/metrics/coreNumbers/inner_query.pg new file mode 100644 index 00000000000..726020498c5 --- /dev/null +++ b/pgtap/metrics/coreNumbers/inner_query.pg @@ -0,0 +1,32 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2026-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 NOT min_version('4.1.0') THEN plan(1) ELSE plan(54) END; + +CREATE OR REPLACE FUNCTION inner_query() +RETURNS SETOF TEXT AS +$BODY$ +BEGIN + +IF NOT min_version('4.1.0') THEN + RETURN QUERY + SELECT skip(1, 'Function is new on 4.1.0'); + RETURN; +END IF; + +RETURN QUERY +SELECT style_dijkstra('pgr_coreNumbers(', ')'); + +END; +$BODY$ +LANGUAGE plpgsql; + +SELECT inner_query(); + +SELECT finish(); +ROLLBACK; diff --git a/pgtap/metrics/coreNumbers/no_crash_test.pg b/pgtap/metrics/coreNumbers/no_crash_test.pg new file mode 100644 index 00000000000..f904c4489ca --- /dev/null +++ b/pgtap/metrics/coreNumbers/no_crash_test.pg @@ -0,0 +1,58 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2026-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 NOT min_version('4.1.0') THEN plan(1) ELSE plan(8) END; + +PREPARE edges_q 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_version('4.1.0') THEN + RETURN QUERY + SELECT skip(1, 'Function is new on 4.1.0'); + RETURN; + END IF; + + RETURN QUERY + SELECT isnt_empty('edges_q', 'Should be not empty to tests be meaningful'); + RETURN QUERY + SELECT is_empty('null_ret', 'Should be empty to tests be meaningful'); + RETURN QUERY + SELECT set_eq('null_ret_arr', 'SELECT NULL::BIGINT[]', 'Should be empty to tests be meaningful'); + + params = ARRAY[ + '$$SELECT id, source, target, cost, reverse_cost FROM edges$$' + ]::TEXT[]; + subs = ARRAY[ + 'NULL' + ]::TEXT[]; + + RETURN query SELECT * FROM no_crash_test('pgr_coreNumbers', params, subs); + RETURN query SELECT throw_on_empty_edges_sql('pgr_coreNumbers', ''); + +END +$BODY$ +LANGUAGE plpgsql VOLATILE; + + +SELECT * FROM no_crash(); + +SELECT finish(); +ROLLBACK; diff --git a/pgtap/metrics/coreNumbers/types_check.pg b/pgtap/metrics/coreNumbers/types_check.pg new file mode 100644 index 00000000000..d5198695abe --- /dev/null +++ b/pgtap/metrics/coreNumbers/types_check.pg @@ -0,0 +1,46 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2026-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ + + +BEGIN; + +SELECT CASE WHEN NOT min_version('4.1.0') THEN plan(1) ELSE plan(5) END; + +CREATE OR REPLACE FUNCTION types_check() +RETURNS SETOF TEXT AS +$BODY$ +BEGIN + + IF NOT min_version('4.1.0') THEN + RETURN QUERY + SELECT skip(1, 'Function is new on 4.1.0'); + RETURN; + END IF; + +RETURN QUERY +SELECT has_function('pgr_corenumbers'); +RETURN QUERY +SELECT has_function('pgr_corenumbers', ARRAY['text']); +RETURN QUERY +SELECT function_returns('pgr_corenumbers', ARRAY['text'],'setof record'); + +RETURN QUERY +SELECT function_args_eq('pgr_corenumbers', + $$VALUES + ('{"","seq","node","core"}'::TEXT[]) + $$); + +RETURN QUERY +SELECT function_types_eq('pgr_corenumbers', + $$VALUES + ('{text,int8,int8,int8}'::TEXT[]) + $$); + +END; +$BODY$ +LANGUAGE plpgsql; +SELECT types_check(); + +SELECT finish(); +ROLLBACK; From 84afcfa71dfbc38be5654cc0a61e8425202ae3f7 Mon Sep 17 00:00:00 2001 From: sakirr05 Date: Sat, 22 Aug 2026 04:06:08 +0530 Subject: [PATCH 4/6] [coreNumbers/doc] Adding documentation for pgr_coreNumbers --- doc/metrics/CMakeLists.txt | 1 + doc/metrics/metrics-family.rst | 3 + doc/metrics/pgr_coreNumbers.rst | 555 ++++++++++++++++++++++++++++++++ 3 files changed, 559 insertions(+) create mode 100644 doc/metrics/pgr_coreNumbers.rst diff --git a/doc/metrics/CMakeLists.txt b/doc/metrics/CMakeLists.txt index d7c96ef1646..142d948cb3a 100644 --- a/doc/metrics/CMakeLists.txt +++ b/doc/metrics/CMakeLists.txt @@ -5,6 +5,7 @@ set(LOCAL_FILES metrics-family.rst pgr_betweennessCentrality.rst + pgr_coreNumbers.rst pgr_degree.rst pgr_bandwidth.rst ) diff --git a/doc/metrics/metrics-family.rst b/doc/metrics/metrics-family.rst index d01e655d22b..8c5826c9507 100644 --- a/doc/metrics/metrics-family.rst +++ b/doc/metrics/metrics-family.rst @@ -28,6 +28,8 @@ Metrics - Family of functions * :doc:`pgr_bandwidth` - Computes the bandwidth of a graph. +* :doc:`pgr_coreNumbers` - Computes the core number of each vertex. + .. experimental-end .. toctree:: @@ -35,6 +37,7 @@ Metrics - Family of functions pgr_degree pgr_betweennessCentrality + pgr_coreNumbers pgr_bandwidth See Also diff --git a/doc/metrics/pgr_coreNumbers.rst b/doc/metrics/pgr_coreNumbers.rst new file mode 100644 index 00000000000..590ea71535d --- /dev/null +++ b/doc/metrics/pgr_coreNumbers.rst @@ -0,0 +1,555 @@ +:file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2026-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 + +.. index:: + single: Metrics Family ; pgr_coreNumbers - Experimental + single: coreNumbers - Experimental on v4.1 + +| + +``pgr_coreNumbers`` - Experimental +=============================================================================== + +``pgr_coreNumbers`` - Computes the core number of each vertex in an undirected +graph using k-core decomposition. + +.. include:: experimental.rst + :start-after: warning-begin + :end-before: end-warning + +.. rubric:: Availability + +.. rubric:: Version 4.1.0 + +* New experimental function. + +Description +------------------------------------------------------------------------------- + +The **core number** of a vertex is the largest value :math:`k` such that the +vertex **belongs to the** :math:`k`-core of the graph. + +The :math:`k`-core of a graph is the maximal subgraph in which every vertex has +degree at least :math:`k` within that subgraph. K-core decomposition iteratively +removes vertices of degree less than :math:`k` until no such vertices remain, +increasing :math:`k` at each stage. The process assigns one core number to every +vertex and runs in :math:`O(m)` time on a graph with :math:`m` edges. + +The core number says how deeply a vertex sits inside the graph: + +* Core :math:`1` vertices are peeled in the first step. They are **terminal + nodes**, adjacent to at most one other vertex in the induced subgraph: the + dead-ends and cul-de-sacs of a road network. +* Core :math:`2` vertices survive that step. They lie on cycles, or on the + chains joining them, like a rural highway that runs on before it branches. +* Core :math:`3` and above mark dense regions where every vertex keeps three or + more neighbors however much of the graph is peeled away. Urban street grids, + which offer alternate routes at most intersections, behave this way. + +The function applies to **undirected** graphs only. Edge direction and traversal +costs are ignored; only the edge endpoints matter. Each vertex receives exactly +one core number, all vertices in the graph are returned, and the number of rows +is :math:`|V|`. Results are ordered by ``node`` ascending. When the edge SQL +returns no rows, the function emits a notice and returns no rows. + +**Parallel edges** between the same pair of vertices are collapsed into a single +edge before the peeling starts, so edge multiplicity does not inflate core +numbers: three parallel edges between two vertices give both vertices core +:math:`1`, the same as a single edge. This matters when importing road networks +that contain duplicate geometry. A **self loop** is not a neighbor of its own +vertex, so it is dropped before peeling and does not contribute to that +vertex's degree. A vertex whose only edge is a self loop has no real +neighbors left, and therefore does not appear in the result. + +|Boost| Boost Graph Inside + +.. rubric:: References + +* Batagelj, V. and Zaversnik, M. (2003). An O(m) Algorithm for Cores Decomposition + of Networks. arXiv:cs/0310049. + +Signatures +------------------------------------------------------------------------------- + +.. rubric:: Summary + +.. admonition:: \ \ + :class: signatures + + | pgr_coreNumbers(`Edges SQL`_) + + | Returns set of ``(seq, node, core)`` + +:Example: Core numbers on a subgraph of :doc:`sampledata` + +.. literalinclude:: coreNumbers.queries + :start-after: -- q1 + :end-before: -- q2 + +.. rubric:: Explanation + +Vertices are colored by core number in the diagrams below: +``deepskyblue`` for core :math:`1` and ``green`` for core :math:`2`. + +* All returned vertices have core number :math:`1`; they belong to the 1-core but + not to any higher core. +* Vertices :math:`5` and :math:`15` are terminal nodes in this subgraph (degree + :math:`1`). +* Vertices :math:`6`, :math:`7`, and :math:`10` have higher degree here, but + peeling degree-:math:`1` vertices first removes the entire subgraph before a + 2-core can form. + +The following diagram shows edges :math:`\{1, \ldots, 7\}` from +:doc:`sampledata`; every vertex label shows ``node (core)``. + +.. graphviz:: + + graph G { + node [shape=circle;style=filled;width=.5;fixedsize=true;fontsize=8]; + 1,3,5,6,7,10,11,15 [color=deepskyblue]; + 5 [pos="0,0!";label="5 (1)"]; + 6 [pos="1,0!";label="6 (1)"]; + 10 [pos="2,0!";label="10 (1)"]; + 15 [pos="3,0!";label="15 (1)"]; + 7 [pos="1,1!";label="7 (1)"]; + 11 [pos="2,1!";label="11 (1)"]; + 1 [pos="0,2!";label="1 (1)"]; + 3 [pos="1,2!";label="3 (1)"]; + 5 -- 6 [label="1"]; + 6 -- 10 [label="2"]; + 10 -- 15 [label="3"]; + 6 -- 7 [label="4"]; + 10 -- 11 [label="5"]; + 1 -- 3 [label="6"]; + 3 -- 7 [label="7"]; + } + +The next subgraph uses edges :math:`\{1, 2, 3, 4\}`, the same set as the main +example of :doc:`pgr_betweennessCentrality`. Every vertex is still core +:math:`1` because the structure is a tree-like path with terminal nodes at +:math:`5` and :math:`15`. + +.. graphviz:: + + graph G { + 5,7,15,6,10 [shape=circle;style=filled;width=.5;fixedsize=true;fontsize=8]; + 5,7,15,6,10 [color=deepskyblue]; + 5 [pos="0,0!";label="5 (1)"]; + 6 [pos="0,1!";label="6 (1)"]; + 7 [pos="0,2!";label="7 (1)"]; + 10 [pos="1,1!";label="10 (1)"]; + 15 [pos="2,1!";label="15 (1)"]; + 5 -- 6 [label="1"]; + 6 -- 10 [label="2"]; + 10 -- 15 [label="3"]; + 6 -- 7 [label="4"]; + } + +.. rubric:: K-core peeling (illustration) + +On edges :math:`id < 10`, vertices :math:`5` and :math:`15` are removed first +because they have degree :math:`1`. After further peeling, vertices +:math:`6`, :math:`7`, :math:`10`, and :math:`11` remain as the 2-core (shown in +green below). + +.. graphviz:: + + graph G { + node [shape=circle;style=filled;width=.5;fixedsize=true;fontsize=8]; + 1,3,5,15,16 [color=deepskyblue]; + 6,7,10,11 [color=green]; + 5 [pos="0,0!";label="5 (1)"]; + 6 [pos="1,0!";label="6 (2)"]; + 10 [pos="2,0!";label="10 (2)"]; + 15 [pos="3,0!";label="15 (1)"]; + 7 [pos="1,1!";label="7 (2)"]; + 11 [pos="2,1!";label="11 (2)"]; + 16 [pos="3,2!";label="16 (1)"]; + 1 [pos="0,2!";label="1 (1)"]; + 3 [pos="1,2!";label="3 (1)"]; + 5 -- 6 [label="1"]; + 6 -- 10 [label="2"]; + 10 -- 15 [label="3"]; + 6 -- 7 [label="4"]; + 10 -- 11 [label="5"]; + 7 -- 11 [label="8"]; + 11 -- 16 [label="9"]; + 1 -- 3 [label="6"]; + 3 -- 7 [label="7"]; + } + +Parameters +------------------------------------------------------------------------------- + +.. include:: pgRouting-concepts.rst + :start-after: only_edge_param_start + :end-before: only_edge_param_end + +Inner Queries +------------------------------------------------------------------------------- + +Edges SQL +............................................................................... + +.. include:: pgRouting-concepts.rst + :start-after: basic_edges_sql_start + :end-before: basic_edges_sql_end + +Result columns +------------------------------------------------------------------------------- + +.. list-table:: + :width: 81 + :widths: auto + :header-rows: 1 + + * - Column + - Type + - Description + * - ``seq`` + - ``BIGINT`` + - Sequential value starting from 1. + * - ``node`` + - ``BIGINT`` + - Identifier of the vertex. + * - ``core`` + - ``BIGINT`` + - Core number of the vertex: the largest :math:`k` for which the vertex + belongs to the :math:`k`-core. + +Additional Examples +------------------------------------------------------------------------------- + +The examples of this section are based on the :doc:`sampledata` network. + +Sample network overview +............................................................................... + +.. figure:: /images/Fig1-originalData.png + :scale: 50% + + Directed sample network. ``pgr_coreNumbers`` treats every edge as undirected, + so ``cost`` and ``reverse_cost`` are not used in the calculation. + +Comparing with :doc:`pgr_betweennessCentrality` on the same edges +............................................................................... + +Both functions use the same ``Edges SQL`` format. The betweenness example on +edges :math:`\{1, 2, 3, 4\}` measures shortest-path influence; core numbers +measure peeling resilience. + +1) Core numbers on edges :math:`\{1, 2, 3, 4\}` ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. literalinclude:: coreNumbers.queries + :start-after: -- q2 + :end-before: -- q3 + +.. rubric:: Explanation + +* Vertices :math:`5` and :math:`15` are terminal (core :math:`1`). +* Vertex :math:`6` has degree :math:`3` in this subgraph but still has core + :math:`1`, because removing :math:`5` and :math:`15` first leaves no subgraph + where every vertex has degree at least :math:`2`. +* Compare with :doc:`pgr_betweennessCentrality` on the same edges: vertex + :math:`6` has the highest betweenness, but core number does not rank vertices + by path importance. + +Subgraph with core-:math:`2` vertices +............................................................................... + +Adding edges :math:`5` through :math:`9` connects the inner vertices into a +2-core. The undirected layout below shows the same network. + +.. figure:: /images/Fig6-undirected.png + :scale: 50% + + Undirected view of the sample network + +2) Core numbers on edges :math:`id < 10` ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. literalinclude:: coreNumbers.queries + :start-after: -- q3 + :end-before: -- q4 + +.. rubric:: Explanation + +* Vertices :math:`6`, :math:`7`, :math:`10`, and :math:`11` have core :math:`2`. +* Vertices :math:`1`, :math:`3`, :math:`5`, :math:`15`, and :math:`16` are + attached by tree-like branches and remain core :math:`1`. +* Edge :math:`8` (between :math:`7` and :math:`11`) closes a cycle with edges + :math:`4`, :math:`2`, and :math:`5`, which is why the inner four vertices + survive peeling. + +Filtering results in SQL +............................................................................... + +``pgr_coreNumbers`` always returns every vertex. To keep only the dense core, +wrap the call in a standard SQL query. + +3) Vertices with core :math:`\geq 2` on edges :math:`id < 10` ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. literalinclude:: coreNumbers.queries + :start-after: -- q4 + :end-before: -- q5 + +.. rubric:: Explanation + +* The ``WHERE core >= 2`` clause keeps only the 2-core vertices. +* Use it to export only the hub vertices for further analysis. + +Full sample graph +............................................................................... + +4) Core numbers on all edges ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. literalinclude:: coreNumbers.queries + :start-after: -- q5 + :end-before: -- q6 + +.. rubric:: Explanation + +* The full network has :math:`17` vertices and maximum core number :math:`2`. +* Terminal branches (vertices :math:`1`, :math:`2`, :math:`3`, :math:`4`, + :math:`5`, :math:`9`, :math:`13`, :math:`14`) remain core :math:`1`. +* The 2-core is vertices :math:`6`, :math:`7`, :math:`8`, :math:`10`, + :math:`11`, :math:`12`, :math:`15`, :math:`16` and :math:`17`: the central + block of the city network. Vertex :math:`9` hangs off :math:`8` by edge + :math:`14` alone, so it is peeled first and stays core :math:`1`. + +Small graphs +............................................................................... + +5) Single edge (two terminal nodes) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Both endpoints have core number :math:`1`. + +.. graphviz:: + + graph G { + node [shape=circle;style=filled;width=.5;color=deepskyblue;fixedsize=true;fontsize=8]; + 5 [label="5 (1)"]; + 6 [label="6 (1)"]; + 5 -- 6 [label="1"]; + } + +.. literalinclude:: coreNumbers.queries + :start-after: -- q6 + :end-before: -- q7 + +.. rubric:: Explanation + +* Edge :math:`1` from :doc:`sampledata` connects vertices :math:`5` and :math:`6`. +* Each endpoint has degree :math:`1`, so both are peeled immediately at + :math:`k = 1`. + +6) Triangle (all vertices core :math:`2`) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +In a triangle every vertex has degree :math:`2`, so all three belong to the +2-core. + +.. graphviz:: + + graph G { + node [shape=circle;style=filled;width=.5;color=green;fixedsize=true;fontsize=8]; + 1 [label="1 (2)"]; + 2 [label="2 (2)"]; + 3 [label="3 (2)"]; + 1 -- 2 [label="e1"]; + 2 -- 3 [label="e2"]; + 3 -- 1 [label="e3"]; + } + +.. literalinclude:: coreNumbers.queries + :start-after: -- q7 + :end-before: -- q8 + +.. rubric:: Explanation + +* The ``VALUES`` clause builds a minimal three-edge graph inline. +* No vertex can be peeled before :math:`k = 2`, so every core number is + :math:`2`. + +7) Square cycle from :doc:`sampledata` (all vertices core :math:`2`) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Edges :math:`\{2, 4, 5, 8\}` form a 4-cycle on vertices +:math:`\{6, 7, 10, 11\}`. + +.. graphviz:: + + graph G { + node [shape=circle;style=filled;width=.5;color=green;fixedsize=true;fontsize=8]; + 6 [label="6 (2)"]; + 7 [label="7 (2)"]; + 10 [label="10 (2)"]; + 11 [label="11 (2)"]; + 6 -- 10 [label="2"]; + 6 -- 7 [label="4"]; + 10 -- 11 [label="5"]; + 7 -- 11 [label="8"]; + } + +.. literalinclude:: coreNumbers.queries + :start-after: -- q8 + :end-before: -- q9 + +.. rubric:: Explanation + +* This is the 2-core heart of example 2) before branches are attached. +* Every vertex has degree :math:`2` in the cycle, so all core numbers are + :math:`2`. + +8) Star graph (hub and spokes, all core :math:`1`) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +A star has a central hub connected to leaf vertices. Every vertex is peeled at +:math:`k = 1`. + +.. graphviz:: + + graph G { + node [shape=circle;style=filled;width=.5;color=deepskyblue;fixedsize=true;fontsize=8]; + 1 [label="1 (1)"]; + 2 [label="2 (1)"]; + 3 [label="3 (1)"]; + 4 [label="4 (1)"]; + 5 [label="5 (1)"]; + 1 -- 2; + 1 -- 3; + 1 -- 4; + 1 -- 5; + } + +.. literalinclude:: coreNumbers.queries + :start-after: -- q9 + :end-before: -- q10 + +.. rubric:: Explanation + +* The hub has degree :math:`4`, but removing the four leaves leaves an isolated + vertex, so the hub never belongs to a 2-core. +* Star-like cul-de-sac layouts in road networks often produce core :math:`1` + vertices throughout. + +Empty edge set +............................................................................... + +9) No edges in the SQL query ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. literalinclude:: coreNumbers.queries + :start-after: -- q10 + :end-before: -- q11 + +.. rubric:: Explanation + +* When the inner query returns no edges, a notice is emitted and the result is + empty. +* No exception is raised. + +Core numbers and planarity +............................................................................... + +Vertices with higher core numbers often lie in densely connected regions. The +workflow below computes core numbers, filters the 2-core, and checks whether the +subgraph can be drawn without edge crossings using :doc:`pgr_isPlanar`. + +10) Planarity of the subgraph with edges :math:`id < 10` ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +First compute core numbers (example 2)), then filter the 2-core (example 3)), +then test planarity: + +.. code-block:: sql + + -- Step 1: core numbers on edges id < 10 + SELECT * FROM pgr_coreNumbers( + 'SELECT id, source, target, cost, reverse_cost + FROM edges WHERE id < 10' + ) ORDER BY node; + + -- Step 2: keep only vertices in the 2-core + SELECT node, core + FROM pgr_coreNumbers( + 'SELECT id, source, target, cost, reverse_cost + FROM edges WHERE id < 10' + ) WHERE core >= 2 + ORDER BY node; + + -- Step 3: check planarity of the same edge set + SELECT pgr_isPlanar( + 'SELECT id, source, target, cost, reverse_cost + FROM edges WHERE id < 10' + ); + +.. rubric:: Explanation + +* The subgraph with edges :math:`id < 10` is planar (``pgr_isPlanar`` returns + ``true``). +* The four vertices with core :math:`2` lie in the densest part of this subgraph. +* Planarity and core number measure different properties: a graph can be planar + while still having a mix of core-:math:`1` and core-:math:`2` vertices. + +11) Complete graph :math:`K_4` (all vertices core :math:`3`) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +The examples above reach at most core :math:`2`, because the +:doc:`sampledata` network has degeneracy :math:`2`. Higher cores need a denser +graph. In the complete graph :math:`K_4` every vertex is adjacent to the other +three, so no vertex can ever be peeled at :math:`k \leq 3` and the whole graph +is its own 3-core. + +.. literalinclude:: coreNumbers.queries + :start-after: -- q11 + :end-before: -- q12 + +.. rubric:: Explanation + +* Every vertex has degree :math:`3`, so nothing is removed while peeling at + :math:`k = 1`, :math:`2` or :math:`3`: all four vertices have core :math:`3`. +* In general the complete graph :math:`K_n` has core number :math:`n - 1` for + every vertex, which is the maximum possible core number on :math:`n` vertices. +* The degeneracy of this graph is :math:`3`, compared with :math:`2` for the + :doc:`sampledata` network. + +.. graphviz:: + + graph G { + node [shape=circle;style=filled;width=.5;fixedsize=true;fontsize=8]; + 1,2,3,4 [color=orange]; + 1 [pos="0,1!";label="1 (3)"]; + 2 [pos="1,1!";label="2 (3)"]; + 3 [pos="1,0!";label="3 (3)"]; + 4 [pos="0,0!";label="4 (3)"]; + 1 -- 2 [label="1"]; + 1 -- 3 [label="2"]; + 1 -- 4 [label="3"]; + 2 -- 3 [label="4"]; + 2 -- 4 [label="5"]; + 3 -- 4 [label="6"]; + } + +See Also +------------------------------------------------------------------------------- + +* :doc:`sampledata` +* :doc:`pgr_betweennessCentrality` +* :doc:`pgr_degree` +* :doc:`pgr_isPlanar` +* :doc:`metrics-family` +* Batagelj, V. and Zaversnik, M. (2003). `An O(m) Algorithm for Cores Decomposition + of Networks `__ +* `Wikipedia: Degeneracy (graph theory) + `__ + +.. rubric:: Indices and tables + +* :ref:`genindex` +* :ref:`search` From 6a2531746402a7d04b503d9697a40f6d8dbda7df Mon Sep 17 00:00:00 2001 From: sakirr05 Date: Sat, 22 Aug 2026 05:12:52 +0530 Subject: [PATCH 5/6] [coreNumbers/docqueries] Adding test documentation examples code for pgr_coreNumbers --- docqueries/metrics/CMakeLists.txt | 1 + docqueries/metrics/coreNumbers.pg | 75 ++++++++++ docqueries/metrics/coreNumbers.result | 188 ++++++++++++++++++++++++++ docqueries/metrics/test.conf | 1 + 4 files changed, 265 insertions(+) create mode 100644 docqueries/metrics/coreNumbers.pg create mode 100644 docqueries/metrics/coreNumbers.result diff --git a/docqueries/metrics/CMakeLists.txt b/docqueries/metrics/CMakeLists.txt index b3eee4dd3ec..5cb25d63f21 100644 --- a/docqueries/metrics/CMakeLists.txt +++ b/docqueries/metrics/CMakeLists.txt @@ -5,6 +5,7 @@ set(LOCAL_FILES bandwidth betweennessCentrality + coreNumbers degree ) diff --git a/docqueries/metrics/coreNumbers.pg b/docqueries/metrics/coreNumbers.pg new file mode 100644 index 00000000000..5d90ac1a1b1 --- /dev/null +++ b/docqueries/metrics/coreNumbers.pg @@ -0,0 +1,75 @@ +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2015-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ + +/* -- q1 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 8' +) ORDER BY node; +/* -- q2 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 5' +) ORDER BY node; +/* -- q3 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 10' +) ORDER BY node; +/* -- q4 */ +SELECT node, core +FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 10' +) WHERE core >= 2 +ORDER BY node; +/* -- q5 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges' +) ORDER BY node; +/* -- q6 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 2' +) ORDER BY node; +/* -- q7 */ +SELECT * FROM pgr_coreNumbers( +$$SELECT * FROM (VALUES + (1, 1::BIGINT, 2::BIGINT, 1::FLOAT, 1::FLOAT), + (2, 2::BIGINT, 3::BIGINT, 1::FLOAT, 1::FLOAT), + (3, 3::BIGINT, 1::BIGINT, 1::FLOAT, 1::FLOAT) +) AS t(id, source, target, cost, reverse_cost)$$ +) ORDER BY node; +/* -- q8 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id IN (2, 4, 5, 8)' +) ORDER BY node; +/* -- q9 */ +SELECT * FROM pgr_coreNumbers( +$$SELECT * FROM (VALUES + (1, 1::BIGINT, 2::BIGINT, 1::FLOAT, 1::FLOAT), + (2, 1::BIGINT, 3::BIGINT, 1::FLOAT, 1::FLOAT), + (3, 1::BIGINT, 4::BIGINT, 1::FLOAT, 1::FLOAT), + (4, 1::BIGINT, 5::BIGINT, 1::FLOAT, 1::FLOAT) +) AS t(id, source, target, cost, reverse_cost)$$ +) ORDER BY node; +/* -- q10 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 0' +) ORDER BY node; +/* -- q11 */ +SELECT * FROM pgr_coreNumbers( +$$SELECT * FROM (VALUES + (1, 1::BIGINT, 2::BIGINT, 1::FLOAT, 1::FLOAT), + (2, 1::BIGINT, 3::BIGINT, 1::FLOAT, 1::FLOAT), + (3, 1::BIGINT, 4::BIGINT, 1::FLOAT, 1::FLOAT), + (4, 2::BIGINT, 3::BIGINT, 1::FLOAT, 1::FLOAT), + (5, 2::BIGINT, 4::BIGINT, 1::FLOAT, 1::FLOAT), + (6, 3::BIGINT, 4::BIGINT, 1::FLOAT, 1::FLOAT) +) AS t(id, source, target, cost, reverse_cost)$$ +) ORDER BY node; +/* -- q12 */ diff --git a/docqueries/metrics/coreNumbers.result b/docqueries/metrics/coreNumbers.result new file mode 100644 index 00000000000..312d3d7e464 --- /dev/null +++ b/docqueries/metrics/coreNumbers.result @@ -0,0 +1,188 @@ +BEGIN; +BEGIN +SET client_min_messages TO NOTICE; +SET +/* :file: This file is part of the pgRouting project. +:copyright: Copyright (c) 2015-2026 pgRouting developers +:license: Creative Commons Attribution-Share Alike 3.0 https://creativecommons.org/licenses/by-sa/3.0 */ +/* -- q1 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 8' +) ORDER BY node; + seq | node | core +-----+------+------ + 1 | 1 | 1 + 2 | 3 | 1 + 3 | 5 | 1 + 4 | 6 | 1 + 5 | 7 | 1 + 6 | 10 | 1 + 7 | 11 | 1 + 8 | 15 | 1 +(8 rows) + +/* -- q2 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 5' +) ORDER BY node; + seq | node | core +-----+------+------ + 1 | 5 | 1 + 2 | 6 | 1 + 3 | 7 | 1 + 4 | 10 | 1 + 5 | 15 | 1 +(5 rows) + +/* -- q3 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 10' +) ORDER BY node; + seq | node | core +-----+------+------ + 1 | 1 | 1 + 2 | 3 | 1 + 3 | 5 | 1 + 4 | 6 | 2 + 5 | 7 | 2 + 6 | 10 | 2 + 7 | 11 | 2 + 8 | 15 | 1 + 9 | 16 | 1 +(9 rows) + +/* -- q4 */ +SELECT node, core +FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 10' +) WHERE core >= 2 +ORDER BY node; + node | core +------+------ + 6 | 2 + 7 | 2 + 10 | 2 + 11 | 2 +(4 rows) + +/* -- q5 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges' +) ORDER BY node; + seq | node | core +-----+------+------ + 1 | 1 | 1 + 2 | 2 | 1 + 3 | 3 | 1 + 4 | 4 | 1 + 5 | 5 | 1 + 6 | 6 | 2 + 7 | 7 | 2 + 8 | 8 | 2 + 9 | 9 | 1 + 10 | 10 | 2 + 11 | 11 | 2 + 12 | 12 | 2 + 13 | 13 | 1 + 14 | 14 | 1 + 15 | 15 | 2 + 16 | 16 | 2 + 17 | 17 | 2 +(17 rows) + +/* -- q6 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 2' +) ORDER BY node; + seq | node | core +-----+------+------ + 1 | 5 | 1 + 2 | 6 | 1 +(2 rows) + +/* -- q7 */ +SELECT * FROM pgr_coreNumbers( +$$SELECT * FROM (VALUES + (1, 1::BIGINT, 2::BIGINT, 1::FLOAT, 1::FLOAT), + (2, 2::BIGINT, 3::BIGINT, 1::FLOAT, 1::FLOAT), + (3, 3::BIGINT, 1::BIGINT, 1::FLOAT, 1::FLOAT) +) AS t(id, source, target, cost, reverse_cost)$$ +) ORDER BY node; + seq | node | core +-----+------+------ + 1 | 1 | 2 + 2 | 2 | 2 + 3 | 3 | 2 +(3 rows) + +/* -- q8 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id IN (2, 4, 5, 8)' +) ORDER BY node; + seq | node | core +-----+------+------ + 1 | 6 | 2 + 2 | 7 | 2 + 3 | 10 | 2 + 4 | 11 | 2 +(4 rows) + +/* -- q9 */ +SELECT * FROM pgr_coreNumbers( +$$SELECT * FROM (VALUES + (1, 1::BIGINT, 2::BIGINT, 1::FLOAT, 1::FLOAT), + (2, 1::BIGINT, 3::BIGINT, 1::FLOAT, 1::FLOAT), + (3, 1::BIGINT, 4::BIGINT, 1::FLOAT, 1::FLOAT), + (4, 1::BIGINT, 5::BIGINT, 1::FLOAT, 1::FLOAT) +) AS t(id, source, target, cost, reverse_cost)$$ +) ORDER BY node; + seq | node | core +-----+------+------ + 1 | 1 | 1 + 2 | 2 | 1 + 3 | 3 | 1 + 4 | 4 | 1 + 5 | 5 | 1 +(5 rows) + +/* -- q10 */ +SELECT * FROM pgr_coreNumbers( +'SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 0' +) ORDER BY node; +NOTICE: No edges found +HINT: SELECT id, source, target, cost, reverse_cost +FROM edges WHERE id < 0 + seq | node | core +-----+------+------ +(0 rows) + +/* -- q11 */ +SELECT * FROM pgr_coreNumbers( +$$SELECT * FROM (VALUES + (1, 1::BIGINT, 2::BIGINT, 1::FLOAT, 1::FLOAT), + (2, 1::BIGINT, 3::BIGINT, 1::FLOAT, 1::FLOAT), + (3, 1::BIGINT, 4::BIGINT, 1::FLOAT, 1::FLOAT), + (4, 2::BIGINT, 3::BIGINT, 1::FLOAT, 1::FLOAT), + (5, 2::BIGINT, 4::BIGINT, 1::FLOAT, 1::FLOAT), + (6, 3::BIGINT, 4::BIGINT, 1::FLOAT, 1::FLOAT) +) AS t(id, source, target, cost, reverse_cost)$$ +) ORDER BY node; + seq | node | core +-----+------+------ + 1 | 1 | 3 + 2 | 2 | 3 + 3 | 3 | 3 + 4 | 4 | 3 +(4 rows) + +/* -- q12 */ +ROLLBACK; +ROLLBACK diff --git a/docqueries/metrics/test.conf b/docqueries/metrics/test.conf index e85f6a7a293..9e836734aef 100644 --- a/docqueries/metrics/test.conf +++ b/docqueries/metrics/test.conf @@ -8,6 +8,7 @@ 'files' => [qw( bandwidth.pg betweennessCentrality.pg + coreNumbers.pg degree.pg )] }, From 232425f668edf1a220d3f82d41636dca05ac7fbb Mon Sep 17 00:00:00 2001 From: sakirr05 Date: Sat, 22 Aug 2026 05:17:42 +0530 Subject: [PATCH 6/6] Updating release notes and NEWS about the new function pgr_coreNumbers --- NEWS.md | 4 ++++ doc/_static/page_history.js | 2 ++ doc/src/release_notes.rst | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/NEWS.md b/NEWS.md index e4dc47bb984..d718b2eeb8d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -37,6 +37,10 @@ To see all issues & pull requests closed by this release see the **Summary of changes by function** +* pgr_coreNumbers + + * 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..c3235a6a381 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_coreNumbers']}, + {v: '4.0', pages: ['pgr_bandwidth', 'pgr_kingOrdering', 'pgr_sloanOrdering']}, {v: '3.8', pages: ['pgr_contractionDeadEnd', 'pgr_contractionLinear', 'pgr_separateCrossing', diff --git a/doc/src/release_notes.rst b/doc/src/release_notes.rst index 40390c34c70..b84a0e0754a 100644 --- a/doc/src/release_notes.rst +++ b/doc/src/release_notes.rst @@ -62,6 +62,12 @@ To see all issues & pull requests closed by this release see the .. rubric:: Summary of changes by function +* pgr_coreNumbers + + .. include:: pgr_coreNumbers.rst + :start-after: Version 4.1.0 + :end-before: Description + * pgr_edgeColoring .. include:: pgr_edgeColoring.rst