From 849d297c8731b7d6d8362a61224ab8a3b28d0319 Mon Sep 17 00:00:00 2001 From: jalving Date: Sat, 29 Jun 2024 22:14:11 -0700 Subject: [PATCH] add topology tests --- src/Plasmo.jl | 2 +- src/aggregate.jl | 4 +- src/backends/moi_backend.jl | 7 +- src/graph_functions/topology.jl | 12 +- src/optigraph.jl | 24 ++-- test/test_aggregate.jl | 11 +- test/test_optigraph.jl | 17 +-- test/test_topology_functions.jl | 206 ++++++++++++++++---------------- 8 files changed, 144 insertions(+), 139 deletions(-) diff --git a/src/Plasmo.jl b/src/Plasmo.jl index 4b1f3cf..bee73ac 100644 --- a/src/Plasmo.jl +++ b/src/Plasmo.jl @@ -20,7 +20,7 @@ export OptiGraph, NodeVariableRef, graph_backend, graph_index, -add_node, add_edge, add_subgraph, has_edge, get_edge, +add_node, get_node, add_edge, add_subgraph, has_edge, get_edge, get_edge_by_index, collect_nodes, local_nodes, all_nodes, local_edges, all_edges, diff --git a/src/aggregate.jl b/src/aggregate.jl index 79c372d..142aec9 100644 --- a/src/aggregate.jl +++ b/src/aggregate.jl @@ -42,8 +42,8 @@ end function GraphReferenceMap() return GraphReferenceMap( - Dict{NodeVariableRef,NodeVariableRef}(), - Dict{JuMP.ConstraintRef,JuMP.ConstraintRef}(), + OrderedDict{NodeVariableRef,NodeVariableRef}(), + OrderedDict{JuMP.ConstraintRef,JuMP.ConstraintRef}(), ) end function Base.merge!(ref_map1::GraphReferenceMap, ref_map2::GraphReferenceMap) diff --git a/src/backends/moi_backend.jl b/src/backends/moi_backend.jl index 7b1a4b1..060f05a 100644 --- a/src/backends/moi_backend.jl +++ b/src/backends/moi_backend.jl @@ -601,14 +601,13 @@ Aggregate the moi backends from each subgraph within `graph` to create a single # function _copy_subgraph_backends!(graph::OptiGraph) function _copy_subgraph_backends!(backend::GraphMOIBackend) graph = backend.optigraph - for subgraph in get_subgraphs(graph) + for subgraph in local_subgraphs(graph) _copy_subgraph_nodes!(backend, subgraph) _copy_subgraph_edges!(backend, subgraph) - # TODO: pass non-objective graph attributes (use an MOI Filter?) + # TODO: pass non-objective graph attributes we may need (use an MOI Filter?) end end -#function _copy_subgraph_nodes!(graph::OptiGraph, subgraph::OptiGraph) function _copy_subgraph_nodes!(backend::GraphMOIBackend, subgraph::OptiGraph) graph = backend.optigraph for node in all_nodes(subgraph) # NOTE: hits ALL NODES in the subgraph. @@ -619,7 +618,6 @@ function _copy_subgraph_nodes!(backend::GraphMOIBackend, subgraph::OptiGraph) end end -# function _copy_subgraph_edges!(graph::OptiGraph, subgraph::OptiGraph) function _copy_subgraph_edges!(backend::GraphMOIBackend, subgraph::OptiGraph) graph = backend.optigraph for edge in all_edges(subgraph) @@ -630,7 +628,6 @@ function _copy_subgraph_edges!(backend::GraphMOIBackend, subgraph::OptiGraph) end end -# function _append_node_to_backend!(graph::OptiGraph, node::OptiNode) function _append_node_to_backend!(backend::GraphMOIBackend, node::OptiNode) graph = backend.optigraph _add_node(backend, node) diff --git a/src/graph_functions/topology.jl b/src/graph_functions/topology.jl index f1eec36..a34aa48 100644 --- a/src/graph_functions/topology.jl +++ b/src/graph_functions/topology.jl @@ -36,7 +36,7 @@ function incident_edges(hyper::HyperGraphProjection, nodes::Vector{OptiNode}) hypernodes = Base.getindex.(Ref(hyper), nodes) #hypernodes = get_mapped_elements(hyper, nodes) inc_edges = GOI.incident_edges(hyper.projected_graph, hypernodes) - return get_mapped_elements(hyper, inc_edges) #getindex.(Ref(hyper), incidentedges)) + return get_mapped_elements(hyper, inc_edges) end function incident_edges(hyper::HyperGraphProjection, node::OptiNode) @@ -51,7 +51,7 @@ Retrieve induced edges to a set of optinodes. function induced_edges(hyper::HyperGraphProjection, nodes::Vector{OptiNode}) hypernodes = get_mapped_elements(hyper, nodes) induced = GOI.induced_edges(hyper.projected_graph, hypernodes) - optiedges = get_mapped_elements(hyper, induced) + optiedges = convert(Vector{OptiEdge}, get_mapped_elements(hyper, induced)) return optiedges end @@ -104,7 +104,7 @@ end Return the optinodes within `distance` of the given `nodes` in the optigraph `graph`. """ -function neighborhood(hyper::HyperGraphProjection, nodes::Vector{OptiNode}, distance::Int64) +function Graphs.neighborhood(hyper::HyperGraphProjection, nodes::Vector{OptiNode}, distance::Int64) vertices = get_mapped_elements(hyper, nodes) new_nodes = GOI.neighborhood(hyper.projected_graph, vertices, distance) return get_mapped_elements(hyper, new_nodes) #getindex.(Ref(hyper), new_nodes) @@ -118,7 +118,11 @@ The returned subgraph contains the expanded neighborhood within `distance` of th """ function expand(hyper::HyperGraphProjection, subgraph::OptiGraph, distance::Int64) nodes = all_nodes(subgraph) - new_optinodes = neighborhood(hyper, nodes, distance) + return expand(hyper, nodes, distance) +end + +function expand(hyper::HyperGraphProjection, nodes::Vector{OptiNode}, distance::Int64) + new_optinodes = Graphs.neighborhood(hyper, nodes, distance) new_optiedges = induced_edges(hyper, new_optinodes) expanded_subgraph = assemble_optigraph(new_optinodes, new_optiedges) return expanded_subgraph diff --git a/src/optigraph.jl b/src/optigraph.jl index 86edba5..44e4f94 100644 --- a/src/optigraph.jl +++ b/src/optigraph.jl @@ -32,7 +32,7 @@ Base.print(io::IO, graph::OptiGraph) = Base.print(io, Base.string(graph)) Base.show(io::IO, graph::OptiGraph) = Base.print(io, graph) function Base.getindex(graph::OptiGraph, idx::Int) - return graph.optinodes[idx] + return collect(graph.optinodes)[idx] end Base.broadcastable(graph::OptiGraph) = Ref(graph) @@ -134,7 +134,7 @@ function add_node(graph::OptiGraph, node::OptiNode) end function get_node(graph::OptiGraph, idx::Int) - return graph.optinodes[idx] + return collect(graph.optinodes)[idx] end """ @@ -234,6 +234,14 @@ function get_edge(graph::OptiGraph, nodes::Set{OptiNode}) return graph.optiedge_map[nodes] end +function get_edge(graph::OptiGraph, nodes::OptiNode...) + return get_edge(graph, Set(nodes)) +end + +function get_edge_by_index(graph::OptiGraph, idx::Int64) + return collect(graph.optiedges)[idx] +end + function local_edges(graph::OptiGraph) return collect(graph.optiedges) end @@ -527,18 +535,6 @@ function JuMP.value(graph::OptiGraph, expr::GenericNonlinearExpr; result::Int = end end -### Expression values - -# function JuMP.value(var_value::Function, ex::GenericAffExpr{T,V}) where {T,V} -# S = Base.promote_op(var_value, V) -# U = Base.promote_op(*, T, S) -# ret = convert(U, ex.constant) -# for (var, coef) in ex.terms -# ret += coef * var_value(var) -# end -# return ret -# end - ### Constraints function JuMP.add_constraint( diff --git a/test/test_aggregate.jl b/test/test_aggregate.jl index a8b1003..2710b99 100644 --- a/test/test_aggregate.jl +++ b/test/test_aggregate.jl @@ -34,10 +34,10 @@ function test_aggregate_solution() graph = _create_test_optigraph() agg_node, ref_map = aggregate(graph) - set_optimizer(graph, Ipopt.Optimizer) + set_optimizer(graph, optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0)) optimize!(graph) - agg_graph = set_optimizer(agg_node, Ipopt.Optimizer) + agg_graph = set_optimizer(agg_node, optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0)) optimize!(agg_graph) @test objective_value(agg_graph) == objective_value(graph) @@ -51,11 +51,12 @@ function test_set_model() n1 = add_node(graph) set_jump_model(n1, m) - set_optimizer(m, Ipopt.Optimizer) + set_optimizer(m, optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0)) optimize!(m) - set_optimizer(n1, Ipopt.Optimizer) - optimize!(n1) + set_optimizer(graph, optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0)) + set_to_node_objectives(graph) + optimize!(graph) @test objective_value(m) == objective_value(graph, n1) @test value.(all_variables(m)) == value.(graph, all_variables(n1)) diff --git a/test/test_optigraph.jl b/test/test_optigraph.jl index effe4bd..deb9b95 100644 --- a/test/test_optigraph.jl +++ b/test/test_optigraph.jl @@ -3,6 +3,7 @@ module TestOptiGraph using Plasmo using Ipopt using HiGHS +using Suppressor using Test function test_simple_graph() @@ -15,7 +16,7 @@ function test_simple_graph() @objective(graph, Max, nodes[1][:x] + 2*nodes[2][:x]) set_optimizer(graph, HiGHS.Optimizer) - optimize!(graph) + @suppress optimize!(graph) @test objective_value(graph) == 7.0 @test value(nodes[1][:x]) == 1.0 @@ -173,8 +174,8 @@ function test_subgraphs() sg1 = _create_test_nonlinear_optigraph() sg2 = _create_test_nonlinear_optigraph() - add_subgraph!(graph, sg1) - add_subgraph!(graph, sg2) + add_subgraph(graph, sg1) + add_subgraph(graph, sg2) n11,n12,n13,n14 = all_nodes(sg1) n21,n22,n23,n24 = all_nodes(sg2) @@ -285,25 +286,25 @@ function test_variable_constraints() # fix variables JuMP.fix(n1[:x], 1; force=true) - optimize!(graph) + @suppress optimize!(graph) @test value(n1[:x]) == 1 JuMP.fix(n1[:x], 2) - optimize!(graph) + @suppress optimize!(graph) @test value(n1[:x]) == 2 JuMP.fix(n1[:x], 0) - optimize!(graph) + @suppress optimize!(graph) @test value(n1[:x]) == 0 # integer and binary set_binary(n1[:x]) @test is_binary(n1[:x]) == true - optimize!(graph) + @suppress optimize!(graph) set_integer(n2[:x]) @test is_integer(n2[:x]) == true - optimize!(graph) + @suppress optimize!(graph) end function test_nonlinear_operators() diff --git a/test/test_topology_functions.jl b/test/test_topology_functions.jl index 5acaf21..99b14e0 100644 --- a/test/test_topology_functions.jl +++ b/test/test_topology_functions.jl @@ -4,6 +4,36 @@ using Plasmo using Graphs using Test +function _create_test_optigraph() + graph = OptiGraph() + @optinode(graph, nodes[1:4]) + + #node 1 + @variable(nodes[1], 0 <= x <= 2) + @variable(nodes[1], 0 <= y <= 3) + @constraint(nodes[1], 0 <= x + y <= 4) + + #node 2 + @variable(nodes[2], x >= 1) + @variable(nodes[2], 0 <= y <= 5) + @constraint(nodes[2], exp(x) + y <= 7) + + #node 3 + @variable(nodes[3], x >= 0) + @variable(nodes[3], y >= 0) + @constraint(nodes[3], x + y == 2) + + #node 4 + @variable(nodes[4], 0 <= x <= 1) + @variable(nodes[4], y >= 0) + @constraint(nodes[4], x + y <= 3) + + @linkconstraint(graph, nodes[1][:x] == nodes[2][:x]) + @linkconstraint(graph, nodes[2][:y] == nodes[3][:x]) + @linkconstraint(graph, nodes[3][:x] == nodes[4][:x]) + return graph +end + function _create_chain_optigraph() graph = OptiGraph() @optinode(graph, nodes[1:100]) @@ -16,106 +46,82 @@ function _create_chain_optigraph() return graph end -# function _create_test_optigraph_w_subgraphs() -# graph = _create_test_optigraph() -# node_vectors = [ -# graph.optinodes[1:20], -# graph.optinodes[21:40], -# graph.optinodes[41:60], -# graph.optinodes[61:80], -# graph.optinodes[81:100], -# ] -# partition = Partition(graph, node_vectors) -# apply_partition!(graph, partition) -# return graph -# end - - -# function test_hypergraph_functions() -# graph = _create_test_optigraph() - -# n1 = optinode(graph, 1) -# n2 = optinode(graph, 2) -# n3 = optinode(graph, 3) - -# @test LightGraphs.all_neighbors(graph, n1) == [n2] -# @test LightGraphs.all_neighbors(graph, n2) == [n1, n3] - -# #3 nodes and 2 edges -# induced1 = LightGraphs.induced_subgraph(graph, [n1, n2, n3]) -# @test num_all_nodes(induced1) == 3 -# @test num_all_edges(induced1) == 2 - -# #2 nodes and no edges -# induced2 = LightGraphs.induced_subgraph(graph, [n1, n3]) -# @test num_all_nodes(induced2) == 2 -# @test num_all_edges(induced2) == 0 - -# e1 = optiedge(graph, 1) -# e2 = optiedge(graph, 2) -# incident_es1 = incident_edges(graph, n2) -# @test incident_es1 == [e1, e2] - -# incident_es2 = incident_edges(graph, [n1, n2, n3]) -# n4 = optinode(graph, 4) -# e3 = optiedge(graph, n3, n4) -# @test length(incident_es2) == 1 -# @test incident_es2[1] == e3 - -# induced_es = induced_edges(graph, [n1, n2, n3]) -# @test induced_es == [e1, e2] - -# neigh = Plasmo.neighborhood(graph, [n2, n3], 1) -# @test Set(neigh) == Set([n1, n2, n3, n4]) -# end - -# function test_subgraph_functions() -# graph = _create_test_optigraph_w_subgraphs() -# sub1 = subgraph(graph, 1) -# @test num_all_nodes(sub1) == 20 - -# ex_sub1 = expand(graph, sub1, 1) -# @test num_all_nodes(ex_sub1) == 21 - -# ex_sub2 = expand(graph, sub1, 10) -# @test num_all_nodes(ex_sub2) == 30 - -# @test length(Plasmo.cross_edges(graph)) == 4 - -# main_node = add_node!(graph) -# @variable(main_node, z >= 0) -# @linkconstraint(graph, main_node[:z] == optinode(sub1, 1)[:x]) - -# @test length(Plasmo.hierarchical_edges(graph)) == 1 -# @test length(Plasmo.cross_edges(graph)) == 4 -# @test num_linkconstraints(graph) == 5 -# end - -# function test_partition_functions() -# graph = _create_test_optigraph() -# node_vectors = [ -# graph.optinodes[1:20], -# graph.optinodes[21:40], -# graph.optinodes[41:60], -# graph.optinodes[61:80], -# graph.optinodes[81:100], -# ] - -# identified_edges = Plasmo.identify_edges(graph, node_vectors) -# @test length(identified_edges[1]) == 5 #5 partitions -# @test length(identified_edges[2]) == 4 #4 linking edges - -# edge_vectors = [ -# graph.optiedges[1:20], -# graph.optiedges[21:40], -# graph.optiedges[41:60], -# graph.optiedges[61:80], -# graph.optiedges[81:99], -# ] -# identified_nodes = Plasmo.identify_nodes(graph, edge_vectors) -# @test length(identified_nodes[1]) == 5 -# @test length(identified_nodes[2]) == 4 -# end +function test_hypergraph_functions() + graph = _create_test_optigraph() + + n1 = get_node(graph, 1) + n2 = get_node(graph, 2) + n3 = get_node(graph, 3) + + projection = hyper_projection(graph) + + @test Graphs.all_neighbors(projection, n1) == [n2] + @test Graphs.all_neighbors(projection, n2) == [n1, n3] + + # 3 nodes and 2 edges + induced_graph_1 = Graphs.induced_subgraph(projection, [n1, n2, n3]) + @test num_nodes(induced_graph_1) == 3 + @test num_edges(induced_graph_1) == 2 + + #2 nodes and no edges + induced_graph_2 = Graphs.induced_subgraph(projection, [n1, n3]) + @test num_nodes(induced_graph_2) == 2 + @test num_edges(induced_graph_2) == 0 + + e1 = get_edge_by_index(graph, 1) + e2 = get_edge_by_index(graph, 2) + incident_edges_1 = incident_edges(projection, n2) + @test incident_edges_1 == [e1, e2] + + incident_edges_2 = incident_edges(projection, [n1, n2, n3]) + n4 = get_node(graph, 4) + e3 = get_edge(graph, n3, n4) + @test length(incident_edges_2) == 1 + @test incident_edges_2[1] == e3 + + induced_edges_1 = induced_edges(projection, [n1, n2, n3]) + @test induced_edges_1 == [e1, e2] + + neigh = Graphs.neighborhood(projection, [n2, n3], 1) + @test Set(neigh) == Set([n1, n2, n3, n4]) + + expanded_graph_1 = expand(projection, [n1,n2], 1) + @test all_nodes(expanded_graph_1) == [n1,n2,n3] + + expanded_graph_2 = expand(projection, [n1,n2], 2) + @test all_nodes(expanded_graph_2) == [n1,n2,n3,n4] +end + +function test_identify_functions() + graph = _create_chain_optigraph() + + graph_nodes = all_nodes(graph) + + node_vectors = [ + graph_nodes[1:20], + graph_nodes[21:40], + graph_nodes[41:60], + graph_nodes[61:80], + graph_nodes[81:100], + ] + projection = hyper_projection(graph) + + identified_edges = identify_edges(projection, node_vectors) + @test length(identified_edges[1]) == 5 #5 partitions + @test length(identified_edges[2]) == 4 #4 linking edges + + graph_edges = all_edges(graph) + edge_vectors = [ + graph_edges[1:20], + graph_edges[21:40], + graph_edges[41:60], + graph_edges[61:80], + graph_edges[81:99], + ] + identified_nodes = identify_nodes(projection, edge_vectors) + @test length(identified_nodes[1]) == 5 + @test length(identified_nodes[2]) == 4 +end function run_tests() for name in names(@__MODULE__; all=true)