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

Add mycielski operator #177

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Conversation

mcognetta
Copy link
Contributor

This adds the Mycielski operator (wiki), a unary graph operator that increases the chromatic number of a graph by 1.

This can be found in other graph libraries: NetworkX, SageMath.

I wasn't sure if it was best to put it here or in the generators module (which is how it is implemented in NetworkX/SageMath). Happy to move it if necessary.

Copy link
Member

@aurorarossi aurorarossi left a comment

Choose a reason for hiding this comment

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

Hello! I'm not a maintainer, I'm just contributing. I wrote some suggestions and comments.

src/operators.jl Outdated Show resolved Hide resolved
"""
@traitfn function mycielski(g::AbstractGraph::(!IsDirected); iterations = 1)
ref = g
out = deepcopy(g)
Copy link
Member

Choose a reason for hiding this comment

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

I think that deepcopy(g) could slow down and uses a lot of memory especially for big graphs.

Copy link
Contributor Author

@mcognetta mcognetta Oct 5, 2022

Choose a reason for hiding this comment

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

These operators are not supposed to mutate the input right? I see some have ! variants, but not all. Perhaps there should be a mycielski! and mycielski(g; iterations) = mycielski!(deepcopy(g); iterations=iterations) method. WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Yes I agree with you. I was only thinking about the case in which we can mutate the input.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would also support having both mycielski! and mycielski. We currently have something similar for transitiveclosure and transitiveclosure! at the moment.

src/operators.jl Outdated Show resolved Hide resolved
src/operators.jl Outdated Show resolved Hide resolved
@mcognetta
Copy link
Contributor Author

@aurorarossi thanks for the comments!

@codecov
Copy link

codecov bot commented Oct 5, 2022

Codecov Report

Merging #177 (fa6c65d) into master (1783617) will increase coverage by 0.00%.
The diff coverage is 100.00%.

@@           Coverage Diff           @@
##           master     #177   +/-   ##
=======================================
  Coverage   97.47%   97.48%           
=======================================
  Files         109      109           
  Lines        6349     6366   +17     
=======================================
+ Hits         6189     6206   +17     
  Misses        160      160           

Edge 10 => 11
```
"""
@traitfn function mycielski(g::AbstractGraph::(!IsDirected); iterations = 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
@traitfn function mycielski(g::AbstractGraph::(!IsDirected); iterations = 1)
@traitfn function mycielski(g::SimpleGraph; iterations::Int = 1)

The problem we have at the moment, that for graphs where we have some kind of edge weights/data, it is not clear what we mean by adding an edge. This is definitely something we should sort out in the future, but in the meantime I would suggest to restrict this function to SimpleGraphs.

Also, would suggest to restrict the type of iterations to to Int or Integer.

"""
@traitfn function mycielski(g::AbstractGraph::(!IsDirected); iterations = 1)
out = deepcopy(g)
for _ in 1:iterations
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
for _ in 1:iterations
for _ in Base.OneTo(iterations)

To be honest though, I am not sure if that makes a big difference for the case where iterations is not of type Int.

mycielski(g)

Returns a graph after applying the Mycielski operator to the input. The Mycielski operator
returns a new graph with `2n+1` vertices and `3e+n` edges and will increase the chromatic
Copy link
Contributor

Choose a reason for hiding this comment

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

I think using the symbol e for the number of edges is a bit unusual, when we use n for the number of vertices at the same time. Maybe there is a better way?

apply the operator to the previous iterations graph.

For each vertex in the original graph, that vertex and a copy of it are added to the new graph.
Then, for each edge `(x, y)` in the original graph, the edges `(x, y)`, `(x', y)`, and `(x, y')`
Copy link
Contributor

Choose a reason for hiding this comment

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

I think, the usual convention for variables that represent vertices are the symbols u, v and w or s and t if we talk about some path finding algorithm.

julia> c = CycleGraph(5)
{5, 5} undirected simple Int64 graph

julia> m = Graphs.mycielski(c)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
julia> m = Graphs.mycielski(c)
julia> gm = Graphs.mycielski(g)

We usually use m to represent some kind of number such as the number of vertices or the number of edges and use symbols such as g and h for graphs.

out = deepcopy(g)
for _ in 1:iterations
N = nv(out)
add_vertices!(out, N + 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

This might fail, if the graph eltype does not support adding that many vertices. This might be especially bad, if we also create a mutating mycielski! function, as then this function might fail and leave g in some in-between state. So we probably should check at the start of the function if we have enough capacity to add that many vertices.

For the non-mutating mycielski function, we might also allow one to provide another larger eltype as an optional argument, such that we can retu

N = nv(out)
add_vertices!(out, N + 1)
w = nv(out)
for e in collect(edges(out))
Copy link
Contributor

Choose a reason for hiding this comment

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

Here we allocate an new vector for all edges, this would need quite a lot of space. Of course it is not possible to just iterate over the edges without collecting them, but maybe we can iterate of the first N vertices and allocate just a vector of the outneighbors of each vertex?

# ensure it is not done in-place
@test nv(g) == 15
@test ne(g) == 50
end
Copy link
Contributor

Choose a reason for hiding this comment

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

These tests are good, but I think we need to cover some additional test cases such as:

  • tests for graphs with self-loops
  • tests for graphs with zero vertices
  • tests for graphs with isolated vertices
  • tests for graphs with different eltypes - the testgraphs utility function can help here

@simonschoelly
Copy link
Contributor

This is an interesting operator, I added a few suggestions.

@gdalle gdalle added the enhancement New feature or request label Jun 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants