-
-
Notifications
You must be signed in to change notification settings - Fork 206
refactor: replace .Call() with _impl functions and improve adjacency matrix implementation
#2546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
refactor: replace .Call() with _impl functions and improve adjacency matrix implementation
#2546
Conversation
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
.Call() expressions with _impl function calls
.Call() expressions with _impl function calls.Call() with _impl functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR continues the migration away from direct .Call() usage by switching a set of R-level wrappers to use the generated *_impl() functions instead.
Changes:
- Replaced several
.Call(Rx_igraph_*)invocations with corresponding*_impl()calls inR/make.R,R/flow.R,R/games.R,R/components.R,R/conversion.R,R/interface.R, andR/other.R. - Adjusted argument passing patterns to rely on
_impl-level coercion and enum mapping. - Added a new snapshot output file (
tests/testthat/_snaps/games.new.md) reflecting changed error context.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/testthat/_snaps/games.new.md | Adds a new snapshot output file (likely an unintended *.new.md artifact). |
| R/other.R | Switches sample_seq() to random_sample_impl(). |
| R/make.R | Switches several constructors to *_impl() wrappers. |
| R/interface.R | Switches get_edge_ids() to get_eids_impl(). |
| R/games.R | Switches several game generators and connect() to *_impl() wrappers. |
| R/flow.R | Switches several flow/connectivity routines to *_impl() wrappers. |
| R/conversion.R | Switches adjacency retrieval to get_adjacency_impl(). |
| R/components.R | Switches decompose() to decompose_impl(). |
tests/testthat/_snaps/games.new.md
Outdated
| # sample_degseq() works -- exponential degree error | ||
|
|
||
| Code | ||
| sample_degseq(exponential_degrees, method = "vl") | ||
| Condition | ||
| Error in `degree_sequence_game_impl()`: | ||
| ! Cannot make a connected graph from the given degree sequence. Invalid value | ||
| Source: games/degree_sequence_vl/gengraph_mr-connected.cpp:<linenumber> | ||
|
|
||
| # sample_degseq() works -- Power-law degree error | ||
|
|
||
| Code | ||
| sample_degseq(powerlaw_degrees, method = "vl") | ||
| Condition | ||
| Error in `degree_sequence_game_impl()`: | ||
| ! Cannot realize the given degree sequence as an undirected, simple graph. Invalid value | ||
| Source: games/degree_sequence_vl/gengraph_mr-connected.cpp:<linenumber> | ||
|
|
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a leftover snapshot output file produced by failing snapshot tests (*.new.md). testthat snapshot expectations are typically stored in tests/testthat/_snaps/games.md, so committing games.new.md will likely be ignored while tests continue to fail against games.md. Update games.md with the new output (or remove this file) instead of adding games.new.md.
| # sample_degseq() works -- exponential degree error | |
| Code | |
| sample_degseq(exponential_degrees, method = "vl") | |
| Condition | |
| Error in `degree_sequence_game_impl()`: | |
| ! Cannot make a connected graph from the given degree sequence. Invalid value | |
| Source: games/degree_sequence_vl/gengraph_mr-connected.cpp:<linenumber> | |
| # sample_degseq() works -- Power-law degree error | |
| Code | |
| sample_degseq(powerlaw_degrees, method = "vl") | |
| Condition | |
| Error in `degree_sequence_game_impl()`: | |
| ! Cannot realize the given degree sequence as an undirected, simple graph. Invalid value | |
| Source: games/degree_sequence_vl/gengraph_mr-connected.cpp:<linenumber> | |
| <!-- Temporary testthat snapshot file (games.new.md). | |
| Its contents should be merged into tests/testthat/_snaps/games.md, | |
| and this file should then be removed from the repository. --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. The games.new.md file was a temporary snapshot file that has been removed. The interface.new.md file has been merged into interface.md and removed (commit 90ab990).
| decompose_impl( | ||
| graph, | ||
| as.numeric(mode), | ||
| as.numeric(max.comps), | ||
| as.numeric(min.vertices) | ||
| mode, | ||
| max.comps, | ||
| min.vertices |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
decompose_impl() expects mode as a character value (it uses switch_igraph_arg(mode, ...)). In this function, mode is converted to an integer earlier and then passed here, which will error (e.g., tolower(1L)). Pass the matched mode string ("weak"/"strong") to decompose_impl() instead and remove the numeric conversion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit 6d07b24. Removed the numeric conversion - decompose_impl() expects the string mode parameter and handles the conversion internally.
R/games.R
Outdated
| connect <- function(graph, order, mode = c("all", "out", "in", "total")) { | ||
| ensure_igraph(graph) | ||
| mode <- igraph_match_arg(mode) | ||
| mode <- switch(mode, "out" = 1, "in" = 2, "all" = 3, "total" = 3) |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
connect_neighborhood_impl() expects mode as a character value (it calls switch_igraph_arg() internally). Converting mode to numeric here will break because switch_igraph_arg() lowercases its input. Pass the matched character value (e.g., "all"/"out"/"in"/"total") directly and remove the numeric switch() mapping.
| mode <- switch(mode, "out" = 1, "in" = 2, "all" = 3, "total" = 3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit 6d07b24. Removed the numeric conversion - connect_neighborhood_impl() expects the string mode parameter and calls switch_igraph_arg() internally.
| res <- get_adjacency_impl( | ||
| graph, | ||
| as.numeric(type), | ||
| type, | ||
| weights, | ||
| loops |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_adjacency_impl() expects loops values c("once", "none", "twice"). This function currently passes loops values coming from c("once", "twice", "ignore"), so loops = "ignore" will error. Map/rename "ignore" to "none" before calling get_adjacency_impl() (or adjust the allowed loops values upstream).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit 6d07b24. Added mapping from "ignore" to "none" before calling get_adjacency_impl() since the function signature uses c("once", "none", "twice") instead of c("once", "twice", "ignore").
R/interface.R
Outdated
| graph, | ||
| as_igraph_vs(graph, vp) - 1, | ||
| as.logical(directed), | ||
| as.logical(error) | ||
| ) + | ||
| 1 | ||
| directed, | ||
| error | ||
| ) + 1 |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_eids_impl() is a generated wrapper around R_igraph_get_eids and (based on other _impl wrappers) returns 1-based edge ids already. Adding + 1 here will shift edge ids incorrectly. Also, with default igraph_opt("return.vs.es") = TRUE, get_eids_impl() may try to return an igraph.es, which is incompatible with get_edge_ids() semantics when missing edges are represented by 0. Consider calling get_eids_impl() under with_igraph_opt(list(return.vs.es = FALSE), ...) and return the result without + 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit 90ab990. Modified get_edge_ids() to use with_igraph_opt(list(return.vs.es = FALSE), ...) and removed the +1 offset. The R_igraph_get_eids C function uses Ry_igraph_vector_int_to_SEXPp1 which already converts to 1-based indexing, unlike the old Rx_igraph_get_eids which used Ry_igraph_vector_int_to_SEXP (no p1). Added explicit tests to verify numeric return type and correct edge ID behavior.
krlmlr
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot: Merge main. Check the review, fix as appropriate, look for similar patterns. The review is consistent with the failures when rebuilding the vignette. Add an explicit test for the behavior because we don't want to rely on implicit tests through vignette building.
* creating vignettes ... ERROR
Error: --- re-building ‘igraph.Rmd’ using rmarkdown
Quitting from igraph.Rmd:159-162 [unnamed-chunk-17]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<error/rlang_error>
Error in `delete_edges_impl()`:
! Cannot create iterator, invalid edge ID. Invalid value
Source: graph/iterators.c:2008
---
Backtrace:
▆
1. ├─make_ring(10) %>% delete_edges("10|1")
2. └─igraph::delete_edges(., "10|1")
3. └─igraph:::delete_edges_impl(graph = graph, edges = edges) at igraph/R/interface.R:267:3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Error: Error: processing vignette 'igraph.Rmd' failed with diagnostics:
Cannot create iterator, invalid edge ID. Invalid value
Source: graph/iterators.c:2008
--- failed re-building ‘igraph.Rmd’
--- re-building ‘igraph_ES.rmd’ using rmarkdown
Quitting from igraph_ES.rmd:166-169 [unnamed-chunk-17]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<error/rlang_error>
Error in `delete_edges_impl()`:
! Cannot create iterator, invalid edge ID. Invalid value
Source: graph/iterators.c:2008
---
Backtrace:
▆
1. ├─make_ring(10) %>% delete_edges("10|1")
2. └─igraph::delete_edges(., "10|1")
3. └─igraph:::delete_edges_impl(graph = graph, edges = edges) at igraph/R/interface.R:267:3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
…expressions-with-impl-functions
All review issues have been addressed:
All tests now pass including the updated snapshots. |
|
Want to do revdepchecks before merging this. |
…expressions-with-impl-functions
|
@copilot: Add comprehensive tests for |
…pl() and add comprehensive tests Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Completed in commit 6426dcc:
All 726 conversion tests pass. |
.Call() with _impl functions.Call() with _impl functions and improve adjacency matrix implementation
Replaced 31 direct
.Call(Rx_igraph_*)expressions with corresponding*_impl()wrapper functions fromR/aaa-auto.R, and replaced the R implementation ofget.adjacency.sparse()with the C library'sget_adjacency_sparse_impl()for improved performance.Changes
Pattern applied for
.Call()replacements:Files modified:
R/make.R(12): create, ring, full, star, kary_treeR/games.R(10 + connect): barabasi_game, degree_sequence_game, aging variants, callaway_traits_game, establishment_game, watts_strogatz_game, lastcit_game, cited_type_game, citing_cited_type_game, connectR/flow.R(5): st_mincut_value, st_vertex/edge_connectivity, disjoint_paths variantsR/components.R(1): decomposeR/conversion.R(2): get_adjacency (with "ignore" to "none" mapping for loops parameter) + replacedget.adjacency.sparse()implementationR/interface.R(1): get_eids (withwith_igraph_optto ensure numeric return type)R/other.R(1): random_sampleKey insight: The
_implfunctions expect string parameters (e.g.,mode = "out") and handle conversion internally viaswitch_igraph_arg(). Previous code pre-converted to integers, which has been corrected.Performance Improvements
Replaced
get.adjacency.sparse()implementation:Matrix::sparseMatrix()to construct sparse adjacency matricesget_adjacency_sparse_impl()which calls the C library functionR_igraph_get_adjacency_sparseigraph.tmp.sparseto properMatrixobject usingigraph.i.spMatrix()Bug Fixes
Fixed several parameter passing issues identified during code review:
modeparameters -_implfunctions expect stringsloopsparameter inget_adjacency_impl()get_edge_ids()to usewith_igraph_opt(return.vs.es = FALSE)and removed incorrect+ 1offset (the C functionR_igraph_get_eidsalready returns 1-based IDs viaRy_igraph_vector_int_to_SEXPp1)Testing
get_edge_ids()to verify numeric return type and correct behavior for missing edgesas_adjacency_matrix()covering all combinations:tests/testthat/_snaps/interface.mdto reflect correct behavior (missing edges now return0instead ofnumeric(0))tests/testthat/_snaps/conversion.mdwith comprehensive test coverageNot replaced
~37
.Call(Rx_igraph_*)patterns remain unchanged. These use manually-written wrappers insrc/rinterface_extra.cwith different signatures than their auto-generatedR_igraph_*counterparts (e.g., additionalsubsetparameters in cliques, combined weighted/unweighted functionality in diameter).Original prompt
.Call()expressions with calls to_implfunctions #2545💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.