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

Heterogeneous graph and new associated RL pipeline. #229

Merged
merged 186 commits into from
Jun 9, 2022
Merged

Conversation

gostreap
Copy link
Collaborator

@gostreap gostreap commented May 10, 2022

The purpose of this pull request is to allow the implementation of the new graph convolution discussed in issue #225.

The major novelties are :

  • Introduction of a heterogeneous graph with 3 distinct node types for constraints, variables and values.
  • The modification of the tripartite graph to better manage VarViews:
    VarView relations were formerly represented as an edge between 2 distinct variables (the parent and its child). With this PR, VarView relations are now represented as constraints of type ViewConstraint linked to these 2 variables. When constraint_type is set to true in chosen_features, and a problem containts VarView relations, it now encodes the type of the constraint with 4 new dimensions reflecting the exact nature of the VarView relation. Please refer to featurize function in heterogeneousstaterepresentation.jl for more information.
  • The implementation of the new convolutions as described in Refacto of the Graph Convolution #225.
    • The initialization layer of the convolution : HeterogeneousGraphConvInit
    • The heterogeneous version of GraphConv: HeterogeneousGraphConv.
  • The compatibility of all downstream neural networks (CPNNs) with this new heterogeneous pipeline: HeterogeneousCPNN, HeterogeneousFullFeaturedCPNN and HeterogeneousVariableOutputCPNN.
  • The resolution of major bugs in the pipeline:
    • Fix of model emptying after the resolution of each instance
    • Fix of value ordering in the graph construction
  • The implementation of a new mean aggregation in the convolutional layers.

Minor updates also concern:

  • The removal of symmetries in the generation of graphcoloring problems
  • The correction of errors in the generation of KEP and RB problems

gostreap and others added 30 commits May 9, 2022 09:38
ef=fgs.ef,
gf=fgs.gf
)
end
Copy link
Collaborator

@3rdCore 3rdCore Jun 1, 2022

Choose a reason for hiding this comment

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

The Zigote.ignore() should not contain the code block :

return BatchedFeaturedGraph{Float32}(
    fgs.graph;
    nf=g.σ.(g.weight1  X .+ g.weight2  X  A .+ g.bias),
    ef=fgs.ef,
    gf=fgs.gf
)

As long as there no operation computed, this is maybe not a problem, but I'd rather remove this block from the ignore() if it doesn't lead to an error

Comment on lines 71 to 79
return BatchedHeterogeneousFeaturedGraph{Float32}(
contovar,
valtovar,
g.σ.(g.weightsvar ⊠ vcat(X1, H1, H2 ⊠ contovarN, H3 ⊠ valtovarN) .+ g.biasvar),
g.σ.(g.weightscon ⊠ vcat(X2, H2, H1 ⊠ vartoconN) .+ g.biascon),
g.σ.(g.weightsval ⊠ vcat(X3, H3, H1 ⊠ vartovalN) .+ g.biasval),
fgs.gf
)
end
Copy link
Collaborator

@3rdCore 3rdCore Jun 1, 2022

Choose a reason for hiding this comment

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

Same comment here. What do you think of this @gostreap ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We should not forget to correct this before merging with master. As the function is not recursive, it is ok to return under Zygote.ignore() statement but the operations that are computed inside the return here should be copied outside thr Zygote.ignore() domain. We have solved the same issue on heterogeneousgraphtransformer.jl.

Copy link
Collaborator

Choose a reason for hiding this comment

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

What causes the Zygote errors is the construction of the BatchedHeterogeneousFeaturedGraph. It would even be safer to do separate construction of the object under Zygote.ignore() and return outside it.

@louis-gautier louis-gautier marked this pull request as ready for review June 6, 2022 21:34
@@ -81,7 +81,7 @@ struct CPLayerGraph <: LightGraphs.AbstractGraph{Int}
"""
function CPLayerGraph(cpmodel::CPModel)
variables = Set{AbstractVar}(values(cpmodel.variables))
valuesOfVariables = branchable_values(cpmodel)
valuesOfVariables = sort(branchable_values(cpmodel))
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you explain briefly what the issue was with the previous pipeline ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

In short, the ordering of the actions taken by the agent was not consistent with the ordering of value node features in the graph. As a result, the values embeddings in FullFeaturedCPNN didn't match the actions taken by the agent afterwards. But this fix doesn't impact other CPNNs.

Copy link
Collaborator

@louis-gautier louis-gautier left a comment

Choose a reason for hiding this comment

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

We just have to check that Zygote.ignore() statements in all convolutional layers are well positioned and we can merge this branch on master.

@@ -257,6 +257,8 @@ Empty the CPModel.
"""
function Base.empty!(model::CPModel)
empty!(model.variables)
empty!(model.branchable_variables)
empty!(model.branchable)
Copy link
Collaborator

Choose a reason for hiding this comment

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

These lines fix a major bug caused by the fact that the model was not properly emptied after the resolution of each instance in the training process.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice spot !

if !isempty(x.children)
for child in x.children
cardinality += length(child.domain)
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

We now take all variables, including VarViews into consideration in global_domain_cardinality

Comment on lines -148 to -151
for (x1, x2) in variableConnections
v1, v2 = VariableVertex(x1), VariableVertex(x2)
add_edge!(fixedEdgesGraph, nodeToId[v1], nodeToId[v2])
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes absolutely. They are now represented as simple constraints (of type ViewConstraint) between 2 variables.

else
error("WARNING: Unknwon VarViewType: please implement DefaultFeaturization for this type!")
end
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

If the problem we are trying to solve has ViewConstraints, in constraint_type, we encode them thanks to 4 new components, respectively: one-hot encoding of the fact the considered constraint is a ViewConstraint, multiplication coefficient if the ViewConstraint is of type IntVarViewMul or IntVarViewOpposite, offset coefficient if the if the ViewConstraint is of type IntVarViewOffset, and one-hot encoding of the fact the ViewConstraint is of type BoolVarViewNot.

Comment on lines +38 to +40
@assert all(var .<= size(fg.varnf,2)) "The variable index is out of bound"
@assert all(reduce(vcat,val) .<= size(fg.valnf,2)) "One of the values index is out of bound"
@assert all(reduce(vcat,[[tuple[2] for tuple in countmap([c for c in vals])] for vals in val]) .== 1) "values array contains one element at least twice"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we remove them now as they haven't caused any error so far?

@@ -94,8 +95,8 @@ function fill_with_generator!(cpmodel::CPModel, gen::HomogenousGraphColoringGene

# edge constraints
for i in 1:n
for j in 1:n
if i != j && rand(rng) <= p
for j in (i+1):n
Copy link
Collaborator

Choose a reason for hiding this comment

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

This helps to remove symmetries when generating graphcoloring instances.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we remove them now as they haven't caused any error so far?

As long as computing performance is not a true bottleneck, I'd prefer to keep them in place as long as SeaPearl won't be (at least currently) put into production even if they are computationally inefficient.

Or we can remove only the last one which is the most greedy.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok no problem!

test/datagen/coloring.jl Outdated Show resolved Hide resolved
ef=fgs.ef,
gf=fgs.gf
)
sum = replace(reshape(mapslices(x -> sum(eachrow(x)), A, dims=[1, 2]), 1, :, size(A, 3)), 0=>1)
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the reason for adding a replace( M, 0=>1) ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

edit : This is to avoid any division by 0, in case a node has no neighbors, which can happen for value that belongs to no domain after pruning.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In the case where a node has no neighbor, the sum is zero, which will then cause problems in the division.

Putting a 1 in the division should not be a problem, because it will divide a vector composed only of 0. Any constant other than 1 would have done the job as well.

Copy link
Collaborator

@louis-gautier louis-gautier left a comment

Choose a reason for hiding this comment

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

I think we are now ready to merge with master.

gostreap and others added 3 commits June 8, 2022 11:23
* Add MaximumIndependentSet Generator

* Change NotEqual to SumLessThan

* Fix test by introducing LigthGraphs. everywhere

* Introduce test for maximum independent set

* Fix testset for maximumindependentset

* Update src/CP/constraints/algorithms/matching.jl

Co-authored-by: Tom Marty <59280588+3rdCore@users.noreply.github.com>

* Update src/CP/constraints/alldifferent.jl

Co-authored-by: Tom Marty <59280588+3rdCore@users.noreply.github.com>

* Add missing LightGraphs. prefix

Co-authored-by: Tom Marty <59280588+3rdCore@users.noreply.github.com>
Co-authored-by: louis-gautier <louisgautier99@gmail.com>
src/CP/valueselection/learning/utils.jl Outdated Show resolved Hide resolved
@@ -112,7 +111,11 @@ Update the StateRepesentation according to its Type and Featurization.
"""
function update_representation!(sr::DefaultStateRepresentation, model::CPModel, x::AbstractIntVar)
update_features!(sr, model)
ncon = sr.cplayergraph.numberOfConstraints
nvar = sr.cplayergraph.numberOfVariables
sr.possibleValuesIdx = map(v -> indexFromCpVertex(sr.cplayergraph, ValueVertex(v)), collect(x.domain))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did we specifically test this change ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think we tested it. Maybe we should create an issue for each behavior we want to test, without postponing the merge?

Comment on lines +152 to +172
function adjacency_matrices(cplayergraph::CPLayerGraph)
g = LightGraphs.Graph(cplayergraph) # Update the graph with the new information
nvar = cplayergraph.numberOfVariables
ncon = cplayergraph.numberOfConstraints
nval = cplayergraph.numberOfValues
contovar = zeros(ncon, nvar)
valtovar = zeros(nval, nvar)
for (i, node) in enumerate(cplayergraph.idToNode)
if isa(node, ConstraintVertex)
neighbors = LightGraphs.outneighbors(g, i)
for neighbor in neighbors
contovar[i, neighbor - ncon] = 1
end
elseif isa(node, ValueVertex)
neighbors = LightGraphs.outneighbors(g, i)
for neighbor in neighbors
valtovar[i - ncon - nvar, neighbor - ncon] = 1
end
end
end
return contovar, valtovar
Copy link
Collaborator

Choose a reason for hiding this comment

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

Did we tested this ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, we have unit tests for this in test/heterogeneousstaterepresentation.jl.

gostreap and others added 2 commits June 8, 2022 14:09
@gostreap gostreap merged commit 61220b5 into master Jun 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants