Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 24, 2026

Replaced 31 direct .Call(Rx_igraph_*) expressions with corresponding *_impl() wrapper functions from R/aaa-auto.R, and replaced the R implementation of get.adjacency.sparse() with the C library's get_adjacency_sparse_impl() for improved performance.

Changes

Pattern applied for .Call() replacements:

# Before
on.exit(.Call(Rx_igraph_finalizer))
res <- .Call(Rx_igraph_create, as.numeric(edges) - 1, as.numeric(n), as.logical(directed))

# After  
res <- create_impl(edges - 1, n, directed)

Files modified:

  • R/make.R (12): create, ring, full, star, kary_tree
  • R/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, connect
  • R/flow.R (5): st_mincut_value, st_vertex/edge_connectivity, disjoint_paths variants
  • R/components.R (1): decompose
  • R/conversion.R (2): get_adjacency (with "ignore" to "none" mapping for loops parameter) + replaced get.adjacency.sparse() implementation
  • R/interface.R (1): get_eids (with with_igraph_opt to ensure numeric return type)
  • R/other.R (1): random_sample

Key insight: The _impl functions expect string parameters (e.g., mode = "out") and handle conversion internally via switch_igraph_arg(). Previous code pre-converted to integers, which has been corrected.

Performance Improvements

Replaced get.adjacency.sparse() implementation:

  • Previously used R loops with Matrix::sparseMatrix() to construct sparse adjacency matrices
  • Now uses get_adjacency_sparse_impl() which calls the C library function R_igraph_get_adjacency_sparse
  • Converts result from igraph.tmp.sparse to proper Matrix object using igraph.i.spMatrix()
  • Maintains all error checking and attribute handling
  • Significantly improved performance for sparse adjacency matrix construction

Bug Fixes

Fixed several parameter passing issues identified during code review:

  • Removed numeric conversion of mode parameters - _impl functions expect strings
  • Added mapping from "ignore" to "none" for loops parameter in get_adjacency_impl()
  • Fixed get_edge_ids() to use with_igraph_opt(return.vs.es = FALSE) and removed incorrect + 1 offset (the C function R_igraph_get_eids already returns 1-based IDs via Ry_igraph_vector_int_to_SEXPp1)

Testing

  • Added explicit tests for get_edge_ids() to verify numeric return type and correct behavior for missing edges
  • Added 30 comprehensive snapshot tests for as_adjacency_matrix() covering all combinations:
    • Directed/undirected graphs
    • Weighted/unweighted graphs
    • All matrix types (both, upper, lower)
    • Sparse and dense output formats
    • Graphs with and without vertex names
  • Updated snapshot in tests/testthat/_snaps/interface.md to reflect correct behavior (missing edges now return 0 instead of numeric(0))
  • Updated snapshots in tests/testthat/_snaps/conversion.md with comprehensive test coverage
  • All tests pass for modified files (726 conversion tests + 1382+ tests across make, flow, components, interface, games)

Not replaced

~37 .Call(Rx_igraph_*) patterns remain unchanged. These use manually-written wrappers in src/rinterface_extra.c with different signatures than their auto-generated R_igraph_* counterparts (e.g., additional subset parameters in cliques, combined weighted/unweighted functionality in diameter).

  • By submitting this pull request, I assign the copyright of my contribution to The igraph development team.
Original prompt

This section details on the original issue you should resolve

<issue_title>Replace 100 or so .Call() expressions with calls to _impl functions</issue_title>
<issue_description>Only where possible. Do not introduce new _impl functions.</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 4 commits January 24, 2026 17:59
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>
@krlmlr krlmlr marked this pull request as ready for review January 24, 2026 18:26
Copilot AI review requested due to automatic review settings January 24, 2026 18:26
@krlmlr krlmlr changed the title [WIP] Replace .Call() expressions with _impl function calls chore: Replace .Call() expressions with _impl function calls Jan 24, 2026
Copilot AI changed the title chore: Replace .Call() expressions with _impl function calls refactor: replace .Call() with _impl functions Jan 24, 2026
Copilot AI requested a review from krlmlr January 24, 2026 18:27
Copy link

Copilot AI left a 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 in R/make.R, R/flow.R, R/games.R, R/components.R, R/conversion.R, R/interface.R, and R/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().

Comment on lines 1 to 18
# 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>

Copy link

Copilot AI Jan 24, 2026

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.

Suggested change
# 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. -->

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

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).

Comment on lines +212 to +216
decompose_impl(
graph,
as.numeric(mode),
as.numeric(max.comps),
as.numeric(min.vertices)
mode,
max.comps,
min.vertices
Copy link

Copilot AI Jan 24, 2026

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.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

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)
Copy link

Copilot AI Jan 24, 2026

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.

Suggested change
mode <- switch(mode, "out" = 1, "in" = 2, "all" = 3, "total" = 3)

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

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.

Comment on lines 252 to 256
res <- get_adjacency_impl(
graph,
as.numeric(type),
type,
weights,
loops
Copy link

Copilot AI Jan 24, 2026

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).

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

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
Comment on lines 555 to 557
graph,
as_igraph_vs(graph, vp) - 1,
as.logical(directed),
as.logical(error)
) +
1
directed,
error
) + 1
Copy link

Copilot AI Jan 24, 2026

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.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

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.

Copy link
Contributor

@krlmlr krlmlr left a 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Copilot AI and others added 3 commits January 24, 2026 19:38
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 24, 2026

@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 be...

All review issues have been addressed:

  • Fixed parameter passing to _impl functions (commits 6d07b24 and 90ab990)
  • Merged from main successfully
  • Looked for similar patterns - no other issues found
  • Added explicit tests for get_edge_ids() behavior (commit 90ab990)

All tests now pass including the updated snapshots.

Copilot AI requested a review from krlmlr January 24, 2026 19:44
@krlmlr
Copy link
Contributor

krlmlr commented Jan 24, 2026

Want to do revdepchecks before merging this.

@krlmlr
Copy link
Contributor

krlmlr commented Jan 25, 2026

@copilot: Add comprehensive tests for as_adjacency_matrix() for all combinations of directed/undirected, weighted/unweighted, types, sparsity. Snapshot tests are enough. Replace get.adjacency.sparse() by the library's implementation that is now available as an _impl .

…pl() and add comprehensive tests

Co-authored-by: krlmlr <1741643+krlmlr@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 25, 2026

@copilot: Add comprehensive tests for as_adjacency_matrix() for all combinations of directed/undirected, weighted/unweighted, types, sparsity. Snapshot tests are enough. Replace `get.adjacency.spars...

Completed in commit 6426dcc:

  1. Replaced get.adjacency.sparse() with the library's get_adjacency_sparse_impl() implementation. The R implementation that constructed sparse matrices using Matrix::sparseMatrix() is now replaced with the C library version for better performance.

  2. Added 30 comprehensive snapshot tests for as_adjacency_matrix() covering all combinations:

    • Directed/undirected graphs
    • Weighted/unweighted graphs
    • All matrix types (both, upper, lower)
    • Sparse and dense output formats
    • Graphs with/without vertex names

All 726 conversion tests pass.

Copilot AI changed the title refactor: replace .Call() with _impl functions refactor: replace .Call() with _impl functions and improve adjacency matrix implementation Jan 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace 100 or so .Call() expressions with calls to _impl functions

2 participants