From 6fd4bce71bbd2e88312dbe04e87d8658472e3ac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szabolcs=20Horva=CC=81t?= Date: Mon, 12 Feb 2024 09:15:22 +0000 Subject: [PATCH] chore: update C core and regenerate interfaces --- R/aaa-auto.R | 3 +- src/rinterface.c | 2 +- src/vendor/cigraph/CHANGELOG.md | 11 +- src/vendor/cigraph/interfaces/functions.yaml | 10 +- src/vendor/cigraph/src/centrality/truss.cpp | 2 +- src/vendor/cigraph/src/graph/basic_query.c | 2 +- .../cigraph/src/graph/type_indexededgelist.c | 11 +- .../cigraph/src/paths/all_shortest_paths.c | 4 +- src/vendor/cigraph/src/paths/sparsifier.c | 41 +++--- src/vendor/cigraph/src/properties/trees.c | 124 ++++++++++++------ 10 files changed, 133 insertions(+), 77 deletions(-) diff --git a/R/aaa-auto.R b/R/aaa-auto.R index ccdd094283..e845ca5a3a 100644 --- a/R/aaa-auto.R +++ b/R/aaa-auto.R @@ -927,7 +927,7 @@ widest_path_widths_floyd_warshall_impl <- function(graph, from=V(graph), to=V(gr res } -spanner_impl <- function(graph, stretch, weights) { +spanner_impl <- function(graph, stretch, weights=NULL) { # Argument checks ensure_igraph(graph) stretch <- as.numeric(stretch) @@ -1880,7 +1880,6 @@ pseudo_diameter_impl <- function(graph, start.vid, directed=TRUE, unconnected=TR res } -# https://github.com/igraph/igraph/commit/d455f61f4832d5207cb0a0475fb5ddc02409ae76#r138442064 pseudo_diameter_dijkstra_impl <- function(graph, weights=NULL, start.vid, directed=TRUE, unconnected=TRUE) { # Argument checks ensure_igraph(graph) diff --git a/src/rinterface.c b/src/rinterface.c index ee195e426f..31b446b36c 100644 --- a/src/rinterface.c +++ b/src/rinterface.c @@ -2725,7 +2725,7 @@ SEXP R_igraph_spanner(SEXP graph, SEXP stretch, SEXP weights) { c_stretch = REAL(stretch)[0]; if (!Rf_isNull(weights)) { R_SEXP_to_vector(weights, &c_weights); } /* Call igraph */ - IGRAPH_R_CHECK(igraph_spanner(&c_graph, &c_spanner, c_stretch, (Rf_isNull(weights) ? 0 : &c_weights))); + IGRAPH_R_CHECK(igraph_spanner(&c_graph, &c_spanner, c_stretch, (Rf_isNull(weights) ? 0 : (Rf_isNull(weights) ? 0 : &c_weights)))); /* Convert output */ PROTECT(spanner=R_igraph_vector_int_to_SEXPp1(&c_spanner)); diff --git a/src/vendor/cigraph/CHANGELOG.md b/src/vendor/cigraph/CHANGELOG.md index bfa54b2363..b9bc34a645 100644 --- a/src/vendor/cigraph/CHANGELOG.md +++ b/src/vendor/cigraph/CHANGELOG.md @@ -2,6 +2,15 @@ ## [master] +### Fixed + + - `igraph_is_forest()` determined that a graph is not a directed forest, and the `roots` output parameter was set to `NULL`, it would incorrectly cache that the graph is also not an undirected forest. + - `igraph_spanner()` now correctly ignores edge directions, and no longer crashes on directed graphs. + +### Other + + - Documentation improvements. + ## [0.10.9] - 2024-02-02 ### Added @@ -26,7 +35,7 @@ - Performance: Reduced memory usage and improved initialization performance for `igraph_strvector_t`. - Performance: Improved cache use by `igraph_is_bipartite()`. - The documentation is now also generated in Texinfo format. - - Documentation improvements + - Documentation improvements. ## [0.10.8] - 2023-11-17 diff --git a/src/vendor/cigraph/interfaces/functions.yaml b/src/vendor/cigraph/interfaces/functions.yaml index 4ca71b52db..1818f35b78 100644 --- a/src/vendor/cigraph/interfaces/functions.yaml +++ b/src/vendor/cigraph/interfaces/functions.yaml @@ -17,6 +17,9 @@ igraph_add_edges: PARAMS: INOUT GRAPH graph, VERTEX_INDEX_PAIRS edges, ATTRIBUTES attr DEPS: edges ON graph +igraph_empty_attrs: + PARAMS: OUT GRAPH graph, INTEGER n, BOOLEAN directed, ATTRIBUTES attr + igraph_add_vertices: PARAMS: INOUT GRAPH graph, INTEGER nv, ATTRIBUTES attr @@ -66,9 +69,6 @@ igraph_edges: PARAMS: GRAPH graph, EDGE_SELECTOR eids, OUT VECTOR_INT edges DEPS: eids ON graph -igraph_empty_attrs: - PARAMS: OUT GRAPH graph, INTEGER n, BOOLEAN directed, ATTRIBUTES attr - igraph_get_eid: PARAMS: |- GRAPH graph, OUT EDGE eid, VERTEX from, VERTEX to, @@ -621,7 +621,7 @@ igraph_widest_path_widths_floyd_warshall: igraph_spanner: PARAMS: |- - GRAPH graph, OUT EDGE_INDICES spanner, REAL stretch, EDGEWEIGHTS weights + GRAPH graph, OUT EDGE_INDICES spanner, REAL stretch, OPTIONAL EDGEWEIGHTS weights DEPS: weights ON graph igraph_subcomponent: @@ -1065,14 +1065,12 @@ igraph_radius_dijkstra: DEPS: weights ON graph igraph_pseudo_diameter: - NAME-R: pseudo_diameter PARAMS: |- GRAPH graph, OUT REAL diameter, VERTEX start_vid, OUT INTEGER from=NULL, OUT INTEGER to=NULL, BOOLEAN directed=True, BOOLEAN unconnected=True igraph_pseudo_diameter_dijkstra: - NAME-R: pseudo_diameter PARAMS: |- GRAPH graph, EDGEWEIGHTS weights=NULL, OUT REAL diameter, VERTEX start_vid, diff --git a/src/vendor/cigraph/src/centrality/truss.cpp b/src/vendor/cigraph/src/centrality/truss.cpp index d1f817b793..b4fbc60b6b 100644 --- a/src/vendor/cigraph/src/centrality/truss.cpp +++ b/src/vendor/cigraph/src/centrality/truss.cpp @@ -248,7 +248,7 @@ igraph_error_t igraph_trussness(const igraph_t* graph, igraph_vector_int_t* trus IGRAPH_CHECK(igraph_has_mutual(graph, &is_multigraph, /* loops */ false)); } if (is_multigraph) { - IGRAPH_ERROR("Trussness is not implemented for graph with multi-edges.", IGRAPH_UNIMPLEMENTED); + IGRAPH_ERROR("Trussness is not implemented for graphs with multi-edges.", IGRAPH_UNIMPLEMENTED); } /* Manage the stack to make it memory safe: do not change the order of diff --git a/src/vendor/cigraph/src/graph/basic_query.c b/src/vendor/cigraph/src/graph/basic_query.c index dcb6fad9c1..7c86ca6c09 100644 --- a/src/vendor/cigraph/src/graph/basic_query.c +++ b/src/vendor/cigraph/src/graph/basic_query.c @@ -29,7 +29,7 @@ /** * \ingroup structural * \function igraph_are_connected - * \brief Decides whether two vertices are connected. + * \brief Decides whether two vertices are adjacent. * * Decides whether there are any edges that have \p v1 and \p v2 * as endpoints. This function is of course symmetric for undirected diff --git a/src/vendor/cigraph/src/graph/type_indexededgelist.c b/src/vendor/cigraph/src/graph/type_indexededgelist.c index b37da1dd90..19d5da75f5 100644 --- a/src/vendor/cigraph/src/graph/type_indexededgelist.c +++ b/src/vendor/cigraph/src/graph/type_indexededgelist.c @@ -1352,20 +1352,23 @@ igraph_error_t igraph_degree(const igraph_t *graph, igraph_vector_int_t *res, /** * \function igraph_get_eid - * \brief Get the edge ID from the end points of an edge. + * \brief Get the edge ID from the endpoints of an edge. * * For undirected graphs \c from and \c to are exchangeable. * * \param graph The graph object. * \param eid Pointer to an integer, the edge ID will be stored here. + * If \p error is false and no edge was found, -1 + * will be returned. * \param from The starting point of the edge. * \param to The end point of the edge. * \param directed Logical constant, whether to search for directed * edges in a directed graph. Ignored for undirected graphs. * \param error Logical scalar, whether to report an error if the edge - * was not found. If it is false, then -1 will be assigned to \p eid. - * Note that invalid vertex IDs in input arguments (\p from or \p to) - * always return an error code. + * was not found. If it is false, then -1 will be + * assigned to \p eid. Note that invalid vertex IDs in input + * arguments (\p from or \p to) always trigger an error, + * regardless of this setting. * \return Error code. * \sa \ref igraph_edge() for the opposite operation, \ref igraph_get_all_eids_between() * to retrieve all edge IDs between a pair of vertices. diff --git a/src/vendor/cigraph/src/paths/all_shortest_paths.c b/src/vendor/cigraph/src/paths/all_shortest_paths.c index 536586f80c..4cfa2196f1 100644 --- a/src/vendor/cigraph/src/paths/all_shortest_paths.c +++ b/src/vendor/cigraph/src/paths/all_shortest_paths.c @@ -36,7 +36,9 @@ * \brief All shortest paths (geodesics) from a vertex. * * When there is more than one shortest path between two vertices, - * all of them will be returned. + * all of them will be returned. Every edge is considered separately, + * therefore in graphs with multi-edges, this function may produce + * a very large number of results. * * \param graph The graph object. * \param vertices The result, the IDs of the vertices along the paths. diff --git a/src/vendor/cigraph/src/paths/sparsifier.c b/src/vendor/cigraph/src/paths/sparsifier.c index 5c7d103e0c..9f961aa6e8 100644 --- a/src/vendor/cigraph/src/paths/sparsifier.c +++ b/src/vendor/cigraph/src/paths/sparsifier.c @@ -119,15 +119,15 @@ static void igraph_i_clear_lightest_edges_to_clusters( * \function igraph_spanner * \brief Calculates a spanner of a graph with a given stretch factor. * - * A spanner of a graph G = (V,E) with a stretch t is a subgraph - * H = (V,Es) such that Es is a subset of E and the distance - * between any pair of nodes in H is at most t times the distance - * in G. The returned graph is always a spanner of the - * given graph with the specified stretch. For weighted graphs the - * number of edges in the spanner is O(k * n^(1 + 1 / k)), where k is - * k = (stretch + 1) / 2, m is the number of edges and n is the number - * of nodes in G. For unweighted graphs the number of edges is - * O(n^(1 + 1 / k) + kn). + * A spanner of a graph G = (V,E) with a stretch \c t is a + * subgraph H = (V,Es) such that \c Es is a subset of \c E + * and the distance between any pair of nodes in \c H is at most \c t + * times the distance in \c G. The returned graph is always a spanner of + * the given graph with the specified stretch. For weighted graphs the + * number of edges in the spanner is O(k n^(1 + 1 / k)), where + * \c k is k = (t + 1) / 2, \c m is the number of edges + * and \c n is the number of nodes in \c G. For unweighted graphs the number + * of edges is O(n^(1 + 1 / k) + kn). * * * This function is based on the algorithm of Baswana and Sen: "A Simple and @@ -138,10 +138,10 @@ static void igraph_i_clear_lightest_edges_to_clusters( * is directed, the directions of the edges will be ignored. * \param spanner An initialized vector, the IDs of the edges that constitute * the calculated spanner will be returned here. Use - * \ref igraph_subgraph_from_edges() to extract the spanner as a separate + * \ref igraph_subgraph_from_edges() to extract the spanner as a separate * graph object. - * \param stretch The stretch factor of the spanner. - * \param weights The edge weights or NULL. + * \param stretch The stretch factor \c t of the spanner. + * \param weights The edge weights or \c NULL. * * \return Error code: * \clist @@ -150,13 +150,14 @@ static void igraph_i_clear_lightest_edges_to_clusters( * \endclist * * Time complexity: The algorithm is a randomized Las Vegas algorithm. The expected - * running time is O(km) where k is the value mentioned above. + * running time is O(km) where k is the value mentioned above and m is the number + * of edges. */ igraph_error_t igraph_spanner(const igraph_t *graph, igraph_vector_int_t *spanner, igraph_real_t stretch, const igraph_vector_t *weights) { - igraph_integer_t no_of_nodes = igraph_vcount(graph); - igraph_integer_t no_of_edges = igraph_ecount(graph); + const igraph_integer_t no_of_nodes = igraph_vcount(graph); + const igraph_integer_t no_of_edges = igraph_ecount(graph); igraph_integer_t i, j, v, nlen, neighbor, cluster; igraph_real_t sample_prob, k = (stretch + 1) / 2, weight, lightest_sampled_weight; igraph_vector_int_t clustering, lightest_eid; @@ -178,21 +179,21 @@ igraph_error_t igraph_spanner(const igraph_t *graph, igraph_vector_int_t *spanne /* Test validity of stretch factor */ if (stretch < 1) { - IGRAPH_ERROR("Stretch factor must be at least 1", IGRAPH_EINVAL); + IGRAPH_ERROR("Stretch factor must be at least 1.", IGRAPH_EINVAL); } /* Test validity of weights vector */ if (weights) { if (igraph_vector_size(weights) != no_of_edges) { - IGRAPH_ERROR("Weight vector length does not match", IGRAPH_EINVAL); + IGRAPH_ERROR("Weight vector length does not match.", IGRAPH_EINVAL); } if (no_of_edges > 0) { igraph_real_t min = igraph_vector_min(weights); if (min < 0) { - IGRAPH_ERROR("Weight vector must be non-negative", IGRAPH_EINVAL); + IGRAPH_ERROR("Weight vector must be non-negative.", IGRAPH_EINVAL); } else if (isnan(min)) { - IGRAPH_ERROR("Weight vector must not contain NaN values", IGRAPH_EINVAL); + IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL); } } } @@ -205,7 +206,7 @@ igraph_error_t igraph_spanner(const igraph_t *graph, igraph_vector_int_t *spanne // explicitly; it will only exist in terms of the incidence and the adjacency // lists, maintained in parallel as the edges are removed from the residual // graph. - IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_OUT, IGRAPH_NO_LOOPS)); + IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_NO_LOOPS)); IGRAPH_FINALLY(igraph_inclist_destroy, &inclist); IGRAPH_CHECK(igraph_adjlist_init_from_inclist(graph, &adjlist, &inclist)); IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist); diff --git a/src/vendor/cigraph/src/properties/trees.c b/src/vendor/cigraph/src/properties/trees.c index b2c962f4bf..d94407b83a 100644 --- a/src/vendor/cigraph/src/properties/trees.c +++ b/src/vendor/cigraph/src/properties/trees.c @@ -215,13 +215,14 @@ static igraph_error_t igraph_i_is_tree_visitor(const igraph_t *graph, igraph_int * An undirected graph is a tree if it is connected and has no cycles. * * - * In the directed case, a possible additional requirement is that all - * edges are oriented away from a root (out-tree or arborescence) or all edges + * In the directed case, an additional requirement is that all edges + * are oriented away from a root (out-tree or arborescence) or all edges * are oriented towards a root (in-tree or anti-arborescence). * This test can be controlled using the \p mode parameter. * * - * By convention, the null graph (i.e. the graph with no vertices) is considered not to be a tree. + * By convention, the null graph (i.e. the graph with no vertices) is considered + * not to be connected, and therefore not a tree. * * \param graph The graph object to analyze. * \param res Pointer to a logical variable, the result will be stored @@ -256,20 +257,29 @@ igraph_error_t igraph_is_tree(const igraph_t *graph, igraph_bool_t *res, igraph_ vcount = igraph_vcount(graph); ecount = igraph_ecount(graph); - /* For undirected graphs and for directed graphs with mode == IGRAPH_ALL, - * we can return early if we know from the cache that the graph is weakly - * connected and is a forest. We can do this even if the user wants the - * root vertex because we always return zero as the root vertex for - * undirected graphs */ - if (treat_as_undirected && - igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_FOREST) && - igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED) && - igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_FOREST) && - igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED) - ) { - is_tree = true; - iroot = 0; - goto success; + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED)) { + igraph_bool_t weakly_connected = igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED); + + if (weakly_connected) { + /* For undirected graphs and for directed graphs with mode == IGRAPH_ALL, + * we can return early if we know from the cache that the graph is weakly + * connected and is a forest. We can do this even if the user wants the + * root vertex because we always return zero as the root vertex for + * undirected graphs */ + if (treat_as_undirected && + igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_FOREST) && + igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_FOREST) + ) { + is_tree = true; + iroot = 0; + goto success; + } + } else /* ! weakly_connected */ { + /* If the graph is not weakly connected, then it is neither an undirected + * not a directed tree. There is no root, so we can return early. */ + is_tree = false; + goto success; + } } /* A tree must have precisely vcount-1 edges. */ @@ -369,10 +379,10 @@ igraph_error_t igraph_is_tree(const igraph_t *graph, igraph_bool_t *res, igraph_ *root = iroot; } - if (is_tree && treat_as_undirected) { - /* For undirected graphs (or directed graphs that are treated as - * undirected in this calculation), a tree is weakly connected and is - * a forest, so we can cache this */ + if (is_tree) { + /* A graph that is a directed tree is also an undirected tree. + * An undirected tree is weakly connected and is a forest, + * so we can cache this. */ igraph_i_property_cache_set_bool(graph, IGRAPH_PROP_IS_FOREST, true); igraph_i_property_cache_set_bool(graph, IGRAPH_PROP_IS_WEAKLY_CONNECTED, true); } @@ -464,22 +474,23 @@ static igraph_error_t igraph_i_is_forest( * \function igraph_is_forest * \brief Decides whether the graph is a forest. * - * An undirected graph is a forest if it has no cycles. - * + * An undirected graph is a forest if it has no cycles. Equivalently, + * a graph is a forest if all connected components are trees. * - * In the directed case, a possible additional requirement is that edges in each + * + * In the directed case, an additional requirement is that edges in each * tree are oriented away from the root (out-trees or arborescences) or all edges * are oriented towards the root (in-trees or anti-arborescences). * This test can be controlled using the \p mode parameter. - * * - * By convention, the null graph (i.e. the graph with no vertices) is considered to be a forest. * + * By convention, the null graph (i.e. the graph with no vertices) is considered to be a forest. * + * * The \p res return value of this function is cached in the graph itself if * \p mode is set to \c IGRAPH_ALL or if the graph is undirected. Calling the * function multiple times with no modifications to the graph in between - * will return a cached value in O(1) time if the roots are not asked for. + * will return a cached value in O(1) time if the roots are not requested. * * \param graph The graph object to analyze. * \param res Pointer to a logical variable. If not \c NULL, then the result will be stored @@ -502,28 +513,61 @@ static igraph_error_t igraph_i_is_forest( */ igraph_error_t igraph_is_forest(const igraph_t *graph, igraph_bool_t *res, igraph_vector_int_t *roots, igraph_neimode_t mode) { - /* Caching is enabled only if the graph is undirected or mode == IGRAPH_ALL. - * Also, we can't return early if we need to calculate the roots */ - igraph_bool_t use_cache = ( - !igraph_is_directed(graph) || mode == IGRAPH_ALL - ); + + const igraph_bool_t treat_as_undirected = !igraph_is_directed(graph) || mode == IGRAPH_ALL; if (!roots && !res) { return IGRAPH_SUCCESS; } - if (use_cache && !roots && res) { - IGRAPH_RETURN_IF_CACHED_BOOL(graph, IGRAPH_PROP_IS_FOREST, res); + /* Note on cache use: + * + * The IGRAPH_PROP_IS_FOREST cached property is equivalent to this function's + * result ONLY in the undirected case. Keep in mind that a graph that is not + * a directed forest may still be an undirected forest, i.e. may still be free + * of undirected cycles. Example: 1->2<-3->4. + */ + + if (igraph_i_property_cache_has(graph, IGRAPH_PROP_IS_FOREST)) { + const igraph_bool_t no_undirected_cycles = igraph_i_property_cache_get_bool(graph, IGRAPH_PROP_IS_FOREST); + if (treat_as_undirected && res && ! roots) { + /* If the graph is treated as undirected and no roots are requested, + * we can directly use the cached IGRAPH_PROP_IS_FOREST value. */ + *res = no_undirected_cycles; + return IGRAPH_SUCCESS; + } else { + /* Otherwise we can use negative cached values (i.e. "false"): + * - A graph with undirected cycles cannot be a directed forest. + * - If the graph is not a forest, we don't need to look for roots. + */ + if (! no_undirected_cycles) { + if (res) { res = false; } + if (roots) { igraph_vector_int_clear(roots); } + return IGRAPH_SUCCESS; + } + } } IGRAPH_CHECK(igraph_i_is_forest(graph, res, roots, mode)); - /* At this point we know whether the graph is a forest if we have at least - * one of 'res' or 'roots' */ - if (res) { - igraph_i_property_cache_set_bool(graph, IGRAPH_PROP_IS_FOREST, *res); - } else if (roots) { - igraph_i_property_cache_set_bool(graph, IGRAPH_PROP_IS_FOREST, !igraph_vector_int_empty(roots)); + /* At this point we know whether the graph is an (undirected or directed) forest + * as we have at least one of 'res' or 'roots'. The case when both are NULL was + * caught above. */ + igraph_bool_t is_forest; + if (res != NULL) { + is_forest = *res; + } else /* roots != NULL */ { + is_forest = igraph_vcount(graph) == 0 || !igraph_vector_int_empty(roots); + } + if (is_forest) { + /* If the graph is a directed forest, then it has no undirected cycles. + * We can enter positive results in the cache unconditionally. */ + igraph_i_property_cache_set_bool(graph, IGRAPH_PROP_IS_FOREST, true); + } else if (treat_as_undirected) { + /* However, if the graph is not a directed forest, it might still be + * an undirected forest. We can only enter negative results in the cache + * when edge directions were ignored, but NOT in the directed case. */ + igraph_i_property_cache_set_bool(graph, IGRAPH_PROP_IS_FOREST, false); } return IGRAPH_SUCCESS;