From 4d0b6dae434b5d6d3b5ddc345e1980246a1d5b11 Mon Sep 17 00:00:00 2001 From: jalving Date: Sat, 13 Jan 2024 12:33:35 -0800 Subject: [PATCH] got backend aggregation fully working --- src/moi_aggregate.jl | 58 ++++++----- src/moi_graph_backend.jl | 127 +++++++++++------------- src/optiedge.jl | 49 ++++++++-- src/optigraph.jl | 192 ++++++++++++++++++++----------------- src/optimizer_interface.jl | 16 +++- src/optinode.jl | 4 +- 6 files changed, 250 insertions(+), 196 deletions(-) diff --git a/src/moi_aggregate.jl b/src/moi_aggregate.jl index 183f286..309ed63 100644 --- a/src/moi_aggregate.jl +++ b/src/moi_aggregate.jl @@ -5,20 +5,23 @@ Aggregate the moi backends from each subgraph within `graph` to create a single """ function aggregate_backends!(graph::OptiGraph) for subgraph in get_subgraphs(graph) - _aggregate_subgraph_nodes!(graph, subgraph) - # _aggregate_subgraph_edges!(subgraph) + _copy_subgraph_nodes!(graph, subgraph) + _copy_subgraph_edges!(graph, subgraph) + + # TODO: pass non-objective graph attributes (use an MOI Filter?) + end end -function _aggregate_subgraph_nodes!(graph::OptiGraph, subgraph::OptiGraph) +function _copy_subgraph_nodes!(graph::OptiGraph, subgraph::OptiGraph) for node in all_nodes(subgraph) _append_node_to_backend!(graph, node) end end -function _aggregate_subgraph_edges!(graph::OptiGraph, subgraph::OptiGraph) - for edges in all_edges(subgraph) - _append_edge_to_backend!(graph, node) +function _copy_subgraph_edges!(graph::OptiGraph, subgraph::OptiGraph) + for edge in all_edges(subgraph) + _append_edge_to_backend!(graph, edge) end end @@ -38,7 +41,7 @@ function _append_node_to_backend!(graph::OptiGraph, node::OptiNode) variable_constraint_types = filter(all_constraint_types) do (F, S) return MOIU._is_variable_function(F) end - _copy_node_constraints( + _copy_element_constraints( dest, node, index_map, @@ -49,15 +52,12 @@ function _append_node_to_backend!(graph::OptiGraph, node::OptiNode) nonvariable_constraint_types = filter(all_constraint_types) do (F, S) return !MOIU._is_variable_function(F) end - _copy_node_constraints( + _copy_element_constraints( dest, node, index_map, nonvariable_constraint_types ) - - # TODO: pass non-objective graph attributes (use an MOI Filter?) - return end @@ -91,52 +91,53 @@ function _copy_node_variables( return end -function _copy_node_constraints( +function _copy_element_constraints( dest::GraphMOIBackend, - node::OptiNode, + element::OptiElement, index_map::MOIU.IndexMap, constraint_types ) for (F, S) in constraint_types - cis_src = MOI.get(node, MOI.ListOfConstraintIndices{F,S}()) - _copy_node_constraints(dest, node, index_map, cis_src) + cis_src = MOI.get(element, MOI.ListOfConstraintIndices{F,S}()) + _copy_element_constraints(dest, element, index_map, cis_src) end - src = graph_backend(node) + # pass constraint attributes + src = graph_backend(element) for (F, S) in constraint_types MOIU.pass_attributes( dest.moi_backend, src.moi_backend, index_map, - MOI.get(node, MOI.ListOfConstraintIndices{F,S}()), + MOI.get(element, MOI.ListOfConstraintIndices{F,S}()), ) end return end -function _copy_node_constraints( +function _copy_element_constraints( dest::GraphMOIBackend, - node::OptiNode, + element::OptiElement, index_map::MOIU.IndexMap, cis_src::Vector{MOI.ConstraintIndex{F,S}} ) where {F,S} - return _copy_node_constraints(dest, node, index_map, index_map[F, S], cis_src) + return _copy_element_constraints(dest, element, index_map, index_map[F, S], cis_src) end -function _copy_node_constraints( +function _copy_element_constraints( dest::GraphMOIBackend, - node::OptiNode, + element::OptiElement, index_map::MOIU.IndexMap, index_map_FS, cis_src::Vector{<:MOI.ConstraintIndex}, ) - src = graph_backend(node) + src = graph_backend(element) for ci in cis_src f = MOI.get(src.moi_backend, MOI.ConstraintFunction(), ci) s = MOI.get(src.moi_backend, MOI.ConstraintSet(), ci) cref = src.graph_to_element_map[ci] cref in keys(dest.element_to_graph_map.con_map) && return - dest_index = _add_node_constraint_to_backend( + dest_index = _add_element_constraint_to_backend( dest, cref, MOIU.map_indices(index_map, f), @@ -150,10 +151,17 @@ end function _append_edge_to_backend!(graph::OptiGraph, edge::OptiEdge) src = graph_backend(edge) dest = graph_backend(graph) + + # populate index map with node data for src -- > dest index_map = MOIU.IndexMap() + vars = all_variables(edge) + for var in vars + index_map[src.element_to_graph_map[var]] = dest.element_to_graph_map[var] + end + # copy the constraints constraint_types = MOI.get(edge, MOI.ListOfConstraintTypesPresent()) - _copy_edge_constraints( + _copy_element_constraints( dest, edge, index_map, diff --git a/src/moi_graph_backend.jl b/src/moi_graph_backend.jl index 0d6dc08..b69c400 100644 --- a/src/moi_graph_backend.jl +++ b/src/moi_graph_backend.jl @@ -75,8 +75,8 @@ mutable struct GraphMOIBackend <: MOI.AbstractOptimizer # map of variables and constraints on nodes and edges to graph backend indices node_variables::OrderedDict{OptiNode,Vector{MOI.VariableIndex}} - node_constraints::OrderedDict{OptiNode,Vector{MOI.ConstraintIndex}} - edge_constraints::OrderedDict{OptiEdge,Vector{MOI.ConstraintIndex}} + element_constraints::OrderedDict{OptiElement,Vector{MOI.ConstraintIndex}} + #edge_constraints::OrderedDict{OptiEdge,Vector{MOI.ConstraintIndex}} # TODO (maybe): legacy JuMP nonlinear support # nlp_model::MOI.Nonlinear.Model @@ -97,14 +97,18 @@ function GraphMOIBackend(optigraph::AbstractOptiGraph) ElementToGraphMap(), GraphToElementMap(), OrderedDict{OptiNode,Vector{MOI.VariableIndex}}(), - OrderedDict{OptiNode,Vector{MOI.ConstraintIndex}}(), - OrderedDict{OptiEdge,Vector{MOI.ConstraintIndex}}() + OrderedDict{OptiElement,Vector{MOI.ConstraintIndex}}(), + #OrderedDict{OptiEdge,Vector{MOI.ConstraintIndex}}() ) end function add_node(gb::GraphMOIBackend, node::OptiNode) gb.node_variables[node] = MOI.VariableIndex[] - gb.node_constraints[node] = MOI.ConstraintIndex[] + gb.element_constraints[node] = MOI.ConstraintIndex[] +end + +function add_edge(gb::GraphMOIBackend, edge::OptiEdge) + gb.element_constraints[edge] = MOI.ConstraintIndex[] end # JuMP Extension @@ -218,7 +222,7 @@ function _add_variable_to_backend( return graph_var_index end -### Node Constraints +### Constraints function next_constraint_index( node::OptiNode, @@ -250,39 +254,70 @@ function _moi_add_node_constraint( moi_func_graph = _create_graph_moi_func(graph_backend(graph), moi_func, jump_func) # add to optinode backend - _add_node_constraint_to_backend(graph_backend(graph), cref, moi_func_graph, moi_set) + # _add_node_constraint_to_backend(graph_backend(graph), cref, moi_func_graph, moi_set) + _add_element_constraint_to_backend(graph_backend(graph), cref, moi_func_graph, moi_set) + end + return cref +end + +function next_constraint_index( + edge::OptiEdge, + ::Type{F}, + ::Type{S} +)::MOI.ConstraintIndex{F,S} where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} + index = num_constraints(edge, F, S) + return MOI.ConstraintIndex{F,S}(index + 1) +end + +function _moi_add_edge_constraint( + edge::OptiEdge, + con::JuMP.AbstractConstraint +) + # get moi function and set + jump_func = JuMP.jump_function(con) + moi_func = JuMP.moi_function(con) + moi_set = JuMP.moi_set(con) + + # create constraint index and reference + constraint_index = next_constraint_index( + edge, + typeof(moi_func), + typeof(moi_set) + )::MOI.ConstraintIndex{typeof(moi_func),typeof(moi_set)} + cref = ConstraintRef(edge, constraint_index, JuMP.shape(con)) + + # update graph backends + for graph in containing_optigraphs(edge) + # add backend variables if linking across optigraphs + _add_backend_variables(graph_backend(graph), jump_func) + + # update the moi function variable indices + moi_func_graph = _create_graph_moi_func(graph_backend(graph), moi_func, jump_func) + + # add the constraint to the backend + _add_element_constraint_to_backend(graph_backend(graph), cref, moi_func_graph, moi_set) end + return cref end -function _add_node_constraint_to_backend( +function _add_element_constraint_to_backend( graph_backend::GraphMOIBackend, cref::ConstraintRef, func::F, set::S ) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} cref in keys(graph_backend.element_to_graph_map.con_map) && return - if !haskey(graph_backend.node_constraints, cref.model) - graph_backend.node_constraints[cref.model] = MOI.ConstraintIndex[] + if !haskey(graph_backend.element_constraints, cref.model) + graph_backend.element_constraints[cref.model] = MOI.ConstraintIndex[] end graph_con_index = MOI.add_constraint(graph_backend.moi_backend, func, set) graph_backend.element_to_graph_map[cref] = graph_con_index graph_backend.graph_to_element_map[graph_con_index] = cref - push!(graph_backend.node_constraints[cref.model], graph_con_index) + push!(graph_backend.element_constraints[cref.model], graph_con_index) return graph_con_index end -### Edge Constraints - -function next_constraint_index( - edge::OptiEdge, - ::Type{F}, - ::Type{S} -)::MOI.ConstraintIndex{F,S} where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} - index = num_constraints(edge, F, S) - return MOI.ConstraintIndex{F,S}(index + 1) -end - ### Graph MOI Utilities """ @@ -384,9 +419,7 @@ function _update_nonlinear_func!( return end -### add variables to a backend for purpose of creating linking constraints or objectives -# across subgraphs - +# add variables to a backend for linking across subgraphs function _add_backend_variables( backend::GraphMOIBackend, jump_func::JuMP.GenericAffExpr @@ -433,50 +466,6 @@ function _add_backend_variables( return end -function _moi_add_edge_constraint( - edge::OptiEdge, - con::JuMP.AbstractConstraint -) - # get moi function and set - jump_func = JuMP.jump_function(con) - moi_func = JuMP.moi_function(con) - moi_set = JuMP.moi_set(con) - - # create constraint index and reference - constraint_index = next_constraint_index( - edge, - typeof(moi_func), - typeof(moi_set) - )::MOI.ConstraintIndex{typeof(moi_func),typeof(moi_set)} - cref = ConstraintRef(edge, constraint_index, JuMP.shape(con)) - - # update graph backends - for graph in containing_optigraphs(edge) - # add backend variables if linking across optigraphs - _add_backend_variables(graph_backend(graph), jump_func) - - # update the moi function variable indices - moi_func_graph = _create_graph_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_graph, moi_set) - end - - return cref -end - -function _add_edge_constraint_to_backend( - graph_backend::GraphMOIBackend, - cref::ConstraintRef, - func::F, - set::S -) where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} - graph_con_index = MOI.add_constraint(graph_backend.moi_backend, func, set) - graph_backend.element_to_graph_map[cref] = graph_con_index - graph_backend.graph_to_element_map[graph_con_index] = cref - return graph_con_index -end - ### Objective Function function _moi_set_objective_function( diff --git a/src/optiedge.jl b/src/optiedge.jl index bfc2c24..e9b2b38 100644 --- a/src/optiedge.jl +++ b/src/optiedge.jl @@ -69,7 +69,7 @@ function MOI.get(edge::OptiEdge, attr::MOI.AbstractConstraintAttribute, ref::Con end function MOI.get(edge::OptiEdge, attr::MOI.ListOfConstraintTypesPresent) - cons = graph_backend(edge).edge_constraints[edge] + cons = graph_backend(edge).element_constraints[edge] con_types = unique(typeof.(cons)) type_tuple = [(type.parameters[1],type.parameters[2]) for type in con_types] return type_tuple @@ -80,7 +80,7 @@ function MOI.get( attr::MOI.ListOfConstraintIndices{F,S} ) where {F <: MOI.AbstractFunction, S <: MOI.AbstractSet} con_inds = MOI.ConstraintIndex{F,S}[] - for con in graph_backend(edge).edge_constraints[edge] + for con in graph_backend(edge).element_constraints[edge] if (typeof(con).parameters[1] == F && typeof(con).parameters[2] == S) push!(con_inds, con) end @@ -96,16 +96,20 @@ function JuMP.backend(edge::OptiEdge) return JuMP.backend(graph_backend(edge)) end +function JuMP.all_variables(edge::OptiEdge) + gb = graph_backend(edge) + con_refs = getindex.(Ref(gb.graph_to_element_map), gb.element_constraints[edge]) + vars = vcat(_extract_variables.(con_refs)...) + return unique(vars) +end + function JuMP.num_constraints( edge::OptiEdge, ::Type{F}, ::Type{S} )::Int64 where {F<:MOI.AbstractFunction,S<:MOI.AbstractSet} - # TODO: more efficent method to track number of constraints on nodes and edges - g2e = graph_backend(edge).graph_to_element_map - cons = MOI.get(JuMP.backend(edge),MOI.ListOfConstraintIndices{F,S}()) - refs = [g2e[con] for con in cons] - return length(filter((cref) -> cref.model == edge, refs)) + cons = MOI.get(edge, MOI.ListOfConstraintIndices{F,S}()) + return length(cons) end ### Edge Constraints @@ -115,6 +119,35 @@ function JuMP.add_constraint( ) con = JuMP.model_convert(edge, con) cref = _moi_add_edge_constraint(edge, con) - # TODO: set constraint name return cref +end + +### Utilities for querying variables used in constraints + +function _extract_variables(ref::ConstraintRef) + func = JuMP.jump_function(JuMP.constraint_object(ref)) + return _extract_variables(func) +end + +function _extract_variables(func::JuMP.GenericAffExpr) + return collect(keys(func.terms)) +end + +function _extract_variables(func::JuMP.GenericQuadExpr) + quad_vars = vcat([[term[2];term[3]] for term in JuMP.quad_terms(func)]...) + aff_vars = _extract_variables(func.aff) + return union(quad_vars,aff_vars) +end + +function _extract_variables(func::JuMP.GenericNonlinearExpr) + vars = NodeVariableRef[] + for i = 1:length(func.args) + func_arg = func.args[i] + if func_arg isa JuMP.GenericNonlinearExpr + append!(vars, _extract_variables(func_arg)) + elseif typeof(func_arg) == NodeVariableRef + push!(vars, func_arg) + end + end + return vars end \ No newline at end of file diff --git a/src/optigraph.jl b/src/optigraph.jl index f5b7765..9cc7f0d 100644 --- a/src/optigraph.jl +++ b/src/optigraph.jl @@ -1,10 +1,12 @@ +const OptiElement = Union{OptiNode,OptiEdge} + mutable struct OptiGraph <: AbstractOptiGraph label::Symbol # topology: TODO: OrderedSets - optinodes::Vector{OptiNode} # local optinodes - optiedges::Vector{OptiEdge} # local optiedges - subgraphs::Vector{OptiGraph} # local subgraphs + optinodes::OrderedSet{OptiNode} # local optinodes + optiedges::OrderedSet{OptiEdge} # local optiedges + subgraphs::OrderedSet{OptiGraph} # local subgraphs # subgraphs keep a reference to their parent parent_graph::Union{Nothing,OptiGraph} @@ -31,9 +33,9 @@ mutable struct OptiGraph <: AbstractOptiGraph #Constructor function OptiGraph(;name::Symbol=Symbol(:g,gensym())) optigraph = new() - optigraph.optinodes = Vector{OptiNode}() - optigraph.optiedges = Vector{OptiEdge}() - optigraph.subgraphs = Vector{OptiGraph}() + optigraph.optinodes = OrderedSet{OptiNode}() + optigraph.optiedges = OrderedSet{OptiEdge}() + optigraph.subgraphs = OrderedSet{OptiGraph}() optigraph.parent_graph = nothing optigraph.optimizer_graph = optigraph @@ -53,58 +55,51 @@ mutable struct OptiGraph <: AbstractOptiGraph end end -# TODO: numerical precision like JuMP Models do -# JuMP.value_type(::Type{OptiGraph{T}}) where {T} = T - -function graph_backend(graph::OptiGraph) - return graph.backend -end - function Base.string(graph::OptiGraph) return "OptiGraph" * " " * string(graph.label) end Base.print(io::IO, graph::OptiGraph) = Base.print(io, Base.string(graph)) Base.show(io::IO, graph::OptiGraph) = Base.print(io, graph) -### JuMP Extension - -function MOI.get(graph::OptiGraph, attr::MOI.AnyAttribute) - MOI.get(graph_backend(graph), attr) -end +# TODO: numerical precision like JuMP Models do +# JuMP.value_type(::Type{OptiGraph{T}}) where {T} = T -function MOI.set(graph::OptiGraph, attr::MOI.AnyAttribute, args...) - MOI.set(graph_backend(graph), attr, args...) +function graph_backend(graph::OptiGraph) + return graph.backend end -function JuMP.backend(graph::OptiGraph) - return graph_backend(graph).moi_backend -end +### Add subgraph -function JuMP.object_dictionary(graph::OptiGraph) - return graph.obj_dict +function add_subgraph( + graph::OptiGraph; + optimizer_graph=nothing, + name::Symbol=Symbol(:sg,gensym()) +) + subgraph = OptiGraph(; name=name) + subgraph.parent_graph=graph + if optimizer_graph != nothing + if optimizer_graph in traverse_parents(subgraph) + subgraph.optimizer_graph = optimizer_graph + else + error("Invalid optigraph passed as `optimizer_graph`") + end + else + subgraph.optimizer_graph = subgraph + end + push!(graph.subgraphs, subgraph) + return subgraph end -function JuMP.add_nonlinear_operator( - graph::OptiGraph, - dim::Int, - f::Function, - args::Vararg{Function,N}; - name::Symbol = Symbol(f), -) where {N} - nargs = 1 + N - if !(1 <= nargs <= 3) - error( - "Unable to add operator $name: invalid number of functions " * - "provided. Got $nargs, but expected 1 (if function only), 2 (if " * - "function and gradient), or 3 (if function, gradient, and " * - "hesssian provided)", - ) +function traverse_parents(graph::OptiGraph) + parents = OptiGraph[] + if graph.parent_graph != nothing + push!(parents, graph.parent_graph) + append!(parents, traverse_parents(graph.parent_graph)) end - MOI.set(graph, MOI.UserDefinedFunction(name, dim), tuple(f, args...)) - return JuMP.NonlinearOperator(f, name) + return parents end -### Add Node +### Add nodes function add_node( graph::OptiGraph; @@ -117,15 +112,6 @@ function add_node( return optinode end -""" - get_subgraphs(graph::OptiGraph)::Vector{OptiGraph} - -Retrieve the local subgraphs of `graph`. -""" -function get_subgraphs(optigraph::OptiGraph) - return optigraph.subgraphs -end - """ all_nodes(graph::OptiGraph)::Vector{OptiNode} @@ -139,12 +125,7 @@ function all_nodes(graph::OptiGraph) return nodes end -function JuMP.index(graph::OptiGraph, vref::NodeVariableRef) - gb = graph_backend(graph) - return gb.element_to_graph_map[vref] -end - -### Add Edges +### Add edges function add_edge( graph::OptiGraph, @@ -153,46 +134,81 @@ function add_edge( ) edge = OptiEdge{OptiGraph}(graph, label, OrderedSet(collect(nodes))) push!(graph.optiedges, edge) + add_edge(graph.backend, edge) return edge end -### Add subgraph +""" + all_edges(graph::OptiGraph)::Vector{OptiNode} -function add_subgraph( - graph::OptiGraph; - optimizer_graph=nothing, - name::Symbol=Symbol(:sg,gensym()) -) - subgraph = OptiGraph(; name=name) - subgraph.parent_graph=graph - if optimizer_graph != nothing - if optimizer_graph in traverse_parents(subgraph) - subgraph.optimizer_graph = optimizer_graph - else - error("Invalid optigraph passed as `optimizer_graph`") - end - else - subgraph.optimizer_graph = subgraph +Recursively collect all optiedges in `graph` by traversing each of its subgraphs. +""" +function all_edges(graph::OptiGraph) + edges = graph.optiedges + for subgraph in graph.subgraphs + edges = [edges; all_edges(subgraph)] end - push!(graph.subgraphs, subgraph) - return subgraph + return edges end -function traverse_parents(graph::OptiGraph) - parents = OptiGraph[] - if graph.parent_graph != nothing - push!(parents, graph.parent_graph) - append!(parents, traverse_parents(graph.parent_graph)) - end - return parents +### Add subgraphs + +""" + get_subgraphs(graph::OptiGraph)::Vector{OptiGraph} + +Retrieve the local subgraphs of `graph`. +""" +function get_subgraphs(optigraph::OptiGraph) + return optigraph.subgraphs end -function _optimizer_has_subgraphs(graph::OptiGraph) - if all(sg -> sg.optimizer_graph == graph, graph.subgraphs) - return true - else - return false +### MOI Extension + +function MOI.get(graph::OptiGraph, attr::MOI.AnyAttribute) + MOI.get(graph_backend(graph), attr) +end + +function MOI.set(graph::OptiGraph, attr::MOI.AnyAttribute, args...) + MOI.set(graph_backend(graph), attr, args...) +end + +### JuMP Extension + +function JuMP.index(graph::OptiGraph, vref::NodeVariableRef) + gb = graph_backend(graph) + return gb.element_to_graph_map[vref] +end + +function JuMP.value(graph::OptiGraph, nvref::NodeVariableRef; result::Int = 1) + return MOI.get(graph_backend(graph), MOI.VariablePrimal(result), nvref) +end + +function JuMP.backend(graph::OptiGraph) + return graph_backend(graph).moi_backend +end + +function JuMP.object_dictionary(graph::OptiGraph) + return graph.obj_dict +end + +function JuMP.add_nonlinear_operator( + graph::OptiGraph, + dim::Int, + f::Function, + args::Vararg{Function,N}; + name::Symbol = Symbol(f), +) where {N} + nargs = 1 + N + if !(1 <= nargs <= 3) + error( + "Unable to add operator $name: invalid number of functions " * + "provided. Got $nargs, but expected 1 (if function only), 2 (if " * + "function and gradient), or 3 (if function, gradient, and " * + "hesssian provided)", + ) end + MOI.set(graph, MOI.UserDefinedFunction(name, dim), tuple(f, args...)) + return JuMP.NonlinearOperator(f, name) end ### Objective Function diff --git a/src/optimizer_interface.jl b/src/optimizer_interface.jl index a7b32f7..6f2a221 100644 --- a/src/optimizer_interface.jl +++ b/src/optimizer_interface.jl @@ -73,10 +73,9 @@ function JuMP.optimize!( throw(JuMP.NoOptimizer()) end - # TODO: check subgraphs; determine whether we need to aggregate - # if !(_optimizer_has_subgraphs(graph)) - # aggregate_backends!(graph) - # end + if !(_optimizer_has_full_backend(graph)) + aggregate_backends!(graph) + end try MOI.optimize!(JuMP.backend(graph)) @@ -92,4 +91,13 @@ function JuMP.optimize!( end graph.is_model_dirty = false return +end + +# to do; easier check if we already aggregated +function _optimizer_has_full_backend(graph::OptiGraph) + if all(sg -> sg.optimizer_graph == graph, graph.subgraphs) + return true + else + return false + end end \ No newline at end of file diff --git a/src/optinode.jl b/src/optinode.jl index c73b084..f341e1d 100644 --- a/src/optinode.jl +++ b/src/optinode.jl @@ -79,7 +79,7 @@ end # TODO: look into caching constraint types in graph backend versus using unique and filters function MOI.get(node::OptiNode, attr::MOI.ListOfConstraintTypesPresent) - cons = graph_backend(node).node_constraints[node] + cons = graph_backend(node).element_constraints[node] con_types = unique(typeof.(cons)) type_tuple = [(type.parameters[1],type.parameters[2]) for type in con_types] return type_tuple @@ -90,7 +90,7 @@ function MOI.get( attr::MOI.ListOfConstraintIndices{F,S} ) where {F <: MOI.AbstractFunction, S <: MOI.AbstractSet} con_inds = MOI.ConstraintIndex{F,S}[] - for con in graph_backend(node).node_constraints[node] + for con in graph_backend(node).element_constraints[node] if (typeof(con).parameters[1] == F && typeof(con).parameters[2] == S) push!(con_inds, con) end