diff --git a/R/cliques.R b/R/cliques.R index 9005ce8f8cc..78d35b82201 100644 --- a/R/cliques.R +++ b/R/cliques.R @@ -221,8 +221,11 @@ clique.number <- function(graph) { #' vectors of vertex IDs. Each list element is a clique, i.e. a vertex sequence #' of class [igraph.vs][V]. #' -#' `max_cliques()` returns `NULL`, invisibly, if its `file` -#' argument is not `NULL`. The output is written to the specified file in +#' `max_cliques()` returns: +#' - When `file` is `NULL` and `callback` is `NULL`, a list of vertex sequences. +#' - When `callback` is not `NULL`, the callback function is called for each clique, +#' then `max_cliques()` returns `NULL` invisibly. +#' - When `file` is not `NULL`, the output is written to the specified file in #' this case. #' #' `clique_num()` and `count_max_cliques()` return an integer @@ -286,24 +289,26 @@ largest_cliques <- function(graph) { } #' @rdname cliques -#' @param subset If not `NULL`, then it must be a vector of vertex IDs, +#' @param subset A vector of vertex IDs, #' numeric or symbolic if the graph is named. The algorithm is run from these #' vertices only, so only a subset of all maximal cliques is returned. See the #' Eppstein paper for details. This argument makes it possible to easily -#' parallelize the finding of maximal cliques. -#' @param file If not `NULL`, then it must be a file name, i.e. a -#' character scalar. The output of the algorithm is written to this file. (If -#' it exists, then it will be overwritten.) Each clique will be a separate line -#' in the file, given with the numeric IDs of its vertices, separated by -#' whitespace. +#' parallelize the finding of maximal cliques. Default: `NULL`, the algorithm +#' is run from all vertices. +#' @param file A file name, i.e. a character scalar. +#' The output of the algorithm is written to this file. +#' (If it exists, then it will be overwritten.) +#' Each clique will be a separate line in the file, +#' given with the numeric IDs of its vertices, separated by whitespace. +#' Default: `NULL`, the function returns a list of vertex sequences. #' @export max_cliques <- function( graph, min = NULL, max = NULL, subset = NULL, - file = NULL, ..., + file = NULL, callback = NULL ) { ensure_igraph(graph) @@ -311,30 +316,21 @@ max_cliques <- function( # Handle file and subset modes (original functionality) if (!is.null(file)) { - if ( - !is.character(file) || - length(grep("://", file, fixed = TRUE)) > 0 || - length(grep("~", file, fixed = TRUE)) > 0 - ) { - tmpfile <- TRUE - origfile <- file - file <- tempfile() - } else { - tmpfile <- FALSE + if (!is.character(file)) { + cli::cli_abort("{.arg file} must be a file path, not a connection.") + } + if (grepl("://", file, fixed = TRUE)) { + cli::cli_abort("{.arg file} must be a file path, not a URL.") } on.exit(.Call(Rx_igraph_finalizer)) - res <- .Call( + .Call( Rx_igraph_maximal_cliques_file, graph, subset, - file, + path.expand(file), as.numeric(min %||% 0), as.numeric(max %||% 0) ) - if (tmpfile) { - buffer <- read.graph.toraw(file) - write.graph.fromraw(buffer, origfile) - } return(invisible(NULL)) } diff --git a/man/cliques.Rd b/man/cliques.Rd index a9d7497d016..80cbde32e0d 100644 --- a/man/cliques.Rd +++ b/man/cliques.Rd @@ -21,8 +21,8 @@ max_cliques( min = NULL, max = NULL, subset = NULL, - file = NULL, ..., + file = NULL, callback = NULL ) @@ -61,17 +61,19 @@ so will cause R to crash due to reentrancy issues. Extract any needed graph information before calling the function with a callback, or use collector mode (the default) and process results afterward.} -\item{subset}{If not \code{NULL}, then it must be a vector of vertex IDs, +\item{subset}{A vector of vertex IDs, numeric or symbolic if the graph is named. The algorithm is run from these vertices only, so only a subset of all maximal cliques is returned. See the Eppstein paper for details. This argument makes it possible to easily -parallelize the finding of maximal cliques.} +parallelize the finding of maximal cliques. Default: \code{NULL}, the algorithm +is run from all vertices.} -\item{file}{If not \code{NULL}, then it must be a file name, i.e. a -character scalar. The output of the algorithm is written to this file. (If -it exists, then it will be overwritten.) Each clique will be a separate line -in the file, given with the numeric IDs of its vertices, separated by -whitespace.} +\item{file}{A file name, i.e. a character scalar. +The output of the algorithm is written to this file. +(If it exists, then it will be overwritten.) +Each clique will be a separate line in the file, +given with the numeric IDs of its vertices, separated by whitespace. +Default: \code{NULL}, the function returns a list of vertex sequences.} \item{vertex.weights}{Vertex weight vector. If the graph has a \code{weight} vertex attribute, then this is used by default. If the graph does not have a @@ -95,9 +97,14 @@ of class \link[=V]{igraph.vs}. If \code{callback} is provided, returns \code{NUL vectors of vertex IDs. Each list element is a clique, i.e. a vertex sequence of class \link[=V]{igraph.vs}. -\code{max_cliques()} returns \code{NULL}, invisibly, if its \code{file} -argument is not \code{NULL}. The output is written to the specified file in +\code{max_cliques()} returns: +\itemize{ +\item When \code{file} is \code{NULL} and \code{callback} is \code{NULL}, a list of vertex sequences. +\item When \code{callback} is not \code{NULL}, the callback function is called for each clique, +then \code{max_cliques()} returns \code{NULL} invisibly. +\item When \code{file} is not \code{NULL}, the output is written to the specified file in this case. +} \code{clique_num()} and \code{count_max_cliques()} return an integer scalar. diff --git a/man/maximal.cliques.Rd b/man/maximal.cliques.Rd index 0570f2408ac..ae209406885 100644 --- a/man/maximal.cliques.Rd +++ b/man/maximal.cliques.Rd @@ -15,17 +15,19 @@ maximal.cliques(graph, min = NULL, max = NULL, subset = NULL, file = NULL) \item{max}{Numeric constant, upper limit on the size of the cliques to find. \code{NULL} means no limit.} -\item{subset}{If not \code{NULL}, then it must be a vector of vertex IDs, +\item{subset}{A vector of vertex IDs, numeric or symbolic if the graph is named. The algorithm is run from these vertices only, so only a subset of all maximal cliques is returned. See the Eppstein paper for details. This argument makes it possible to easily -parallelize the finding of maximal cliques.} +parallelize the finding of maximal cliques. Default: \code{NULL}, the algorithm +is run from all vertices.} -\item{file}{If not \code{NULL}, then it must be a file name, i.e. a -character scalar. The output of the algorithm is written to this file. (If -it exists, then it will be overwritten.) Each clique will be a separate line -in the file, given with the numeric IDs of its vertices, separated by -whitespace.} +\item{file}{A file name, i.e. a character scalar. +The output of the algorithm is written to this file. +(If it exists, then it will be overwritten.) +Each clique will be a separate line in the file, +given with the numeric IDs of its vertices, separated by whitespace. +Default: \code{NULL}, the function returns a list of vertex sequences.} } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} diff --git a/man/maximal.cliques.count.Rd b/man/maximal.cliques.count.Rd index 5dad067399f..954024b1908 100644 --- a/man/maximal.cliques.count.Rd +++ b/man/maximal.cliques.count.Rd @@ -15,11 +15,12 @@ maximal.cliques.count(graph, min = NULL, max = NULL, subset = NULL) \item{max}{Numeric constant, upper limit on the size of the cliques to find. \code{NULL} means no limit.} -\item{subset}{If not \code{NULL}, then it must be a vector of vertex IDs, +\item{subset}{A vector of vertex IDs, numeric or symbolic if the graph is named. The algorithm is run from these vertices only, so only a subset of all maximal cliques is returned. See the Eppstein paper for details. This argument makes it possible to easily -parallelize the finding of maximal cliques.} +parallelize the finding of maximal cliques. Default: \code{NULL}, the algorithm +is run from all vertices.} } \description{ \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} diff --git a/src/rinterface_extra.c b/src/rinterface_extra.c index b9699995125..c9d1834a5c4 100644 --- a/src/rinterface_extra.c +++ b/src/rinterface_extra.c @@ -4787,7 +4787,9 @@ SEXP Rx_igraph_read_graph_edgelist(SEXP pvfile, SEXP pn, SEXP pdirected) { #endif if (file==0) { igraph_error("Cannot read edgelist", __FILE__, __LINE__, IGRAPH_EFILE); } + IGRAPH_FINALLY(fclose, file); IGRAPH_R_CHECK(igraph_read_graph_edgelist(&g, file, n, directed)); + IGRAPH_FINALLY_CLEAN(1); fclose(file); PROTECT(result=Ry_igraph_to_SEXP(&g)); IGRAPH_I_DESTROY(&g); @@ -4815,11 +4817,13 @@ SEXP Rx_igraph_read_graph_ncol(SEXP pvfile, SEXP ppredef, #endif if (file==0) { igraph_error("Cannot read edgelist", __FILE__, __LINE__, IGRAPH_EFILE); } + IGRAPH_FINALLY(fclose, file); if (Rf_xlength(ppredef)>0) { Rx_igraph_SEXP_to_strvector(ppredef, &predef); predefptr=&predef; } IGRAPH_R_CHECK(igraph_read_graph_ncol(&g, file, predefptr, names, weights, directed)); + IGRAPH_FINALLY_CLEAN(1); fclose(file); PROTECT(result=Ry_igraph_to_SEXP(&g)); IGRAPH_I_DESTROY(&g); @@ -4859,7 +4863,9 @@ SEXP Rx_igraph_write_graph_ncol(SEXP graph, SEXP file, SEXP pnames, #endif if (stream==0) { igraph_error("Cannot write .ncol file", __FILE__, __LINE__, IGRAPH_EFILE); } + IGRAPH_FINALLY(fclose, stream); IGRAPH_R_CHECK(igraph_write_graph_ncol(&g, stream, names, weights)); + IGRAPH_FINALLY_CLEAN(1); fclose(stream); #if HAVE_OPEN_MEMSTREAM == 1 PROTECT(result=Rf_allocVector(RAWSXP, size)); @@ -4889,7 +4895,9 @@ SEXP Rx_igraph_read_graph_lgl(SEXP pvfile, SEXP pnames, SEXP pweights, SEXP pdir #endif if (file==0) { igraph_error("Cannot read edgelist", __FILE__, __LINE__, IGRAPH_EFILE); } + IGRAPH_FINALLY(fclose, file); IGRAPH_R_CHECK(igraph_read_graph_lgl(&g, file, names, weights, directed)); + IGRAPH_FINALLY_CLEAN(1); fclose(file); PROTECT(result=Ry_igraph_to_SEXP(&g)); IGRAPH_I_DESTROY(&g); @@ -4928,7 +4936,11 @@ SEXP Rx_igraph_write_graph_lgl(SEXP graph, SEXP file, SEXP pnames, #else stream=fopen(CHAR(STRING_ELT(file, 0)), "w"); #endif + if (stream==0) { igraph_error("Cannot write LGL file", __FILE__, __LINE__, + IGRAPH_EFILE); } + IGRAPH_FINALLY(fclose, stream); IGRAPH_R_CHECK(igraph_write_graph_lgl(&g, stream, names, weights, isolates)); + IGRAPH_FINALLY_CLEAN(1); fclose(stream); #if HAVE_OPEN_MEMSTREAM == 1 PROTECT(result=Rf_allocVector(RAWSXP, size)); @@ -5375,10 +5387,12 @@ SEXP Rx_igraph_read_graph_dimacs(SEXP pvfile, SEXP pdirected) { if (file==0) { igraph_error("Cannot read edgelist", __FILE__, __LINE__, IGRAPH_EFILE); } + IGRAPH_FINALLY(fclose, file); igraph_vector_int_init(&label, 0); igraph_strvector_init(&problem, 0); igraph_vector_init(&cap, 0); IGRAPH_R_CHECK(igraph_read_graph_dimacs_flow(&g, file, &problem, &label, &source, &target, &cap, directed)); + IGRAPH_FINALLY_CLEAN(1); fclose(file); if (!strcmp(igraph_strvector_get(&problem, 0), "max")) { PROTECT(result=NEW_LIST(5)); px++; @@ -5437,7 +5451,9 @@ SEXP Rx_igraph_write_graph_dimacs(SEXP graph, SEXP file, if (stream==0) { igraph_error("Cannot write edgelist", __FILE__, __LINE__, IGRAPH_EFILE); } + IGRAPH_FINALLY(fclose, stream); IGRAPH_R_CHECK(igraph_write_graph_dimacs_flow(&g, stream, source, target, &cap)); + IGRAPH_FINALLY_CLEAN(1); fclose(stream); #if HAVE_OPEN_MEMSTREAM == 1 PROTECT(result=Rf_allocVector(RAWSXP, size)); @@ -5682,15 +5698,10 @@ SEXP Rx_igraph_maximal_cliques(SEXP graph, SEXP psubset, SEXP Rx_igraph_maximal_cliques_file(SEXP graph, SEXP psubset, SEXP file, SEXP pminsize, SEXP pmaxsize) { igraph_t g; - igraph_integer_t minsize=(igraph_integer_t) REAL(pminsize)[0]; - igraph_integer_t maxsize=(igraph_integer_t) REAL(pmaxsize)[0]; + igraph_integer_t minsize = (igraph_integer_t) REAL(pminsize)[0]; + igraph_integer_t maxsize = (igraph_integer_t) REAL(pmaxsize)[0]; igraph_vector_int_t subset; - SEXP result; FILE *stream; -#if HAVE_OPEN_MEMSTREAM == 1 - char *bp; - size_t size; -#endif Rz_SEXP_to_igraph(graph, &g); if (!Rf_isNull(psubset)) { @@ -5699,29 +5710,19 @@ SEXP Rx_igraph_maximal_cliques_file(SEXP graph, SEXP psubset, SEXP file, IGRAPH_R_CHECK(igraph_vector_int_init(&subset, 0)); } IGRAPH_FINALLY_PV(igraph_vector_int_destroy, &subset); -#if HAVE_OPEN_MEMSTREAM == 1 - stream=open_memstream(&bp, &size); -#else - stream=fopen(CHAR(STRING_ELT(file, 0)), "w"); -#endif - if (stream==0) { igraph_error("Cannot write cliques", __FILE__, - __LINE__, IGRAPH_EFILE); } + stream = fopen(CHAR(STRING_ELT(file, 0)), "w"); + if (stream == 0) { + igraph_error("Cannot write cliques", __FILE__, __LINE__, IGRAPH_EFILE); + } + IGRAPH_FINALLY(fclose, stream); igraph_maximal_cliques_subset(&g, Rf_isNull(psubset) ? 0 : &subset, /*ptr=*/ 0, /*no=*/ 0, /*file=*/ stream, minsize, maxsize); + IGRAPH_FINALLY_CLEAN(1); fclose(stream); igraph_vector_int_destroy(&subset); IGRAPH_FINALLY_CLEAN(1); -#if HAVE_OPEN_MEMSTREAM == 1 - PROTECT(result=Rf_allocVector(RAWSXP, size)); - memcpy(RAW(result), bp, sizeof(char)*size); - free(bp); -#else - PROTECT(result=NEW_NUMERIC(0)); -#endif - - UNPROTECT(1); - return result; + return R_NilValue; } /* TOP-LEVEL: called from R via .Call; must use IGRAPH_R_CHECK */