From e8d6c398ea7a138b5e29e90d8bc10f11bb6b8d79 Mon Sep 17 00:00:00 2001 From: jalving Date: Tue, 2 Jan 2024 12:09:38 -0800 Subject: [PATCH] got edge constraints working --- src/backend.jl | 224 +++++++++++++++++----------------- src/dev.jl | 29 +---- src/notes.txt | 14 ++- src/optigraph.jl | 308 ++++++++++++++++++++++++++--------------------- 4 files changed, 296 insertions(+), 279 deletions(-) diff --git a/src/backend.jl b/src/backend.jl index 5a8a95e..d3dae3b 100644 --- a/src/backend.jl +++ b/src/backend.jl @@ -1,7 +1,9 @@ -# maps node/edge variable and constraints to the optigraph backend +""" + Mapping of node variables and constraints to the optigraph backend. +""" mutable struct NodeToGraphMap - var_map::OrderedDict{NodeVariableRef,MOI.VariableIndex} #node variable to optimizer - con_map::OrderedDict{ConstraintRef,MOI.ConstraintIndex} #node constraint to optimizer + var_map::OrderedDict{NodeVariableRef,MOI.VariableIndex} + con_map::OrderedDict{ConstraintRef,MOI.ConstraintIndex} end function NodeToGraphMap() return NodeToGraphMap( @@ -9,44 +11,54 @@ function NodeToGraphMap() OrderedDict{ConstraintRef,MOI.ConstraintIndex}(), ) end + function Base.setindex!(n2g_map::NodeToGraphMap, idx::MOI.VariableIndex, vref::NodeVariableRef) n2g_map.var_map[vref] = idx return end + function Base.getindex(n2g_map::NodeToGraphMap, vref::NodeVariableRef) return n2g_map.var_map[vref] end + function Base.setindex!(n2g_map::NodeToGraphMap, idx::MOI.ConstraintIndex, cref::ConstraintRef) n2g_map.con_map[cref] = idx return end + function Base.getindex(n2g_map::NodeToGraphMap, cref::ConstraintRef) return n2g_map.con_map[cref] end -# maps optigraph backend to node/edge +""" + Mapping of graph backend variable and constraint indices to + node variables and constraints. +""" mutable struct GraphToNodeMap - var_map::OrderedDict{MOI.VariableIndex,NodeVariableRef} #node variable to optimizer - con_map::OrderedDict{MOI.ConstraintIndex,ConstraintRef} #node constraint to optimizer + var_map::OrderedDict{MOI.VariableIndex,NodeVariableRef} + con_map::OrderedDict{MOI.ConstraintIndex,ConstraintRef} end - function GraphToNodeMap() return GraphToNodeMap( OrderedDict{MOI.VariableIndex,NodeVariableRef}(), OrderedDict{MOI.ConstraintIndex,ConstraintRef}(), ) end + function Base.setindex!(g2n_map::GraphToNodeMap, vref::NodeVariableRef, idx::MOI.VariableIndex) g2n_map.var_map[idx] = vref return end + function Base.getindex(g2n_map::GraphToNodeMap, idx::MOI.VariableIndex) return g2n_map.var_map[idx] end + function Base.setindex!(g2n_map::GraphToNodeMap, cref::ConstraintRef, idx::MOI.ConstraintIndex) g2n_map.con_map[idx] = cref return end + function Base.getindex(g2n_map::GraphToNodeMap, idx::MOI.ConstraintIndex) return g2n_map.con_map[idx] end @@ -77,7 +89,7 @@ function GraphMOIBackend(optigraph::AbstractOptiGraph) optigraph, cache, NodeToGraphMap(), - GraphToNodeMap(), + GraphToNodeMap() ) end @@ -85,9 +97,9 @@ function JuMP.backend(gb::GraphMOIBackend) return gb.moi_backend end -# function MOI.get(graph_backend::GraphMOIBackend, attr::MOI.AnyAttribute) -# return MOI.get(graph_backend.optimizer, attr) -# end +function MOI.get(gb::GraphMOIBackend, attr::MOI.AnyAttribute) + return MOI.get(gb.moi_backend, attr) +end function MOI.get(gb::GraphMOIBackend, attr::MOI.AnyAttribute, ref::ConstraintRef) graph_index = gb.node_to_graph_map[ref] @@ -100,7 +112,7 @@ end ### Variables -# copied from... +# TODO: attribute where we copied this from in JuMP function _moi_constrain_node_variable( gb::GraphMOIBackend, index, @@ -152,13 +164,10 @@ function _moi_add_node_variable( node::OptiNode, v::JuMP.AbstractVariable ) - # add variable to source graph + # get node index and create variable reference variable_index = next_variable_index(node) vref = NodeVariableRef(node, variable_index) - # graph_var_index = _add_variable_to_backend(graph_backend(node), vref) - # _moi_constrain_node_variable(graph_backend(node), graph_var_index, v.info, Float64) - - # add variable to all other containing optigraphs + # add variable to all containing optigraphs for graph in containing_optigraphs(node) graph_var_index = _add_variable_to_backend(graph_backend(graph), vref) _moi_constrain_node_variable( @@ -175,33 +184,14 @@ function _add_variable_to_backend( graph_backend::GraphMOIBackend, vref::NodeVariableRef ) - graph_index = MOI.add_variable(graph_backend.moi_backend) - graph_backend.node_to_graph_map[vref] = graph_index - graph_backend.graph_to_node_map[graph_index] = vref - return graph_index + graph_var_index = MOI.add_variable(graph_backend.moi_backend) + graph_backend.node_to_graph_map[vref] = graph_var_index + graph_backend.graph_to_node_map[graph_var_index] = vref + return graph_var_index end ### Node Constraints -# copied from: https://github.com/jump-dev/JuMP.jl/blob/0df25a9185ceede762af533bc965c9374c97450c/src/constraints.jl#L673 -function _moi_add_constraint( - model::MOI.ModelLike, - f::F, - s::S, -) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} - if !MOI.supports_constraint(model, F, S) - error( - "Constraints of type $(F)-in-$(S) are not supported by the " * - "solver.\n\nIf you expected the solver to support your problem, " * - "you may have an error in your formulation. Otherwise, consider " * - "using a different solver.\n\nThe list of available solvers, " * - "along with the problem types they support, is available at " * - "https://jump.dev/JuMP.jl/stable/installation/#Supported-solvers.", - ) - end - return MOI.add_constraint(model, f, s) -end - function next_constraint_index( node::OptiNode, ::Type{F}, @@ -234,10 +224,10 @@ function _add_node_constraint_to_backend( func::F, set::S ) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} - graph_index = MOI.add_constraint(graph_backend.moi_backend, func, set) - graph_backend.node_to_graph_map[cref] = graph_index - graph_backend.graph_to_node_map[graph_index] = cref - return graph_index + graph_con_index = MOI.add_constraint(graph_backend.moi_backend, func, set) + graph_backend.node_to_graph_map[cref] = graph_con_index + graph_backend.graph_to_node_map[graph_con_index] = cref + return graph_con_index end ### Edge Constraints @@ -251,37 +241,32 @@ function next_constraint_index( return MOI.ConstraintIndex{F,S}(index + 1) end -# #Add a LinkConstraint to the MOI backend. This is used as part of _aggregate_backends! -# function _add_link_constraint!(id::Symbol, dest::MOI.ModelLike, link::LinkConstraint) -# jump_func = JuMP.jump_function(link) -# moi_func = JuMP.moi_function(link) -# for (i, term) in enumerate(JuMP.linear_terms(jump_func)) -# coeff = term[1] -# var = term[2] - -# src = JuMP.backend(optinode(var)) -# idx_map = src.optimizers[id].node_to_optimizer_map - -# var_idx = JuMP.index(var) -# dest_idx = idx_map[var_idx] - -# moi_func.terms[i] = MOI.ScalarAffineTerm{Float64}(coeff, dest_idx) -# end -# moi_set = JuMP.moi_set(link) -# constraint_index = MOI.add_constraint(dest, moi_func, moi_set) -# return constraint_index -# end - -function _has_var_idx(graph_backend::GraphMOIBackend, var::NodeVariableRef) - return haskey(graph_backend.node_to_graph_map.var_map, var) +function _update_moi_func!( + backend::GraphMOIBackend, + moi_func::MOI.ScalarAffineFunction, + jump_func::JuMP.GenericAffExpr +) + for (i, term) in enumerate(JuMP.linear_terms(jump_func)) + coeff = term[1] + var = term[2] + backend_var_idx = backend.node_to_graph_map[var] + moi_func.terms[i] = MOI.ScalarAffineTerm{Float64}(coeff, backend_var_idx) + end end -function _set_var_idx(graph_backend::GraphMOIBackend, var::NodeVariableRef) - n_vars = MOI.get(graph_backend.moi_backend, MOI.NumberOfVariables()) - graph_backend.node_to_graph_map[var] = MOI.VariableIndex(n_vars + 1) - return +function _add_backend_variables( + backend::GraphMOIBackend, + jump_func::JuMP.GenericAffExpr +) + vars = [term[2] for term in JuMP.linear_terms(jump_func)] + vars_to_add = setdiff(vars, keys(backend.node_to_graph_map.var_map)) + for var in vars_to_add + _add_variable_to_backend(backend, var) + end end +# TODO: QuadExpr + function _moi_add_edge_constraint( edge::OptiEdge, con::JuMP.AbstractConstraint @@ -299,20 +284,18 @@ function _moi_add_edge_constraint( )::MOI.ConstraintIndex{typeof(moi_func),typeof(moi_set)} cref = ConstraintRef(edge, constraint_index, JuMP.shape(con)) - # TODO: figure out edges between subgraphs + # update graph backends for graph in containing_optigraphs(edge) - # update moi_func with actual indices - for (i, term) in enumerate(JuMP.linear_terms(jump_func)) - coeff = term[1] - var = term[2] - if !(_has_var_idx(backend(graph), var)) - _set_var_idx(backend(graph), var) - end - backend_var_idx = graph.backend.node_to_graph_map[var] - moi_func.terms[i] = MOI.ScalarAffineTerm{Float64}(coeff, backend_var_idx) - end + # add backend variables if linking across optigraphs + _add_backend_variables(graph_backend(graph), jump_func) + + # update the moi function variable indices + _update_moi_func!(graph_backend(graph), moi_func, jump_func) + + # add the constraint to the backend _add_edge_constraint_to_backend(graph_backend(graph), cref, moi_func, moi_set) end + return cref end @@ -323,43 +306,64 @@ function _add_edge_constraint_to_backend( set::S ) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} graph_con_index = MOI.add_constraint(graph_backend.moi_backend, func, set) - graph_backend.edge_to_graph_map[cref] = graph_con_index - graph_backend.graph_to_edge_map[graph_con_index] = cref - return graph_index + graph_backend.node_to_graph_map[cref] = graph_con_index + graph_backend.graph_to_node_map[graph_con_index] = cref + return graph_con_index end -### TODO +### JuMP interoperability -function MOI.optimize!(graph_backend::GraphMOIBackend) - # # TODO: support modes - # # if graph_backend.mode == MOIU.AUTOMATIC && graph_backend.state == MOIU.EMPTY_OPTIMIZER - # # normally the `attach_optimizer` gets called in a higher scope, but we can attach here for testing purposes - # if MOIU.state(graph_backend) == MOIU.EMPTY_OPTIMIZER - # MOIU.attach_optimizer(graph_backend) - # else - # @assert MOIU.state(graph_backend) == MOIU.ATTACHED_OPTIMIZER - # end - MOI.optimize!(graph_backend.moi_backend) - return nothing +# copied from: https://github.com/jump-dev/JuMP.jl/blob/0df25a9185ceede762af533bc965c9374c97450c/src/constraints.jl#L673 +function _moi_add_constraint( + model::MOI.ModelLike, + f::F, + s::S, +) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} + if !MOI.supports_constraint(model, F, S) + error( + "Constraints of type $(F)-in-$(S) are not supported by the " * + "solver.\n\nIf you expected the solver to support your problem, " * + "you may have an error in your formulation. Otherwise, consider " * + "using a different solver.\n\nThe list of available solvers, " * + "along with the problem types they support, is available at " * + "https://jump.dev/JuMP.jl/stable/installation/#Supported-solvers.", + ) + end + return MOI.add_constraint(model, f, s) end +### TODO + +# function MOI.optimize!(graph_backend::GraphMOIBackend) +# # # TODO: support modes +# # # if graph_backend.mode == MOIU.AUTOMATIC && graph_backend.state == MOIU.EMPTY_OPTIMIZER +# # # normally the `attach_optimizer` gets called in a higher scope, but we can attach here for testing purposes +# # if MOIU.state(graph_backend) == MOIU.EMPTY_OPTIMIZER +# # MOIU.attach_optimizer(graph_backend) +# # else +# # @assert MOIU.state(graph_backend) == MOIU.ATTACHED_OPTIMIZER +# # end +# MOI.optimize!(graph_backend.moi_backend) +# return nothing +# end + ### Helpful utilities -function _swap_indices(variable::MOI.VariableIndex, idxmap::MOIU.IndexMap) - return idxmap[variable] -end +# function _swap_indices(variable::MOI.VariableIndex, idxmap::MOIU.IndexMap) +# return idxmap[variable] +# end -function _swap_indices(func::MOI.ScalarAffineFunction, idxmap::MOIU.IndexMap) - new_func = copy(func) - terms = new_func.terms - for i in 1:length(terms) - coeff = terms[i].coefficient - var_idx = terms[i].variable - terms[i] = MOI.ScalarAffineTerm{Float64}(coeff, idxmap[var_idx]) - end - return new_func -end +# function _swap_indices(func::MOI.ScalarAffineFunction, idxmap::MOIU.IndexMap) +# new_func = copy(func) +# terms = new_func.terms +# for i in 1:length(terms) +# coeff = terms[i].coefficient +# var_idx = terms[i].variable +# terms[i] = MOI.ScalarAffineTerm{Float64}(coeff, idxmap[var_idx]) +# end +# return new_func +# end #In MOI this uses lots of `map_indices` magic, but we go for something simple diff --git a/src/dev.jl b/src/dev.jl index 7a0056d..f284c5b 100644 --- a/src/dev.jl +++ b/src/dev.jl @@ -1,39 +1,22 @@ using Plasmo -graph = OptiGraph() +graph = OptiGraph(;label=:g1) n1 = Plasmo.add_node(graph) @variable(n1, x >= 0) @variable(n1, y >= 0) @constraint(n1, ref1, x+y==2) -print(n1[:x]) n2 = Plasmo.add_node(graph) @variable(n2, x >= 1) @variable(n2, y >= 2) @constraint(n2, ref2, x+y==4) -# edge1 = Plasmo.add_edge(graph, n1, n2) -# @constraint(edge1) +edge1 = Plasmo.add_edge(graph, n1, n2) -@linkconstraint(graph, n1[:x] + n2[:x] == 2) +@constraint(edge1, ref3, n1[:x] == n2[:x]) +@objective(graph, Min, n1[:x] + n2[:x]) - -# m = Model() -# @variable(m, x[1:100000]) -# Base.summarysize(m) # 11053087 - - -# using Plasmo -# graph = OptiGraph() -# for i = 1:100000 -# node = Plasmo.add_node(graph) -# @variable(node, x) -# end - -#s = Base.summarysize(graph) -# centralized dict: 29607545 - -# dict on each node: 73543193 - +# TODO +#@linkconstraint(graph, n1[:x] + n2[:x] == 2) diff --git a/src/notes.txt b/src/notes.txt index 99baafb..f527c9d 100644 --- a/src/notes.txt +++ b/src/notes.txt @@ -16,17 +16,19 @@ - `add_subgraph!(graph, sg)` uses the subgraph backend. same as `add_subgraph!(graph, modular=True)`. - links between subgraphs use referenced variables if all one backend, otherwise creates new references -## Nodes and edges are light-weight in memory. -- They are associated with variables and constraints through the GraphBackend - ## Creating JuMP models - It should be possible to obtain a JuMP Model from an optigraph. this performs necessary copy functions - - `model = jump_model(graph)` +- `model = jump_model(graph)` + +## Using hypergraphs +- Looking into HyperGraphs.jl and extend it with anything we already did for orginal Plasmo.jl # Other Notes: -## Multiple dictionaries are not memory efficient -- creating a new dictionary for each node does not scale. We need to keep the amount of node data to an absolute minimum and levarage aggregate data structures where possible +## Memory Efficiency +- creating a new dictionary for each node does not scale. We need to keep the amount of node data containers to an absolute minimum and levarage aggregate data structures where possible +- we store such containers on the node's source graph +- nodes and edges are light-weight in memory. They are associated with variables and constraints through the GraphBackend # OptiGraph Backends diff --git a/src/optigraph.jl b/src/optigraph.jl index 2c685c3..b0c85a2 100644 --- a/src/optigraph.jl +++ b/src/optigraph.jl @@ -2,10 +2,14 @@ abstract type AbstractOptiGraph <: JuMP.AbstractModel end ### OptiNode -# TODO: node index? +struct NodeIndex + value::Int +end + struct OptiNode{GT<:AbstractOptiGraph} <: JuMP.AbstractModel source_graph::GT - label::String + idx::NodeIndex + label::Symbol end function Base.string(node::OptiNode) @@ -14,10 +18,6 @@ end Base.print(io::IO, node::OptiNode) = Base.print(io, Base.string(node)) Base.show(io::IO, node::OptiNode) = Base.print(io, node) -function JuMP.object_dictionary(node::OptiNode) - return node.source_graph.node_obj_dict -end - function Base.setindex!(node::OptiNode, value::Any, name::Symbol) t = (node, name) node.source_graph.node_obj_dict[t] = value @@ -29,27 +29,10 @@ function Base.getindex(node::OptiNode, name::Symbol) return node.source_graph.node_obj_dict[t] end -### Node Variables - -struct NodeVariableRef <: JuMP.AbstractVariableRef - node::OptiNode - index::MOI.VariableIndex -end -Base.broadcastable(vref::NodeVariableRef) = Ref(vref) -function Base.string(vref::NodeVariableRef) - return JuMP.name(vref) -end -Base.print(io::IO, vref::NodeVariableRef) = Base.print(io, Base.string(vref)) -Base.show(io::IO, vref::NodeVariableRef) = Base.print(io, vref) - function graph_backend(node::OptiNode) return graph_backend(node.source_graph) end -function JuMP.backend(node::OptiNode) - return JuMP.backend(node.source_graph.backend) -end - function containing_optigraphs(node::OptiNode) source_graph = node.source_graph graphs = [source_graph] @@ -59,30 +42,77 @@ function containing_optigraphs(node::OptiNode) return graphs end +### OptiNode Extension + +function MOI.get(node::OptiNode, attr::MOI.AbstractConstraintAttribute, ref::ConstraintRef) + return MOI.get(graph_backend(node), attr, ref) +end + +function JuMP.object_dictionary(node::OptiNode) + return node.source_graph.node_obj_dict +end + +function JuMP.backend(node::OptiNode) + return JuMP.backend(graph_backend(node)) +end + +function JuMP.jump_function( + node::OptiNode, + f::MOI.ScalarAffineFunction{C}, +) where {C} + return JuMP.GenericAffExpr{C,NodeVariableRef}(node, f) +end + +function JuMP.num_constraints( + node::OptiNode, + ::Type{F}, + ::Type{S} +)::Int64 where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} + return MOI.get(JuMP.backend(node), MOI.NumberOfConstraints{F,S}()) +end + +### Node Variables + +struct NodeVariableRef <: JuMP.AbstractVariableRef + node::OptiNode + index::MOI.VariableIndex +end + +function Base.string(vref::NodeVariableRef) + return JuMP.name(vref) +end +Base.print(io::IO, vref::NodeVariableRef) = Base.print(io, Base.string(vref)) +Base.show(io::IO, vref::NodeVariableRef) = Base.print(io, vref) +Base.broadcastable(vref::NodeVariableRef) = Ref(vref) + ### OptiEdge struct OptiEdge{GT<:AbstractOptiGraph} <: JuMP.AbstractModel source_graph::GT - label::String + label::Symbol nodes::OrderedSet{OptiNode} end +function Base.string(edge::OptiEdge) + return "$(edge.label)" +end +Base.print(io::IO, edge::OptiEdge) = Base.print(io, Base.string(edge)) +Base.show(io::IO, edge::OptiEdge) = Base.print(io, edge) -# const NodeOrEdge = Union{OptiNode,OptiEdge} +function Base.setindex!(edge::OptiEdge, value::Any, name::Symbol) + t = (edge, name) + edge.source_graph.edge_obj_dict[t] = value + return +end -# struct LinkConstraintRef -# edge::OptiEdge -# index::MOI.ConstraintIndex -# end -# Base.broadcastable(c::LinkConstraintRef) = Ref(c) +function Base.getindex(edge::OptiEdge, name::Symbol) + t = (edge,name) + return edge.source_graph.edge_obj_dict[t] +end function graph_backend(edge::OptiEdge) return graph_backend(edge.source_graph) end -function JuMP.backend(edge::OptiEdge) - return JuMP.backend(edge.source_graph.backend) -end - function containing_optigraphs(edge::OptiEdge) source_graph = edge.source_graph graphs = [source_graph] @@ -92,9 +122,49 @@ function containing_optigraphs(edge::OptiEdge) return graphs end +### OptiEdge Extension + +function MOI.get(edge::OptiEdge, attr::MOI.AbstractConstraintAttribute, ref::ConstraintRef) + return MOI.get(graph_backend(edge), attr, ref) +end + +function JuMP.object_dictionary(edge::OptiEdge) + return edge.source_graph.edge_obj_dict +end + +function JuMP.backend(edge::OptiEdge) + return JuMP.backend(graph_backend(edge)) +end + +function JuMP.jump_function( + edge::OptiEdge, + f::MOI.ScalarAffineFunction{C}, +) where {C} + return JuMP.GenericAffExpr{C,NodeVariableRef}(edge, f) +end + +function JuMP.num_constraints( + edge::OptiEdge, + ::Type{F}, + ::Type{S} +)::Int64 where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} + return MOI.get(JuMP.backend(edge), MOI.NumberOfConstraints{F,S}()) +end + + +# const NodeOrEdge = Union{OptiNode,OptiEdge} + +# struct LinkConstraintRef +# edge::OptiEdge +# index::MOI.ConstraintIndex +# end +# Base.broadcastable(c::LinkConstraintRef) = Ref(c) + + + ### OptiGraph -mutable struct OptiGraph <: AbstractOptiGraph #<: JuMP.AbstractModel +mutable struct OptiGraph <: AbstractOptiGraph # topology optinodes::Vector{OptiNode} #Local optinodes optiedges::Vector{OptiEdge} #Local optiedges @@ -107,12 +177,14 @@ mutable struct OptiGraph <: AbstractOptiGraph #<: JuMP.AbstractModel backend::MOI.ModelLike node_obj_dict::OrderedDict{Tuple{OptiNode,Symbol},Any} # object dictionary for nodes - edge_obj_dict::OrderedDict{Tuple{OptiNode,Symbol},Any} # object dictionary for edges + edge_obj_dict::OrderedDict{Tuple{OptiEdge,Symbol},Any} # object dictionary for edges obj_dict::Dict{Symbol,Any} ext::Dict{Symbol,Any} # extension information + label::Symbol + #Constructor - function OptiGraph() + function OptiGraph(;label::Symbol=Symbol(:g,gensym())) optigraph = new() optigraph.optinodes = Vector{OptiNode}() optigraph.optiedges = Vector{OptiEdge}() @@ -126,6 +198,7 @@ mutable struct OptiGraph <: AbstractOptiGraph #<: JuMP.AbstractModel optigraph.backend = GraphMOIBackend(optigraph) optigraph.obj_dict = Dict{Symbol,Any}() optigraph.ext = Dict{Symbol,Any}() + optigraph.label = label return optigraph end end @@ -147,8 +220,12 @@ Base.show(io::IO, graph::OptiGraph) = Base.print(io, graph) ### Add Node -function add_node(graph::OptiGraph; label::String="n$(length(graph.optinodes) + 1)") - optinode = OptiNode{OptiGraph}(graph, label) +function add_node( + graph::OptiGraph; + label=Symbol(graph.label,Symbol(".n"),length(graph.optinodes)+1) +) + node_index = NodeIndex(length(graph.optinodes)+1) + optinode = OptiNode{OptiGraph}(graph, node_index, label) push!(graph.optinodes, optinode) return optinode end @@ -196,58 +273,13 @@ end ### Constraints -# TODO: figure out if JuMP really needs this level of customization -# Adapted from: https://github.com/jump-dev/JuMP.jl/blob/0df25a9185ceede762af533bc965c9374c97450c/src/aff_expr.jl#L633-L641 -function MOI.ScalarAffineFunction( - a::GenericAffExpr{C,<:NodeVariableRef}, -) where {C} - _assert_isfinite(a) - terms = MOI.ScalarAffineTerm{C}[ - MOI.ScalarAffineTerm(t[1], index(t[2])) for t in linear_terms(a) - ] - return MOI.ScalarAffineFunction(terms, a.constant) -end - -# Adapted from: https://github.com/jump-dev/JuMP.jl/blob/0df25a9185ceede762af533bc965c9374c97450c/src/aff_expr.jl#L706-L719 -function JuMP.GenericAffExpr{C,NodeVariableRef}( - node::OptiNode, - f::MOI.ScalarAffineFunction, -) where {C} - aff = GenericAffExpr{C,NodeVariableRef}(f.constant) - for t in f.terms - JuMP.add_to_expression!( - aff, - t.coefficient, - NodeVariableRef(node, t.variable), - ) - end - return aff -end - ### Node Constraints # NOTE: Using an alias on ConstraintRef{M,C,S} causes issues with dispatching JuMP functions. I'm not sure it is really necessary vs just using ConstraintRef for dispatch. # const NodeConstraintRef = JuMP.ConstraintRef{OptiNode, MOI.ConstraintIndex{F,S} where {F,S}, Shape where Shape <: JuMP.AbstractShape} # const NodeConstraintRef = JuMP.ConstraintRef{OptiNode, MOI.ConstraintIndex} -function JuMP.jump_function( - node::OptiNode, - f::MOI.ScalarAffineFunction{C}, -) where {C} - return JuMP.GenericAffExpr{C,NodeVariableRef}(node, f) -end - -function MOI.get(node::OptiNode, attr::MOI.AbstractConstraintAttribute, ref::ConstraintRef) - return MOI.get(graph_backend(node), attr, ref) -end -function JuMP.num_constraints( - node::OptiNode, - ::Type{F}, - ::Type{S} -)::Int64 where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} - return MOI.get(JuMP.backend(node), MOI.NumberOfConstraints{F,S}()) -end """ JuMP.add_constraint(node::OptiNode, con::JuMP.AbstractConstraint, base_name::String="") @@ -288,76 +320,27 @@ end function add_edge( graph::OptiGraph, nodes::OptiNode...; - label::String="e$(length(graph.optiedges) + 1)" + label=Symbol(graph.label,Symbol(".e"),length(graph.optiedges)+1) ) edge = OptiEdge{OptiGraph}(graph, label, OrderedSet(collect(nodes))) - push!(graph.optiedges, optiedge) + push!(graph.optiedges, edge) return edge end -function JuMP.num_constraints( - edge::OptiEdge, - ::Type{F}, - ::Type{S} -)::Int64 where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} - return MOI.get(JuMP.backend(edge), MOI.NumberOfConstraints{F,S}()) -end - function JuMP.add_constraint( edge::OptiEdge, con::JuMP.AbstractConstraint, name::String="" ) con = JuMP.model_convert(edge, con) # converts coefficient and constant types - cref = _moi_add_edge_constraint(node, con) - # TODO: set name + cref = _moi_add_edge_constraint(edge, con) + # TODO: set constraint name return cref end -function add_link_constraint( - graph::OptiGraph, con::JuMP.ScalarConstraint, name::String="" -) - nodes = get_nodes(con) - optiedge = add_optiedge(graph, nodes) - cref = JuMP.add_constraint(optiedge, con, name) - return cref -end +### Objective Function -### private methods - -# function _moi_add_node_variable( -# vref::NodeVariableRef, -# v::JuMP.AbstractVariable -# ) -# # add variable to source graph -# node = vref.node -# graph_var_index = MOI.add_variable(graph_backend(node), vref) -# _moi_constrain_node_variable(JuMP.backend(node), graph_var_index, v.info, Float64) - -# # add variable to all other contained graphs -# for graph in contained_optigraphs(node) -# graph_var_index = MOI.add_variable(graph_backend(graph), vref) -# _moi_constrain_node_variable( -# JuMP.backend(graph.backend), -# graph_var_index, -# v.info, -# Float64 -# ) -# end -# return nothing -# end -# modified based on: https://github.com/jump-dev/JuMP.jl/blob/master/src/variables.jl -# function _moi_add_node_constraint( -# cref::ConstraintRef, -# func::F, -# set::S, -# ) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} -# node = cref.model -# graph_index = MOI.add_constraint(node.source_graph.backend, cref, func, set) -# for graph in contained_optigraphs(node) -# MOI.add_constraint(graph.backend, cref, func, set) -# end -# return nothing -# end + +### JuMP interoperability # copied from: https://github.com/jump-dev/JuMP.jl/blob/f496535f560ea1a6bbf5df19031997bdcc1e4022/src/aff_expr.jl#L651 function _assert_isfinite(a::GenericAffExpr) @@ -375,3 +358,48 @@ function _assert_isfinite(a::GenericAffExpr) return end +# Adapted from: https://github.com/jump-dev/JuMP.jl/blob/0df25a9185ceede762af533bc965c9374c97450c/src/aff_expr.jl#L633-L641 +function MOI.ScalarAffineFunction( + a::GenericAffExpr{C,<:NodeVariableRef}, +) where {C} + _assert_isfinite(a) + terms = MOI.ScalarAffineTerm{C}[ + MOI.ScalarAffineTerm(t[1], index(t[2])) for t in linear_terms(a) + ] + return MOI.ScalarAffineFunction(terms, a.constant) +end + +# Adapted from: https://github.com/jump-dev/JuMP.jl/blob/0df25a9185ceede762af533bc965c9374c97450c/src/aff_expr.jl#L706-L719 +function JuMP.GenericAffExpr{C,NodeVariableRef}( + node::OptiNode, + f::MOI.ScalarAffineFunction, +) where {C} + aff = GenericAffExpr{C,NodeVariableRef}(f.constant) + for t in f.terms + JuMP.add_to_expression!( + aff, + t.coefficient, + NodeVariableRef(node, t.variable), + ) + end + return aff +end + +function JuMP.GenericAffExpr{C,NodeVariableRef}( + edge::OptiEdge, + f::MOI.ScalarAffineFunction, +) where {C} + aff = GenericAffExpr{C,NodeVariableRef}(f.constant) + # build JuMP Affine Expression over edge variables + for t in f.terms + node_var = edge.source_graph.backend.graph_to_node_map[t.variable] + node = node_var.node + node_index = node_var.index + JuMP.add_to_expression!( + aff, + t.coefficient, + NodeVariableRef(node, node_index), + ) + end + return aff +end \ No newline at end of file