Skip to content

fix: Validate permutations in permute() and sample_correlated_gnp() - #2982

Open
krlmlr wants to merge 2 commits into
claude/series-land-review-brieffrom
claude/validate-permutation
Open

krlmlr wants to merge 2 commits into
claude/series-land-review-brieffrom
claude/validate-permutation

Conversation

@krlmlr

@krlmlr krlmlr commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Stacked on #2973 (base claude/series-land-review-brief), the top of stack 2977, and carrying only its own commit.

Why

On CRAN igraph 2.3.4, and on main with its C core 0.10.17, permute() leaves invalid permutations to the C core, whose messages name no argument and differ with the input. On a 4-vertex graph, c(-1, 2, 3, 4) gives "Invalid index in permutation vector", c(NA, 2, 3, 4) gives "The value nan is not representable as an integer", and c(1, 1, 2, 3) gives "Duplicate entry in permutation vector". sample_correlated_gnp() and sample_correlated_gnp_pair() are worse: they accept a duplicated permutation such as c(1, 1, 2, 3) silently, and a value beyond the vertex count such as c(1, 2, 3, 5) silently adds a fifth vertex to the result.

The vendoring series makes this worse, because its R code inverts the permutation before the C check runs. There, c(1.5, 2, 3, 4) silently becomes the identity, c(1, 2, 3, 5) reports "invalid permutation vector size", and negative or missing values surface as base R subscript errors ("only 0's may be mixed with negative subscripts", "NAs are not allowed in subscripted assignments"), in permute() and in both games alike. The validator is valid with main's engine, so it lands here first, and the series rebuild carries it in front of its own R changes.

What

A new internal check_permutation() in R/utils-assert-args.R checks, in this order, that the permutation is numeric, has length equal to the number of vertices, has no missing values, holds whole numbers, lies within 1:n, and has no duplicates. Each failure is a cli::cli_abort() error in the caller's name that names the argument and the offending positions or values, for example "permutation must contain values between 1 and 4. ℹ Found 0 and 5 at positions 1 and 4." or "permutation must not contain duplicate values. ℹ Found 1 more than once." permute() calls it first, after checking the graph. sample_correlated_gnp() and sample_correlated_gnp_pair() call it whenever a permutation is given, and NULL still means no permutation.

Valid permutations, integer or integer-valued double, give the same results as before. Against a build of the base branch, a set of seeded calls to all three functions (integer and double permutations, attributes, an empty graph, a canonical labeling, and NULL for the games) returns identical graphs and leaves the RNG in the same state. The one other change concerns invalid input: sample_correlated_gnp_pair() with a permutation and a malformed n such as NA now fails the length check, which shows that n, instead of the C glue's scalar check.

Tests

test-topology.R and test-games.R snapshot every error with expect_snapshot_igraph_error(), which keeps flavored builds stable. They also check that integer-valued doubles such as c(2, 1, 3, 4) give the unchanged result: permute() against a fixed edge list and against the integer permutation, and the two games against permute() with corr = 1. testthat::test_local(filter = "topology|games|permute|correlated") passes, with no warnings on a second run, and so do the other files that call permute() (operators, cliques, sgm, trees, conversion, structural-properties). The full suite passes too, apart from two tests that need the network or an installed package: graph_from_graphdb() cannot reach its URL from the sandbox, and test-other.R passes once the built package is on the library path.

LLM use: this PR was prepared with Claude Code (Claude Opus 5.5), at the maintainer's request.

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

Generated by Claude Code

`permute()`, `sample_correlated_gnp()` and `sample_correlated_gnp_pair()` now check a user-supplied permutation in R,
before it reaches C.
The C core's errors named no argument and differed with the input,
and the correlated games silently accepted duplicates and values beyond the vertex count,
the latter growing the result by extra vertices.

The internal `check_permutation()` checks, in this order,
that the permutation is numeric, has one element per vertex, has no missing values,
holds whole numbers, stays within `1:n`, and has no duplicates.
Each error names the argument and the offending positions or values.
Integer and integer-valued double permutations give the same results as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnA76zWTu4ZD8BQCSYRpk
@krlmlr
krlmlr added this pull request to stack #2977 October 17, 2026 13:13
@github-actions

Copy link
Copy Markdown
Contributor

This is how benchmark results would change (along with a 95% confidence interval in relative change) if 0834dd2 is merged into claude/series-land-review-brief:

  • ✔️E_named: 242ms -> 244ms [-1.92%, +3.95%]
  • ✔️V_named: 518ms -> 521ms [-1.69%, +2.88%]
  • ✔️all_simple_paths_named: 135ms -> 136ms [-0.38%, +0.54%]
  • ✔️as_adjacency_matrix: 248ms -> 247ms [-2.11%, +1.54%]
  • ✔️as_biadjacency_matrix: 264ms -> 264ms [-0.85%, +1.08%]
  • ✔️as_data_frame_both: 252ms -> 256ms [-0.4%, +3.5%]
  • ✔️as_long_data_frame: 197ms -> 197ms [-2.09%, +2.35%]
  • ✔️ego_order2_named: 149ms -> 149ms [-0.54%, +0.93%]
  • ✔️es_attr_assign: 230ms -> 230ms [-0.86%, +1.03%]
  • ✔️es_attr_filter: 237ms -> 235ms [-1.92%, +0.58%]
  • ✔️graph_from_adjacency_matrix: 285ms -> 287ms [-0.22%, +1.22%]
  • ✔️graph_from_data_frame: 270ms -> 271ms [-1.09%, +1.35%]
  • ✔️head_of_named: 170ms -> 170ms [-0.56%, +0.74%]
  • ✔️incident_edges: 193ms -> 197ms [-0.3%, +4.53%]
  • ✔️max_cliques_named: 25.5ms -> 25.6ms [-0.35%, +0.82%]
  • ✔️neighbors_by_name: 261ms -> 261ms [-1.11%, +0.81%]
  • ❗🐌union_named: 236ms -> 241ms [+0.08%, +3.85%]
  • ✔️vs_attr_filter: 299ms -> 299ms [-1.2%, +0.89%]
  • ✔️vs_by_name: 244ms -> 243ms [-0.87%, +0.33%]
    Further explanation regarding interpretation and methodology can be found in the documentation.

This branch has not been deployed

No deployments
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.

2 participants