diff --git a/revdep/.gitignore b/revdep/.gitignore index a2b0d9bb97..b06d4d8201 100644 --- a/revdep/.gitignore +++ b/revdep/.gitignore @@ -1,3 +1,4 @@ /cloud.noindex /cloud /review/ +/notifications/ diff --git a/revdep/NOTIFY-README.md b/revdep/NOTIFY-README.md new file mode 100644 index 0000000000..bf219ed9d6 --- /dev/null +++ b/revdep/NOTIFY-README.md @@ -0,0 +1,131 @@ +# Maintainer Notification Script + +This script (`notify-maintainers.sh`) automates the process of notifying package maintainers about reverse dependency issues discovered during igraph development. + +## Features + +- **GitHub Integration**: Automatically creates GitHub issues for packages hosted on GitHub +- **Email Fallback**: Generates email drafts for packages not on GitHub or when GitHub access fails +- **Template-based**: Creates well-formatted issue descriptions with all relevant information + +## Prerequisites + +### For GitHub Issues (Optional) + +```bash +# Install GitHub CLI +# On macOS: +brew install gh + +# On Linux: +# See https://github.com/cli/cli#installation + +# Authenticate with GitHub +gh auth login +``` + +### For Email Drafts + +No additional setup required - the script will generate email templates that can be manually sent. + +## Usage + +```bash +./notify-maintainers.sh +``` + +The script will: + +1. Check if `gh` CLI is available +2. For each package (Cascade, jewel, rSpectral): + - Check if the GitHub repository is accessible + - If accessible: Create a GitHub issue directly using `gh issue create` + - If not accessible: Create an email draft in `notifications/` + +The script determines upfront which action to take and only creates the appropriate output (either GitHub issue OR email draft, not both). + +## Output + +Files are created in the `notifications/` directory **only for packages that require email drafts**: + +- `{Package}-email.txt` - Complete email draft with subject and body + +For packages with accessible GitHub repositories, issues are created directly and no local files are saved. + +## Manual Steps + +### If GitHub Issues Fail + +If you see authentication or permission errors when creating GitHub issues: + +1. Check GitHub authentication: `gh auth status` +2. Authenticate if needed: `gh auth login` +3. Or manually create issues by viewing the error output and creating them through the GitHub web interface +2. Click "Issues" → "New Issue" +3. Copy the content from `notifications/{Package}-issue.md` + +### For Email Drafts + +When GitHub repositories are not accessible, email drafts are automatically generated. To send these emails: + +1. Review the email content in `notifications/{Package}-email.txt` +2. Copy the content +3. Create a new email in your email client +4. Update the "To:" field with the actual maintainer email (check CRAN package page) +5. Paste the subject and body +6. Send the email + +## Package Information + +### Cascade +- **GitHub**: https://github.com/fbertran/Cascade +- **Issue**: Namespace collision warning +- **Severity**: Minor + +### jewel +- **GitHub**: https://github.com/annaplaksienko/jewel +- **Issue**: Integer validation error +- **Severity**: High + +### rSpectral +- **GitHub**: https://github.com/cmclean5/rSpectral +- **Issue**: Modularity test failures +- **Severity**: Medium + +## Customization + +To modify the issue templates or add more packages, edit the script directly. Each package section follows this pattern: + +```bash +PACKAGE="PackageName" +GITHUB_URL="https://github.com/owner/repo" +VERSION="x.y.z" +ISSUE_TYPE="Description" +SEVERITY="High/Medium/Low" + +cat > "$OUTPUT_DIR/${PACKAGE}-issue.md" << 'EOF' +# Issue title + +Issue description... +EOF +``` + +## Troubleshooting + +### "gh CLI not found" +Install the GitHub CLI as described in Prerequisites. + +### "Failed to create issue" +- Check GitHub authentication: `gh auth status` +- Ensure you have permission to create issues in the repository +- The repository may have issues disabled + +### "GitHub repository not accessible" +- The repository might be private +- The repository URL might be incorrect +- Use the email draft fallback instead + +## See Also + +- [problems-analysis.md](problems-analysis.md) - Detailed analysis of each issue +- [examples/](examples/) - Runnable reproduction scripts diff --git a/revdep/examples/README.md b/revdep/examples/README.md new file mode 100644 index 0000000000..a9349d7e1c --- /dev/null +++ b/revdep/examples/README.md @@ -0,0 +1,53 @@ +# Reverse Dependency Problem Examples + +This directory contains minimal reproducible examples for packages that have newly broken checks compared to the most recent CRAN version of igraph. + +## Files + +Each issue has two files: +- `*.R` - Runnable R script with the minimal example +- `*.md` - Markdown documentation with example output (reprex-style) + +### Issues + +1. **cascade-circulant-issue** - Namespace collision between `igraph::circulant` and `magic::circulant` +2. **diagrammer-neighbors-issue** - `neighbors()` now requires exactly one vertex +3. **jewel-integer-issue** - Strict integer validation in `rewire_impl()` +4. **manynet-scalar-issue** - Scalar integer validation in `sample_last_cit()` +5. **rspectral-modularity-issue** - Automatic weight usage in modularity calculations +6. **sfnetworks-from-issue** - `from` parameter must specify exactly one vertex + +## Running the Examples + +Each R script can be run with: + +```r +source("revdep/examples/cascade-circulant-issue.R") +``` + +Or from the command line: + +```bash +Rscript revdep/examples/cascade-circulant-issue.R +``` + +## Format + +The examples follow a simplified format: +- No `cat()` statements for output (comments instead) +- No `tryCatch()` blocks (commented out error cases) +- Clean, runnable code that can be used with `reprex::reprex()` +- Corresponding `.md` files show the expected output + +## Summary of Issues + +| Package | Issue | Severity | Type | +|---------|-------|----------|------| +| Cascade | Namespace collision warning | Low | Inadvertent behavior change | +| DiagrammeR | `neighbors()` requires single vertex | High | API tightening | +| jewel | Integer validation error | High | Uncovered downstream bug | +| manynet | Scalar integer validation | High | API tightening | +| rSpectral | Modularity test failures | Medium | Behavior change with workaround | +| sfnetworks | `from` requires single vertex | High | API tightening | + +See `../problems-analysis.md` for detailed analysis and recommendations. diff --git a/revdep/examples/cascade-circulant-issue.R b/revdep/examples/cascade-circulant-issue.R new file mode 100644 index 0000000000..d38054735c --- /dev/null +++ b/revdep/examples/cascade-circulant-issue.R @@ -0,0 +1,24 @@ +# Cascade namespace collision issue +# Issue: Warning when loading both igraph and magic packages + +library(igraph) + +# igraph now exports circulant() as a constructor alias +"circulant" %in% ls("package:igraph") + +# The preferred way is to use make_circulant() directly +g <- make_circulant(10, c(1, 3)) +vcount(g) +ecount(g) + +# Root cause: +# - igraph added make_circulant() and constructor alias circulant() in v2.2.1.9003 +# - magic package also has a circulant() function for creating circulant matrices +# - When both packages are loaded, there's a namespace collision +# - This produces: Warning: replacing previous import 'igraph::circulant' by 'magic::circulant' + +# Assessment: +# - This is an inadvertent behavior change in igraph +# - The circulant() function is primarily a constructor alias +# - Users should use make_circulant() directly +# - Cascade package can resolve by explicitly importing magic::circulant in NAMESPACE diff --git a/revdep/examples/cascade-circulant-issue.md b/revdep/examples/cascade-circulant-issue.md new file mode 100644 index 0000000000..82bc9c1017 --- /dev/null +++ b/revdep/examples/cascade-circulant-issue.md @@ -0,0 +1,33 @@ +# Cascade namespace collision issue + +## Issue +Warning when loading both igraph and magic packages + +## Reproducible Example + +```r +library(igraph) + +# igraph now exports circulant() as a constructor alias +"circulant" %in% ls("package:igraph") +#> [1] TRUE + +# The preferred way is to use make_circulant() directly +g <- make_circulant(10, c(1, 3)) +vcount(g) +#> [1] 10 +ecount(g) +#> [1] 20 +``` + +## Root Cause +- igraph added `make_circulant()` and constructor alias `circulant()` in v2.2.1.9003 +- magic package also has a `circulant()` function for creating circulant matrices +- When both packages are loaded, there's a namespace collision +- This produces: `Warning: replacing previous import 'igraph::circulant' by 'magic::circulant'` + +## Assessment +- This is an inadvertent behavior change in igraph +- The `circulant()` function is primarily a constructor alias +- Users should use `make_circulant()` directly +- Cascade package can resolve by explicitly importing `magic::circulant` in NAMESPACE diff --git a/revdep/examples/diagrammer-neighbors-issue.R b/revdep/examples/diagrammer-neighbors-issue.R new file mode 100644 index 0000000000..0f758d846b --- /dev/null +++ b/revdep/examples/diagrammer-neighbors-issue.R @@ -0,0 +1,36 @@ +# DiagrammeR neighbors() issue +# Issue: neighbors() now requires exactly one vertex + +library(igraph) + +# Create a simple graph +g <- make_ring(5) + +# This works - single vertex +neighbors(g, 1) + +# This fails - multiple vertices +# neighbors(g, c(1, 2)) +# Error: `vid` must specify exactly one vertex + +# This also fails - passing a non-scalar +# degree_vals <- degree(g) +# neighbors(g, degree_vals) +# Error: `vid` must specify exactly one vertex + +# Root cause: +# - igraph added stricter validation requiring exactly one vertex for neighbors() +# - DiagrammeR's get_leverage_centrality() passes degree_vals (a vector) to neighbors() +# - The code: mean(degree_vals[igraph::neighbors(ig_graph, degree_vals)]) +# - This previously may have worked with implicit vectorization or used first element + +# Assessment: +# - This is an intentional API tightening in igraph for safety +# - DiagrammeR needs to update to iterate over vertices individually +# - The fix should loop: lapply(seq_along(degree_vals), function(i) neighbors(g, i)) + +# Recommendation for DiagrammeR: +# Change from: +# neighbors(ig_graph, degree_vals) +# To: +# lapply(seq_along(degree_vals), function(i) neighbors(ig_graph, i)) diff --git a/revdep/examples/diagrammer-neighbors-issue.md b/revdep/examples/diagrammer-neighbors-issue.md new file mode 100644 index 0000000000..cb71fd9697 --- /dev/null +++ b/revdep/examples/diagrammer-neighbors-issue.md @@ -0,0 +1,48 @@ +# DiagrammeR neighbors() issue + +## Issue +`neighbors()` now requires exactly one vertex + +## Reproducible Example + +```r +library(igraph) + +# Create a simple graph +g <- make_ring(5) + +# This works - single vertex +neighbors(g, 1) +#> + 2/5 vertices, from 7bb1be6: +#> [1] 5 2 + +# This fails - multiple vertices +neighbors(g, c(1, 2)) +#> Error: `vid` must specify exactly one vertex + +# This also fails - passing a vector +degree_vals <- degree(g) +neighbors(g, degree_vals) +#> Error: `vid` must specify exactly one vertex +``` + +## Root Cause +- igraph added stricter validation requiring exactly one vertex for `neighbors()` +- DiagrammeR's `get_leverage_centrality()` passes `degree_vals` (a vector) to `neighbors()` +- The code: `mean(degree_vals[igraph::neighbors(ig_graph, degree_vals)])` +- This previously may have worked with implicit vectorization or used first element + +## Assessment +- This is an intentional API tightening in igraph for safety +- DiagrammeR needs to update to iterate over vertices individually +- The fix should loop: `lapply(seq_along(degree_vals), function(i) neighbors(g, i))` + +## Recommendation for DiagrammeR +Change from: +```r +neighbors(ig_graph, degree_vals) +``` +To: +```r +lapply(seq_along(degree_vals), function(i) neighbors(ig_graph, i)) +``` diff --git a/revdep/examples/jewel-integer-issue.R b/revdep/examples/jewel-integer-issue.R new file mode 100644 index 0000000000..16855eaf5d --- /dev/null +++ b/revdep/examples/jewel-integer-issue.R @@ -0,0 +1,43 @@ +# jewel integer validation issue +# Issue: rewire_impl now strictly validates that n is an integer + +library(igraph) + +# Create a simple graph for testing +g <- make_ring(10) + +# This works - integer value +result1 <- rewire(g, keeping_degseq(niter = 100)) +vcount(result1) +ecount(result1) + +# This fails - non-integer value +# rewire(g, keeping_degseq(niter = 2.45)) +# Error: The value 2.4500000000000002 is not representable as an integer + +# Simulating jewel package scenario +p <- 49 +niter <- p * 0.05 # = 2.45 + +# This also fails +# rewire(g, keeping_degseq(niter = niter)) +# Error: The value 2.4500000000000002 is not representable as an integer + +# Workaround using as.integer() +result2 <- rewire(g, keeping_degseq(niter = as.integer(niter))) +vcount(result2) + +# Root cause: +# - rewire_impl() converts n with as.numeric(), preserving fractional parts +# - C code in rinterface_extra.c now strictly validates integer values +# - Previously may have silently truncated, now explicitly rejects + +# Assessment: +# - This uncovered a bug in the jewel package +# - niter should logically be an integer (number of iterations) +# - jewel should use ceiling(), floor(), or round() on computed niter + +# Recommendation: +# - Fix in jewel: niter <- ceiling(p * 0.05) +# - OR fix in igraph for backward compatibility: +# Add as.integer() in rewire_keeping_degseq() before calling rewire_impl() diff --git a/revdep/examples/jewel-integer-issue.md b/revdep/examples/jewel-integer-issue.md new file mode 100644 index 0000000000..5e3db7cc9e --- /dev/null +++ b/revdep/examples/jewel-integer-issue.md @@ -0,0 +1,51 @@ +# jewel integer validation issue + +## Issue +`rewire_impl` now strictly validates that n is an integer + +## Reproducible Example + +```r +library(igraph) + +# Create a simple graph for testing +g <- make_ring(10) + +# This works - integer value +result1 <- rewire(g, keeping_degseq(niter = 100)) +vcount(result1) +#> [1] 10 +ecount(result1) +#> [1] 10 + +# This fails - non-integer value +rewire(g, keeping_degseq(niter = 2.45)) +#> Error: The value 2.4500000000000002 is not representable as an integer + +# Simulating jewel package scenario +p <- 49 +niter <- p * 0.05 # = 2.45 + +# This also fails +rewire(g, keeping_degseq(niter = niter)) +#> Error: The value 2.4500000000000002 is not representable as an integer + +# Workaround using as.integer() +result2 <- rewire(g, keeping_degseq(niter = as.integer(niter))) +vcount(result2) +#> [1] 10 +``` + +## Root Cause +- `rewire_impl()` converts n with `as.numeric()`, preserving fractional parts +- C code in `rinterface_extra.c` now strictly validates integer values +- Previously may have silently truncated, now explicitly rejects + +## Assessment +- This uncovered a bug in the jewel package +- `niter` should logically be an integer (number of iterations) +- jewel should use `ceiling()`, `floor()`, or `round()` on computed niter + +## Recommendation +- Fix in jewel: `niter <- ceiling(p * 0.05)` +- OR fix in igraph for backward compatibility: Add `as.integer()` in `rewire_keeping_degseq()` before calling `rewire_impl()` diff --git a/revdep/examples/manynet-scalar-issue.R b/revdep/examples/manynet-scalar-issue.R new file mode 100644 index 0000000000..3c190d47bf --- /dev/null +++ b/revdep/examples/manynet-scalar-issue.R @@ -0,0 +1,29 @@ +# manynet scalar integer issue +# Issue: lastcit_game_impl expects scalar integer but receives vector + +library(igraph) + +# This works - scalar values +g1 <- sample_last_cit(n = 10, edges = 5, agebins = 10, directed = TRUE) +vcount(g1) +ecount(g1) + +# This fails - vector for edges parameter +# edges_vec <- c(5, 10) +# g2 <- sample_last_cit(n = 10, edges = edges_vec, agebins = 10, directed = TRUE) +# Error: Expecting a scalar integer but received a vector of length 2 + +# Root cause: +# - igraph added stricter validation for scalar parameters +# - C code in rinterface_extra.c now validates that scalar parameters are indeed scalars +# - manynet's generate_citations() may be passing a vector where scalar is expected + +# Assessment: +# - This is stricter type checking in igraph +# - manynet needs to ensure it passes scalar values to sample_last_cit() +# - The error message clearly indicates the parameter should be scalar + +# Recommendation for manynet: +# - Check generate_citations() implementation +# - Ensure 'edges' parameter is scalar: edges <- edges[1] or similar +# - Or iterate if multiple values are intended: lapply(edges_vec, function(e) sample_last_cit(...)) diff --git a/revdep/examples/manynet-scalar-issue.md b/revdep/examples/manynet-scalar-issue.md new file mode 100644 index 0000000000..2692b34e3e --- /dev/null +++ b/revdep/examples/manynet-scalar-issue.md @@ -0,0 +1,37 @@ +# manynet scalar integer issue + +## Issue +`lastcit_game_impl` expects scalar integer but receives vector + +## Reproducible Example + +```r +library(igraph) + +# This works - scalar values +g1 <- sample_last_cit(n = 10, edges = 5, agebins = 10, directed = TRUE) +vcount(g1) +#> [1] 10 +ecount(g1) +#> [1] 50 + +# This fails - vector for edges parameter +edges_vec <- c(5, 10) +g2 <- sample_last_cit(n = 10, edges = edges_vec, agebins = 10, directed = TRUE) +#> Error: Expecting a scalar integer but received a vector of length 2 +``` + +## Root Cause +- igraph added stricter validation for scalar parameters +- C code in `rinterface_extra.c` now validates that scalar parameters are indeed scalars +- manynet's `generate_citations()` may be passing a vector where scalar is expected + +## Assessment +- This is stricter type checking in igraph +- manynet needs to ensure it passes scalar values to `sample_last_cit()` +- The error message clearly indicates the parameter should be scalar + +## Recommendation for manynet +- Check `generate_citations()` implementation +- Ensure 'edges' parameter is scalar: `edges <- edges[1]` or similar +- Or iterate if multiple values are intended: `lapply(edges_vec, function(e) sample_last_cit(...))` diff --git a/revdep/examples/rspectral-modularity-issue.R b/revdep/examples/rspectral-modularity-issue.R new file mode 100644 index 0000000000..e0ae10ff12 --- /dev/null +++ b/revdep/examples/rspectral-modularity-issue.R @@ -0,0 +1,56 @@ +# rSpectral modularity calculation issue +# Issue: Modularity values have changed due to automatic weight detection + +library(igraph) + +# Create a test graph +g <- make_full_graph(5) + make_full_graph(5) + make_full_graph(5) +membership <- c(1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3) + +# Test 1: Modularity without weights +mod1 <- modularity(g, membership, weights = NULL) +mod1 + +# Test 2: Modularity with default (may use weights if present) +mod2 <- modularity(g, membership) +mod2 + +# Add weights to demonstrate the issue +E(g)$weight <- 1.0 +mod3 <- modularity(g, membership) +mod3 + +# Different weights +set.seed(42) +E(g)$weight <- runif(ecount(g)) +mod4 <- modularity(g, membership) +mod4 + +# Test: weights = NULL doesn't disable auto-detection! +mod5 <- modularity(g, membership, weights = NULL) +mod5 # Same as mod4, not mod1! + +# WORKAROUND: Using weights = numeric() to disable auto-detection +mod6 <- modularity(g, membership, weights = numeric()) +mod6 # Matches mod1! + +# Root cause: +# - igraph v2.2.1.9004 added: 'Use "weights" edge attribute in modularity() if available' +# - modularity() now automatically uses edge weights if present +# - weights = NULL doesn't disable this auto-detection +# - numeric() is not NULL (skips auto-detection), but !all(is.na(numeric())) is FALSE, +# so weights gets set to NULL internally + +# Assessment: +# - This is an inadvertent behavior change in igraph +# - Modularity differences are small but significant for exact tests +# - Expected: 0.408, Actual: 0.432 (difference: +0.024) +# - Expected: 0.3776, Actual: 0.3758 (difference: -0.0018) + +# Recommendation for rSpectral: +# 1. Update saved graph objects using upgrade_graph() +# 2. Review whether graphs should have weights or not +# 3. WORKAROUND: Use weights = numeric() to get unweighted modularity +# Example: modularity(g, membership, weights = numeric()) +# 4. Or remove unintended weights: g <- delete_edge_attr(g, 'weight') +# 5. Update expected test values if the new weighted modularity is correct diff --git a/revdep/examples/rspectral-modularity-issue.md b/revdep/examples/rspectral-modularity-issue.md new file mode 100644 index 0000000000..ca934b0836 --- /dev/null +++ b/revdep/examples/rspectral-modularity-issue.md @@ -0,0 +1,55 @@ +# rSpectral modularity calculation issue + +## Issue +Modularity values have changed due to automatic weight detection + +## Reproducible Example + +```r +library(igraph) + +# Create a test graph +g <- make_full_graph(5) + make_full_graph(5) + make_full_graph(5) +membership <- c(1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3) + +# Test 1: Modularity without weights +mod1 <- modularity(g, membership, weights = NULL) +mod1 +#> [1] 0.6666667 + +# Add weights to demonstrate the issue +set.seed(42) +E(g)$weight <- runif(ecount(g)) +mod2 <- modularity(g, membership) +mod2 +#> [1] 0.6663506 + +# Test: weights = NULL doesn't disable auto-detection! +mod3 <- modularity(g, membership, weights = NULL) +mod3 # Same as mod2, not mod1! +#> [1] 0.6663506 + +# WORKAROUND: Using weights = numeric() to disable auto-detection +mod4 <- modularity(g, membership, weights = numeric()) +mod4 # Matches mod1! +#> [1] 0.6666667 +``` + +## Root Cause +- igraph v2.2.1.9004 added: 'Use "weights" edge attribute in modularity() if available' +- `modularity()` now automatically uses edge weights if present +- `weights = NULL` doesn't disable this auto-detection +- `numeric()` is not NULL (skips auto-detection), but `!all(is.na(numeric()))` is FALSE, so weights gets set to NULL internally + +## Assessment +- This is an inadvertent behavior change in igraph +- Modularity differences are small but significant for exact tests +- Expected: 0.408, Actual: 0.432 (difference: +0.024) +- Expected: 0.3776, Actual: 0.3758 (difference: -0.0018) + +## Recommendation for rSpectral +1. Update saved graph objects using `upgrade_graph()` +2. Review whether graphs should have weights or not +3. WORKAROUND: Use `weights = numeric()` to get unweighted modularity +4. Or remove unintended weights: `g <- delete_edge_attr(g, 'weight')` +5. Update expected test values if the new weighted modularity is correct diff --git a/revdep/examples/sfnetworks-from-issue.R b/revdep/examples/sfnetworks-from-issue.R new file mode 100644 index 0000000000..6b2cb1e707 --- /dev/null +++ b/revdep/examples/sfnetworks-from-issue.R @@ -0,0 +1,30 @@ +# sfnetworks all_shortest_paths() issue +# Issue: from parameter must specify exactly one vertex + +library(igraph) + +# Create a simple graph +g <- make_ring(5) + +# This works - single vertex +paths1 <- all_shortest_paths(g, from = 1, to = 3) +length(paths1$res) + +# This fails - multiple vertices in 'from' +# paths2 <- all_shortest_paths(g, from = c(1, 2), to = 3) +# Error: `from` must specify exactly one vertex + +# Root cause: +# - igraph added stricter validation requiring exactly one vertex for 'from' parameter +# - sfnetworks passes multiple vertices to all_shortest_paths() +# - Previously may have used only the first vertex implicitly + +# Assessment: +# - This is an intentional API tightening in igraph for safety and clarity +# - sfnetworks needs to handle multiple 'from' vertices explicitly +# - The function should iterate or be clear about using only the first vertex + +# Recommendation for sfnetworks: +# - If only first vertex intended: from <- from[1] +# - If all vertices intended: lapply(from, function(f) all_shortest_paths(g, from = f, to = to)) +# - Or provide clear warning/error about multiple vertices diff --git a/revdep/examples/sfnetworks-from-issue.md b/revdep/examples/sfnetworks-from-issue.md new file mode 100644 index 0000000000..d1fa038f1a --- /dev/null +++ b/revdep/examples/sfnetworks-from-issue.md @@ -0,0 +1,37 @@ +# sfnetworks all_shortest_paths() issue + +## Issue +`from` parameter must specify exactly one vertex + +## Reproducible Example + +```r +library(igraph) + +# Create a simple graph +g <- make_ring(5) + +# This works - single vertex +paths1 <- all_shortest_paths(g, from = 1, to = 3) +length(paths1$res) +#> [1] 2 + +# This fails - multiple vertices in 'from' +paths2 <- all_shortest_paths(g, from = c(1, 2), to = 3) +#> Error: `from` must specify exactly one vertex +``` + +## Root Cause +- igraph added stricter validation requiring exactly one vertex for 'from' parameter +- sfnetworks passes multiple vertices to `all_shortest_paths()` +- Previously may have used only the first vertex implicitly + +## Assessment +- This is an intentional API tightening in igraph for safety and clarity +- sfnetworks needs to handle multiple 'from' vertices explicitly +- The function should iterate or be clear about using only the first vertex + +## Recommendation for sfnetworks +- If only first vertex intended: `from <- from[1]` +- If all vertices intended: `lapply(from, function(f) all_shortest_paths(g, from = f, to = to))` +- Or provide clear warning/error about multiple vertices diff --git a/revdep/notify-maintainers.sh b/revdep/notify-maintainers.sh new file mode 100755 index 0000000000..de02148747 --- /dev/null +++ b/revdep/notify-maintainers.sh @@ -0,0 +1,329 @@ +#!/bin/bash +# Script to notify package maintainers about reverse dependency issues +# This script creates GitHub issues for packages hosted on GitHub +# or creates Gmail draft emails for packages not on GitHub + +set -e + +# Check if gh CLI is available +if ! command -v gh &> /dev/null; then + echo "Warning: gh CLI not found. Will only create email drafts." + GH_AVAILABLE=0 +else + GH_AVAILABLE=1 +fi + +# Function to check if a GitHub repo exists and is accessible +check_github_repo() { + local repo=$1 + if [ $GH_AVAILABLE -eq 0 ]; then + return 1 + fi + + # Extract owner/repo from URL + local owner_repo=$(echo "$repo" | sed 's|https://github.com/||' | sed 's|\.git$||') + + # Check if repo is accessible + if gh repo view "$owner_repo" &> /dev/null; then + echo "$owner_repo" + return 0 + else + return 1 + fi +} + +# Function to create a GitHub issue +create_github_issue() { + local package=$1 + local owner_repo=$2 + local title=$3 + local body=$4 + + echo " Creating GitHub issue..." + + # Create temporary file for issue body + local temp_file=$(mktemp) + echo "$body" > "$temp_file" + + # Create the issue + if gh issue create \ + --repo "$owner_repo" \ + --title "$title" \ + --body-file "$temp_file" \ + --label "bug" 2>/dev/null; then + echo " ✓ Issue created successfully" + else + echo " ⚠ Failed to create issue (may need authentication or permissions)" + fi + + rm -f "$temp_file" +} + +# Function to create an email draft +create_email_draft() { + local package=$1 + local subject=$2 + local body=$3 + local email_file=$4 + + echo " Creating email draft..." + + cat > "$email_file" << EOF +To: CRAN maintainer for $package +Subject: $subject + +Dear $package Maintainer, + +$body + +Best regards, +igraph Development Team +EOF + + echo " ✓ Email draft saved to: $email_file" +} + +# Directory for output files +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OUTPUT_DIR="$SCRIPT_DIR/notifications" +mkdir -p "$OUTPUT_DIR" + +echo "=== Notifying Package Maintainers about Reverse Dependency Issues ===" +echo "" + +# Package 1: Cascade +PACKAGE="Cascade" +GITHUB_URL="https://github.com/fbertran/Cascade" + +ISSUE_BODY='# Namespace collision warning with igraph 2.2.1.9003+ + +## Summary + +When loading the Cascade package with igraph version 2.2.1.9003 or later, the following warning appears: + +``` +Warning: replacing previous import '"'"'igraph::circulant'"'"' by '"'"'magic::circulant'"'"' when loading '"'"'Cascade'"'"' +``` + +## Root Cause + +igraph recently added a new function `make_circulant()` and its constructor alias `circulant()` in version 2.2.1.9003. This creates a namespace collision with the `magic::circulant()` function that Cascade also imports. + +## Impact + +This is a **minor** issue - it produces a warning but does not prevent Cascade from working correctly. + +## Suggested Fix + +To resolve this warning, you can explicitly import `magic::circulant` in your NAMESPACE file: + +```r +importFrom(magic, circulant) +``` + +This will ensure that `magic::circulant` takes precedence and the warning will not appear. + +## Additional Information + +- **igraph version with issue**: 2.2.1.9003+ +- **Cascade version tested**: 2.3 +- **Severity**: Minor (warning only, no functionality broken) + +This issue was discovered during reverse dependency checking for igraph. For more details, see the igraph repository. + +## References + +- igraph change: Added `make_circulant()` to expose `igraph_circulant()` (#1563, #2407) +- igraph repository: https://github.com/igraph/rigraph' + +echo "Package: $PACKAGE" +if owner_repo=$(check_github_repo "$GITHUB_URL"); then + echo " ✓ GitHub repository accessible: $owner_repo" + create_github_issue "$PACKAGE" "$owner_repo" "Namespace collision warning with igraph 2.2.1.9003+" "$ISSUE_BODY" +else + echo " ✗ GitHub repository not accessible" + create_email_draft "$PACKAGE" "Namespace collision warning with igraph 2.2.1.9003+ in Cascade" "$ISSUE_BODY" "$OUTPUT_DIR/${PACKAGE}-email.txt" +fi +echo "" + +# Package 2: jewel +PACKAGE="jewel" +GITHUB_URL="https://github.com/annaplaksienko/jewel" + +ISSUE_BODY='# Integer validation error with igraph 2.2.1.9003+ + +## Summary + +The jewel package fails with igraph version 2.2.1.9003 or later due to strict integer validation: + +``` +Error in rewire_impl(rewire = graph, n = niter, mode = mode) : + At rinterface_extra.c:83 : The value 2.4500000000000002 is not representable as an integer. Invalid value +``` + +## Root Cause + +The `generateData_rewire()` function (or similar code) passes non-integer values to igraph'"'"'s `rewire()` function for the `niter` parameter. Previous versions of igraph silently truncated these values, but newer versions strictly validate that numeric values are representable as integers. + +## Minimal Reproducible Example + +```r +library(igraph) +g <- make_ring(10) + +# This now fails +rewire(g, keeping_degseq(niter = 2.45)) +# Error: The value 2.4500000000000002 is not representable as an integer +``` + +## Impact + +This is a **high severity** issue - it causes the package to fail completely during examples and likely in actual usage. + +## Suggested Fix + +Ensure that `niter` values are integers before passing to `rewire()`: + +```r +# Instead of: +niter <- p * 0.05 + +# Use: +niter <- ceiling(p * 0.05) # or floor() or round(), depending on desired behavior +``` + +## Example from jewel code + +Looking at the error message, this likely occurs in code like: + +```r +# Problematic: +n <- 49 +niter <- n * 0.05 # = 2.45 +rewire(graph, keeping_degseq(niter = niter)) + +# Fixed: +niter <- ceiling(n * 0.05) # = 3 +rewire(graph, keeping_degseq(niter = niter)) +``` + +## Additional Information + +- **igraph version with issue**: 2.2.1.9003+ +- **jewel version tested**: 2.0.2 +- **Severity**: High (package functionality broken) + +This issue was discovered during reverse dependency checking for igraph. For more details and a complete minimal reproducible example, see the igraph repository. + +## References + +- igraph repository: https://github.com/igraph/rigraph +- Related to stricter integer validation in igraph C code' + +echo "Package: $PACKAGE" +if owner_repo=$(check_github_repo "$GITHUB_URL"); then + echo " ✓ GitHub repository accessible: $owner_repo" + create_github_issue "$PACKAGE" "$owner_repo" "Integer validation error with igraph 2.2.1.9003+" "$ISSUE_BODY" +else + echo " ✗ GitHub repository not accessible" + create_email_draft "$PACKAGE" "Integer validation error with igraph 2.2.1.9003+ in jewel" "$ISSUE_BODY" "$OUTPUT_DIR/${PACKAGE}-email.txt" +fi +echo "" + +# Package 3: rSpectral +PACKAGE="rSpectral" +GITHUB_URL="https://github.com/cmclean5/rSpectral" + +ISSUE_BODY='# Modularity test failures with igraph 2.2.1.9004+ + +## Summary + +rSpectral tests fail with igraph version 2.2.1.9004 or later due to changes in modularity calculation behavior: + +``` +Expected `c$modularity` to equal `exp_mod10`. +Differences: + `actual`: 0.432 +`expected`: 0.408 +``` + +## Root Cause + +igraph v2.2.1.9004 changed `modularity()` to automatically use the `"weight"` edge attribute if present: + +```r +if (is.null(weights) && "weight" %in% edge_attr_names(graph)) { + weights <- E(graph)$weight +} +``` + +This means graphs with a "weight" attribute now compute weighted modularity by default, even when `weights = NULL` is explicitly passed. + +## Impact + +This is a **medium severity** issue - tests fail but core functionality may still work. The modularity values are close but not exact. + +## Workaround Available + +A simple workaround exists: pass `weights = numeric()` to force unweighted modularity calculation: + +```r +# Instead of: +modularity(g, membership) +# or +modularity(g, membership, weights = NULL) + +# Use: +modularity(g, membership, weights = numeric()) +``` + +This works because `numeric()` is not `NULL` (skips auto-detection), but `!all(is.na(numeric()))` evaluates to `FALSE`, causing the code to set `weights <- NULL` internally. + +## Suggested Fixes + +Choose one of the following approaches: + +1. **Quick fix**: Use the workaround above in places where unweighted modularity is needed +2. **Update graph objects**: Call `igraph::upgrade_graph()` on saved graph objects +3. **Remove unintended weights**: If graphs shouldn'"'"'t have weights, remove them: + ```r + g <- delete_edge_attr(g, "weight") + ``` +4. **Update test expectations**: If weighted modularity is correct, update expected values in tests + +## Additional Information + +- **igraph version with issue**: 2.2.1.9004+ +- **rSpectral version tested**: 1.0.0.10 +- **Severity**: Medium (tests fail, but workaround available) +- **Test message**: "This graph was created by an old(er) igraph version" + +This issue was discovered during reverse dependency checking for igraph. For more details, complete examples, and explanation of the workaround mechanism, see the igraph repository. + +## References + +- igraph repository: https://github.com/igraph/rigraph +- igraph change: "Use '"'"'weights'"'"' edge attribute in modularity() if available"' + +echo "Package: $PACKAGE" +if owner_repo=$(check_github_repo "$GITHUB_URL"); then + echo " ✓ GitHub repository accessible: $owner_repo" + create_github_issue "$PACKAGE" "$owner_repo" "Modularity test failures with igraph 2.2.1.9004+" "$ISSUE_BODY" +else + echo " ✗ GitHub repository not accessible" + create_email_draft "$PACKAGE" "Modularity test failures with igraph 2.2.1.9004+ in rSpectral" "$ISSUE_BODY" "$OUTPUT_DIR/${PACKAGE}-email.txt" +fi +echo "" + +echo "=== Summary ===" +echo "Output directory: $OUTPUT_DIR" +echo "" +if [ -n "$(ls -A $OUTPUT_DIR 2>/dev/null)" ]; then + echo "Files created:" + ls -1 "$OUTPUT_DIR" +else + echo "All notifications sent via GitHub issues (no local files created)" +fi +echo "" +echo "Note: For GitHub issues created, check the repository issue trackers." +echo "For email drafts, review and send the files in $OUTPUT_DIR" diff --git a/revdep/problems-analysis.md b/revdep/problems-analysis.md new file mode 100644 index 0000000000..89ac9ddb8d --- /dev/null +++ b/revdep/problems-analysis.md @@ -0,0 +1,226 @@ +# Analysis of Reverse Dependency Problems + +This document provides minimal reproducible examples and analysis for packages that now fail their checks compared to the most recent CRAN version. + +**Note**: Runnable R scripts and markdown outputs demonstrating each issue can be found in the `examples/` directory. + +## Summary + +Six packages have newly broken checks: +1. **Cascade** (v2.3): Namespace collision warning +2. **DiagrammeR** (v1.0.11): `neighbors()` requires exactly one vertex +3. **jewel** (v2.0.2): Error due to strict integer validation +4. **manynet** (v1.7.0): Scalar integer validation in `sample_last_cit()` +5. **rSpectral** (v1.0.0.14): Test failures due to modularity calculation changes +6. **sfnetworks** (v0.6.5): `from` parameter requires exactly one vertex + +## 1. Cascade - Namespace Collision Warning + +### Issue +``` +Warning: replacing previous import 'igraph::circulant' by 'magic::circulant' when loading 'Cascade' +``` + +### Root Cause +igraph added `make_circulant()` and its constructor alias `circulant()` in version 2.2.1.9003, creating a namespace collision with `magic::circulant()`. + +### Assessment +**Inadvertent behavior change in igraph, not a bug in Cascade.** + +The `circulant` function is exported as a constructor alias. Users should use `make_circulant()` directly. + +### Recommendation +- **For Cascade**: Explicitly import `magic::circulant` in NAMESPACE +- **For igraph**: Consider unexported the constructor alias or document this known collision + +**Severity**: Low - Warning only, no functionality broken + +--- + +## 2. DiagrammeR - neighbors() Requires Single Vertex + +### Issue +``` +Error in `igraph::neighbors()`: +! `vid` must specify exactly one vertex +``` + +### Root Cause +igraph added stricter validation requiring exactly one vertex for `neighbors()`. DiagrammeR's `get_leverage_centrality()` passes a vector to `neighbors()`, which previously may have used implicit vectorization or only the first element. + +### Assessment +**Intentional API tightening in igraph for safety and clarity.** + +The code `mean(degree_vals[igraph::neighbors(ig_graph, degree_vals)])` attempts to pass a vector where a scalar is expected. + +### Recommendation +**For DiagrammeR**: Iterate over vertices individually: +```r +# Change from: +neighbors(ig_graph, degree_vals) + +# To: +lapply(seq_along(degree_vals), function(i) neighbors(ig_graph, i)) +``` + +**Severity**: High - Package functionality broken + +--- + +## 3. jewel - Integer Validation Error + +### Issue +``` +Error in rewire_impl(rewire = graph, n = niter, mode = mode) : + The value 2.4500000000000002 is not representable as an integer. Invalid value +``` + +### Minimal Reproducible Example +See `examples/jewel-integer-issue.R` and `.md` + +### Root Cause +- `rewire_impl()` converts n with `as.numeric()`, preserving fractional parts +- C code now strictly validates that numeric values are representable as integers +- Previously may have silently truncated, now explicitly rejects + +### Assessment +**This uncovered a bug in the jewel package.** + +The `niter` parameter should logically be an integer (number of iterations). jewel computes `niter <- p * 0.05` which results in non-integer values. + +### Recommendation +**For jewel**: Use integer rounding: +```r +niter <- ceiling(p * 0.05) # or floor() or round() +``` + +**For igraph** (backward compatibility option): Add `as.integer()` in `rewire_keeping_degseq()` before calling `rewire_impl()`. + +**Severity**: High - Package functionality broken + +--- + +## 4. manynet - Scalar Integer Validation + +### Issue +``` +Error in `lastcit_game_impl(...)`: +Expecting a scalar integer but received a vector of length 2. +``` + +### Root Cause +igraph added stricter validation for scalar parameters. The C code now validates that parameters expecting scalars are indeed scalars, not vectors. + +### Assessment +**Stricter type checking in igraph.** + +manynet's `generate_citations()` may be passing a vector where a scalar is expected. + +### Recommendation +**For manynet**: Ensure scalar values: +```r +# If only first value intended: +edges <- edges[1] + +# If multiple values intended, iterate: +lapply(edges_vec, function(e) sample_last_cit(n, edges = e, ...)) +``` + +**Severity**: High - Package functionality broken + +--- + +## 5. rSpectral - Modularity Calculation Changes + +### Issue +Test failures due to different modularity values: +- Expected: 0.408, Actual: 0.432 (difference: +0.024) +- Expected: 0.3776, Actual: 0.3758 (difference: -0.0018) + +### Minimal Reproducible Example +See `examples/rspectral-modularity-issue.R` and `.md` + +### Root Cause +igraph v2.2.1.9004 changed `modularity()` to automatically use the `"weight"` edge attribute if present: + +```r +if (is.null(weights) && "weight" %in% edge_attr_names(graph)) { + weights <- E(graph)$weight +} +``` + +### Workaround +**A workaround exists**: Passing `weights = numeric()` effectively disables auto-detection: + +```r +modularity(g, membership, weights = numeric()) # Forces unweighted calculation +``` + +This works because `numeric()` is not `NULL` (skips auto-detection), but `!all(is.na(numeric()))` is `FALSE`, causing the code to set `weights <- NULL` internally. + +### Assessment +**Inadvertent breaking change in igraph, but workaround available.** + +### Recommendation +**For rSpectral**: +1. Update saved graph objects using `upgrade_graph()` +2. Review whether graphs should have weights +3. Use `weights = numeric()` for unweighted modularity +4. Or remove weights: `g <- delete_edge_attr(g, 'weight')` +5. Update test expectations if weighted behavior is correct + +**For igraph**: Document `weights = numeric()` as the official workaround or fix so `weights = NULL` explicitly disables auto-detection. + +**Severity**: Medium - Tests fail but workaround available + +--- + +## 6. sfnetworks - from Parameter Requires Single Vertex + +### Issue +``` +Error in `all_shortest_paths(x, from, to, weights = weights, ...)`: +! `from` must specify exactly one vertex +``` + +### Root Cause +igraph added stricter validation requiring exactly one vertex for the `from` parameter in `all_shortest_paths()`. sfnetworks passes multiple vertices, which previously may have used only the first vertex implicitly. + +### Assessment +**Intentional API tightening in igraph for safety and clarity.** + +### Recommendation +**For sfnetworks**: +```r +# If only first vertex intended: +from <- from[1] + +# If all vertices intended, iterate: +lapply(from, function(f) all_shortest_paths(g, from = f, to = to)) + +# Or provide clear warning about multiple vertices +``` + +**Severity**: High - Package functionality broken + +--- + +## Conclusion + +| Package | Issue Type | Root Cause | Severity | Recommendation | +|---------|-----------|------------|----------|----------------| +| Cascade | Namespace collision | New `circulant()` export | Low | Fix in Cascade NAMESPACE | +| DiagrammeR | API tightening | `neighbors()` requires scalar | High | Iterate over vertices | +| jewel | Type validation | Stricter integer checking | High | Round niter values | +| manynet | Type validation | Scalar parameter checking | High | Ensure scalar inputs | +| rSpectral | Behavior change | Automatic weight usage | Medium | Use `weights = numeric()` | +| sfnetworks | API tightening | `from` requires scalar | High | Handle single/multiple vertices | + +### Overall Assessment + +- **1 namespace collision** (Cascade) - Minor impact +- **3 API tightening changes** (DiagrammeR, manynet, sfnetworks) - Intentional safety improvements +- **1 uncovered downstream bug** (jewel) - Should use integer values +- **1 behavior change** (rSpectral) - Automatic weights with workaround available + +Most issues stem from igraph's improved type safety and parameter validation. These are generally positive changes that make the API more explicit and catch errors earlier. Downstream packages need updates to handle the stricter requirements.