Skip to content
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

fix!: Subset assignment of a graph avoids addition of double edges and ignores loops unless the new loops argument is set to TRUE #1661

Merged
merged 7 commits into from
Jan 23, 2025

Conversation

schochastics
Copy link
Contributor

@schochastics schochastics commented Jan 19, 2025

This PR refactors single bracket manipulating of a graph (g[1:3,4:6] <- 2) (#1465).

The only real change is the use of get_edge_ids instead of the old [.igraph to get edge ids which makes the function more readable, slightly faster and a lower memory footprint.

Fixes an unintended behaviour (fix #1662)

Copy link
Contributor

aviator-app bot commented Jan 19, 2025

Current Aviator status

Aviator will automatically update this comment as the status of the PR changes.
Comment /aviator refresh to force Aviator to re-examine your PR (or learn about other /aviator commands).

This PR was merged manually (without Aviator). Merging manually can negatively impact the performance of the queue. Consider using Aviator next time.


See the real-time status of this PR on the Aviator webapp.
Use the Aviator Chrome Extension to see the status of your PR within GitHub.

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.

Thanks, I like the size and intention of this PR.

} else {
todel <- unlist(x[[i, j, ..., edges = TRUE]])
edge_pairs <- expand.grid(i, j)
edge_ids <- get_edge_ids(x, c(rbind(edge_pairs[, 1], edge_pairs[, 2])))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this covered by tests?

Suggested change
edge_ids <- get_edge_ids(x, c(rbind(edge_pairs[, 1], edge_pairs[, 2])))
edge_ids <- get_edge_ids(x, as.vector(t(edge_pairs)))

The interface of get_edge_ids() is interesting. Should we extend that to accept two-column data frames?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this covered by tests?

Going through the existing tests, I realize there are some gaps. I will add a set of tests for this functionality

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface of get_edge_ids() is interesting. Should we extend that to accept two-column data frames?

It has been bothering me mildly for years as a user that edges need to be supplied as a vector (same with add_edges() and delete_edges() and probably more). However that's required by the c core. It might be too much of a fundamental change at this point?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The c(rbind(...)) pattern is probably fine, I forgot the semantics for vectors:

c(rbind(1:3, 4:6))
#> [1] 1 4 2 5 3 6
c(t(data.frame(1:3, 4:6)))
#> [1] 1 4 2 5 3 6
as.vector(t(data.frame(1:3, 4:6)))
#> [1] 1 4 2 5 3 6

Created on 2025-01-19 with reprex v2.1.1

There are two layers here: the C core and the R interface. We should provide an idiomatic R user interface that translates to what the C core needs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c(rbind(...)) is faster for data frames by an order of magnitude, but not for matrices:

df <- as.data.frame(cbind(1:30, 4:33))

bench::mark(
  c(t(df)),
  c(rbind(df[, 1], df[, 2])),
  c(rbind(df[[1]], df[[2]]))
)
#> # A tibble: 3 × 6
#>   expression                      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>                 <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 c(t(df))                    12.96µs  14.51µs    57926.    74.4KB     34.8
#> 2 c(rbind(df[, 1], df[, 2]))   5.33µs   5.99µs   157537.      576B     47.3
#> 3 c(rbind(df[[1]], df[[2]]))   3.65µs   4.26µs   219083.      576B     43.8

m <- cbind(1:30, 4:33)

bench::mark(
  c(t(m)),
  c(rbind(m[, 1], m[, 2]))
)
#> # A tibble: 2 × 6
#>   expression                    min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>               <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 c(t(m))                     820ns 983.94ns   964028.      576B     96.4
#> 2 c(rbind(m[, 1], m[, 2]))    984ns   1.15µs   791706.      576B      0

Created on 2025-01-20 with reprex v2.1.1

Draft PR for new UI in #1663.

R/indexing.R Show resolved Hide resolved
R/indexing.R Show resolved Hide resolved
@schochastics schochastics marked this pull request as draft January 19, 2025 11:40
@schochastics
Copy link
Contributor Author

should probably be blocked until #1662 is resolved

R/indexing.R Outdated Show resolved Hide resolved
R/indexing.R Outdated Show resolved Hide resolved
} else {
todel <- unlist(x[[i, j, ..., edges = TRUE]])
edge_pairs <- expand.grid(i, j)
edge_ids <- get_edge_ids(x, c(rbind(edge_pairs[, 1], edge_pairs[, 2])))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c(rbind(...)) is faster for data frames by an order of magnitude, but not for matrices:

df <- as.data.frame(cbind(1:30, 4:33))

bench::mark(
  c(t(df)),
  c(rbind(df[, 1], df[, 2])),
  c(rbind(df[[1]], df[[2]]))
)
#> # A tibble: 3 × 6
#>   expression                      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>                 <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 c(t(df))                    12.96µs  14.51µs    57926.    74.4KB     34.8
#> 2 c(rbind(df[, 1], df[, 2]))   5.33µs   5.99µs   157537.      576B     47.3
#> 3 c(rbind(df[[1]], df[[2]]))   3.65µs   4.26µs   219083.      576B     43.8

m <- cbind(1:30, 4:33)

bench::mark(
  c(t(m)),
  c(rbind(m[, 1], m[, 2]))
)
#> # A tibble: 2 × 6
#>   expression                    min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>               <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 c(t(m))                     820ns 983.94ns   964028.      576B     96.4
#> 2 c(rbind(m[, 1], m[, 2]))    984ns   1.15µs   791706.      576B      0

Created on 2025-01-20 with reprex v2.1.1

Draft PR for new UI in #1663.

R/indexing.R Outdated Show resolved Hide resolved
@schochastics
Copy link
Contributor Author

schochastics commented Jan 20, 2025

waiting for #1663 to be merged (then rebase and adapt)

@krlmlr krlmlr changed the title refactor: single bracket manipulating of a graph (#1465) fix!: Subset assignment of a graph avoids addition of double edges and ignores loops unless the new loops argument is set to TRUE Jan 20, 2025
@krlmlr
Copy link
Contributor

krlmlr commented Jan 20, 2025

Do you want to add more tests here?

@schochastics
Copy link
Contributor Author

Do you want to add more tests here?

Yes!

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.

Looks good to me after #1663. Please auto-squash or squash when good on your end.

@schochastics schochastics marked this pull request as ready for review January 23, 2025 20:07
@schochastics schochastics merged commit cee57e1 into igraph:main Jan 23, 2025
22 checks passed
schochastics added a commit to schochastics/rigraph that referenced this pull request Jan 27, 2025
…d ignores loops unless the new `loops` argument is set to `TRUE` (igraph#1661)
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.

Intended behaviour of [<-.igraph
2 participants