diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index f74e433..9f7af9f 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.6.7","generation_timestamp":"2024-10-02T20:14:29","documenter_version":"1.7.0"}} \ No newline at end of file +{"documenter":{"julia_version":"1.6.7","generation_timestamp":"2024-11-02T05:04:04","documenter_version":"1.7.0"}} \ No newline at end of file diff --git a/dev/documentation/api_docs/index.html b/dev/documentation/api_docs/index.html index d705e22..4f8208d 100644 --- a/dev/documentation/api_docs/index.html +++ b/dev/documentation/api_docs/index.html @@ -1,85 +1,85 @@ -API Documentation · Plasmo.jl

API Documentation

OptiGraph Methods

Plasmo.OptiGraphType
OptiGraph

The core modeling object of Plasmo.jl. An optigraph represents an optimization model as a set of OptiNode and OptiEdge objects.

source
Plasmo.OptiNodeType
OptiNode{GT<:AbstractOptiGraph} <: AbstractOptiNode

A data structure meant to encapsulate variables, constraints, an objective function, and other model data. An optinode is "lightweight" in the sense that it does not directly contain model data, but instead acts as an interface that maps to a backend where the model data is stored. This avoids the need to generate memory overhead through container structures in cases when a node contains very little model data.

source
Plasmo.OptiEdgeType
OptiEdge{GT<:AbstractOptiGraph} <: AbstractOptiEdge

A data structure meant to encapsulate linking constraints and other model data. An optiedge is "lightweight" in the sense that it does not directly contain model data, but instead acts as an interface that maps to a backend where the model data is stored. This avoids the need to generate memory overhead through container structures in cases when a node contains very little model data.

source
Plasmo.@optinodeMacro
@optinode(optigraph, expr...)

Add a new optinode to optigraph. The expression expr can either be

  • of the form nodename creating a single optinode with the variable name varname
  • of the form nodename[...] or [...] creating a container of optinodes using JuMP Containers
source
Plasmo.@linkconstraintMacro
@linkconstraint(graph::OptiGraph, expr)

Add a linking constraint described by the expression expr.

@linkconstraint(graph::OptiGraph, ref[i=..., j=..., ...], expr)

Add a group of linking constraints described by the expression expr parametrized by i, j, ...

The @linkconstraint macro works the same way as the JuMP.@constraint macro.

source
Plasmo.@nodevariablesMacro
@nodevariables(iterable, expr...)

Call the JuMP.@variable macro for each optinode in a given container

source
Plasmo.set_to_node_objectivesFunction
set_to_node_objectives(graph::OptiGraph)

Set the graph objective to the summation of all of its optinode objectives. Assumes the objective sense is an MOI.MIN_SENSE and adjusts the signs of node objective functions accordingly.

source
Plasmo.graph_backendFunction
graph_backend(graph::OptiGraph)

Return the intermediate backend used to map the optigraph to an optimizer. Plasmo.jl currently only supports a backend to MathOptInterface.jl optimizers, but future versions intend to support GraphOptInterface.jl as a structured backend.

source
graph_backend(node::OptiNode)

Return the GraphMOIBackend that holds the associated node model attributes

source
graph_backend(edge::OptiEdge)

Return the GraphMOIBackend that holds the associated edge model attributes

source
Plasmo.graph_indexFunction
graph_index(ref::RT) where {RT<:Union{NodeVariableRef,ConstraintRef}}

Return the the corresponding variable or constraint index corresponding to a reference.

source
graph_index(
+API Documentation · Plasmo.jl

API Documentation

OptiGraph Methods

Plasmo.OptiGraphType
OptiGraph

The core modeling object of Plasmo.jl. An optigraph represents an optimization model as a set of OptiNode and OptiEdge objects.

source
Plasmo.OptiNodeType
OptiNode{GT<:AbstractOptiGraph} <: AbstractOptiNode

A data structure meant to encapsulate variables, constraints, an objective function, and other model data. An optinode is "lightweight" in the sense that it does not directly contain model data, but instead acts as an interface that maps to a backend where the model data is stored. This avoids the need to generate memory overhead through container structures in cases when a node contains very little model data.

source
Plasmo.OptiEdgeType
OptiEdge{GT<:AbstractOptiGraph} <: AbstractOptiEdge

A data structure meant to encapsulate linking constraints and other model data. An optiedge is "lightweight" in the sense that it does not directly contain model data, but instead acts as an interface that maps to a backend where the model data is stored. This avoids the need to generate memory overhead through container structures in cases when a node contains very little model data.

source
Plasmo.@optinodeMacro
@optinode(optigraph, expr...)

Add a new optinode to optigraph. The expression expr can either be

  • of the form nodename creating a single optinode with the variable name varname
  • of the form nodename[...] or [...] creating a container of optinodes using JuMP Containers
source
Plasmo.@linkconstraintMacro
@linkconstraint(graph::OptiGraph, expr)

Add a linking constraint described by the expression expr.

@linkconstraint(graph::OptiGraph, ref[i=..., j=..., ...], expr)

Add a group of linking constraints described by the expression expr parametrized by i, j, ...

The @linkconstraint macro works the same way as the JuMP.@constraint macro.

source
Plasmo.@nodevariablesMacro
@nodevariables(iterable, expr...)

Call the JuMP.@variable macro for each optinode in a given container

source
Plasmo.set_to_node_objectivesFunction
set_to_node_objectives(graph::OptiGraph)

Set the graph objective to the summation of all of its optinode objectives. Assumes the objective sense is an MOI.MIN_SENSE and accounts for the sense of node objectives accordingly.

Note that building nonlinear objective functions is much slower than linear or quadratic because nonlienar expressions cannot be updated in place.

source
Plasmo.graph_backendFunction
graph_backend(graph::OptiGraph)

Return the intermediate backend used to map the optigraph to an optimizer. Plasmo.jl currently only supports a backend to MathOptInterface.jl optimizers, but future versions intend to support GraphOptInterface.jl as a structured backend.

source
graph_backend(node::OptiNode)

Return the GraphMOIBackend that holds the associated node model attributes

source
graph_backend(edge::OptiEdge)

Return the GraphMOIBackend that holds the associated edge model attributes

source
Plasmo.graph_indexFunction
graph_index(ref::RT) where {RT<:Union{NodeVariableRef,ConstraintRef}}

Return the the corresponding variable or constraint index corresponding to a reference.

source
graph_index(
     backend::GraphMOIBackend, 
     ref::RT
-) where {RT<:Union{NodeVariableRef,ConstraintRef}}

Return the actual variable or constraint index of the backend model that corresponds to the local index of a node or edge.

source
Plasmo.source_graphFunction
source_graph(node::OptiNode)

Return the optigraph that contains the optinode. This is the optigraph that defined said node and stores node object dictionary data.

source
source_graph(edge::OptiEdge)

Return the optigraph that contains the optiedge. This is the optigraph that defined said edge and stores edge object dictionary data.

source
Plasmo.add_subgraphFunction
add_subgraph(graph::OptiGraph; name::Symbol=Symbol(:sg,gensym()))

Create and add a new subgraph to the optigraph graph.

source
add_subgraph(graph::OptiGraph; name::Symbol=Symbol(:sg,gensym()))

Add an existing subgraph to an optigraph. The subgraph cannot already be part of another optigraph. It also should not have nodes that already exist in the optigraph.

source
Plasmo.all_subgraphsFunction
all_subgraphs(graph::OptiGraph)::Vector{OptiGraph}

Retrieve all subgraphs of graph. Includes subgraphs within other subgraphs.

source
Plasmo.num_local_subgraphsFunction
num_local_subgraphs(graph::OptiGraph)::Int

Retrieve the number of local subgraphs in graph. Does not include graph in subgraphs.

source
Plasmo.num_subgraphsFunction
num_subgraphs(graph::OptiGraph)::Int

Retrieve the total number of subgraphs in graph. Include subgraphs within subgraphs.

source
Plasmo.add_nodeFunction
add_node(
+) where {RT<:Union{NodeVariableRef,ConstraintRef}}

Return the actual variable or constraint index of the backend model that corresponds to the local index of a node or edge.

source
Plasmo.source_graphFunction
source_graph(node::OptiNode)

Return the optigraph that contains the optinode. This is the optigraph that defined said node and stores node object dictionary data.

source
source_graph(edge::OptiEdge)

Return the optigraph that contains the optiedge. This is the optigraph that defined said edge and stores edge object dictionary data.

source
Plasmo.add_subgraphFunction
add_subgraph(graph::OptiGraph; name::Symbol=Symbol(:sg,gensym()))

Create and add a new subgraph to the optigraph graph.

source
add_subgraph(graph::OptiGraph; name::Symbol=Symbol(:sg,gensym()))

Add an existing subgraph to an optigraph. The subgraph cannot already be part of another optigraph. It also should not have nodes that already exist in the optigraph.

source
Plasmo.all_subgraphsFunction
all_subgraphs(graph::OptiGraph)::Vector{OptiGraph}

Retrieve all subgraphs of graph. Includes subgraphs within other subgraphs.

source
Plasmo.num_local_subgraphsFunction
num_local_subgraphs(graph::OptiGraph)::Int

Retrieve the number of local subgraphs in graph. Does not include graph in subgraphs.

source
Plasmo.num_subgraphsFunction
num_subgraphs(graph::OptiGraph)::Int

Retrieve the total number of subgraphs in graph. Include subgraphs within subgraphs.

source
Plasmo.add_nodeFunction
add_node(
     graph::OptiGraph; label=Symbol(graph.label, Symbol(".n"), length(graph.optinodes)+1
-)

Add a new optinode to graph. By default, the node label is set to be "n<i+1>" where "i" is the number of nodes in the graph.

source
add_node(graph::OptiGraph, node::OptiNode)

Add an existing optinode (created in another optigraph) to graph. This copies model data from the other graph to the new graph.

source
Plasmo.get_nodeFunction
get_node(graph::OptiGraph, idx::Int)

Retrieve the optinode in graph at the given index.

source
Plasmo.local_nodesFunction
local_nodes(graph::OptiGraph)::Vector{<:OptiNode}

Retrieve the optinodes defined within the optigraph graph. This does not return nodes that exist in subgraphs.

source
Plasmo.all_nodesFunction
all_nodes(graph::OptiGraph)::Vector{<:OptiNode}

Recursively collect all optinodes in graph by traversing each of its subgraphs.

source
Plasmo.collect_nodesFunction
collect_nodes(jump_func::T where T <: JuMP.AbstractJuMPScalar)

Retrieve the optinodes contained in a JuMP expression.

source
Plasmo.num_nodesFunction
num_nodes(graph::OptiGraph)::Int

Return the total number of nodes in graph by recursively checking subgraphs.

source
Plasmo.num_local_variablesFunction
num_local_variables(graph::OptiGraph)

Return the number of local variables in graph. Does not include variables in subgraphs.

source
Plasmo.add_edgeFunction
add_edge(
+)

Add a new optinode to graph. By default, the node label is set to be "n<i+1>" where "i" is the number of nodes in the graph.

source
add_node(graph::OptiGraph, node::OptiNode)

Add an existing optinode (created in another optigraph) to graph. This copies model data from the other graph to the new graph.

source
Plasmo.get_nodeFunction
get_node(graph::OptiGraph, idx::Int)

Retrieve the optinode in graph at the given index.

source
Plasmo.local_nodesFunction
local_nodes(graph::OptiGraph)::Vector{<:OptiNode}

Retrieve the optinodes defined within the optigraph graph. This does not return nodes that exist in subgraphs.

source
Plasmo.all_nodesFunction
all_nodes(graph::OptiGraph)::Vector{<:OptiNode}

Recursively collect all optinodes in graph by traversing each of its subgraphs.

source
Plasmo.collect_nodesFunction
collect_nodes(jump_func::T where T <: JuMP.AbstractJuMPScalar)

Retrieve the optinodes contained in a JuMP expression.

source
Plasmo.num_nodesFunction
num_nodes(graph::OptiGraph)::Int

Return the total number of nodes in graph by recursively checking subgraphs.

source
Plasmo.num_local_variablesFunction
num_local_variables(graph::OptiGraph)

Return the number of local variables in graph. Does not include variables in subgraphs.

source
Plasmo.add_edgeFunction
add_edge(
     graph::OptiGraph,
     nodes::OptiNode...;
     label=Symbol(graph.label, Symbol(".e"), length(graph.optiedges) + 1),
-)

Add a new optiedge to graph that connects nodes. By default, the edge label is set to be "e<i+1>" where "i" is the number of edges in the graph.

source
add_edge(graph::OptiGraph, edge::OptiEdge)

Add an existing optiedge (created in another optigraph) to graph. This copies model data from the other graph to the new graph.

source
Plasmo.get_edgeFunction
get_edge(graph::OptiGraph, nodes::Set{<:OptiNode})

Retrieve the optiedge in graph. that connects nodes.

source
get_edge(graph::OptiGraph, nodes::OptiNode...)

Convenience method. Retrieve the optiedge in graph that connects nodes.

source
Plasmo.get_edge_by_indexFunction
get_edge_by_index(graph::OptiGraph, idx::Int64)

Retrieve the optiedge in graph that corresponds to the given index.

source
Plasmo.has_edgeFunction
has_edge(graph::OptiGraph, nodes::Set{<:OptiNode})

Return whether an edge that connects nodes exists in the graph.

source
Plasmo.local_edgesFunction
local_edges(graph::OptiGraph)

Retrieve the edges that exists in graph. Does not return edges that exist in subgraphs.

source
Plasmo.all_edgesFunction
all_edges(graph::OptiGraph)::Vector{<:OptiNode}

Recursively collect all optiedges in graph by traversing each of its subgraphs.

source
Plasmo.num_edgesFunction
num_edges(graph::OptiGraph)::Int

Return the total number of nodes in graph by recursively checking subgraphs.

source
Plasmo.num_local_link_constraintsFunction
num_local_link_constraints(
+)

Add a new optiedge to graph that connects nodes. By default, the edge label is set to be "e<i+1>" where "i" is the number of edges in the graph.

source
add_edge(graph::OptiGraph, edge::OptiEdge)

Add an existing optiedge (created in another optigraph) to graph. This copies model data from the other graph to the new graph.

source
Plasmo.get_edgeFunction
get_edge(graph::OptiGraph, nodes::Set{<:OptiNode})

Retrieve the optiedge in graph. that connects nodes.

source
get_edge(graph::OptiGraph, nodes::OptiNode...)

Convenience method. Retrieve the optiedge in graph that connects nodes.

source
Plasmo.get_edge_by_indexFunction
get_edge_by_index(graph::OptiGraph, idx::Int64)

Retrieve the optiedge in graph that corresponds to the given index.

source
Plasmo.has_edgeFunction
has_edge(graph::OptiGraph, nodes::Set{<:OptiNode})

Return whether an edge that connects nodes exists in the graph.

source
Plasmo.local_edgesFunction
local_edges(graph::OptiGraph)

Retrieve the edges that exists in graph. Does not return edges that exist in subgraphs.

source
Plasmo.all_edgesFunction
all_edges(graph::OptiGraph)::Vector{<:OptiNode}

Recursively collect all optiedges in graph by traversing each of its subgraphs.

source
Plasmo.num_edgesFunction
num_edges(graph::OptiGraph)::Int

Return the total number of nodes in graph by recursively checking subgraphs.

source
Plasmo.num_local_link_constraintsFunction
num_local_link_constraints(
     graph::OptiGraph,
     func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},
     set_type::Type{<:MOI.AbstractSet},
-)

Retrieve the number of local linking constraints with function func_type and set set_type in graph. Does not include linking constraints in subgraphs.

source
num_local_link_constraints(graph::OptiGraph)

Retrieve the number of local linking constraints (all constraint types) in graph. Does not include linking constraints in subgraphs.

source
Plasmo.num_link_constraintsFunction
num_link_constraints(
+)

Retrieve the number of local linking constraints with function func_type and set set_type in graph. Does not include linking constraints in subgraphs.

source
num_local_link_constraints(graph::OptiGraph)

Retrieve the number of local linking constraints (all constraint types) in graph. Does not include linking constraints in subgraphs.

source
Plasmo.num_link_constraintsFunction
num_link_constraints(
     graph::OptiGraph,
     func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},
     set_type::Type{<:MOI.AbstractSet},
-)

Retrieve the total number of linking constraints with function func_type and set set_type in graph. Includes constraints in subgraphs.

source
num_link_constraints(graph::OptiGraph)

Retrieve the number of local linking constraints (all constraint types) in graph. Does not include constraints in subgraphs.

source
Plasmo.local_link_constraintsFunction
local_link_constraints(
+)

Retrieve the total number of linking constraints with function func_type and set set_type in graph. Includes constraints in subgraphs.

source
num_link_constraints(graph::OptiGraph)

Retrieve the number of local linking constraints (all constraint types) in graph. Does not include constraints in subgraphs.

source
Plasmo.local_link_constraintsFunction
local_link_constraints(
     graph::OptiGraph,
     func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},
     set_type::Type{<:MOI.AbstractSet},
-)

Retrieve the local linking constraints with function func_type and set set_type in graph. Does not include linking constraints in subgraphs.

source
local_link_constraints(graph::OptiGraph)

Retrieve the local linking constraints (all constraint types) in graph. Does not include constraints in subgraphs.

source
Plasmo.all_link_constraintsFunction
all_link_constraints(
+)

Retrieve the local linking constraints with function func_type and set set_type in graph. Does not include linking constraints in subgraphs.

source
local_link_constraints(graph::OptiGraph)

Retrieve the local linking constraints (all constraint types) in graph. Does not include constraints in subgraphs.

source
Plasmo.all_link_constraintsFunction
all_link_constraints(
     graph::OptiGraph,
     func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},
     set_type::Type{<:MOI.AbstractSet},
-)

Retrieve all linking constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.

source
all_link_constraints(graph::OptiGraph)

Retrieve all linking constraints (all constraint types) in graph. Includes linking constraints in subgraphs.

source
Plasmo.num_local_constraintsFunction
num_local_constraints(
+)

Retrieve all linking constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.

source
all_link_constraints(graph::OptiGraph)

Retrieve all linking constraints (all constraint types) in graph. Includes linking constraints in subgraphs.

source
Plasmo.num_local_constraintsFunction
num_local_constraints(
     graph::OptiGraph,
     func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},
     set_type::Type{<:MOI.AbstractSet},
-)

Retrieve the number of local constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.

source
num_local_constraints(graph::OptiGraph)

Retrieve the number of local constraints (all constraint types) in graph. Does not include constraints in subgraphs.

source
Plasmo.local_constraintsFunction
local_constraints(
+)

Retrieve the number of local constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.

source
num_local_constraints(graph::OptiGraph)

Retrieve the number of local constraints (all constraint types) in graph. Does not include constraints in subgraphs.

source
Plasmo.local_constraintsFunction
local_constraints(
     graph::OptiGraph,
     func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},
     set_type::Type{<:MOI.AbstractSet},
-)

Retrieve the local constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.

source
local_constraints(graph::OptiGraph)

Retrieve the local constraints (all constraint types) in graph. Does not include constraints in subgraphs.

source
Plasmo.local_elementsFunction
local_elements(graph::OptiGraph)

Retrieve the local elements (nodes and edges) in graph. Does not include elements in subgraphs.

source
Plasmo.all_elementsFunction
local_elements(graph::OptiGraph)

Retrieve all elements (nodes and edges) in graph. Includes elements in subgraphs.

source
Base.getindexMethod
Base.getindex(graph::OptiGraph, idx::Int)

Get the optinode at the given index.

source

JuMP.jl Extended Methods

JuMP.nameFunction
JuMP.name(graph::OptiGraph)

Return the name of graph.

source
JuMP.set_nameFunction
JuMP.set_name(graph::OptiGraph, name::Symbol)

Set the name of graph to name.

source
JuMP.indexFunction
JuMP.index(graph::OptiGraph, nvref::NodeVariableRef)

Return the backend model index of node variable nvref

source
JuMP.backendFunction
JuMP.backend(graph::OptiGraph)

Return the backend model object for graph.

source
JuMP.valueFunction
JuMP.value(graph::OptiGraph, nvref::NodeVariableRef; result::Int=1)

Return the primal value of nvref in graph. Note that this value is specific to the optimizer solution to the graph. The nvref can have different values for different optigraphs it is contained in.

source
JuMP.add_variableFunction
JuMP.add_variable(node::OptiNode, v::JuMP.AbstractVariable, name::String="")

Add variable v to optinode node. This function supports use of the @variable JuMP macro. Optionally add a base_name to the variable for printing.

source
JuMP.start_valueFunction
JuMP.start_value(graph::OptiGraph, nvref::NodeVariableRef)

Return the start value for variable nvref in graph. Note that different graphs can have different start values for node variables.

source
JuMP.set_start_valueFunction
JuMP.set_start_value(
+)

Retrieve the local constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.

source
local_constraints(graph::OptiGraph)

Retrieve the local constraints (all constraint types) in graph. Does not include constraints in subgraphs.

source
Plasmo.local_elementsFunction
local_elements(graph::OptiGraph)

Retrieve the local elements (nodes and edges) in graph. Does not include elements in subgraphs.

source
Plasmo.all_elementsFunction
local_elements(graph::OptiGraph)

Retrieve all elements (nodes and edges) in graph. Includes elements in subgraphs.

source
Base.getindexMethod
Base.getindex(graph::OptiGraph, idx::Int)

Get the optinode at the given index.

source

JuMP.jl Extended Methods

JuMP.nameFunction
JuMP.name(graph::OptiGraph)

Return the name of graph.

source
JuMP.set_nameFunction
JuMP.set_name(graph::OptiGraph, name::Symbol)

Set the name of graph to name.

source
JuMP.indexFunction
JuMP.index(graph::OptiGraph, nvref::NodeVariableRef)

Return the backend model index of node variable nvref

source
JuMP.backendFunction
JuMP.backend(graph::OptiGraph)

Return the backend model object for graph.

source
JuMP.valueFunction
JuMP.value(graph::OptiGraph, nvref::NodeVariableRef; result::Int=1)

Return the primal value of nvref in graph. Note that this value is specific to the optimizer solution to the graph. The nvref can have different values for different optigraphs it is contained in.

source
JuMP.add_variableFunction
JuMP.add_variable(node::OptiNode, v::JuMP.AbstractVariable, name::String="")

Add variable v to optinode node. This function supports use of the @variable JuMP macro. Optionally add a base_name to the variable for printing.

source
JuMP.start_valueFunction
JuMP.start_value(graph::OptiGraph, nvref::NodeVariableRef)

Return the start value for variable nvref in graph. Note that different graphs can have different start values for node variables.

source
JuMP.set_start_valueFunction
JuMP.set_start_value(
     graph::OptiGraph, 
     nvref::NodeVariableRef, 
     value::Union{Nothing,Real}
-)

Set the start value of variable nvref in graph. Note that different graphs can have different start values for node variables.

source
JuMP.add_constraintFunction
JuMP.add_constraint(graph::OptiGraph, con::JuMP.AbstractConstraint, name::String="")

Add a new constraint to graph. This method is called internall when a user uses the JuMP.@constraint macro.

source
JuMP.add_constraint(node::OptiNode, con::JuMP.AbstractConstraint, name::String="")

Add a constraint con to optinode node. This function supports use of the @constraint JuMP macro.

source
JuMP.num_constraintsFunction
JuMP.num_constraints(
+)

Set the start value of variable nvref in graph. Note that different graphs can have different start values for node variables.

source
JuMP.add_constraintFunction
JuMP.add_constraint(graph::OptiGraph, con::JuMP.AbstractConstraint, name::String="")

Add a new constraint to graph. This method is called internall when a user uses the JuMP.@constraint macro.

source
JuMP.add_constraint(node::OptiNode, con::JuMP.AbstractConstraint, name::String="")

Add a constraint con to optinode node. This function supports use of the @constraint JuMP macro.

source
JuMP.num_constraintsFunction
JuMP.num_constraints(
     graph::OptiGraph,
     func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},
     set_type::Type{<:MOI.AbstractSet},
-)

Return all the number of contraints in graph with func_type and set_type.

source
JuMP.num_constraints(graph::OptiGraph; count_variable_in_set_constraints=true)

Return the total number of constraints in graph. If count_variable_in_set_constraints is set to true, this also includes variable bound constraints.

source
JuMP.num_constraints(
+)

Return all the number of contraints in graph with func_type and set_type.

source
JuMP.num_constraints(graph::OptiGraph; count_variable_in_set_constraints=true)

Return the total number of constraints in graph. If count_variable_in_set_constraints is set to true, this also includes variable bound constraints.

source
JuMP.num_constraints(
 element::OptiElement,
 function_type::Type{
     <:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}},
-},set_type::Type{<:MOI.AbstractSet})::Int64

Return the total number of constraints on an element.

source
JuMP.all_constraintsFunction
JuMP.all_constraints(
+},set_type::Type{<:MOI.AbstractSet})::Int64

Return the total number of constraints on an element.

source
JuMP.all_constraintsFunction
JuMP.all_constraints(
     graph::OptiGraph,
     func_type::Type{
         <:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}},
     },
     set_type::Type{<:MOI.AbstractSet}
-)

Return all of the constraints in graph with func_type and set_type.

source
JuMP.all_constraints(graph::OptiGraph)

Return all of the constraints in graph (all function and set types).

source
JuMP.objective_valueFunction
JuMP.objective_value(graph::OptiGraph)

Retrieve the current objective value on optigraph graph.

source
JuMP.dual_objective_valueFunction
JuMP.dual_objective_value(graph::OptiGraph; result::Int=1)

Return the dual objective value for graph. Specify result for cases when a solver returns multiple results.

source
JuMP.objective_boundFunction
JuMP.objective_bound(graph::OptiGraph)

Return the objective bound for the current solution for graph.

source
JuMP.set_objectiveFunction
JuMP.set_objective(
+)

Return all of the constraints in graph with func_type and set_type.

source
JuMP.all_constraints(graph::OptiGraph)

Return all of the constraints in graph (all function and set types).

source
JuMP.objective_valueFunction
JuMP.objective_value(graph::OptiGraph)

Retrieve the current objective value on optigraph graph.

source
JuMP.dual_objective_valueFunction
JuMP.dual_objective_value(graph::OptiGraph; result::Int=1)

Return the dual objective value for graph. Specify result for cases when a solver returns multiple results.

source
JuMP.objective_boundFunction
JuMP.objective_bound(graph::OptiGraph)

Return the objective bound for the current solution for graph.

source
JuMP.set_objectiveFunction
JuMP.set_objective(
     graph::OptiGraph, 
     sense::MOI.OptimizationSense, 
     func::JuMP.AbstractJuMPScalar
-)

Set the objective function and objective sense for graph. This method is called internally when a user uses the JuMP.@objective macro.

source
JuMP.set_objective_coefficientFunction
JuMP.set_objective_coefficient(
+)

Set the objective function and objective sense for graph. This method is called internally when a user uses the JuMP.@objective macro.

source
JuMP.set_objective_coefficientFunction
JuMP.set_objective_coefficient(
     graph::OptiGraph, 
     variable::NodeVariableRef, 
     coeff::Real
-)

Set the objective function coefficient for variable to coefficient coeff.

source
JuMP.set_optimizerFunction
JuMP.set_optimizer(
+)

Set the objective function coefficient for variable to coefficient coeff.

source
JuMP.set_optimizerFunction
JuMP.set_optimizer(
     graph::OptiGraph, 
     JuMP.@nospecialize(optimizer_constructor); 
     add_bridges::Bool=true
-)

Set the optimizer on graph by passing an optimizer_constructor.

source
JuMP.set_optimizer(
+)

Set the optimizer on graph by passing an optimizer_constructor.

source
JuMP.set_optimizer(
     node::OptiNode, 
     JuMP.@nospecialize(optimizer_constructor); 
     add_bridges::Bool=true
-)

Set the optimizer for an optinode.This internally creates a new optigraph that is used to optimize the node. Calling this method on a node returns the newly created graph.

source
JuMP.add_nonlinear_operatorFunction
JuMP.add_nonlinear_operator(
+)

Set the optimizer for an optinode.This internally creates a new optigraph that is used to optimize the node. Calling this method on a node returns the newly created graph.

source
JuMP.add_nonlinear_operatorFunction
JuMP.add_nonlinear_operator(
     graph::OptiGraph,
     dim::Int,
     f::Function,
     args::Vararg{Function,N};
     name::Symbol=Symbol(f),
-) where {N}

Add a nonlinear operator to a graph.

source
JuMP.optimize!Function
JuMP.optimize!(
     graph::OptiGraph;
     kwargs...,
-)

Optimize graph using the current set optimizer.

source
JuMP.termination_statusFunction
JuMP.termination_status(graph::OptiGraph)

Return the solver termination status of graph if a solver has been executed.

source
JuMP.primal_statusFunction
JuMP.primal_status(graph::OptiGraph; result::Int=1)

Return the primal status of graph if a solver has been executed.

source
JuMP.dual_statusFunction
JuMP.dual_status(graph::OptiGraph; result::Int=1)

Return the dual status of graph if a solver has been executed.

source
JuMP.relative_gapFunction
JuMP.relative_gap(graph::OptiGraph)

Return the relative gap in the current solution for graph.

source
JuMP.termination_statusFunction
JuMP.termination_status(graph::OptiGraph)

Return the solver termination status of graph if a solver has been executed.

source
JuMP.primal_statusFunction
JuMP.primal_status(graph::OptiGraph; result::Int=1)

Return the primal status of graph if a solver has been executed.

source
JuMP.dual_statusFunction
JuMP.dual_status(graph::OptiGraph; result::Int=1)

Return the dual status of graph if a solver has been executed.

source
JuMP.relative_gapFunction
JuMP.relative_gap(graph::OptiGraph)

Return the relative gap in the current solution for graph.

source
JuMP.constraint_ref_with_indexFunction
JuMP.constraint_ref_with_index(
 element::OptiElement, 
 idx::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction, <:MOI.AbstractScalarSet}
-)

Return a ConstraintRef given an optigraph element and MOI.ConstraintIndex. Note that the index is the index corresponding to the graph backend, not the element index.

source
JuMP.constraint_ref_with_index(backend::GraphMOIBackend, idx::MOI.Index)

Return the constraint reference (or variable reference) associated with the graph index in backend. Returns a JuMP.ConstraintRef (or NodeVariableRef) object.

source

Interop with JuMP.jl

Plasmo.set_jump_modelFunction
Set a JuMP.Model to `node`. This copies the model data over and does not mutate

the model in any way.

source

Graph Projections

Plasmo.GraphProjectionType
GraphProjection

A mapping between OptiGraph elements (nodes and edges) and elements in a graph projection. A graph projection can be for example a hypergraph, a bipartite graph or a standard graph.

source
Plasmo.hyper_projectionFunction
hyper_projection(graph::OptiGraph)

Retrieve a hypergraph representation of the optigraph graph. Returns a GraphProjection that maps elements between the optigraph and the projected graph.

source
Plasmo.edge_hyper_projectionFunction
edge_hyper_projection(graph::OptiGraph)

Retrieve an edge-hypergraph representation of the optigraph graph. This is sometimes called the dual-hypergraph representation of a hypergraph.

source
Plasmo.clique_projectionFunction
clique_projection(graph::OptiGraph)

Retrieve a standard graph representation of graph. The projection contains a standard Graphs.Graph and a mapping between its elements and the given optigraph. This projection works by creating an edge for each pair of nodes in each hyperedge.

source
Plasmo.edge_clique_projectionFunction
edge_clique_projection(graph::OptiGraph)

Retrieve the edge-graph representation of optigraph graph. This is sometimes called the line graph of a hypergraph.

source
Plasmo.bipartite_projectionFunction
bipartite_graph(graph::OptiGraph)

Create a bipartite graph representation from graph. The bipartite graph contains two sets of vertices corresponding to optinodes and optiedges respectively.

source

Partitioning and Aggregation

Plasmo.PartitionType
Partition

A data structure that describes a (possibly recursive) graph partition.

source
Plasmo.assemble_optigraphFunction
assemble_optigraph(nodes::Vector{<:OptiNode}, edges::Vector{OptiEdge})

Create a new optigraph from a collection of nodes and edges.

source
Assemble a new optigraph from a given `Partition`.
source
Plasmo.apply_partition!Function
apply_partition!(graph::OptiGraph, partition::Partition)

Generate subgraphs in an optigraph using a partition.

source
Plasmo.aggregateFunction
aggregate(graph::OptiGraph; name=gensym())

Aggregate an optigraph into a graph containing a single optinode. An optional name can be used to name the new optigraph. Returns the new optinode (which points to a new graph with source_graph(node)) and a mapping from the original graph variables and constraints to the new node variables and constraints.

source
Plasmo.aggregate_to_depthFunction
aggregate_to_depth(graph::OptiGraph, max_depth::Int64=0)

Aggregate graph by converting subgraphs into optinodes. The max_depth determines how many levels of subgraphs remain in the new aggregated optigraph. For example, a max_depth of 0 signifies there should be no subgraphs in the aggregated optigraph. Return a new aggregated optigraph and reference map that maps elements from the old optigraph to the new aggregate optigraph.

source
Plasmo.aggregate_to_depth!Function
aggregate_to_depth!(graph::OptiGraph, max_depth::Int64=0)

Aggregate graph by converting subgraphs into optinodes. The max_depth determines how many levels of subgraphs remain in the new aggregated optigraph. For example, a max_depth of 0 signifies there should be no subgraphs in the aggregated optigraph. This version of the method modifies the optigraph and transforms it into the aggregated version.

source

Graph Topology

Graphs.all_neighborsFunction
Graphs.all_neighbors(hyper::HyperGraphProjection, node::OptiNode)

Retrieve the optinode neighbors of node in the optigraph graph. Uses an underlying hypergraph to query for neighbors.

source
Graphs.induced_subgraphFunction
Graphs.induced_subgraph(graph::OptiGraph, nodes::Vector{OptiNode})

Create an induced subgraph of optigraph given a vector of optinodes.

source
Graphs.neighborhoodFunction
neighborhood(
+)

Return a ConstraintRef given an optigraph element and MOI.ConstraintIndex. Note that the index is the index corresponding to the graph backend, not the element index.

source
JuMP.constraint_ref_with_index(backend::GraphMOIBackend, idx::MOI.Index)

Return the constraint reference (or variable reference) associated with the graph index in backend. Returns a JuMP.ConstraintRef (or NodeVariableRef) object.

source

Interop with JuMP.jl

Plasmo.set_jump_modelFunction
Set a JuMP.Model to `node`. This copies the model data over and does not mutate

the model in any way.

source

Graph Projections

Plasmo.GraphProjectionType
GraphProjection

A mapping between OptiGraph elements (nodes and edges) and elements in a graph projection. A graph projection can be for example a hypergraph, a bipartite graph or a standard graph.

source
Plasmo.hyper_projectionFunction
hyper_projection(graph::OptiGraph)

Retrieve a hypergraph representation of the optigraph graph. Returns a GraphProjection that maps elements between the optigraph and the projected graph.

source
Plasmo.edge_hyper_projectionFunction
edge_hyper_projection(graph::OptiGraph)

Retrieve an edge-hypergraph representation of the optigraph graph. This is sometimes called the dual-hypergraph representation of a hypergraph.

source
Plasmo.clique_projectionFunction
clique_projection(graph::OptiGraph)

Retrieve a standard graph representation of graph. The projection contains a standard Graphs.Graph and a mapping between its elements and the given optigraph. This projection works by creating an edge for each pair of nodes in each hyperedge.

source
Plasmo.edge_clique_projectionFunction
edge_clique_projection(graph::OptiGraph)

Retrieve the edge-graph representation of optigraph graph. This is sometimes called the line graph of a hypergraph.

source
Plasmo.bipartite_projectionFunction
bipartite_graph(graph::OptiGraph)

Create a bipartite graph representation from graph. The bipartite graph contains two sets of vertices corresponding to optinodes and optiedges respectively.

source

Partitioning and Aggregation

Plasmo.PartitionType
Partition

A data structure that describes a (possibly recursive) graph partition.

source
Plasmo.assemble_optigraphFunction
assemble_optigraph(nodes::Vector{<:OptiNode}, edges::Vector{OptiEdge})

Create a new optigraph from a collection of nodes and edges.

source
Assemble a new optigraph from a given `Partition`.
source
Plasmo.apply_partition!Function
apply_partition!(graph::OptiGraph, partition::Partition)

Generate subgraphs in an optigraph using a partition.

source
Plasmo.aggregateFunction
aggregate(graph::OptiGraph; name=gensym())

Aggregate an optigraph into a graph containing a single optinode. An optional name can be used to name the new optigraph. Returns the new optinode (which points to a new graph with source_graph(node)) and a mapping from the original graph variables and constraints to the new node variables and constraints.

source
Plasmo.aggregate_to_depthFunction
aggregate_to_depth(graph::OptiGraph, max_depth::Int64=0)

Aggregate graph by converting subgraphs into optinodes. The max_depth determines how many levels of subgraphs remain in the new aggregated optigraph. For example, a max_depth of 0 signifies there should be no subgraphs in the aggregated optigraph. Return a new aggregated optigraph and reference map that maps elements from the old optigraph to the new aggregate optigraph.

source
Plasmo.aggregate_to_depth!Function
aggregate_to_depth!(graph::OptiGraph, max_depth::Int64=0)

Aggregate graph by converting subgraphs into optinodes. The max_depth determines how many levels of subgraphs remain in the new aggregated optigraph. For example, a max_depth of 0 signifies there should be no subgraphs in the aggregated optigraph. This version of the method modifies the optigraph and transforms it into the aggregated version.

source

Graph Topology

Graphs.all_neighborsFunction
Graphs.all_neighbors(hyper::HyperGraphProjection, node::OptiNode)

Retrieve the optinode neighbors of node in the optigraph graph. Uses an underlying hypergraph to query for neighbors.

source
Graphs.induced_subgraphFunction
Graphs.induced_subgraph(graph::OptiGraph, nodes::Vector{OptiNode})

Create an induced subgraph of optigraph given a vector of optinodes.

source
Graphs.neighborhoodFunction
neighborhood(
     hyper::HyperGraphProjection, 
     nodes::Vector{OptiNode}, 
     distance::Int64
-)::Vector{OptiNode}

Return the optinodes within distance of the given nodes in the optigraph graph.

source
Plasmo.incident_edgesFunction
incident_edges(hyper::HyperGraphProjection, nodes::Vector{OptiNode})

Retrieve incident edges to a set of optinodes.

incident_edges(hyper::HyperGraphProjection, node::OptiNode)

Retrieve incident edges to a single optinode.

source
Plasmo.induced_edgesFunction
induced_edges(graph::OptiGraph, nodes::Vector{OptiNode})

Retrieve induced edges to a set of optinodes.

source
Plasmo.identify_edgesFunction
identify_edges(hyper::HyperGraphProjection, node_vectors::Vector{Vector{OptiNode}})

Identify induced edges and edge separators from a vector of optinode partitions.

Arguments

  • hyper::HyperGraphProjection: A HyperGraphProjection obtained from hyper_projection.
  • node_vectors::Vector{Vector{OptiNode}}: A vector of vectors that contain OptiNodes.

Returns

  • partition_optiedges::Vector{Vector{OptiEdge}}: The OptiEdge vectors for each partition.
  • cross_optiedges::Vector{OptiEdge}: A vector of optiedges that cross partitions.
source
Plasmo.identify_nodesFunction
identify_nodes(hyper::HyperGraphProjection, node_vectors::Vector{Vector{OptiEdge}})

Identify induced nodes and node separators from a vector of optiedge partitions.

source
Plasmo.expandFunction
expand(hyper::HyperGraphProjection, subgraph::OptiGraph, distance::Int64)

Return a new expanded subgraph given the optigraph graph and an existing subgraph subgraph. The returned subgraph contains the expanded neighborhood within distance of the given subgraph.

source

<!– @docs PlasmoPlots.layout_plot PlasmoPlots.matrix_plot –>

+)::Vector{OptiNode}

Return the optinodes within distance of the given nodes in the optigraph graph.

source
Plasmo.incident_edgesFunction
incident_edges(hyper::HyperGraphProjection, nodes::Vector{OptiNode})

Retrieve incident edges to a set of optinodes.

incident_edges(hyper::HyperGraphProjection, node::OptiNode)

Retrieve incident edges to a single optinode.

source
Plasmo.induced_edgesFunction
induced_edges(graph::OptiGraph, nodes::Vector{OptiNode})

Retrieve induced edges to a set of optinodes.

source
Plasmo.identify_edgesFunction
identify_edges(hyper::HyperGraphProjection, node_vectors::Vector{Vector{OptiNode}})

Identify induced edges and edge separators from a vector of optinode partitions.

Arguments

  • hyper::HyperGraphProjection: A HyperGraphProjection obtained from hyper_projection.
  • node_vectors::Vector{Vector{OptiNode}}: A vector of vectors that contain OptiNodes.

Returns

  • partition_optiedges::Vector{Vector{OptiEdge}}: The OptiEdge vectors for each partition.
  • cross_optiedges::Vector{OptiEdge}: A vector of optiedges that cross partitions.
source
Plasmo.identify_nodesFunction
identify_nodes(hyper::HyperGraphProjection, node_vectors::Vector{Vector{OptiEdge}})

Identify induced nodes and node separators from a vector of optiedge partitions.

source
Plasmo.expandFunction
expand(hyper::HyperGraphProjection, subgraph::OptiGraph, distance::Int64)

Return a new expanded subgraph given the optigraph graph and an existing subgraph subgraph. The returned subgraph contains the expanded neighborhood within distance of the given subgraph.

source

<!– @docs PlasmoPlots.layout_plot PlasmoPlots.matrix_plot –>

diff --git a/dev/documentation/graph_processing/index.html b/dev/documentation/graph_processing/index.html index 47b21b8..819ad31 100644 --- a/dev/documentation/graph_processing/index.html +++ b/dev/documentation/graph_processing/index.html @@ -245,4 +245,4 @@ node_labels=true, node_colors=true ); -

partition_layout_3 partition_matrix_3

+

partition_layout_3 partition_matrix_3

diff --git a/dev/documentation/modeling/index.html b/dev/documentation/modeling/index.html index 2100fa1..42a6cbe 100644 --- a/dev/documentation/modeling/index.html +++ b/dev/documentation/modeling/index.html @@ -338,4 +338,4 @@ julia> termination_status(graph0) LOCALLY_SOLVED::TerminationStatusCode = 4 -

While somewhat simple, this example shows what kinds of model approaches can be taken with Plasmo.jl. Checkout Graph Processing and Analysis for more advanced functionality that makes use of the optigraph structure to define and solve problems.

+

While somewhat simple, this example shows what kinds of model approaches can be taken with Plasmo.jl. Checkout Graph Processing and Analysis for more advanced functionality that makes use of the optigraph structure to define and solve problems.

diff --git a/dev/documentation/quickstart/index.html b/dev/documentation/quickstart/index.html index 1a0bf7b..10807ee 100644 --- a/dev/documentation/quickstart/index.html +++ b/dev/documentation/quickstart/index.html @@ -111,4 +111,4 @@ :axis=>nothing) ) -plt_matrix = PlasmoPlots.matrix_plot(graph, node_labels=true, markersize=15)

graph_quickstart matrix_quickstart

+plt_matrix = PlasmoPlots.matrix_plot(graph, node_labels=true, markersize=15)

graph_quickstart matrix_quickstart

diff --git a/dev/index.html b/dev/index.html index c479220..6fd8ef2 100644 --- a/dev/index.html +++ b/dev/index.html @@ -16,4 +16,4 @@ volume = {125}, year = {2019}, doi = {10.1016/j.compchemeng.2019.03.009} -}

A pre-print of this paper can also be found here

+}

A pre-print of this paper can also be found here

diff --git a/dev/objects.inv b/dev/objects.inv index d30f689..bcacbad 100644 Binary files a/dev/objects.inv and b/dev/objects.inv differ diff --git a/dev/search_index.js b/dev/search_index.js index afd29b5..75f7071 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"tutorials/supply_chain/#Supply-Chain-Optimization","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"","category":"section"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"This tutorial shows how to model a supply chain using Plasmo.jl. This problem has a natural graph representation which we will follow in constructing the problem. Code for this problem is also available here. This example is from the textbook Introduction to Software for Chemical Engineers (3rd edition) in Chapter 22, \"Optimization in Chemical and Biological Engineering using Julia.\"","category":"page"},{"location":"tutorials/supply_chain/#Mathematical-Formulation","page":"Supply Chain Optimization","title":"Mathematical Formulation","text":"","category":"section"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"The supply chain being modeled includes a set of suppliers and consumers, where the suppliers ship products to technologies (for product conversion) or directly to the consumers. Suppliers, consumers, and technologies are located at specific locations (represented by nodes) with varying transportation capacity and costs between nodes. This problem includes the following sets: ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Products (P)\nSuppliers (S)\nDemands (D)\nLines for transporting products (L)\nTechnologies (T)\nNodes/Locations (N)","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Each supplier and consumer only sells or consumes a single product. In addition L connects two nodes in N and has an associated direction (i.e., this is a directed graph). As each supplier, consumer, and technology are located at a specific node, we will denote these objects at a given node n in N by S(n), D(n), and T(n), and we will denote the set of all lines originating at node n as L_in(n) and the set of all lines ending at at node n as L_out(n). We will also add superscripts to these sets for specific products when applicable (e.g., D^p(n) represents the consumers at node n in N that consume product p in P). Mathematically, this multi-product supply chain problem is given by:","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"beginalign* \n max sum_n in N left( sum_i in D(n) alpha^d_i d_i - sum_i in S(n) alpha^s_i s_i - sum_i in T(n) alpha^t_i xi_i right) - sum_i in L alpha^f_i f_i\n textrmst sum_i in S^p(n)s_i - sum_i in D^p(n) d_i + sum_i in L_in^p(n) f_i - sum_i in L_out^p(n) f_i + sum_i in T(n) gamma_i p xi_i = 0 quad n in N p in P\n 0 le s_i le overlines_i quad i in S(n) n in N \n 0 le d_i le overlined_i quad i in D(n) n in N \n 0 le xi_i le overlinexi_i quad i in T(n) n in N \n 0 le f_i le overlinef_i quad i in L\nendalign*","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Here, s_i is the amount of product supplied by supplier i, d_i is the amount of product purchased by consumer i, xi_i is the amount of product used to produce a new product by technology i, and f_i is the amount of product transported by line i in L. Since each supplier, consumer, and line only handle a single product, these variables do not include the product notation. The technologies consume certain products to produce other products, and the ratio of consumption to generation is captured by the parameter gamma_ip i in T p in P, where gamma_i p is negative if product p is consumed by technology i, positive if product p is produced by technology i, and 0 otherwise. alpha^d_i is the price paid by consumer i for each unit of product, alpha^s_i is the cost for supplier i to supply each unit of product, alpha^t_i is the cost to operate technology i to consume one unit of product, and alpha^f_i is the cost to ship one unit of product on line i. The parameters overlines_i, overlined_i, overlinexi_i, and overlinef_i are the upper bounds on their respective variables. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"The objective function seeks to maximize profit by providing product to consumers while being penalized for the costs from supply, technology, and transport. Each node includes a balance on each product (defined by the first constraint) which requires that the units of product supplied, produced, transported, or consumed (by consumers or by technologies) at each node must be equal to zero. ","category":"page"},{"location":"tutorials/supply_chain/#Defining-the-Data","page":"Supply Chain Optimization","title":"Defining the Data","text":"","category":"section"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"The data for the supply chain is given below. Here, we have two nodes, n_1 and n_2, with two suppliers, one consumer, and two technologies located at n_1 and two other consumers located at node n_2. There are three different products, p_1, p_2, and p_3, and each supplier only produces p_1. One technology can produce p_2 and the other can produce p_3. There are two lines between the nodes; one transfers p_2 and the other transfers p_3. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"First, we define the sets in Julia:","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Data sets\nN = [\"n1\", \"n2\"]; P = [\"p1\", \"p2\", \"p3\"];\nS = [\"s1(p1)\", \"s2(p1)\"]; D = [\"n1(p2)\", \"n2(p2)\", \"n2(p3)\"];\nL = [\"n1->n2(p2)\", \"n1->n2(p3)\"]; T = [\"p1-p2\", \"p1-p3\"];","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"We next define data for the suppliers, including mapping each node to a set of suppliers and mapping each supplier to a product, upper bound, and cost of supply. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Map nodes to supplies\nsloc = Dict(\"n1\" => [S[1], S[2]], \"n2\" => [])\n\n# Map supplies to products they supply\nsprod = [\"p1\",\"p1\"]; sprod = Dict(zip(S,sprod));\n\n# Define upper bounds on supplies\nsub = [1000,500]; sub = Dict(zip(S,sub)); # unit\n\n# Define cost of supplying one unit of a supply's product\nsbid = [3,2.5]; sbid = Dict(zip(S, sbid)); # $/unit","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Next, we define the consumers and similarly map each node to a set of consumers and map each consumer to a product, upper bound, and price for the products. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Map nodes to demands\ndloc = Dict(\"n1\" => [D[1]], \"n2\" => [D[2], D[3]])\n\n# Map demands to the products they require\ndprod = [\"p2\",\"p2\",\"p3\"]; dprod = Dict(zip(D,dprod));\n\n# Define upper bounds on demands\ndub = [100,200,500]; dub = Dict(zip(D,dub)); # unit\n\n# Define price for one unit of the demand's product\ndbid = [300,300,15]; dbid = Dict(zip(D,dbid)) # $/unit","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Next, we define the set of technologies, again mapping the nodes to a set of technologies and mapping the technologies to upper bounds on production and the cost of conversion. We also define the gamma variable which contains the conversion rates for the products. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Map nodes to technologies\nξloc = Dict(\"n1\" => [T[1], T[2]], \"n2\" => [])\n\n# Define upper bounds on technology conversions\nξub = [1000,1000]; ξub = Dict(zip(T,ξub)); # unit\n\n# Define cost of converting one unit of product\nξbid = [1,0.5]; ξbid = Dict(zip(T,ξbid)); # $/unit\n\n# Product conversion matrices; rows are technologies, columns are products\nγ = [-1.0 0.9 0.0;\n -1.0 0.0 0.3]\nγ = Dict((T[i], P[j]) => γ[i, j] for j in eachindex(P), i in eachindex(T));","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Finally, we define data for the transportation lines. Here, flocs is a mapping of the source node and flocr is a mapping of the destination nodes for each line. Finally, we set upper bounds on each line, define the products the lines can transport, and define the cost of transportation. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Map transport data (flocs = supplying node; flocr = receiving node)\nflocs = [\"n1\",\"n1\"]; flocs = Dict(zip(L,flocs));\nflocr = [\"n2\",\"n2\"]; flocr = Dict(zip(L,flocr));\n\n# Define upper bounds on transport\nfub = [1000,1000]; fub = Dict(zip(L,fub)); # unit\n\n# Map transport flows to the product they support\nfprod = [[\"p2\"], [\"p3\"]]; fprod = Dict(zip(L,fprod));\n\n# Define cost of transporting a unit of product\nfbid = [0.1,0.1]; fbid = Dict(zip(L,fbid)); # $/unit","category":"page"},{"location":"tutorials/supply_chain/#Modeling-in-Plasmo.jl","page":"Supply Chain Optimization","title":"Modeling in Plasmo.jl","text":"","category":"section"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"With the data defined, we can now model the supply chain with Plasmo. Because variables must be placed on nodes, we will create OptiNodes for each node in N and for each line in L. We first load in the required packages, instantiate the OptiGraph, and define OptiNodes:","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"using Plasmo, HiGHS\ngraph = OptiGraph()\n\n@optinode(graph, nodes[N])\n@optinode(graph, transport[L])","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Next, we loop through the set of nodes, N, and add variables for the suppliers (s_i), consumers (d_i), and technology production (xi_i). We then define upper bounds on the variables and we define expressions that sum the supplies, demands, and technologies on each node. We next define expressions for the costs and finally set the objective on the node, which is maximizing a summation of the profit from selling a product minus the cost of supply and the cost of technology conversion.","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"for n in N\n # Define variables based on above mappings\n @variable(nodes[n], s[sloc[n]]>=0)\n @variable(nodes[n], d[dloc[n]]>=0)\n @variable(nodes[n], ξ[ξloc[n]]>=0)\n\n # Define upper bounds on variables\n @constraint(nodes[n], [i in sloc[n]], s[i] <= sub[i])\n @constraint(nodes[n], [j in dloc[n]], d[j] <= dub[j])\n @constraint(nodes[n], [t in ξloc[n]], ξ[t] <= ξub[t])\n\n # Define expressions for summing supplies, demands, techs on a node\n @expression(nodes[n], sum_supplies[p in P],\n sum(s[i] for i in sloc[n] if sprod[i] == p)\n )\n @expression(nodes[n], sum_demands[p in P],\n sum(d[j] for j in dloc[n] if dprod[j] == p)\n )\n @expression(nodes[n], sum_techs[p in P], sum(ξ[t] * γ[t, p] for t in ξloc[n]))\n\n # Define cost expressions for a node\n scost = @expression(nodes[n], sum(sbid[i] * s[i] for i in sloc[n]))\n dcost = @expression(nodes[n], sum(dbid[j] * d[j] for j in dloc[n]))\n ξcost = @expression(nodes[n], sum(ξbid[t] * ξ[t] for t in ξloc[n]))\n\n # Set objective on each node\n @objective(nodes[n], Max, dcost - scost - ξcost)\nend","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Next, we similarly loop through the set of lines, L, and define a variable for the flow of product (f_i). We then set the upper bound and define an expression for the flow in and out for the line for each product (this is to simplify the product mass balances later). We also set an objective on these nodes. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Define variables/constraints/objectives for transport nodes\nfor l in L\n # Define flow variable\n @variable(transport[l], f[fprod[l]] >= 0)\n\n # Set upper bound on flow variable\n @constraint(transport[l], [j in fprod[l]], f[j] <= fub[l])\n\n # Define flow in and out of nodes\n @expression(transport[l], flow_in[p in P, n in N],\n sum(f[i] for i in fprod[l] if flocr[l] == n && i == p)\n )\n @expression(transport[l], flow_out[p in P, n in N],\n sum(f[i] for i in fprod[l] if flocs[l] == n && i == p)\n )\n\n # Set objective (penalizing transport costs)\n @objective(transport[l], Max, - sum(fbid[l] * f[j] for j in fprod[l]))\nend","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"With the nodes defined, we can now set the linking constraints for each product. Here, we define a node balance for each product, where, for each product, we have a mass balance (supply - consumption - flow out + flow in - technology consumption). ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# For each node, do a product balance for each product\nfor n in N\n for p in P\n # Node balance must be equal to zero\n node_balance = (nodes[n][:sum_supplies][p] - nodes[n][:sum_demands][p] +\n nodes[n][:sum_techs][p] + sum(transport[l][:flow_in][p, n] for l in L) -\n sum(transport[l][:flow_out][p, n] for l in L)\n )\n if node_balance != 0\n @linkconstraint(graph, node_balance == 0)\n end\n end\nend","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"The form of this graph network is visualized below, where the (hyper)edges contain the flow constraints defined above: ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"(Image: sc_optigraph)","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Finally, we set the optimizer on the graph and the overall objective on the graph. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"note: Note\nAfter the changes made between Plasmo v0.5.4 and v0.6.0, we must now include the function set_to_node_objectives(graph) in the script below to set the graph's objective to use the individual node objectives. In former versions of Plasmo, the graph used the node objectives automatically. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"set_optimizer(graph, HiGHS.Optimizer)\nset_to_node_objectives(graph)","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Finally, we can call optimize! and then query the solutions on each node. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"optimize!(graph)\nprintln(objective_value(graph))\nprintln(\"Node 1 demand solutions = \", value.(graph[:nodes][\"n1\"][:d]))\nprintln(\"Node 2 demand solutions = \", value.(graph[:nodes][\"n2\"][:d]))\nprintln(\"Technology conversion = \", value.(graph[:nodes][\"n1\"][:ξ]))","category":"page"},{"location":"documentation/modeling/#Modeling-with-OptiGraphs","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"The primary data structure in Plasmo.jl is the OptiGraph, a mathematical model composed of OptiNodes (which represent self-contained optimization problems) that are connected by OptiEdges (which encapsulate linking constraints that couple optinodes). The optigraph is meant to offer a modular mechanism to create optimization problems and provide methods that can help develop specialized solution strategies, visualize problem structure, or perform graph processing tasks such as partitioning.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"The optigraph ultimately describes the following mathematical representation of an optimization problem:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"beginaligned\n min_x_n_n in mathcalN(mathcalG) quad F(f_n(x_n)_n in mathcalN(mathcalG)) quad (textrmObjective) \n textrmst quad x_n in mathcalX_n quad n in mathcalN(mathcalG) quad (textrmNode Constraints)\n quad g_e(x_n_n in mathcalN(e)) = 0 quad e in mathcalE(mathcalG) (textrmEdge (Link) Constraints)\nendaligned","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"In this formulation, mathcalG represents the optigraph, x_n_n in mathcalN(mathcalG) describes a collection of decision variables over the set of optinodes mathcalN(mathcalG), and x_n is the set of decision variables on optinode n. The objective function for the optigraph F(f_n(x_n)_n in mathcalN(mathcalG)) is given as a composition of optinode objective functions f_n(x_n) (it could be a separable summation of node objectives or any nonlinear function defined over optinode variables). The constraints of an optinode n are represented by the set mathcalX_n while the linking constraints that correspond to an edge e are represented by the vector function g_e(x_n_n in mathcalN(e)).","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"From an implementation standpoint, an optigraph extends much of the modeling functionality and syntax from JuMP. We also sometimes drop the 'opti' prefix and refer to objects as graphs, nodes, and edges throughout the documentation, but we note when the 'opti' distincition is important.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"info: Info\nThe OptiNode and OptiGraph each extend a JuMP.AbstractModel and support JuMP macros such as @variable, @constraint, @expression, and @objective as well as many other JuMP methods that work on a JuMP.Model. The OptiEdge supports most JuMP methods as well but does not support @variable or @objective.","category":"page"},{"location":"documentation/modeling/#Creating-a-New-OptiGraph","page":"Modeling with OptiGraphs","title":"Creating a New OptiGraph","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"An optigraph does not require any arguments to construct but it is recommended to include the optional name argument for tracking and model management purposes. We begin by creating a new optigraph named graph1.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> using Plasmo\n\njulia> graph1 = OptiGraph(;name=:graph1)\nAn OptiGraph\n graph1 #local elements #total elements\n--------------------------------------------------\n Nodes: 0 0\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 0 0\n Constraints: 0 0","category":"page"},{"location":"documentation/modeling/#Add-Variables-and-Constraints-using-OptiNodes","page":"Modeling with OptiGraphs","title":"Add Variables and Constraints using OptiNodes","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Optinodes contain modular groups of optimization variables, constraints, and other model data. The typical way to add optinodes to an graph is by using the @optinode macro where the below snippet adds the node n1 to graph1. ","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> @optinode(graph1, n1)\nn1","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"note: Note\nYou can also use the add_node method to add individual optinodes. In this case, the above snippet would look like: n1 = add_node(graph1)","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"The @optinode macro is more useful for creating containers of optinodes like shown in the below code snippet. Here, we create two more optinodes referred to as nodes1. This macro returns a JuMP.DenseAxisArray which allows us to refer to each optinode using the produced index sets. For example, nodes1[2] and nodes1[3] each return the corresponding optinode.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> @optinode(graph1, nodes1[2:3])\n1-dimensional DenseAxisArray{OptiNode{OptiGraph},1,...} with index sets:\n Dimension 1, 2:3\nAnd data, a 2-element Vector{OptiNode{OptiGraph}}:\n nodes1[2]\n nodes1[3]\n\njulia> nodes1[2]\nnodes1[2]\n\njulia> nodes1[3]\nnodes1[3]","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Each optinode supports adding variables, constraints, expressions, and an objective function. Here we loop through each optinode in graph1 using the local_nodes function and we construct underlying model elements.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> for node in all_nodes(graph1)\n @variable(node,x >= 0)\n @variable(node, y >= 2)\n @constraint(node, node_constraint_1, x + y >= 3)\n @constraint(node, node_constraint_2, x^3 >= 1)\n @objective(node, Min, x + y)\n end\n\njulia> graph1\nAn OptiGraph\n graph1 #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 12 12\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Variables within an optinode can be accessed directly by indexing the associated symbol. This enclosed name-space is useful for referencing variables on different optinodes when creating linking constraints or optigraph objective functions.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> n1[:x]\nn1[:x]\n\njulia> nodes1[2][:x]\nnodes1[2][:x]","category":"page"},{"location":"documentation/modeling/#Add-Linking-Constraints-using-Edges","page":"Modeling with OptiGraphs","title":"Add Linking Constraints using Edges","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"An OptiEdge can be used to store linking constraints that couple variables across optinodes. The simplest way to create a linking constraint is to use the @linkconstraint macro which accepts the same input as the JuMP.@constraint macro, but it requires an expression with at least two variables that exists on two different optinodes. The actual constraint is stored on the edge that connects the optinodes referred to in the linking constraint. This optiedge is created if it does not already exist.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> @linkconstraint(graph1, link_reference, n1[:x] + nodes1[2][:x] + nodes1[3][:x] == 3)\nn1[:x] + nodes1[2][:x] + nodes1[3][:x] = 3\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"note: Note\nSome users may choose to create the edge manually and add the constraint like following:edge = add_edge(graph1, n1, nodes1[2], nodes1[3])\n@constraint(edge, link_reference, n1[:x] + nodes1[2][:x] + nodes1[3][:x] == 3)Both approaches are equivalent.","category":"page"},{"location":"documentation/modeling/#Add-an-Objective-Function","page":"Modeling with OptiGraphs","title":"Add an Objective Function","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"By default, the graph objective is empty even if objective functions exist on nodes. We leave it up to the user to determine what the objective function for the graph should be given its contained nodes. We provide the convenience function set_to_node_objectives which will set the graph objective function to the sum of all the node objectives. We can set the graph objective like following:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> set_to_node_objectives(graph1)\n\njulia> objective_function(graph1)\nn1[:x] + n1[:y] + nodes1[2][:x] + nodes1[2][:y] + nodes1[3][:x] + nodes1[3][:y]\n","category":"page"},{"location":"documentation/modeling/#Solving-and-Querying-Solutions","page":"Modeling with OptiGraphs","title":"Solving and Querying Solutions","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"An optimizer can be specified using the set_optimizer function which supports any MathOptInterface.jl optimizer. For example, we use the Ipopt.Optimizer from the Ipopt.jl to solve the optigraph like following:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> using Ipopt\n\njulia> using Suppressor # suppress complete output\n\njulia> set_optimizer(graph1, Ipopt.Optimizer);\n\njulia> set_optimizer_attribute(graph1, \"print_level\", 0); #suppress Ipopt output\n\njulia> @suppress optimize!(graph1)\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"The solution of an optigraph is stored directly on its optinodes and optiedges. Variables values, constraint duals, objective function values, and solution status codes can be queried just like in JuMP.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> termination_status(graph1) \nLOCALLY_SOLVED::TerminationStatusCode = 4\n\njulia> value(n1[:x]) \n1.0\n\njulia> value(nodes1[2][:x])\n1.0\n\njulia> value(nodes1[3][:x])\n1.0\n\njulia> round(objective_value(graph1))\n9.0\n\njulia> round(dual(link_reference), digits = 2)\n-0.25\n\njulia> round(dual(n1[:node_constraint_1]), digits = 2)\n0.5\n\njulia> round(dual(n1[:node_constraint_2]), digits = 2)\n0.25","category":"page"},{"location":"documentation/modeling/#Plotting-OptiGraphs","page":"Modeling with OptiGraphs","title":"Plotting OptiGraphs","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We can also plot the structure of graph1 using both graph and matrix layouts from the PlasmoPlots package.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"using PlasmoPlots\n\nplt_graph = layout_plot(\n graph1,\n node_labels=true,\n markersize=30,\n labelsize=15,\n linewidth=4,\n layout_options=Dict(\n :tol=>0.01,\n :iterations=>2\n ),\n plt_options=Dict(\n :legend=>false,\n :framestyle=>:box,\n :grid=>false,\n :size=>(400,400),\n :axis => nothing\n )\n);\n\nplt_matrix = matrix_layout(graph1, node_labels=true, markersize=15); ","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"(Image: graph_modeling1) (Image: matrix_modeling1)","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"info: Info\nThe layout_plot and matrix_plot functions both return a Plots.plot object which can be used for further customization and saving using Plots.jl","category":"page"},{"location":"documentation/modeling/#Modeling-with-Subgraphs","page":"Modeling with OptiGraphs","title":"Modeling with Subgraphs","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"A fundamental feature of modeling with optigraphs is the ability to create nested optimization structures using subgraphs (i.e. sub-optigraphs). Subgraphs are created using the add_subgraph method which embeds an optigraph as a subgraph within a higher level optigraph. This is demonstrated in the below snippets. First, we create two new optigraphs in the same fashion as above.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"# create graph2\ngraph2 = OptiGraph(;name=:graph2);\n@optinode(graph2, nodes2[1:3]);\nfor node in all_nodes(graph2)\n @variable(node, x >= 0)\n @variable(node, y >= 2)\n @constraint(node,x + y >= 5)\n @objective(node, Min, y)\nend\n@linkconstraint(graph2, nodes2[1][:x] + nodes2[2][:x] + nodes2[3][:x] == 5);\n\n# create graph3\ngraph3 = OptiGraph(;name=:graph3);\n@optinode(graph3, nodes3[1:3]);\nfor node in all_nodes(graph3)\n @variable(node, x >= 0)\n @variable(node, y >= 2)\n @constraint(node,x + y >= 5)\n @objective(node, Min, y)\nend\n@linkconstraint(graph3, nodes3[1][:x] + nodes3[2][:x] + nodes3[3][:x] == 7);\n\ngraph3\n\n# output\n\nAn OptiGraph\n graph3 #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 1 1\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 10 10\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We now have three optigraphs (graph1,graph2, and graph3), each with their own local optinodes and optiedges. These three optigraphs can be embedded into a higher level optigraph using the following snippet:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> graph0 = OptiGraph(;name=:root_graph)\nAn OptiGraph\n root_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 0\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 0 0\n Constraints: 0 0\n\n\njulia> add_subgraph(graph0, graph1);\n\njulia> graph0\nAn OptiGraph\n root_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 3\n Edges: 0 1\n Subgraphs: 1 1\n Variables: 0 6\n Constraints: 0 13\n\njulia> add_subgraph(graph0, graph2);\n\njulia> graph0\nAn OptiGraph\n root_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 6\n Edges: 0 2\n Subgraphs: 2 2\n Variables: 0 12\n Constraints: 0 23\n\n\njulia> add_subgraph(graph0, graph3);\n\njulia> graph0\nAn OptiGraph\n root_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 9\n Edges: 0 3\n Subgraphs: 3 3\n Variables: 0 18\n Constraints: 0 33\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Here, we see the distinction between local and total graph elements. After we add all three subgraphs to graph0, we see that it contains 0 local optinodes, but contains 9 total optinodes which are elements of its subgraphs. This hierarchical distinction is also made for optiedges and nested subgraphs.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Using this nested approach, linking constraints can be expressed both locally and globally. For instance, we can add a linking constraint to graph0 that connects optinodes across its subgraphs like following:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> @linkconstraint(graph0, nodes1[3][:x] + nodes2[2][:x] + nodes3[1][:x] == 10)\nnodes1[3][:x] + nodes2[2][:x] + nodes3[1][:x] = 10\n\njulia> graph0\nAn OptiGraph\n root_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 9\n Edges: 1 4\n Subgraphs: 3 3\n Variables: 0 18\n Constraints: 1 34\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"graph0 now contains 1 local edge, and 4 total edges (3 from the subgraphs). The higher level edge linking constraint can be thought of as a global constraint that connects subgraphs. This hierarchical construction can be useful for developing optimization problems separately and then coupling them in a higher level optigraph.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We can lastly plot the hierarchical optigraph and see the nested subgraph structure.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"using PlasmoPlots\n\nplt_graph0 = PlasmoPlots.layoutplot(\n graph0,\n node_labels=true,\n markersize=60,\n labelsize=30,\n linewidth=4,\n subgraph_colors=true,\n layout_options = Dict(\n :tol=>0.001,\n :C=>2,\n :K=>4,\n :iterations=>5\n )\n)\n\nplt_matrix0 = PlasmoPlots.matrix_plot(\n graph0,\n node_labels = true,\n subgraph_colors = true,\n markersize = 16\n)","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"(Image: graph_modeling2) (Image: matrix_modeling2)","category":"page"},{"location":"documentation/modeling/#Query-OptiGraph-Attributes","page":"Modeling with OptiGraphs","title":"Query OptiGraph Attributes","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Plasmo.jl offers various methods to inspect the optigraph data structures (see the API Documentation for a full list). We can use local_nodes to retrieve an array of the optinodes contained directly within an optigraph, or we can use all_nodes to recursively retrieve all of the optinodes in an optigraph (which includes the nodes in its subgraphs).","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> local_nodes(graph1)\n3-element Vector{OptiNode{OptiGraph}}:\n n1\n nodes1[2]\n nodes1[3]\n\njulia> local_nodes(graph0)\nOptiNode{OptiGraph}[]\n\njulia> all_nodes(graph0)\n9-element Vector{OptiNode{OptiGraph}}:\n n1\n nodes1[2]\n nodes1[3]\n nodes2[1]\n nodes2[2]\n nodes2[3]\n nodes3[1]\n nodes3[2]\n nodes3[3]\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"It is also possible to query for optiedges in the same way using local_edges and all_edges.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> local_edges(graph1)\n1-element Vector{OptiEdge{OptiGraph}}:\n graph1.e1\n\njulia> local_edges(graph0)\n1-element Vector{OptiEdge{OptiGraph}}:\n root_graph.e1\n\njulia> all_edges(graph0)\n4-element Vector{OptiEdge{OptiGraph}}:\n root_graph.e1\n graph1.e1\n graph2.e1\n graph3.e1\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We can query linking constraints using local_link_constraints and all_link_constraints.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> local_link_constraints(graph1)\n1-element Vector{ConstraintRef}:\n n1[:x] + nodes1[2][:x] + nodes1[3][:x] = 3\n\njulia> local_link_constraints(graph0)\n1-element Vector{ConstraintRef}:\n nodes1[3][:x] + nodes2[2][:x] + nodes3[1][:x] = 10\n\njulia> all_link_constraints(graph0)\n4-element Vector{ConstraintRef}:\n nodes1[3][:x] + nodes2[2][:x] + nodes3[1][:x] = 10\n n1[:x] + nodes1[2][:x] + nodes1[3][:x] = 3\n nodes2[1][:x] + nodes2[2][:x] + nodes2[3][:x] = 5\n nodes3[1][:x] + nodes3[2][:x] + nodes3[3][:x] = 7","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We can lastly query subgraphs using local_subgraphs and all_subgraphs methods.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> local_subgraphs(graph0)\n3-element Vector{OptiGraph}:\n An OptiGraph\n graph1 #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 1 1\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 13 13\n\n An OptiGraph\n graph2 #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 1 1\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 10 10\n\n An OptiGraph\n graph3 #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 1 1\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 10 10\n","category":"page"},{"location":"documentation/modeling/#Managing-Solutions-with-OptiGraphs","page":"Modeling with OptiGraphs","title":"Managing Solutions with OptiGraphs","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"While it is common to use an optigraph as a means to build a singular optimization problem that can be solved with standard solvers, it is also possible to come up with custom solution strategies that consist of solving smaller subgraphs or use optigraph elements to generate new optigraphs we can solve. To demonstrate, assume we first want to solve each subgraph in graph0 in isolation. This can be done like following:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"# optimize each subgraph with Ipopt\nfor subgraph in local_subgraphs(graph0)\n @objective(subgraph, Min, sum(all_variables(subgraph)))\n set_optimizer(subgraph, Ipopt.Optimizer)\n set_optimizer_attribute(subgraph, \"print_level\", 0);\n optimize!(subgraph)\nend\n\n# check termination status of each solve\ntermination_status.(local_subgraphs(graph0))\n\n# output\n\n3-element Vector{MathOptInterface.TerminationStatusCode}:\n LOCALLY_SOLVED::TerminationStatusCode = 4\n LOCALLY_SOLVED::TerminationStatusCode = 4\n LOCALLY_SOLVED::TerminationStatusCode = 4","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We can query the value of each solution using value like before, but lets instead specify the graph argument for clarity.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> value(graph1, n1[:x])\n1.0\n\njulia> n2 = graph2[1] # get first node on graph2\nnodes2[1]\n\njulia> round(value(graph2, n2[:x]); digits=2)\n1.67\n\njulia> n3 = graph3[1] # get first node on graph3\nnodes3[1]\n\njulia> round(value(graph3, n3[:x]); digits=2)\n2.33","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Now assume we want to use these subgraph solutions to initialize the full graph solution. We could do this using JuMP.set_start_value like following:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> set_start_value.(all_variables(graph1), value.(graph1, all_variables(graph1)));\n\njulia> set_start_value.(all_variables(graph2), value.(graph2, all_variables(graph2)));\n\njulia> set_start_value.(all_variables(graph3), value.(graph3, all_variables(graph3)));","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Now that each subgraph has a new initial solution, the total initial solution can be used to optimize graph0 since all of the subgraph attributes will be copied over.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> @objective(graph0, Min, sum(all_variables(graph0))); # set graph0 objective\n\njulia> set_optimizer(graph0, Ipopt.Optimizer);\n\njulia> set_optimizer_attribute(graph0, \"print_level\", 0);\n\njulia> optimize!(graph0);\n\njulia> termination_status(graph0)\nLOCALLY_SOLVED::TerminationStatusCode = 4\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"While somewhat simple, this example shows what kinds of model approaches can be taken with Plasmo.jl. Checkout Graph Processing and Analysis for more advanced functionality that makes use of the optigraph structure to define and solve problems.","category":"page"},{"location":"tutorials/gas_pipeline/#Optimal-Control-of-a-Natural-Gas-Network","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"This tutorial shows how to model a natural-gas network optimal control problem by constructing a hierarchical optigraph. We show the resulting structure of the optimization problem and demonstrate how to use Plasmo.jl to partition and decompose the problem. The details of this model and a description of its parameters can be found in this manuscript. The actual implementation of this tutorial can be found in this git repository.","category":"page"},{"location":"tutorials/gas_pipeline/#Problem-Description","page":"Optimal Control of a Natural Gas Network","title":"Problem Description","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"We consider the system of connected pipelines in series shown in the below figure. This linear network includes a gas supply at one end, a time-varying demand at the other end, and twelve compressor stations. The gas junctions connect thirteen pipelines which forms an optigraph with a linear topology.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"(Image: 13pipeline_sketch)","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"We seek to solve an optimal control problem that maximizes revenue over a 24 hour time period given a forecast of gas demand profiles. That is, we wish to obtain a compressor control policy that will meet the gas demand at junction j_25, whilst simultaneously minimizing compressor costs and meeting operational constraints. In the formulation below, alpha_ell and P_ellt are the compression cost ($/kW), and compression power for each compressor ell at time t, and alpha_d and f^target_dt are the demand price and target demand flow for each demand d at time t. This formulation includes physical equations and constraints that describe the network junctions, the pipeline dynamics, and compressors. The network link equations describe how the devices within the topology are coupled together such as conservation of mass and boundary conditions. The sets that describe the elements of the optimization problem are also presented here.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n min_ substack eta_elltf_jdt ell in mathcalL_c d in mathcalD_j j in mathcalJ t in mathcalT quad \n sum_substackell in mathcalL_c t in mathcalT alpha_ell P_ellt -\n sum_substackd in mathcalD_j j in mathcalJ t in mathcalT alpha_jd f_jdt \n st quad textJunction Limits \n textPipeline Dynamics \n textCompressor Equations \n textNetwork Link Equations \nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/#Sets-used-for-optimization-problem","page":"Optimal Control of a Natural Gas Network","title":"Sets used for optimization problem","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Set Description Elements\nmathcalJ Set of gas network nodes j in mathcalJ\nmathcalS Set of gas supplies s in mathcalS\nmathcalD Set of gas demands d in mathcalD\nmathcalD_j Set of gas demands on junction j d in mathcalD_j\nmathcalS_j Set of gas supplies on junction j s in mathcalS_j\nmathcalL Set of gas network links ell in mathcalL\nmathcalL_p Set of network pipeline links mathcalL_p subseteq mathcalL\nmathcalL_c Set of network compressor links mathcalL_c subseteq mathcalL\nmathcalX Set of spatial discretization points k in mathcalX\nmathcalT Set of temporal discretization points t in mathcalT","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The following sections describe each component of the network in further detail.","category":"page"},{"location":"tutorials/gas_pipeline/#Junction-OptiGraph","page":"Optimal Control of a Natural Gas Network","title":"Junction OptiGraph","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The gas junctions in a gas network describe the connection points between pipelines and compressors. The junction model is described by the below equations, where theta_jt is the pressure at junction j and time t. underlinetheta_j is the lower pressure bound for the junction, overlinetheta_j is the upper pressure bound, f_jdt^target is the target demand flow for demand d on junction j and overlinef_js is the available gas generation from supply s on junction j.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n underlinetheta_j le theta_jt le overlinetheta_j quad j in mathcalJ t in mathcalT \n 0 le f_jdt le f_jdt^target quad d in mathcalD_j j in mathcalJ t in mathcalT \n 0 le f_jst le overlinef_js quad s in mathcalS_j j in mathcalJ t in mathcalT\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The optigraph that is used to create the junction model is given by the following julia function. We define the function create_junction_model which accepts junction specific data and the number of time periods nt. We create the optigraph graph, add an optignode for each time interval (using @optinode), and then create the variables and constraints for each node in a loop. We also use the JuMP specific @expression macro to refer to expressions for total gas supplied, total gas delivered, and total cost for convenience. The junction optigraph is finally returned from the function","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"#Define function to create junction model-graph\nfunction create_junction_model(data,nt)\n graph = OptiGraph()\n\n #Add model-node for each time interval\n @optinode(graph, nodes[1:nt])\n\n #query number of supply and demands on the junction\n n_demands = length(data[:demand_values])\n n_supplies = length(data[:supplies])\n\n #Loop and create variables, constraints, and objective for each model-node\n for (i,node) in enumerate(nodes)\n @variable(node, data[:pmin] <= pressure <= data[:pmax], start=60)\n @variable(node, 0 <= fgen[1:n_supplies] <= 200, start=10)\n @variable(node, fdeliver[1:n_demands] >= 0)\n @variable(node, fdemand[1:n_demands] >= 0)\n\n @constraint(node,[d = 1:n_demands],fdeliver[d] <= fdemand[d])\n\n @expression(node, total_supplied, sum(fgen[s] for s = 1:n_supplies))\n @expression(node, total_delivered,sum(fdeliver[d] for d = 1:n_demands))\n @expression(node, total_delivercost,sum(1000*fdeliver[d] for d = 1:n_demands))\n\n @objective(node,Min,total_delivercost)\n end\n\n #Return the junction graph\n return graph\nend","category":"page"},{"location":"tutorials/gas_pipeline/#Compressor-OptiGraph","page":"Optimal Control of a Natural Gas Network","title":"Compressor OptiGraph","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Compressors constitute the primary control decisions in the optimal control problem and are described by the following simple formulation. We use an ideal isentropic compressor model where eta_ellt, p_ellt^in, and p_ellt^out are the compression ratio, suction pressure, and discharge pressure at time t, and P_ellt is power at time t. We also introduceduce the dummy variables f_ellt^in and f_ellt^out to be consistent with the pipeline model in the next section.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n p_ellt^out = eta_ellt p_ellt^in quad ell in mathcalL_cquad t in mathcalT \n P_ellt = c_p cdot T cdot f_ellt left(left(fracp_ellt^outp_ellt^inright)^fracgamma-1gamma-1right)\n quad ell in mathcalL_cquad t in mathcalT\n f_ellt = f_ellt^in = f_ellt^out quad ell in mathcalL_cquad t in mathcalT\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The compressor optigraph construction is straightforward as shown by the following Julia code. Like the above model, we define a function called create_compressor_model to create a compressor optigraph given data and number of time periods nt. We create the compressor optigraph by creating nodes, variables, and constraints, as well as expressions to refer to flow in and out of each compressor. We lastly return the created optigraph from the function.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"function create_compressor_model(data,nt)\n #Create compressor model-graph\n graph = OptiGraph()\n @optinode(graph,nodes[1:nt])\n\n #Setup variables, constraints, and objective\n for node in nodes\n @variable(node, 1 <= psuction <= 100)\n @variable(node, 1 <= pdischarge <= 100)\n @variable(node, 0 <= power <= 1000)\n @variable(node, flow >= 0)\n @variable(node, 1 <= eta <= 2.5)\n @constraint(node, pdischarge == eta*psuction)\n @constraint(node, power == c4*flow*((pdischarge/psuction)^om-1) )\n @objective(node, Min, cost*power*(dt/3600.0))\n end\n\n #Create references for flow in and out\n @expression(graph, fin[t=1:nt], nodes[t][:flow])\n @expression(graph, fout[t=1:nt], nodes[t][:flow])\n\n #Return compressor graph\n return graph\nend","category":"page"},{"location":"tutorials/gas_pipeline/#Pipeline-OptiGraph","page":"Optimal Control of a Natural Gas Network","title":"Pipeline OptiGraph","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"We now implement the pipeline equations to describe the dynamic transport throughout the gas network. For each pipeline model we assume isothermal flow through horizontal segments with constant pipe friction. We ultimately produce the following discretized pipeline model.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n fracp_ellt+1k - p_elltkDelta t = -c_1ell fracf_ellt+1k+1 - f_ellt+1kDelta x_ell ell in mathcalL_pt in mathcalT k in mathcalX_ell \n fracf_ellt+1k - f_elltkDelta t = -c_2ellfracp_ellt+1k+1 - p_ellt+1kDelta x_ell - c_3ellfracf_ellt+1k f_ellt+1kp_ellt+1k ell in mathcalL_p t in mathcalT k in mathcalX_ell \n f_elltN_x = f_ellt^outquad ell in mathcalL_p quad t in mathcalT \n f_ellt1 = f_ellt^inquad ell in mathcalL_p quad t in mathcalT \n p_elltN_x = p_ellt^outquad ell in mathcalL_p quad t in mathcalT \n p_ellt1 = p_ellt^inquad ell in mathcalL_p quad t in mathcalT\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The model contains the transport equations defined in terms of pressure and mass flow rate. It also contains dummy flows and pressures which represent in the inlet and outlet flow and pressure into each pipeline segment. We also express a steady-state initial condition which is typical for this control problem and is given by the following equations.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n fracf_ell1k+1 - f_ell1kDelta x = 0 quad ell in mathcalL_p k in mathcalX_ell \n c_2ellfracp_ell1k - p_ell1kDelta x + c_3fracf_ell1k f_ell1kp_ell1k = 0\n quad ell in mathcalL_p k in mathcalX_ell\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Lastly, we require the total line-pack in each segment (i.e. the inventory of gas) to be refilled at the end of the planning horizon. This is represented by the following approximation of line-pack and constraint for refilling it.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n m_ellt = fracA_ellc^2 sum_k=1^N_x p_elltk Delta x_ellquad ell in mathcalL_p t in mathcalT \n m_ellN_t ge m_ell1 quad ell in mathcalL_p\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"We express the pipeline model with optinodes distributed on a space-time grid. Specifically, the nodes of each pipeline optigraph form a nt x nx grid wherein pressure and flow variables are assigned to each node. Flow dynamics within pipelines are then expressed with linking constraints that describe the discretized PDE equations for mass and momentum using finite differences. We lastly include linking constraints that represent the initial steady-state condition and line-pack constraint.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"function create_pipeline_model(data, nt, nx)\n #unpack data\n c1 = data[:c1]; c2 = data[:c2]; c3 = data[:c3]\n dx = data[:pipe_length] / (nx - 1)\n\n #Create pipeline model-graph\n graph = OptiGraph()\n\n #Create grid of optinodes\n @optinode(graph, grid[1:nt,1:nx])\n\n #Create variables on each node in the grid\n for node in grid\n @variable(node, 1 <= px <= 100)\n @variable(node, 0 <= fx <= 100)\n @variable(node, slack >= 0)\n @constraint(node, slack*px - c3*fx*fx == 0)\n end\n\n # setup useful expressions\n @expression(graph, fin[t=1:nt], grid[:,1][t][:fx])\n @expression(graph, fout[t=1:nt], grid[:,end][t][:fx])\n @expression(graph, pin[t=1:nt], grid[:,1][t][:px])\n @expression(graph, pout[t=1:nt], grid[:,end][t][:px])\n @expression(graph, linepack[t=1:nt], c2/A*sum(grid[t,x][:px]*dx for x in 1:nx-1))\n\n # finite differencing. Backward difference in time from t, forward difference in space from x.\n @linkconstraint(\n graph, \n press[t=2:nt,x=1:nx-1],\n (grid[t,x][:px]-grid[t-1,x][:px])/dt + c1*(grid[t,x+1][:fx] - grid[t,x][:fx])/dx == 0\n )\n\n @linkconstraint(\n graph, \n flow[t=2:nt,x=1:nx-1], \n (grid[t,x][:fx] - grid[t-1,x][:fx])/dt == -c2*(grid[t,x+1][:px] - grid[t,x][:px])/dx - grid[t,x][:slack]\n )\n\n # initially at steady state\n @linkconstraint(graph, ssflow[x=1:nx-1], grid[1,x+1][:fx] - grid[1,x][:fx] == 0)\n @linkconstraint(\n graph, \n sspress[x = 1:nx-1], \n -c2*(grid[1,x+1][:px] - grid[1,x][:px])/dx - grid[1,x][:slack] == 0\n )\n\n # refill pipeline linepack\n @linkconstraint(graph, linepack[end] >= linepack[1])\n return graph\nend","category":"page"},{"location":"tutorials/gas_pipeline/#Network-OptiGraph","page":"Optimal Control of a Natural Gas Network","title":"Network OptiGraph","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The network connections define the topology that connect junctions and equipment links (i.e. pipelines and compressors). Specifically, the network equations express mass conservation around each junction and boundary conditions for pipelines and compressors. Mass conservation around each junction j is given by the following equation.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n sum_ellinmathcalL_rec(j) f^out_ellt - sum_ell inmathcalL_snd(j) f^in_ellt +\n sum_sinmathcalS_jf_jst - sum_din mathcalD_jf_jdt = 0 quad j inmathcalJ\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"where we define mathcalL_rec(j) and mathcalL_snd(j) as the set of receiving and sending links to each junction j respectively.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The next equations define pipeline and compressor link boundary conditions.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n p_ellt^in = theta_rec(ell)t quad ell in mathcalL t in mathcalT \n p_ellt^out = theta_snd(ell)t quad ell in mathcalL t in mathcalT\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Here, theta_rec(ell)t and theta_snd(ell)t are the receiving and sending junction pressure for each link ell in mathcalL at time t.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The Julia code required to create the network optigraph is a bit more involved, but mostly because we have to define some data structures to capture the network topology. The below piece of code defines the function create_gas_network which accepts a dictionary of network data and calls the above defined functions to create the hierarchical optigraph. That is, the below code creates junction, compressor, and pipeline optigraphs, adds these optigraphs as subgraphs within a higher level network optigraph, and then creates linking constraints that couple the subgraphs to eachother in the form of mass conservation and boundary conditions.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"function create_gas_network(net_data)\n pipe_data = net_data[:pipeline_data]\n comp_data = net_data[:comp_data]\n junc_data = net_data[:junc_data]\n pipe_map = net_data[:pipe_map]; comp_map = net_data[:comp_map]\n\n #Create OptiGraph for entire gas network\n network = OptiGraph()\n network[:pipelines] = [];network[:compressors] = [];network[:junctions] = []\n j_map = Dict()\n\n #Create device OptiGraphs and setup data structures\n for j_data in junc_data\n junc= create_junction_optigraph(j_data)\n add_subgraph(network,junc); push!(network[:junctions],junc)\n j_map[j_data[:id]] = junc\n junc[:devices_in] = []; junc[:devices_out] = []\n end\n for p_data in pipe_data\n pipe = create_pipeline_optigraph(p_data); push!(network[:pipelines],pipe)\n add_subgraph(network,pipe);\n pipe[:junc_from] = j_map[p_data[:junc_from]]\n pipe[:junc_to] = j_map[p_data[:junc_to]]\n push!(pipe[:junc_from][:devices_out],pipe); push!(pipe[:junc_to][:devices_in],pipe)\n end\n for c_data in comp_data\n comp = create_compressor_optigraph(c_data)\n add_subgraph(gas_network,comp); comp[:data] = c_data\n comp[:junc_from] = j_map[c_data[:junc_from]]\n comp[:junc_to] = j_map[c_data[:junc_to]]\n push!(comp[:junc_from][:devices_out],comp); push!(comp[:junc_to][:devices_in],comp)\n end\n\n # link pipelines in gas network\n for pipe in network[:pipelines]\n junc_from,junc_to = [pipe[:junc_from],pipe[:junc_to]]\n @linkconstraint(network,[t = 1:nt],pipe[:pin][t] == junc_from[:pressure][t])\n @linkconstraint(gas_network,[t = 1:nt],pipe[:pout][t] == junc_to[:pressure][t])\n end\n\n # link compressors in gas network\n for comp in network[:compressors]\n junc_from,junc_to = [comp[:junc_from].comp[:junc_to]]\n @linkconstraint(network,[t = 1:nt],comp[:pin][t] == junc_from[:pressure][t])\n @linkconstraint(network,[t = 1:nt],comp[:pout][t] == junc_to[:pressure][t])\n end\n\n # link junctions in gas network\n for junc in network[:junctions]\n devices_in = junc[:devices_in]; devices_out = junc[:devices_out]\n\n flow_in = [sum(device[:fout][t] for device in devices_in) for t = 1:nt]\n flow_out = [sum(device[:fin][t] for device in devices_out) for t = 1:nt]\n\n total_supplied = [junction[:total_supplied][t] for t = 1:nt]\n total_delivered = [junction[:total_delivered][t] for t = 1:nt]\n\n @linkconstraint(\n gas_network,\n [t = 1:nt], \n flow_in[t] - flow_out[t] + total_supplied[t] - total_delivered[t] == 0\n )\n end\n return gas_network\nend","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Using the above function, we can obtain a complete optigraph representation of the optimal control problem. It is now possible to plot the graph layout using Plotting functions or export the graph structure and use another graph visualization tool. Gephi was used to produce the below figure. Here, the green colors correspond to compressor nodes, blue corresponds to junctions, and grey corresponds to pipelines. Notice that the optigraph captures the space-time structure of the optimization problem. We also observe a cylindrical shape to the problem which results from the line-pack constraint which couples the initial and final time optinodes for each pipeline.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"(Image: space_time)","category":"page"},{"location":"tutorials/gas_pipeline/#Partitioning","page":"Optimal Control of a Natural Gas Network","title":"Partitioning","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Now that we have an optigraph representation of our optimal control problem, we can use hypergraph partitioning to decompose the space-time structure. To do so, we use KaHyPar and the functions described in Graph Partitioning and Processing. the below code creates a hypergraph representation of the optigraph, sets up node and edge weights, partitions the problem, and forms new subgraphs based on the partitions. We also aggregate the subgraphs to produce solvable optinode subproblems which will communicate to our solver.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"# import the KaHyPar interface\nusing KaHyPar\n\n# get the hypergraph representation of the gas network\nprojection = hyper_projection(gas_network)\n\n# setup node and edge weights\nn_vertices = length(vertices(hgraph))\nnode_weights = [num_variables(node) for node in all_nodes(gas_network)]\nedge_weights = [num_constraints(edge) for edge in all_edges(gas_network)]\n\n#Use KaHyPar to partition the hypergraph\nnode_vector = KaHyPar.partition(\n projection,\n 13,\n configuration=:edge_cut,\n imbalance=0.01,\n node_weights=node_weights,\n edge_weights=edge_weights\n)\n\n# create a Partition object\npartition = Partition(projection, node_vector)\n\n# setup subgraphs based on the partition\napply_partition!(gas_network, partition)","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The partitioned optimal control problem is visualized in the below figure and depicts the optimization problem partitioned into 13 distinct partitions.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"(Image: partition)","category":"page"},{"location":"tutorials/gas_pipeline/#Querying-Solutions","page":"Optimal Control of a Natural Gas Network","title":"Querying Solutions","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The modular construction of an OptiGraph often results in variables with the same names being stored on different subgraphs or nodes, and accessing variables in OptiGraphs with multiple nodes or subgraphs is not always as simple as calling the symbol for the variable. Variable references for variables in a graph can be thought of as being \"nested\" on nodes that are \"nested\" on subgraphs. The \"owning\" subgraph can be accessed by calling getsubgraphs(::OptiGraph). In addition, in the above code, each pipeline, junction, and compressor subgraph was saved on the OptiGraph gas_network using the symbols :pipeline, :junction, and :compressor, respectively. Thus, the subgraphs for each of these objects can be called by using these symbols, such as using gas_network[:pipeline] to get the vector of all pipeline subgraphs. Importantly, the vectors of subgraphs are not in the order they appear in the network because they were formed by iterating through entries to dictionaries, which are order-free. In other words, the pipelines are not in order from 1 - 13. To identify the order the pipelines, junctions, and compressors appear in their respective vectors, the user will have to track the order to which these are added in the for loop.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"As an example of the \"nested\" nature of the variables, we can consider the problem above. There is a set of nodes called grid (that contain nt times nx nodes) for each pipeline OptiGraph, and each of these nodes contain a variable called px and fx. To access the px variable at t = 1 and x = 2 on the first pipeline subgraph of the vector, we can call gas_network[:pipelines][1][:grid][1, 2][:px]. Here, [:pipelines] acesses the vector of pipelines, [1] accesses the first subgraph of that vector, [:grid] accesses the set of nodes called \"grid\", [1, 2] accesses the node at time t = 1 and x = 2, and [:px] accesses the variable called px. Alternatively, instead of calling gas_network[:pipelines][1], the same pipeline subgraph could be accessed by calling getsubgraphs(gas_network)[26]. Subgraphs appear in getsubgraphs(::OptiGraph) in the order they were added to the OptiGraph; as the junctions are added to gas_network first, followed by pipelines and then compressors in the above code, the first 25 entries of getsubgraphs(gas_network) will correspond to junctions and entries 26 - 38 will contain the pipelines. With the variable references, a user can query solutions to these variables after calling optimize!(gas_network) by using JuMP.value, such as calling value(gas_network[:pipelines][1][:grid][1, 2][:px]).","category":"page"},{"location":"documentation/graph_processing/#Graph-Processing-and-Analysis","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"In Modeling with OptiGraphs we describe how to construct optigraphs using a bottom-up approach that manages problem structure using nodes, edges, and subgraphs. Plasmo.jl also supports managing optigraphs in a more top-down manner using graph analysis functions and interfaces to standard graph partitioning tools such as Metis and KaHyPar. ","category":"page"},{"location":"documentation/graph_processing/#Illustrative-Example:-Dynamic-Optimization","page":"Graph Processing and Analysis","title":"Illustrative Example: Dynamic Optimization","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"To help demonstrate some graph processing capabilities in Plasmo.jl, we construct a simple optimal control problem described by the following equations. In this problem, x is a vector of states and u is a vector of control actions which are both indexed over the set of time indices t in 1T. The objective function minimizes the state trajectory distance from zero with minimal control effort, the second equation describes the state dynamics, and the third equation defines the initial condition. The last two equations define limits on the state and control actions.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"beginaligned\n min_ xu sum_t = 1^T x_t^2 + u_t^2 \n textrmst quad x_t+1 = x_t + u_t + d_t quad t in 1T-1 \n x_1 = 0 \n x_t ge 0 quad t in 1T\n u_t ge -1000 quad t in 1T-1\nendaligned","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"This snippet shows how to construct the optimal control problem in Plasmo.jl. We create an optigraph, we add optinodes which contain states and controls at each time period, we setup objective functions for each node, and we use linking constraints to describe the dynamics (since each node represents a point in time). When we print the newly created optigraph for our optimal control problem, we see it contains about 200 optinodes (one for each state and control) and contains almost 100 linking constraints (which couple the time periods).","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"using Plasmo\n\nT = 100 # number of time points\nd = sin.(1:T) # disturbance vector (a sin wave)\n\ngraph = OptiGraph(;name=:optimal_control)\n@optinode(graph, state[1:T])\n@optinode(graph, control[1:T-1])\n\nfor node in state\n @variable(node, x)\n @constraint(node, x >= 0)\n @objective(node, Min, x^2)\nend\nfor node in control\n @variable(node, u)\n @constraint(node, u >= -1000)\n @objective(node, Min, u^2)\nend\n\n@linkconstraint(graph, [i = 1:T-1], state[i+1][:x] == state[i][:x] + control[i][:u] + d[i])\nJuMP.fix(state[1][:x], 0)\n\ngraph\n\n# output\n\nAn OptiGraph\n optimal_control #local elements #total elements\n--------------------------------------------------\n Nodes: 199 199\n Edges: 99 99\n Subgraphs: 0 0\n Variables: 199 199\n Constraints: 299 299\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We can also plot the resulting optigraph (see [Plotting]) which produces a simple chain of optinodes.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"using PlasmoPlots\n\nplt_chain_layout = layout_plot(\n graph,\n layout_options=Dict(:tol=>0.1,:iterations=>500),\n linealpha = 0.2,\n markersize = 6\n)\n\nplt_chain_matrix = matrix_plot(graph)","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"(Image: partition_layout_1) (Image: partition_matrix_1)","category":"page"},{"location":"documentation/graph_processing/#OptiGraph-Projections","page":"Graph Processing and Analysis","title":"OptiGraph Projections","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Plasmo.jl lets us query optigraph properties such as all_neighbors, induced_subgraph, and incident_edges. Before we can query any of these properties, we need to create a hypergraph representation of the optigraph using a Plasmo.GraphProjection. Specifically, we want to create a hypergraph projection using hyper_projection method.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> projection = hyper_projection(graph)\nGraph Projection: Plasmo.HyperGraphProjectionType()\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"note: Note\nPlasmo.jl contains a few different Graph Projections. The hypergraph is the most natural representation of an optigraph and is used to perform most processing tasks such as querying neighbors and incident edges. Other projections can be useful for various graph analyses, but no examples exist right now beyond graph partitioning. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"The next most useful projection is probably the clique_projection. This projection replaces each hyperedge with a set of standard edges to create a standard graph (where edges strictly connect 2 nodes). This projection internally contains a Graphs.SimpleGraph.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> clique_proj = clique_projection(graph)\nGraph Projection: Plasmo.CliqueGraphProjectionType()\n\njulia> clique_proj.projected_graph\n{199, 297} undirected simple Int64 graph\n","category":"page"},{"location":"documentation/graph_processing/#Querying-Topology","page":"Graph Processing and Analysis","title":"Querying Topology","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Once we have a projection we can run all sorts of methods to query the topology, many of which are extended from Graphs.jl. The below snippet demonstrates some of the primary methods. We first grab two nodes to start some examples. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> node1 = graph[1]\nstate[1]\n\njulia> node2 = graph[2]\nstate[2]","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Remember, state[2] is a function of state[1] and control[1] based on our modeled equations. We can query the neighbors of node1 (state[1]) to confirm this. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> all_neighbors(projection, node1)\n2-element Vector{OptiNode{OptiGraph}}:\n state[2]\n control[1]","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We can query the neighborhood around a node within a given distance (this function also returns the queried node). For this example we use a distance of 1. We can also query a neighborhood given a set of nodes.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> neighborhood(projection, [node1], 1) \n3-element Vector{OptiNode{OptiGraph}}:\n state[1]\n state[2]\n control[1]\n\njulia> neighborhood(projection, [node1, node2], 1)\n5-element Vector{OptiNode{OptiGraph}}:\n state[1]\n state[2]\n control[1]\n state[3]\n control[2]\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We can look at edges incident to a single node or set of nodes. Note that state[1] has one incident edge that connects it to control[1] and state[2]. We verify this by using all_nodes on the returned edge.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> node1_incident = incident_edges(projection, node1) \n1-element Vector{OptiEdge{OptiGraph}}:\n optimal_control.e1\n\njulia> edge1_nodes = all_nodes(node1_incident[1])\n3-element Vector{OptiNode}:\n state[2]\n state[1]\n control[1]\n\njulia> incident_edges(projection, [node1,node2])\n2-element Vector{OptiEdge{OptiGraph}}:\n optimal_control.e1\n optimal_control.e2\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We lastly show how to query the edges induced by a set of nodes. These are all edges that connect the given nodes.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> induced = induced_edges(projection, edge1_nodes)\n1-element Vector{OptiEdge}:\n optimal_control.e1\n","category":"page"},{"location":"documentation/graph_processing/#Assembling-New-OptiGraphs","page":"Graph Processing and Analysis","title":"Assembling New OptiGraphs","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"A key capability that derives from graph topology queries is the ability to create new optigraphs from subsets of nodes and edges. This is primarily done with the assemble_optigraph(@ref) method which some topology functions implicity call. Here we show how to create new optigraphs using some of these methods.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> queried_nodes = neighborhood(projection, [node1], 5)\n11-element Vector{OptiNode{OptiGraph}}:\n state[1]\n state[2]\n control[1]\n state[3]\n control[2]\n state[4]\n control[3]\n state[5]\n control[4]\n state[6]\n control[5]\n\njulia> queried_edges = induced_edges(projection, queried_nodes)\n5-element Vector{OptiEdge}:\n optimal_control.e1\n optimal_control.e2\n optimal_control.e3\n optimal_control.e4\n optimal_control.e5\n\njulia> new_graph = assemble_optigraph(queried_nodes, queried_edges; name=:new_graph)\nAn OptiGraph\n new_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 11 11\n Edges: 5 5\n Subgraphs: 0 0\n Variables: 11 11\n Constraints: 17 17\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"warning: Warning\nYou must pass valid nodes and edges to assemble_optigraph. All of the edges must be connected to the given nodes.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"An easy alternative to using assemble_optigraph is to use induced_subgraph which takes a vector of nodes and does the above operations internally.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> new_graph = induced_subgraph(projection, queried_nodes; name=:induced_graph)\nAn OptiGraph\n induced_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 11 11\n Edges: 5 5\n Subgraphs: 0 0\n Variables: 11 11\n Constraints: 17 17\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We can lastly expand a set of nodes to create a new graph. We can provide either a subgraph (an optigraph) or a set of nodes to expand with. This would look like the following:","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> expanded_graph = expand(projection, new_graph, 1; name=:expanded_graph)\nAn OptiGraph\n expanded_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 13 13\n Edges: 6 6\n Subgraphs: 0 0\n Variables: 13 13\n Constraints: 20 20\n\njulia> expanded_with_nodes = expand(projection, queried_nodes, 1; name=:expanded_nodes)\nAn OptiGraph\n expanded_nodes #local elements #total elements\n--------------------------------------------------\n Nodes: 13 13\n Edges: 6 6\n Subgraphs: 0 0\n Variables: 13 13\n Constraints: 20 20\n","category":"page"},{"location":"documentation/graph_processing/#Partitioning-OptiGraphs","page":"Graph Processing and Analysis","title":"Partitioning OptiGraphs","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Plasmo.jl supports partitioning optigraphs wherein partitions of nodes and edges can be used to assemble optigraphs that contain subgraphs. This allows users to reveal and create nested optigraph structures that would be difficult (or impractical) to formulate otherwise. Plasmo.jl takes care of creating new optigraphs given partition information. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Since the OptiGraph is a hypergraph at its core, it naturally should interface to various partitioning tools (both standard and hypergraph partitioning). To begin however, we show how to partition an optigraph manually by defining vectors of node partitions. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We first define our manual partition as a vector of vectors. Each internal vector contains the optinodes that correspond to a time interval. In this case, we assemble a vector of 5 time intervals. Using our vector we can construct a Partition object which denotes node and edge partitions and how they are connected.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> all_graph_nodes = all_nodes(graph);\n\njulia> node_vectors = [[state[1:20];control[1:20]],[state[21:40];control[21:40]],[state[41:60];control[41:60]],[state[61:80];control[61:80]],[state[81:100];control[81:99]]];\n\njulia> manual_partition = Partition(graph, node_vectors)\nOptiGraph Partition w/ 5 subpartitions\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Once we construct a Partition, we can assemble a new optigraph from the nodes using assemble_optigraph. Notice that the new graph contains few local elements (just the 4 edges that connect the new subgraphs).","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> new_manual_graph = assemble_optigraph(manual_partition; name=:partitioned_graph)\nAn OptiGraph\npartitioned_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 199\n Edges: 4 99\n Subgraphs: 5 5\n Variables: 0 199\n Constraints: 4 299\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"note: Note\nWe can also modify an existing graph using apply_partition! versus creating a new graph. This can be useful for reducing memory requirements but it keep in mind it fundamentally alters the optigraph structure. Also note that this method is somewhat experimental; we suggest using assemble_optigraph if performance is not critical.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We now demonstrate how to use the hypergraph partitioning with KaHyPar.jl using the hyper_projection we created above. The general workflow is straightforward:","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> using KaHyPar\n\njulia> using Suppressor # suppress KaHyPar output\n\njulia> partition_vector = @suppress KaHyPar.partition(projection, 8, configuration=:connectivity, imbalance=0.01);\n\njulia> partition_kahypar = Partition(projection, partition_vector)\nOptiGraph Partition w/ 8 subpartitions\n\njulia> kahypar_graph = assemble_optigraph(partition_kahypar; name=:kahypar_graph)\nAn OptiGraph\n kahypar_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 199\n Edges: 7 99\n Subgraphs: 8 8\n Variables: 0 199\n Constraints: 7 299\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"In this case, we ended up with a similar partition to the manual one (where instead we ask for 8 partitions as KaHyPar makes it easy to do so). In most cases, the best partition is not this obvious.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"warning: Warning\nKaHyPar does not currently build on Windows. If you are interested in using graph partitioning with Plasmo.jl, read on to see how you can use Metis. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"note: Note\nPlasmo.jl contains a direct interface to KaHyPar which is used here. In general, a user can always construct the manual partition vector however they wish and generate a Partition object.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"As a final example, we show how one might use Metis to partition this optigraph using the clique_projection presented earlier.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> using Metis\n\njulia> clique_proj\nGraph Projection: Plasmo.CliqueGraphProjectionType()\n\njulia> simple_graph = clique_proj.projected_graph\n{199, 297} undirected simple Int64 graph\n\njulia> metis_vector = Int64.(Metis.partition(simple_graph, 5)); # Plasmo.jl requires Int64 vectors.\n\njulia> partition_metis = Partition(clique_proj, metis_vector)\nOptiGraph Partition w/ 5 subpartitions\n\njulia> metis_graph = assemble_optigraph(partition_metis; name=:metis_graph)\nAn OptiGraph\n metis_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 199\n Edges: 4 99\n Subgraphs: 5 5\n Variables: 0 199\n Constraints: 4 299\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"If we plot and of the above partitioned optigraphs, it reveals five distinct partitions and the coupling between them. The plots show that the partitions are well-balanced and the matrix visualization shows the problem is reordered into a banded structure that is typical of dynamic optimization problems.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"plt_chain_partition_layout = layout_plot(\n kahypar_graph,\n layout_options=Dict(\n :tol=>0.01,\n :iterations=>500\n ),\n linealpha=0.2,\n markersize=6,\n subgraph_colors=true\n)\n )\n\nplt_chain_partition_matrix = matrix_layout(kahypar_graph, subgraph_colors=true)\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"(Image: partition_layout_2) (Image: partition_matrix_2)","category":"page"},{"location":"documentation/graph_processing/#Aggregating-OptiGraphs-(Experimental)","page":"Graph Processing and Analysis","title":"Aggregating OptiGraphs (Experimental)","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Optigraphs can be converted into stand-alone optinodes using the using the aggregate and aggregate_to_depth functions. This can be helpful when the user models using optigraphs, but they want to represent subproblems using optinodes. In the snippet below, we aggregate our optigraph that contains 5 subgraphs. We include the argument 0 which specifies how many subgraph levels to retain. In this case, 0 means we aggregate subgraphs at the highest level so graph contains only new aggregated optinodes. For hierarchical graphs with many levels, we can define how many subgraph levels we wish to retain. The function returns a new aggregated graph (aggregate_graph), as well as a reference_map which maps elements in aggregate_graph to the original optigraph graph.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> aggregate_graph, reference_map = aggregate_to_depth(kahypar_graph, 0; name=:agg_graph);\n\njulia> aggregate_graph\nAn OptiGraph\n agg_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 8 8\n Edges: 7 7\n Subgraphs: 0 0\n Variables: 199 199\n Constraints: 299 299\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"note: Note\nA user can also use aggregate! to permanently aggregate an existing optigraph. This avoids maintaining a copy of the original optigraph.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We can lastly plot the aggregated graph structure which simply shows 8 optinodes with 7 linking constraints.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"plt_chain_aggregate = layout_plot(\n aggregate_graph,\n layout_options=Dict(:tol=>0.01,:iterations=>10),\n node_labels=true,\n markersize=30,\n labelsize=20,\n node_colors=true\n);\n\nplt_chain_matrix_aggregate = matrix_plot(\n aggregate_graph,\n node_labels=true,\n node_colors=true\n);\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"(Image: partition_layout_3) (Image: partition_matrix_3)","category":"page"},{"location":"documentation/api_docs/#API-Documentation","page":"API Documentation","title":"API Documentation","text":"","category":"section"},{"location":"documentation/api_docs/#OptiGraph-Methods","page":"API Documentation","title":"OptiGraph Methods","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"OptiGraph\nOptiNode\nOptiEdge\n@optinode\n@linkconstraint\n@nodevariables\nset_to_node_objectives\ngraph_backend\ngraph_index\nsource_graph\nadd_subgraph\nlocal_subgraphs\nall_subgraphs\nnum_local_subgraphs\nnum_subgraphs\nadd_node\nget_node\nlocal_nodes\nall_nodes\ncollect_nodes\nnum_local_nodes\nnum_nodes\nnum_local_variables\nadd_edge\nget_edge\nget_edge_by_index\nPlasmo.has_edge\nlocal_edges\nall_edges\nnum_local_edges\nnum_edges\nnum_local_link_constraints\nnum_link_constraints\nlocal_link_constraints\nall_link_constraints\nnum_local_constraints\nlocal_constraints\nlocal_elements\nall_elements\nBase.getindex(::OptiGraph, ::Int)","category":"page"},{"location":"documentation/api_docs/#Plasmo.OptiGraph","page":"API Documentation","title":"Plasmo.OptiGraph","text":"OptiGraph\n\nThe core modeling object of Plasmo.jl. An optigraph represents an optimization model as a set of OptiNode and OptiEdge objects.\n\n\n\n\n\n","category":"type"},{"location":"documentation/api_docs/#Plasmo.OptiNode","page":"API Documentation","title":"Plasmo.OptiNode","text":"OptiNode{GT<:AbstractOptiGraph} <: AbstractOptiNode\n\nA data structure meant to encapsulate variables, constraints, an objective function, and other model data. An optinode is \"lightweight\" in the sense that it does not directly contain model data, but instead acts as an interface that maps to a backend where the model data is stored. This avoids the need to generate memory overhead through container structures in cases when a node contains very little model data.\n\n\n\n\n\n","category":"type"},{"location":"documentation/api_docs/#Plasmo.OptiEdge","page":"API Documentation","title":"Plasmo.OptiEdge","text":"OptiEdge{GT<:AbstractOptiGraph} <: AbstractOptiEdge\n\nA data structure meant to encapsulate linking constraints and other model data. An optiedge is \"lightweight\" in the sense that it does not directly contain model data, but instead acts as an interface that maps to a backend where the model data is stored. This avoids the need to generate memory overhead through container structures in cases when a node contains very little model data.\n\n\n\n\n\n","category":"type"},{"location":"documentation/api_docs/#Plasmo.@optinode","page":"API Documentation","title":"Plasmo.@optinode","text":"@optinode(optigraph, expr...)\n\nAdd a new optinode to optigraph. The expression expr can either be\n\nof the form nodename creating a single optinode with the variable name varname\nof the form nodename[...] or [...] creating a container of optinodes using JuMP Containers\n\n\n\n\n\n","category":"macro"},{"location":"documentation/api_docs/#Plasmo.@linkconstraint","page":"API Documentation","title":"Plasmo.@linkconstraint","text":"@linkconstraint(graph::OptiGraph, expr)\n\nAdd a linking constraint described by the expression expr.\n\n@linkconstraint(graph::OptiGraph, ref[i=..., j=..., ...], expr)\n\nAdd a group of linking constraints described by the expression expr parametrized by i, j, ...\n\nThe @linkconstraint macro works the same way as the JuMP.@constraint macro.\n\n\n\n\n\n","category":"macro"},{"location":"documentation/api_docs/#Plasmo.@nodevariables","page":"API Documentation","title":"Plasmo.@nodevariables","text":"@nodevariables(iterable, expr...)\n\nCall the JuMP.@variable macro for each optinode in a given container\n\n\n\n\n\n","category":"macro"},{"location":"documentation/api_docs/#Plasmo.set_to_node_objectives","page":"API Documentation","title":"Plasmo.set_to_node_objectives","text":"set_to_node_objectives(graph::OptiGraph)\n\nSet the graph objective to the summation of all of its optinode objectives. Assumes the objective sense is an MOI.MIN_SENSE and adjusts the signs of node objective functions accordingly.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.graph_backend","page":"API Documentation","title":"Plasmo.graph_backend","text":"graph_backend(graph::OptiGraph)\n\nReturn the intermediate backend used to map the optigraph to an optimizer. Plasmo.jl currently only supports a backend to MathOptInterface.jl optimizers, but future versions intend to support GraphOptInterface.jl as a structured backend. \n\n\n\n\n\ngraph_backend(node::OptiNode)\n\nReturn the GraphMOIBackend that holds the associated node model attributes\n\n\n\n\n\ngraph_backend(edge::OptiEdge)\n\nReturn the GraphMOIBackend that holds the associated edge model attributes\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.graph_index","page":"API Documentation","title":"Plasmo.graph_index","text":"graph_index(ref::RT) where {RT<:Union{NodeVariableRef,ConstraintRef}}\n\nReturn the the corresponding variable or constraint index corresponding to a reference.\n\n\n\n\n\ngraph_index(\n backend::GraphMOIBackend, \n ref::RT\n) where {RT<:Union{NodeVariableRef,ConstraintRef}}\n\nReturn the actual variable or constraint index of the backend model that corresponds to the local index of a node or edge.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.source_graph","page":"API Documentation","title":"Plasmo.source_graph","text":"source_graph(node::OptiNode)\n\nReturn the optigraph that contains the optinode. This is the optigraph that defined said node and stores node object dictionary data.\n\n\n\n\n\nsource_graph(edge::OptiEdge)\n\nReturn the optigraph that contains the optiedge. This is the optigraph that defined said edge and stores edge object dictionary data.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.add_subgraph","page":"API Documentation","title":"Plasmo.add_subgraph","text":"add_subgraph(graph::OptiGraph; name::Symbol=Symbol(:sg,gensym()))\n\nCreate and add a new subgraph to the optigraph graph.\n\n\n\n\n\nadd_subgraph(graph::OptiGraph; name::Symbol=Symbol(:sg,gensym()))\n\nAdd an existing subgraph to an optigraph. The subgraph cannot already be part of another optigraph. It also should not have nodes that already exist in the optigraph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_subgraphs","page":"API Documentation","title":"Plasmo.local_subgraphs","text":"local_subgraphs(graph::OptiGraph)::Vector{OptiGraph}\n\nRetrieve the local subgraphs of graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.all_subgraphs","page":"API Documentation","title":"Plasmo.all_subgraphs","text":"all_subgraphs(graph::OptiGraph)::Vector{OptiGraph}\n\nRetrieve all subgraphs of graph. Includes subgraphs within other subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_subgraphs","page":"API Documentation","title":"Plasmo.num_local_subgraphs","text":"num_local_subgraphs(graph::OptiGraph)::Int\n\nRetrieve the number of local subgraphs in graph. Does not include graph in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_subgraphs","page":"API Documentation","title":"Plasmo.num_subgraphs","text":"num_subgraphs(graph::OptiGraph)::Int\n\nRetrieve the total number of subgraphs in graph. Include subgraphs within subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.add_node","page":"API Documentation","title":"Plasmo.add_node","text":"add_node(\n graph::OptiGraph; label=Symbol(graph.label, Symbol(\".n\"), length(graph.optinodes)+1\n)\n\nAdd a new optinode to graph. By default, the node label is set to be \"n\" where \"i\" is the number of nodes in the graph.\n\n\n\n\n\nadd_node(graph::OptiGraph, node::OptiNode)\n\nAdd an existing optinode (created in another optigraph) to graph. This copies model data from the other graph to the new graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.get_node","page":"API Documentation","title":"Plasmo.get_node","text":"get_node(graph::OptiGraph, idx::Int)\n\nRetrieve the optinode in graph at the given index.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_nodes","page":"API Documentation","title":"Plasmo.local_nodes","text":"local_nodes(graph::OptiGraph)::Vector{<:OptiNode}\n\nRetrieve the optinodes defined within the optigraph graph. This does not return nodes that exist in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.all_nodes","page":"API Documentation","title":"Plasmo.all_nodes","text":"all_nodes(graph::OptiGraph)::Vector{<:OptiNode}\n\nRecursively collect all optinodes in graph by traversing each of its subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.collect_nodes","page":"API Documentation","title":"Plasmo.collect_nodes","text":"collect_nodes(jump_func::T where T <: JuMP.AbstractJuMPScalar)\n\nRetrieve the optinodes contained in a JuMP expression.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_nodes","page":"API Documentation","title":"Plasmo.num_local_nodes","text":"num_local_nodes(graph::OptiGraph)::Int\n\nReturn the number of local nodes in the optigraph graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_nodes","page":"API Documentation","title":"Plasmo.num_nodes","text":"num_nodes(graph::OptiGraph)::Int\n\nReturn the total number of nodes in graph by recursively checking subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_variables","page":"API Documentation","title":"Plasmo.num_local_variables","text":"num_local_variables(graph::OptiGraph)\n\nReturn the number of local variables in graph. Does not include variables in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.add_edge","page":"API Documentation","title":"Plasmo.add_edge","text":"add_edge(\n graph::OptiGraph,\n nodes::OptiNode...;\n label=Symbol(graph.label, Symbol(\".e\"), length(graph.optiedges) + 1),\n)\n\nAdd a new optiedge to graph that connects nodes. By default, the edge label is set to be \"e\" where \"i\" is the number of edges in the graph.\n\n\n\n\n\nadd_edge(graph::OptiGraph, edge::OptiEdge)\n\nAdd an existing optiedge (created in another optigraph) to graph. This copies model data from the other graph to the new graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.get_edge","page":"API Documentation","title":"Plasmo.get_edge","text":"get_edge(graph::OptiGraph, nodes::Set{<:OptiNode})\n\nRetrieve the optiedge in graph. that connects nodes.\n\n\n\n\n\nget_edge(graph::OptiGraph, nodes::OptiNode...)\n\nConvenience method. Retrieve the optiedge in graph that connects nodes.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.get_edge_by_index","page":"API Documentation","title":"Plasmo.get_edge_by_index","text":"get_edge_by_index(graph::OptiGraph, idx::Int64)\n\nRetrieve the optiedge in graph that corresponds to the given index.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.has_edge","page":"API Documentation","title":"Plasmo.has_edge","text":"has_edge(graph::OptiGraph, nodes::Set{<:OptiNode})\n\nReturn whether an edge that connects nodes exists in the graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_edges","page":"API Documentation","title":"Plasmo.local_edges","text":"local_edges(graph::OptiGraph)\n\nRetrieve the edges that exists in graph. Does not return edges that exist in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.all_edges","page":"API Documentation","title":"Plasmo.all_edges","text":"all_edges(graph::OptiGraph)::Vector{<:OptiNode}\n\nRecursively collect all optiedges in graph by traversing each of its subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_edges","page":"API Documentation","title":"Plasmo.num_local_edges","text":"num_local_edges(graph::OptiGraph)::Int\n\nReturn the number of local edges in the optigraph graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_edges","page":"API Documentation","title":"Plasmo.num_edges","text":"num_edges(graph::OptiGraph)::Int\n\nReturn the total number of nodes in graph by recursively checking subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_link_constraints","page":"API Documentation","title":"Plasmo.num_local_link_constraints","text":"num_local_link_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve the number of local linking constraints with function func_type and set set_type in graph. Does not include linking constraints in subgraphs.\n\n\n\n\n\nnum_local_link_constraints(graph::OptiGraph)\n\nRetrieve the number of local linking constraints (all constraint types) in graph. Does not include linking constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_link_constraints","page":"API Documentation","title":"Plasmo.num_link_constraints","text":"num_link_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve the total number of linking constraints with function func_type and set set_type in graph. Includes constraints in subgraphs.\n\n\n\n\n\nnum_link_constraints(graph::OptiGraph)\n\nRetrieve the number of local linking constraints (all constraint types) in graph. Does not include constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_link_constraints","page":"API Documentation","title":"Plasmo.local_link_constraints","text":"local_link_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve the local linking constraints with function func_type and set set_type in graph. Does not include linking constraints in subgraphs.\n\n\n\n\n\nlocal_link_constraints(graph::OptiGraph)\n\nRetrieve the local linking constraints (all constraint types) in graph. Does not include constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.all_link_constraints","page":"API Documentation","title":"Plasmo.all_link_constraints","text":"all_link_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve all linking constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.\n\n\n\n\n\nall_link_constraints(graph::OptiGraph)\n\nRetrieve all linking constraints (all constraint types) in graph. Includes linking constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_constraints","page":"API Documentation","title":"Plasmo.num_local_constraints","text":"num_local_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve the number of local constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.\n\n\n\n\n\nnum_local_constraints(graph::OptiGraph)\n\nRetrieve the number of local constraints (all constraint types) in graph. Does not include constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_constraints","page":"API Documentation","title":"Plasmo.local_constraints","text":"local_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve the local constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.\n\n\n\n\n\nlocal_constraints(graph::OptiGraph)\n\nRetrieve the local constraints (all constraint types) in graph. Does not include constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_elements","page":"API Documentation","title":"Plasmo.local_elements","text":"local_elements(graph::OptiGraph)\n\nRetrieve the local elements (nodes and edges) in graph. Does not include elements in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.all_elements","page":"API Documentation","title":"Plasmo.all_elements","text":"local_elements(graph::OptiGraph)\n\nRetrieve all elements (nodes and edges) in graph. Includes elements in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Base.getindex-Tuple{OptiGraph, Int64}","page":"API Documentation","title":"Base.getindex","text":"Base.getindex(graph::OptiGraph, idx::Int)\n\nGet the optinode at the given index.\n\n\n\n\n\n","category":"method"},{"location":"documentation/api_docs/#JuMP.jl-Extended-Methods","page":"API Documentation","title":"JuMP.jl Extended Methods","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"JuMP.name\nJuMP.set_name\nJuMP.index\nJuMP.backend\nJuMP.value\nJuMP.add_variable\nJuMP.num_variables\nJuMP.all_variables\nJuMP.start_value\nJuMP.set_start_value\nJuMP.add_constraint\nJuMP.list_of_constraint_types\nJuMP.num_constraints\nJuMP.all_constraints\nJuMP.objective_value\nJuMP.dual_objective_value\nJuMP.objective_sense\nJuMP.objective_function\nJuMP.objective_function_type\nJuMP.objective_bound\nJuMP.set_objective\nJuMP.set_objective_function\nJuMP.set_objective_sense\nJuMP.set_objective_coefficient\nJuMP.set_optimizer\nJuMP.add_nonlinear_operator\nJuMP.optimize!\nJuMP.termination_status\nJuMP.primal_status\nJuMP.dual_status\nJuMP.relative_gap\nJuMP.constraint_ref_with_index\nJuMP.object_dictionary","category":"page"},{"location":"documentation/api_docs/#JuMP.name","page":"API Documentation","title":"JuMP.name","text":"JuMP.name(graph::OptiGraph)\n\nReturn the name of graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_name","page":"API Documentation","title":"JuMP.set_name","text":"JuMP.set_name(graph::OptiGraph, name::Symbol)\n\nSet the name of graph to name.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.index","page":"API Documentation","title":"JuMP.index","text":"JuMP.index(graph::OptiGraph, nvref::NodeVariableRef)\n\nReturn the backend model index of node variable nvref\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.backend","page":"API Documentation","title":"JuMP.backend","text":"JuMP.backend(graph::OptiGraph)\n\nReturn the backend model object for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.value","page":"API Documentation","title":"JuMP.value","text":"JuMP.value(graph::OptiGraph, nvref::NodeVariableRef; result::Int=1)\n\nReturn the primal value of nvref in graph. Note that this value is specific to the optimizer solution to the graph. The nvref can have different values for different optigraphs it is contained in.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.add_variable","page":"API Documentation","title":"JuMP.add_variable","text":"JuMP.add_variable(node::OptiNode, v::JuMP.AbstractVariable, name::String=\"\")\n\nAdd variable v to optinode node. This function supports use of the @variable JuMP macro. Optionally add a base_name to the variable for printing.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.num_variables","page":"API Documentation","title":"JuMP.num_variables","text":"JuMP.num_variables(graph::OptiGraph)\n\nReturn the total number of variables in graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.all_variables","page":"API Documentation","title":"JuMP.all_variables","text":"JuMP.all_variables(graph::OptiGraph)\n\nReturn all of the variables in graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.start_value","page":"API Documentation","title":"JuMP.start_value","text":"JuMP.start_value(graph::OptiGraph, nvref::NodeVariableRef)\n\nReturn the start value for variable nvref in graph. Note that different graphs can have different start values for node variables.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_start_value","page":"API Documentation","title":"JuMP.set_start_value","text":"JuMP.set_start_value(\n graph::OptiGraph, \n nvref::NodeVariableRef, \n value::Union{Nothing,Real}\n)\n\nSet the start value of variable nvref in graph. Note that different graphs can have different start values for node variables.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.add_constraint","page":"API Documentation","title":"JuMP.add_constraint","text":"JuMP.add_constraint(graph::OptiGraph, con::JuMP.AbstractConstraint, name::String=\"\")\n\nAdd a new constraint to graph. This method is called internall when a user uses the JuMP.@constraint macro.\n\n\n\n\n\nJuMP.add_constraint(node::OptiNode, con::JuMP.AbstractConstraint, name::String=\"\")\n\nAdd a constraint con to optinode node. This function supports use of the @constraint JuMP macro.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.list_of_constraint_types","page":"API Documentation","title":"JuMP.list_of_constraint_types","text":"JuMP.list_of_constraint_types(graph::OptiGraph)::Vector{Tuple{Type,Type}}\n\nList all of the constraint types in graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.num_constraints","page":"API Documentation","title":"JuMP.num_constraints","text":"JuMP.num_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nReturn all the number of contraints in graph with func_type and set_type.\n\n\n\n\n\nJuMP.num_constraints(graph::OptiGraph; count_variable_in_set_constraints=true)\n\nReturn the total number of constraints in graph. If count_variable_in_set_constraints is set to true, this also includes variable bound constraints. \n\n\n\n\n\nJuMP.num_constraints(\nelement::OptiElement,\nfunction_type::Type{\n <:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}},\n},set_type::Type{<:MOI.AbstractSet})::Int64\n\nReturn the total number of constraints on an element.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.all_constraints","page":"API Documentation","title":"JuMP.all_constraints","text":"JuMP.all_constraints(\n graph::OptiGraph,\n func_type::Type{\n <:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}},\n },\n set_type::Type{<:MOI.AbstractSet}\n)\n\nReturn all of the constraints in graph with func_type and set_type.\n\n\n\n\n\nJuMP.all_constraints(graph::OptiGraph)\n\nReturn all of the constraints in graph (all function and set types).\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.objective_value","page":"API Documentation","title":"JuMP.objective_value","text":"JuMP.objective_value(graph::OptiGraph)\n\nRetrieve the current objective value on optigraph graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.dual_objective_value","page":"API Documentation","title":"JuMP.dual_objective_value","text":"JuMP.dual_objective_value(graph::OptiGraph; result::Int=1)\n\nReturn the dual objective value for graph. Specify result for cases when a solver returns multiple results.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.objective_sense","page":"API Documentation","title":"JuMP.objective_sense","text":"JuMP.objective_sense(graph::OptiGraph)\n\nReturn the objective sense for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.objective_function","page":"API Documentation","title":"JuMP.objective_function","text":"JuMP.objective_function(graph::OptiGraph)\n\nReturn the objective function for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.objective_function_type","page":"API Documentation","title":"JuMP.objective_function_type","text":"JuMP.objective_sense(graph::OptiGraph)\n\nReturn the objective function type for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.objective_bound","page":"API Documentation","title":"JuMP.objective_bound","text":"JuMP.objective_bound(graph::OptiGraph)\n\nReturn the objective bound for the current solution for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_objective","page":"API Documentation","title":"JuMP.set_objective","text":"JuMP.set_objective(\n graph::OptiGraph, \n sense::MOI.OptimizationSense, \n func::JuMP.AbstractJuMPScalar\n)\n\nSet the objective function and objective sense for graph. This method is called internally when a user uses the JuMP.@objective macro.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_objective_function","page":"API Documentation","title":"JuMP.set_objective_function","text":"JuMP.set_objective_function(graph::OptiGraph, expr::JuMP.AbstractJuMPScalar)\n\nSet the objective function of graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_objective_sense","page":"API Documentation","title":"JuMP.set_objective_sense","text":"JuMP.set_objective_sense(graph::OptiGraph, sense::MOI.OptimizationSense)\n\nSet the objective sense of graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_objective_coefficient","page":"API Documentation","title":"JuMP.set_objective_coefficient","text":"JuMP.set_objective_coefficient(\n graph::OptiGraph, \n variable::NodeVariableRef, \n coeff::Real\n)\n\nSet the objective function coefficient for variable to coefficient coeff.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_optimizer","page":"API Documentation","title":"JuMP.set_optimizer","text":"JuMP.set_optimizer(\n graph::OptiGraph, \n JuMP.@nospecialize(optimizer_constructor); \n add_bridges::Bool=true\n)\n\nSet the optimizer on graph by passing an optimizer_constructor.\n\n\n\n\n\nJuMP.set_optimizer(\n node::OptiNode, \n JuMP.@nospecialize(optimizer_constructor); \n add_bridges::Bool=true\n)\n\nSet the optimizer for an optinode.This internally creates a new optigraph that is used to optimize the node. Calling this method on a node returns the newly created graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.add_nonlinear_operator","page":"API Documentation","title":"JuMP.add_nonlinear_operator","text":"JuMP.add_nonlinear_operator(\n graph::OptiGraph,\n dim::Int,\n f::Function,\n args::Vararg{Function,N};\n name::Symbol=Symbol(f),\n) where {N}\n\nAdd a nonlinear operator to a graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.optimize!","page":"API Documentation","title":"JuMP.optimize!","text":"JuMP.optimize!(\n graph::OptiGraph;\n kwargs...,\n)\n\nOptimize graph using the current set optimizer.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.termination_status","page":"API Documentation","title":"JuMP.termination_status","text":"JuMP.termination_status(graph::OptiGraph)\n\nReturn the solver termination status of graph if a solver has been executed.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.primal_status","page":"API Documentation","title":"JuMP.primal_status","text":"JuMP.primal_status(graph::OptiGraph; result::Int=1)\n\nReturn the primal status of graph if a solver has been executed.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.dual_status","page":"API Documentation","title":"JuMP.dual_status","text":"JuMP.dual_status(graph::OptiGraph; result::Int=1)\n\nReturn the dual status of graph if a solver has been executed.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.relative_gap","page":"API Documentation","title":"JuMP.relative_gap","text":"JuMP.relative_gap(graph::OptiGraph)\n\nReturn the relative gap in the current solution for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.constraint_ref_with_index","page":"API Documentation","title":"JuMP.constraint_ref_with_index","text":"JuMP.constraint_ref_with_index(\nelement::OptiElement, \nidx::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction, <:MOI.AbstractScalarSet}\n)\n\nReturn a ConstraintRef given an optigraph element and MOI.ConstraintIndex. Note that the index is the index corresponding to the graph backend, not the element index.\n\n\n\n\n\nJuMP.constraint_ref_with_index(backend::GraphMOIBackend, idx::MOI.Index)\n\nReturn the constraint reference (or variable reference) associated with the graph index in backend. Returns a JuMP.ConstraintRef (or NodeVariableRef) object.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.object_dictionary","page":"API Documentation","title":"JuMP.object_dictionary","text":"JuMP.object_dictionary(graph::OptiGraph)\n\nReturn the object dictionary for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Interop-with-JuMP.jl","page":"API Documentation","title":"Interop with JuMP.jl","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"set_jump_model","category":"page"},{"location":"documentation/api_docs/#Plasmo.set_jump_model","page":"API Documentation","title":"Plasmo.set_jump_model","text":"Set a JuMP.Model to `node`. This copies the model data over and does not mutate\n\nthe model in any way. \n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Graph-Projections","page":"API Documentation","title":"Graph Projections","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"Plasmo.GraphProjection\nhyper_projection\nedge_hyper_projection\nclique_projection\nedge_clique_projection\nbipartite_projection","category":"page"},{"location":"documentation/api_docs/#Plasmo.GraphProjection","page":"API Documentation","title":"Plasmo.GraphProjection","text":"GraphProjection\n\nA mapping between OptiGraph elements (nodes and edges) and elements in a graph projection. A graph projection can be for example a hypergraph, a bipartite graph or a standard graph.\n\n\n\n\n\n","category":"type"},{"location":"documentation/api_docs/#Plasmo.hyper_projection","page":"API Documentation","title":"Plasmo.hyper_projection","text":"hyper_projection(graph::OptiGraph)\n\nRetrieve a hypergraph representation of the optigraph graph. Returns a GraphProjection that maps elements between the optigraph and the projected graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.edge_hyper_projection","page":"API Documentation","title":"Plasmo.edge_hyper_projection","text":"edge_hyper_projection(graph::OptiGraph)\n\nRetrieve an edge-hypergraph representation of the optigraph graph. This is sometimes called the dual-hypergraph representation of a hypergraph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.clique_projection","page":"API Documentation","title":"Plasmo.clique_projection","text":"clique_projection(graph::OptiGraph)\n\nRetrieve a standard graph representation of graph. The projection contains a standard Graphs.Graph and a mapping between its elements and the given optigraph. This projection works by creating an edge for each pair of nodes in each hyperedge.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.edge_clique_projection","page":"API Documentation","title":"Plasmo.edge_clique_projection","text":"edge_clique_projection(graph::OptiGraph)\n\nRetrieve the edge-graph representation of optigraph graph. This is sometimes called the line graph of a hypergraph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.bipartite_projection","page":"API Documentation","title":"Plasmo.bipartite_projection","text":"bipartite_graph(graph::OptiGraph)\n\nCreate a bipartite graph representation from graph. The bipartite graph contains two sets of vertices corresponding to optinodes and optiedges respectively.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Partitioning-and-Aggregation","page":"API Documentation","title":"Partitioning and Aggregation","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"Partition\nassemble_optigraph\napply_partition!\naggregate\naggregate_to_depth\naggregate_to_depth!","category":"page"},{"location":"documentation/api_docs/#Plasmo.Partition","page":"API Documentation","title":"Plasmo.Partition","text":"Partition\n\nA data structure that describes a (possibly recursive) graph partition.\n\n\n\n\n\n","category":"type"},{"location":"documentation/api_docs/#Plasmo.assemble_optigraph","page":"API Documentation","title":"Plasmo.assemble_optigraph","text":"assemble_optigraph(nodes::Vector{<:OptiNode}, edges::Vector{OptiEdge})\n\nCreate a new optigraph from a collection of nodes and edges.\n\n\n\n\n\nAssemble a new optigraph from a given `Partition`.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.apply_partition!","page":"API Documentation","title":"Plasmo.apply_partition!","text":"apply_partition!(graph::OptiGraph, partition::Partition)\n\nGenerate subgraphs in an optigraph using a partition.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.aggregate","page":"API Documentation","title":"Plasmo.aggregate","text":"aggregate(graph::OptiGraph; name=gensym())\n\nAggregate an optigraph into a graph containing a single optinode. An optional name can be used to name the new optigraph. Returns the new optinode (which points to a new graph with source_graph(node)) and a mapping from the original graph variables and constraints to the new node variables and constraints.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.aggregate_to_depth","page":"API Documentation","title":"Plasmo.aggregate_to_depth","text":"aggregate_to_depth(graph::OptiGraph, max_depth::Int64=0)\n\nAggregate graph by converting subgraphs into optinodes. The max_depth determines how many levels of subgraphs remain in the new aggregated optigraph. For example, a max_depth of 0 signifies there should be no subgraphs in the aggregated optigraph. Return a new aggregated optigraph and reference map that maps elements from the old optigraph to the new aggregate optigraph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.aggregate_to_depth!","page":"API Documentation","title":"Plasmo.aggregate_to_depth!","text":"aggregate_to_depth!(graph::OptiGraph, max_depth::Int64=0)\n\nAggregate graph by converting subgraphs into optinodes. The max_depth determines how many levels of subgraphs remain in the new aggregated optigraph. For example, a max_depth of 0 signifies there should be no subgraphs in the aggregated optigraph. This version of the method modifies the optigraph and transforms it into the aggregated version. \n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Graph-Topology","page":"API Documentation","title":"Graph Topology","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"Graphs.all_neighbors\nGraphs.induced_subgraph\nGraphs.neighborhood\nincident_edges\ninduced_edges\nidentify_edges\nidentify_nodes\nexpand","category":"page"},{"location":"documentation/api_docs/#Graphs.all_neighbors","page":"API Documentation","title":"Graphs.all_neighbors","text":"Graphs.all_neighbors(hyper::HyperGraphProjection, node::OptiNode)\n\nRetrieve the optinode neighbors of node in the optigraph graph. Uses an underlying hypergraph to query for neighbors.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Graphs.induced_subgraph","page":"API Documentation","title":"Graphs.induced_subgraph","text":"Graphs.induced_subgraph(graph::OptiGraph, nodes::Vector{OptiNode})\n\nCreate an induced subgraph of optigraph given a vector of optinodes.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Graphs.neighborhood","page":"API Documentation","title":"Graphs.neighborhood","text":"neighborhood(\n hyper::HyperGraphProjection, \n nodes::Vector{OptiNode}, \n distance::Int64\n)::Vector{OptiNode}\n\nReturn the optinodes within distance of the given nodes in the optigraph graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.incident_edges","page":"API Documentation","title":"Plasmo.incident_edges","text":"incident_edges(hyper::HyperGraphProjection, nodes::Vector{OptiNode})\n\nRetrieve incident edges to a set of optinodes.\n\nincident_edges(hyper::HyperGraphProjection, node::OptiNode)\n\nRetrieve incident edges to a single optinode.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.induced_edges","page":"API Documentation","title":"Plasmo.induced_edges","text":"induced_edges(graph::OptiGraph, nodes::Vector{OptiNode})\n\nRetrieve induced edges to a set of optinodes.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.identify_edges","page":"API Documentation","title":"Plasmo.identify_edges","text":"identify_edges(hyper::HyperGraphProjection, node_vectors::Vector{Vector{OptiNode}})\n\nIdentify induced edges and edge separators from a vector of optinode partitions.\n\nArguments\n\nhyper::HyperGraphProjection: A HyperGraphProjection obtained from hyper_projection.\nnode_vectors::Vector{Vector{OptiNode}}: A vector of vectors that contain OptiNodes.\n\nReturns\n\npartition_optiedges::Vector{Vector{OptiEdge}}: The OptiEdge vectors for each partition.\ncross_optiedges::Vector{OptiEdge}: A vector of optiedges that cross partitions.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.identify_nodes","page":"API Documentation","title":"Plasmo.identify_nodes","text":"identify_nodes(hyper::HyperGraphProjection, node_vectors::Vector{Vector{OptiEdge}})\n\nIdentify induced nodes and node separators from a vector of optiedge partitions.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.expand","page":"API Documentation","title":"Plasmo.expand","text":"expand(hyper::HyperGraphProjection, subgraph::OptiGraph, distance::Int64)\n\nReturn a new expanded subgraph given the optigraph graph and an existing subgraph subgraph. The returned subgraph contains the expanded neighborhood within distance of the given subgraph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"(Image: Plasmo logo)","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"CurrentModule = Plasmo\nDocTestSetup = quote\n using Plasmo\nend","category":"page"},{"location":"#Plasmo.jl-Platform-for-Scalable-Modeling-and-Optimization","page":"Introduction","title":"Plasmo.jl - Platform for Scalable Modeling and Optimization","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"Plasmo.jl is a graph-based optimization framework written in Julia that builds upon the JuMP.jl modeling language to offer a modular style to construct and solve optimization problems. The package implements what is called an OptiGraph abstraction to create graph-structured optimization models and facilitate graph-based processing functions. An OptiGraph captures the underlying graph topology of an optimization problem using OptiNodes (which represent stand-alone self-contained optimization models) that are coupled by means of OptiEdges (which represent coupling constraints). The resulting topology can be used for tasks such as visualization, graph partitioning, and interfacing (and developing) decomposition-based solvers.","category":"page"},{"location":"#Installation","page":"Introduction","title":"Installation","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"Plasmo.jl works for Julia versions 1.6 and later. From Julia, Plasmo.jl can be installed using the Pkg module:","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"import Pkg\nPkg.add(\"Plasmo\")","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"or alternatively from the Julia package manager by performing the following:","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"pkg> add Plasmo","category":"page"},{"location":"#Contents","page":"Introduction","title":"Contents","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"Pages = [\n \"documentation/quickstart.md\"\n \"documentation/modeling.md\"\n \"documentation/graph_processing.md\"\n \"documentation/api_docs.md\"\n ]\nDepth = 2","category":"page"},{"location":"#Future-Development","page":"Introduction","title":"Future Development","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"There are currently a few major development avenues for Plasmo.jl. Here is a list of some of the major features we intend to add for future releases:","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"Distributed modeling capabilities\nCustom optigraph partitioning algorithms\nDecomposition-based solver development\nGraphOptInterface.jl development\nInterface with InfiniteOpt.jl","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"We are also looking for help from new contributors. If you would like to contribute to Plasmo.jl, please create a new issue or pull request on the GitHub page","category":"page"},{"location":"#Index","page":"Introduction","title":"Index","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"","category":"page"},{"location":"#Citing-Plasmo.jl","page":"Introduction","title":"Citing Plasmo.jl","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"If you find Plasmo.jl useful for your work, we ask that you cite the manuscript:","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"@article{Jalving2022,\n author = {Jalving, Jordan and Shin, Sungho and Zavala, Victor M.},\n title = {A graph-based modeling abstraction for optimization: concepts and implementation in {Plasmo.jl}},\n journal = {Mathematical Programming Computation},\n volume = {14},\n pages = {699--747},\n year = {2022},\n doi = {10.1007/s12532-022-00223-3}\n}","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"You can access an earlier pre-print of this article.","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"There is also an earlier manuscript where we presented the initial ideas behind Plasmo.jl which you can find here:","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"@article{JalvingCaoZavala2019,\nauthor = {Jalving, Jordan and Cao, Yankai and Zavala, Victor M},\njournal = {Computers {\\&} Chemical Engineering},\npages = {134--154},\ntitle = {Graph-based modeling and simulation of complex systems},\nvolume = {125},\nyear = {2019},\ndoi = {10.1016/j.compchemeng.2019.03.009}\n}","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"A pre-print of this paper can also be found here","category":"page"},{"location":"documentation/quickstart/#Quickstart","page":"Quickstart","title":"Quickstart","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"This quickstart gives a brief overview of the functions needed to effectively use Plasmo.jl to build optimization models. If you have used JuMP.jl, much of the functionality here will look familiar. In fact, the primary modeling objects in Plasmo.jl extend the JuMP.AbstractModel and support most JuMP methods. ","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"The below example demonstrates the construction of a simple linear optimization problem that contains two optinodes coupled by a simple linking contraint (which induces an OptiEdge) that is solved with the HiGHS linear optimization solver.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"Once Plasmo.jl has been installed, you can use it from a Julia session as following:","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> using Plasmo","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"For this example we also need to import the HiGHS optimization solver and the PlasmoPlots package which we use to visualize the graph structure.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> using HiGHS","category":"page"},{"location":"documentation/quickstart/#Create-an-OptiGraph","page":"Quickstart","title":"Create an OptiGraph","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"The following command will create the optigraph (referred to as graph). We also see the printed output which denotes the number of optinodes, optiedges, subgraphs, variables, and constraints in the graph.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> graph = OptiGraph(;name=:quickstart_graph)\nAn OptiGraph\nquickstart_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 0\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 0 0\n Constraints: 0 0\n","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"note: Note\nAn OptiGraph distinguishes between its local elements (optinodes and optiedges contained directly within the graph) and its total elements (local elements plus elements contained within subgraphs). This distinction helps to describe nested graph structures as described in Modeling with OptiGraphs.","category":"page"},{"location":"documentation/quickstart/#Add-Variables-and-Constraints-(using-OptiNodes)","page":"Quickstart","title":"Add Variables and Constraints (using OptiNodes)","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"An optigraph consists of OptiNode objects which represent stand-alone optimization models. An optinode supports JuMP macros used to create variables, constraints, expressions, and objective functions (i.e. it supports JuMP macros such as @variable, @constraint, @expression and @objective). The simplest way to add optinodes to an optigraph is to use the @optinode macro as shown in the following code snippet. Here we create the optinode n1 and add two variables x and y. We also add a single constraint and an objective function to the node. By default, the name of a node is pre-pended with the name of the graph it was created in.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> @optinode(graph, n1)\nn1\n\njulia> @variable(n1, y >= 2)\nn1[:y]\n\njulia> @variable(n1, x >= 1)\nn1[:x]\n\njulia> @constraint(n1, x + y >= 3)\nn1[:y] + n1[:x] ≥ 3\n\njulia> @objective(n1, Min, y)\nn1[:y]\n\njulia> graph\nAn OptiGraph\nquickstart_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 1 1\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 2 2\n Constraints: 3 3\n","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"We can create more optinodes and add variables, constraints, and objective functions to each of them.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"@optinode(graph, n2);\n@variable(n2, y >= 0);\n@variable(n2, x >= 2);\n@constraint(n2, x + y >= 3);\n@objective(n2, Min, y);\n\n@optinode(graph, n3);\n@variable(n3, y >= 0);\n@variable(n3, x >= 0);\n@constraint(n3, x + y >= 3);\n@objective(n3, Min, y);\n\nprintln(graph)\n\n# output\n\nAn OptiGraph\nquickstart_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 9 9\n","category":"page"},{"location":"documentation/quickstart/#Add-Linking-Constraints-(using-Edges)","page":"Quickstart","title":"Add Linking Constraints (using Edges)","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"A linking constraint can be created by adding a constraint to an OptiEdge. Linking constraints couple variables across nodes (or graphs!) and can take any valid JuMP expression composed of node variables. We add a simple linear equality constraint here between variables that exist on nodes n1, n2, and n3.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> edge = add_edge(graph, n1, n2, n3);\n\njulia> @constraint(edge, n1[:x] + n2[:x] + n3[:x] == 3)\nn1[:x] + n2[:x] + n3[:x] = 3\n\njulia> println(graph)\nAn OptiGraph\nquickstart_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 1 1\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 10 10\n","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"info: Info\nYou can also create edges implicitly using the @linkconstraint macro which takes the exact same input as the JuMP.@constraint macro above. The above snippet would correspond to doing:@linkconstraint(graph, n1[:x] + n2[:x] + n3[:x] == 3)This would create a new edge between nodes n1, n2, and n3 (if one does not exist) and add the constraint to it. You can also use the JuMP.@constraint macro directly on an optigraph to generate linking constraints, but the syntax displayed here is preferred to help code readability.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"warning: Warning\nPlasmo.jl no longer supports the legacy nonlinear interface from JuMP.jl. Consequently, @NLconstraint, @NLobjective, and @NLexpression will no longer work. If you have code that uses these macros, you will need to update to the current interface using @constraint, @objective, and @expression.","category":"page"},{"location":"documentation/quickstart/#Solve-the-OptiGraph-and-Query-the-Solution","page":"Quickstart","title":"Solve the OptiGraph and Query the Solution","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"We can set the objective function of an optigraph using either the node objectives or by defining an objective directly using the JuMP.@objective macro on the graph. Since we already defined an objective for each node we can use the set_to_node_objectives function to denote the graph objective.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> set_to_node_objectives(graph)\n\njulia> objective_function(graph)\nn1[:y] + n2[:y] + n3[:y]","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"info: Info\nThe graph objective approach would look like:@objective(graph, Min, n1[:y] + n2[:y] + n3[:y])","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"Plasmo.jl uses MathOptInterface.jl internally to interface with optimization solvers. We can optimize an optigraph using the set_optimizer and optimize! functions just like in JuMP.jl. Here we also set the HiGHS attribute log_to_console to false to suppress the output.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> set_optimizer(graph, HiGHS.Optimizer);\n\njulia> set_optimizer_attribute(graph, MOI.RawOptimizerAttribute(\"log_to_console\"), false)\n\njulia> optimize!(graph)\n","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"After returning from the optimizer we can query the termination status using termination_status. We can also query the solution of variables using value and the objective value of the graph using objective_value","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> termination_status(graph) \nOPTIMAL::TerminationStatusCode = 1\n\njulia> value(graph, n1[:x]) \n1.0\n\njulia> value(graph, n2[:x])\n2.0\n\njulia> value(graph, n3[:x])\n0.0\n\njulia> objective_value(graph)\n6.0\n","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"info: Info\nIt is possible to optimize individual optinodes or different optigraphs that contain the same optinode (nodes can be used in multiple optigraphs). Graph-specific solutions can be accessed using value(node, variable) (if optimizing a single node) or value(graph, variable) (if optimizing an optigraph). \\\nNote that optimizing a node creates a new graph internally; the optimizer interface always goes through a graph. It is also possible to use value(variable) without specifying a graph, but this will always return the value corresponding to the graph that created the node (this graph can be queried using source_graph(node)). Using value(node) is likely fine for most use-cases, but be aware that you should use the value(graph, variable) method when dealing with multiple graphs to avoid grabbing a wrong solution.","category":"page"},{"location":"documentation/quickstart/#Visualize-the-Structure","page":"Quickstart","title":"Visualize the Structure","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"Lastly, it is often useful to visualize the structure of an optigraph. The visualization can lead to insights about an optimization problem and understand its connectivity. Plasmo.jl uses PlasmoPlots.jl (which builds on Plots.jl and NetworkLayout.jl) to visualize the layout of an optigraph. The code here shows how to obtain the graph topology using PlasmoPlots.layout_plot and we plot the corresponding incidence matrix structure using PlasmoPlots.matrix_plot. Both of these functions can accept keyword arguments to customize their layout or appearance. The matrix visualization also encodes information on the number of variables and constraints in each optinode and optiedge. The left figure shows a standard graph visualization where we draw an edge between each pair of nodes if they share an edge, and the right figure shows the matrix representation where labeled blocks correspond to nodes and blue marks represent linking constraints that connect their variables. The node layout helps visualize the overall connectivity of the graph while the matrix layout helps visualize the size of nodes and edges.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"plt_graph = PlasmoPlots.layout_plot(\n graph,\n node_labels=true, \n markersize=30, \n labelsize=15, \n linewidth=4,\n layout_options=Dict(\n :tol=>0.01, \n :iterations=>2\n ),\n plt_options=Dict(\n :legend=>false, \n :framestyle=>:box, \n :grid=>false,\n :size=>(400,400), \n :axis=>nothing)\n )\n\nplt_matrix = PlasmoPlots.matrix_plot(graph, node_labels=true, markersize=15) ","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"(Image: graph_quickstart) (Image: matrix_quickstart)","category":"page"},{"location":"tutorials/quadcopter/#Optimal-Control-of-a-Quadcopter","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"","category":"section"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"By: Rishi Mandyam","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"This tutorial notebook is an introduction to the graph-based modeling framework Plasmo.jl (Platform for Scalable Modeling and Optimization) for JuMP (Julia for Mathematical Programming).","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The following problem comes from the paper of Na, Shin, Anitescu, and Zavala (available here).","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"A quadcopter operates in 3-D space with positions (x y z) and angles (gamma, beta, and alpha). g is the graviational constant. The set of state variables at time t are treated as boldsymbolx_t = (x y z dotx doty dotz gamma beta alpha). The input variables at time t are boldsymbolu_t = (a omega_x omega_y omega_z). ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The quadcopter control problem can be written as an optimization problem as:","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"beginalign*\n min phi(t) = int_0^T frac12 (boldsymbolx(t) - boldsymbolx(t)^ref)^top Q (boldsymbolx(t) - boldsymbolx(t)^ref) + boldsymbolu(t)^top R boldsymbolu(t) dt \n textrmst fracd^2xdt^2 = a (cos(gamma) sin( beta) cos (alpha) + sin (gamma) sin (alpha)) \n fracd^2 ydt^2 = a (cos (gamma) sin (beta) sin (alpha) - sin (gamma) cos (alpha)) \n fracd^2 zdt^2 = a cos (gamma) cos (beta) - g \n fracdgammadt = (omega_x cos (gamma) + omega_y sin (gamma)) cos (beta)\nfracdbetadt = -omega_x sin (gamma) + omega_y cos (gamma) \nfracdalphadt = omega_x cos (gamma) tan (beta) + omega_y sin (gamma) tan (beta) + omega_z\nendalign*","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"We will model this problem in Plasmo by discretizing the problem into finite time points and representing each time point with a node. ","category":"page"},{"location":"tutorials/quadcopter/#1.-Import-Packages","page":"Optimal Control of a Quadcopter","title":"1. Import Packages","text":"","category":"section"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"To begin, we will import and use the necessary packages","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"using JuMP\nusing Plasmo\nusing Ipopt\nusing Plots\nusing LinearAlgebra","category":"page"},{"location":"tutorials/quadcopter/#2.-Function-Design","page":"Optimal Control of a Quadcopter","title":"2. Function Design","text":"","category":"section"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"We will define a function called build_quadcopter_graph that will take arguments for the number of nodes and the discretization size (i.e., Delta t), optimize the model, and return the graph and reference values x^ref. ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The function inputs are:","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"number of nodes (N)\ntime discretization (number of seconds between nodes dt)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The function outputs are:","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The objective value of the discretized form of phi\nThe graph\nAn array with the reference values on each node (x^ref)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The build_quadcopter_graph function will use three supporting functions that will add variables, add constraints (both local and linking) and add the objectives to the nodes. These functions will be detailed below before they are used to build the full quadcopter graph. ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The first function we call add_variables!, which defines each of the decision variables as well as some supporting expressions. Here, we loop through the nodes, and define variables on each node. These variables include expressions that will simplify forming the linking constrints (these are the right hand sides of the derivatives). ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"function add_variables!(nodes)\n grav = 9.8 # m/s^2\n\n for (i, node) in enumerate(nodes)\n # Create state variables\n @variable(node, g)\n @variable(node, b)\n @variable(node, a)\n\n @variable(node, X)\n @variable(node, Y)\n @variable(node, Z)\n\n @variable(node, dXdt)\n @variable(node, dYdt)\n @variable(node, dZdt)\n\n # Create input variables\n @variable(node, C_a)\n @variable(node, wx)\n @variable(node, wy)\n @variable(node, wz)\n\n # These expressions to simplify the linking constraints later\n @expression(node, d2Xdt2, C_a * (cos(g) * sin(b) * cos(a) + sin(g) * sin(a)))\n @expression(node, d2Ydt2, C_a * (cos(g) * sin(b) * sin(a) + sin(g) * cos(a)))\n @expression(node, d2Zdt2, C_a * cos(g) * cos(b) - grav)\n\n @expression(node, dgdt, (wx * cos(g) + wy * sin(g)) / (cos(b)))\n @expression(node, dbdt, - wx * sin(g) + wy * cos(g))\n @expression(node, dadt, wx * cos(g) * tan(b) + wy * sin(g) * tan(b) + wz)\n end\nend","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"Next, we define a function for adding the constraints to the graph. We will set the initial values at time 0 and then define the linking constraints, which are discretized derivatives. Note that both linear and nonlinear constraints are handled in the same way by the user in both the @constraint and @linkconstraint macros. ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"function add_constraints!(graph, nodes, dt)\n N = length(nodes)\n\n @constraint(nodes[1], nodes[1][:X] == 0)\n @constraint(nodes[1], nodes[1][:Y] == 0)\n @constraint(nodes[1], nodes[1][:Z] == 0)\n @constraint(nodes[1], nodes[1][:dXdt] == 0)\n @constraint(nodes[1], nodes[1][:dYdt] == 0)\n @constraint(nodes[1], nodes[1][:dZdt] == 0)\n @constraint(nodes[1], nodes[1][:g] == 0)\n @constraint(nodes[1], nodes[1][:b] == 0)\n @constraint(nodes[1], nodes[1][:a] == 0)\n\n for i in 1:(N-1) # iterate through each node except the last\n @linkconstraint(graph, nodes[i+1][:dXdt] == dt*nodes[i][:d2Xdt2] + nodes[i][:dXdt])\n @linkconstraint(graph, nodes[i+1][:dYdt] == dt*nodes[i][:d2Ydt2] + nodes[i][:dYdt])\n @linkconstraint(graph, nodes[i+1][:dZdt] == dt*nodes[i][:d2Zdt2] + nodes[i][:dZdt])\n\n @linkconstraint(graph, nodes[i+1][:g] == dt*nodes[i][:dgdt] + nodes[i][:g])\n @linkconstraint(graph, nodes[i+1][:b] == dt*nodes[i][:dbdt] + nodes[i][:b])\n @linkconstraint(graph, nodes[i+1][:a] == dt*nodes[i][:dadt] + nodes[i][:a])\n\n @linkconstraint(graph, nodes[i+1][:X] == dt*nodes[i][:dXdt] + nodes[i][:X])\n @linkconstraint(graph, nodes[i+1][:Y] == dt*nodes[i][:dYdt] + nodes[i][:Y])\n @linkconstraint(graph, nodes[i+1][:Z] == dt*nodes[i][:dZdt] + nodes[i][:Z])\n end\nend","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"Next, we set a function for defining the objectives. The quadcopter will fly in a linear upward path in the positive X, Y, and Z directions. We combine these vectors into another vector which we call x^ref, and then define the necessary constants and arrays (this will simplify forming the objective function).","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"function add_objective!(nodes, N, dt)\n X_ref = 0:10/N:10;\n dXdt_ref = zeros(N);\n Y_ref = 0:10/N:10;\n dYdt_ref = zeros(N)\n Z_ref = 0:10/N:10;\n dZdt_ref = zeros(N);\n g_ref = zeros(N);\n b_ref = zeros(N);\n a_ref = zeros(N);\n\n xk_ref = [X_ref, dXdt_ref, Y_ref, dYdt_ref, Z_ref, dZdt_ref, g_ref, b_ref, a_ref];\n\n Q = diagm([1, 0, 1, 0, 1, 0, 1, 1, 1]);\n R = diagm([1/10, 1/10, 1/10, 1/10]);\n\n xk_ref1 = zeros(N,9)\n for i in (1:N)\n for j in 1:length(xk_ref)\n xk_ref1[i,j] = xk_ref[j][i]\n end\n end\n\n for (i, node) in enumerate(nodes)\n xk = [ # Array to hold variables\n node[:X],\n node[:dXdt],\n node[:Y],\n node[:dYdt],\n node[:Z],\n node[:dZdt],\n node[:g],\n node[:b],\n node[:a]\n ]\n\n xk1 = xk .- xk_ref1[i, :] # Array to hold the difference between variable values and their setpoints.\n uk = [node[:C_a], node[:wx], node[:wy], node[:wz]]\n @objective(node, Min, (1 / 2 * (xk1') * Q * (xk1) + 1 / 2 * (uk') * R * (uk)) * dt)\n end\n return xk_ref\nend","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"We can now define the function for building the optigraph. We initialize an OptiGraph and set the optimizer. We then define N nodes on the OptiGraph graph. We then call the three functions above and call set_to_node_objectives to set the graph's objective to the nodes' objectives we have defined. We can then call optimize and return the objective value, the graph, and the reference points. ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"function build_quadcopter_graph(N, dt)\n graph = OptiGraph()\n solver = optimizer_with_attributes(Ipopt.Optimizer, \"max_iter\" => 100)\n set_optimizer(graph, solver)\n @optinode(graph, nodes[1:N])\n\n add_variables!(nodes)\n add_constraints!(graph, nodes, dt)\n xk_ref = add_objective!(nodes, N, dt)\n\n set_to_node_objectives(graph)\n\n optimize!(graph);\n\n return objective_value(graph), graph, xk_ref\nend","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"Now that we have created our function to model the behavior of the quadcopter, we can test it using some example cases.","category":"page"},{"location":"tutorials/quadcopter/#Examples","page":"Optimal Control of a Quadcopter","title":"Examples","text":"","category":"section"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"First, we will run an example with 50 time points (each represented by a node) with a time discretization size of 0.1 seconds","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"N = 50\ndt = 0.1\nobjv, graph, xk_ref = build_quadcopter_graph(N, dt)\nnodes = getnodes(graph)\n# create empty arrays\nCAval_array = zeros(length(nodes))\nxval_array = zeros(length(nodes))\nyval_array = zeros(length(nodes))\nzval_array = zeros(length(nodes))\n\n# add values to arrays\nfor (i, node) in enumerate(nodes)\n CAval_array[i] = value(node[:C_a])\n xval_array[i] = value(node[:X])\n yval_array[i] = value(node[:Y])\n zval_array[i] = value(node[:Z])\nend\n\nxarray = Array{Array}(undef, 2)\nxarray[1] = xval_array\nxarray[2] = 0:10/(N-1):10\n\nyarray = Array{Array}(undef, 2)\nyarray[1] = yval_array\nyarray[2] = 0:10/(N-1):10\n\nzarray = Array{Array}(undef, 2)\nzarray[1] = zval_array\nzarray[2] = 0:10/(N-1):10","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"Now, let's visualize the position of the quadcopter in relation to its setpoint in each dimension. Below is the code for doing so in the x-dimension, and the code can be adapted for the y and z dimensions. ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"plot(\n 1:length(xval_array),\n xarray[1:end], \n title = \"X value over time\", \n xlabel = \"Node (N)\", \n ylabel = \"X Value\", \n label = [\"Current X position\" \"X Setpoint\"]\n)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"(Image: QuadcopterXpos) (Image: QuadcopterYpos) (Image: QuadcopterZpos)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"Now that we have solved for the optimal solution, let's explore a correlation. Let's see how increasing the number of nodes changes the objective value of the system. In the code snippet below, we keep the time horizon the same (10 seconds) while changing the number of nodes (i.e., the discretization intervals)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"time_steps = 2:4:50\n\nN = length(time_steps)\ndt = .5\nobj_val_N = zeros(N)\n\nfor i in 1:length(time_steps)\n timing = @elapsed begin\n objval, graph, xk_ref = build_quadcopter_graph(time_steps[i], 10 / time_steps[i]);\n obj_val_N[i] = objval\n end\n println(\"Done with iteration $i after \", timing, \" seconds\")\nend\n\nQuad_Obj_NN = plot(\n time_steps,\n obj_val_N, \n title=\"Objective Value vs Number of Nodes (N)\", \n xlabel=\"Number of Nodes (N)\", \n ylabel=\"Objective Value\",\n label=\"Objective Value\"\n)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"(Image: Quadcopter_Ojb)","category":"page"}] +[{"location":"tutorials/supply_chain/#Supply-Chain-Optimization","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"","category":"section"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"This tutorial shows how to model a supply chain using Plasmo.jl. This problem has a natural graph representation which we will follow in constructing the problem. Code for this problem is also available here. This example is from the textbook Introduction to Software for Chemical Engineers (3rd edition) in Chapter 22, \"Optimization in Chemical and Biological Engineering using Julia.\"","category":"page"},{"location":"tutorials/supply_chain/#Mathematical-Formulation","page":"Supply Chain Optimization","title":"Mathematical Formulation","text":"","category":"section"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"The supply chain being modeled includes a set of suppliers and consumers, where the suppliers ship products to technologies (for product conversion) or directly to the consumers. Suppliers, consumers, and technologies are located at specific locations (represented by nodes) with varying transportation capacity and costs between nodes. This problem includes the following sets: ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Products (P)\nSuppliers (S)\nDemands (D)\nLines for transporting products (L)\nTechnologies (T)\nNodes/Locations (N)","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Each supplier and consumer only sells or consumes a single product. In addition L connects two nodes in N and has an associated direction (i.e., this is a directed graph). As each supplier, consumer, and technology are located at a specific node, we will denote these objects at a given node n in N by S(n), D(n), and T(n), and we will denote the set of all lines originating at node n as L_in(n) and the set of all lines ending at at node n as L_out(n). We will also add superscripts to these sets for specific products when applicable (e.g., D^p(n) represents the consumers at node n in N that consume product p in P). Mathematically, this multi-product supply chain problem is given by:","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"beginalign* \n max sum_n in N left( sum_i in D(n) alpha^d_i d_i - sum_i in S(n) alpha^s_i s_i - sum_i in T(n) alpha^t_i xi_i right) - sum_i in L alpha^f_i f_i\n textrmst sum_i in S^p(n)s_i - sum_i in D^p(n) d_i + sum_i in L_in^p(n) f_i - sum_i in L_out^p(n) f_i + sum_i in T(n) gamma_i p xi_i = 0 quad n in N p in P\n 0 le s_i le overlines_i quad i in S(n) n in N \n 0 le d_i le overlined_i quad i in D(n) n in N \n 0 le xi_i le overlinexi_i quad i in T(n) n in N \n 0 le f_i le overlinef_i quad i in L\nendalign*","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Here, s_i is the amount of product supplied by supplier i, d_i is the amount of product purchased by consumer i, xi_i is the amount of product used to produce a new product by technology i, and f_i is the amount of product transported by line i in L. Since each supplier, consumer, and line only handle a single product, these variables do not include the product notation. The technologies consume certain products to produce other products, and the ratio of consumption to generation is captured by the parameter gamma_ip i in T p in P, where gamma_i p is negative if product p is consumed by technology i, positive if product p is produced by technology i, and 0 otherwise. alpha^d_i is the price paid by consumer i for each unit of product, alpha^s_i is the cost for supplier i to supply each unit of product, alpha^t_i is the cost to operate technology i to consume one unit of product, and alpha^f_i is the cost to ship one unit of product on line i. The parameters overlines_i, overlined_i, overlinexi_i, and overlinef_i are the upper bounds on their respective variables. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"The objective function seeks to maximize profit by providing product to consumers while being penalized for the costs from supply, technology, and transport. Each node includes a balance on each product (defined by the first constraint) which requires that the units of product supplied, produced, transported, or consumed (by consumers or by technologies) at each node must be equal to zero. ","category":"page"},{"location":"tutorials/supply_chain/#Defining-the-Data","page":"Supply Chain Optimization","title":"Defining the Data","text":"","category":"section"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"The data for the supply chain is given below. Here, we have two nodes, n_1 and n_2, with two suppliers, one consumer, and two technologies located at n_1 and two other consumers located at node n_2. There are three different products, p_1, p_2, and p_3, and each supplier only produces p_1. One technology can produce p_2 and the other can produce p_3. There are two lines between the nodes; one transfers p_2 and the other transfers p_3. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"First, we define the sets in Julia:","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Data sets\nN = [\"n1\", \"n2\"]; P = [\"p1\", \"p2\", \"p3\"];\nS = [\"s1(p1)\", \"s2(p1)\"]; D = [\"n1(p2)\", \"n2(p2)\", \"n2(p3)\"];\nL = [\"n1->n2(p2)\", \"n1->n2(p3)\"]; T = [\"p1-p2\", \"p1-p3\"];","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"We next define data for the suppliers, including mapping each node to a set of suppliers and mapping each supplier to a product, upper bound, and cost of supply. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Map nodes to supplies\nsloc = Dict(\"n1\" => [S[1], S[2]], \"n2\" => [])\n\n# Map supplies to products they supply\nsprod = [\"p1\",\"p1\"]; sprod = Dict(zip(S,sprod));\n\n# Define upper bounds on supplies\nsub = [1000,500]; sub = Dict(zip(S,sub)); # unit\n\n# Define cost of supplying one unit of a supply's product\nsbid = [3,2.5]; sbid = Dict(zip(S, sbid)); # $/unit","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Next, we define the consumers and similarly map each node to a set of consumers and map each consumer to a product, upper bound, and price for the products. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Map nodes to demands\ndloc = Dict(\"n1\" => [D[1]], \"n2\" => [D[2], D[3]])\n\n# Map demands to the products they require\ndprod = [\"p2\",\"p2\",\"p3\"]; dprod = Dict(zip(D,dprod));\n\n# Define upper bounds on demands\ndub = [100,200,500]; dub = Dict(zip(D,dub)); # unit\n\n# Define price for one unit of the demand's product\ndbid = [300,300,15]; dbid = Dict(zip(D,dbid)) # $/unit","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Next, we define the set of technologies, again mapping the nodes to a set of technologies and mapping the technologies to upper bounds on production and the cost of conversion. We also define the gamma variable which contains the conversion rates for the products. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Map nodes to technologies\nξloc = Dict(\"n1\" => [T[1], T[2]], \"n2\" => [])\n\n# Define upper bounds on technology conversions\nξub = [1000,1000]; ξub = Dict(zip(T,ξub)); # unit\n\n# Define cost of converting one unit of product\nξbid = [1,0.5]; ξbid = Dict(zip(T,ξbid)); # $/unit\n\n# Product conversion matrices; rows are technologies, columns are products\nγ = [-1.0 0.9 0.0;\n -1.0 0.0 0.3]\nγ = Dict((T[i], P[j]) => γ[i, j] for j in eachindex(P), i in eachindex(T));","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Finally, we define data for the transportation lines. Here, flocs is a mapping of the source node and flocr is a mapping of the destination nodes for each line. Finally, we set upper bounds on each line, define the products the lines can transport, and define the cost of transportation. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Map transport data (flocs = supplying node; flocr = receiving node)\nflocs = [\"n1\",\"n1\"]; flocs = Dict(zip(L,flocs));\nflocr = [\"n2\",\"n2\"]; flocr = Dict(zip(L,flocr));\n\n# Define upper bounds on transport\nfub = [1000,1000]; fub = Dict(zip(L,fub)); # unit\n\n# Map transport flows to the product they support\nfprod = [[\"p2\"], [\"p3\"]]; fprod = Dict(zip(L,fprod));\n\n# Define cost of transporting a unit of product\nfbid = [0.1,0.1]; fbid = Dict(zip(L,fbid)); # $/unit","category":"page"},{"location":"tutorials/supply_chain/#Modeling-in-Plasmo.jl","page":"Supply Chain Optimization","title":"Modeling in Plasmo.jl","text":"","category":"section"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"With the data defined, we can now model the supply chain with Plasmo. Because variables must be placed on nodes, we will create OptiNodes for each node in N and for each line in L. We first load in the required packages, instantiate the OptiGraph, and define OptiNodes:","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"using Plasmo, HiGHS\ngraph = OptiGraph()\n\n@optinode(graph, nodes[N])\n@optinode(graph, transport[L])","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Next, we loop through the set of nodes, N, and add variables for the suppliers (s_i), consumers (d_i), and technology production (xi_i). We then define upper bounds on the variables and we define expressions that sum the supplies, demands, and technologies on each node. We next define expressions for the costs and finally set the objective on the node, which is maximizing a summation of the profit from selling a product minus the cost of supply and the cost of technology conversion.","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"for n in N\n # Define variables based on above mappings\n @variable(nodes[n], s[sloc[n]]>=0)\n @variable(nodes[n], d[dloc[n]]>=0)\n @variable(nodes[n], ξ[ξloc[n]]>=0)\n\n # Define upper bounds on variables\n @constraint(nodes[n], [i in sloc[n]], s[i] <= sub[i])\n @constraint(nodes[n], [j in dloc[n]], d[j] <= dub[j])\n @constraint(nodes[n], [t in ξloc[n]], ξ[t] <= ξub[t])\n\n # Define expressions for summing supplies, demands, techs on a node\n @expression(nodes[n], sum_supplies[p in P],\n sum(s[i] for i in sloc[n] if sprod[i] == p)\n )\n @expression(nodes[n], sum_demands[p in P],\n sum(d[j] for j in dloc[n] if dprod[j] == p)\n )\n @expression(nodes[n], sum_techs[p in P], sum(ξ[t] * γ[t, p] for t in ξloc[n]))\n\n # Define cost expressions for a node\n scost = @expression(nodes[n], sum(sbid[i] * s[i] for i in sloc[n]))\n dcost = @expression(nodes[n], sum(dbid[j] * d[j] for j in dloc[n]))\n ξcost = @expression(nodes[n], sum(ξbid[t] * ξ[t] for t in ξloc[n]))\n\n # Set objective on each node\n @objective(nodes[n], Max, dcost - scost - ξcost)\nend","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Next, we similarly loop through the set of lines, L, and define a variable for the flow of product (f_i). We then set the upper bound and define an expression for the flow in and out for the line for each product (this is to simplify the product mass balances later). We also set an objective on these nodes. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# Define variables/constraints/objectives for transport nodes\nfor l in L\n # Define flow variable\n @variable(transport[l], f[fprod[l]] >= 0)\n\n # Set upper bound on flow variable\n @constraint(transport[l], [j in fprod[l]], f[j] <= fub[l])\n\n # Define flow in and out of nodes\n @expression(transport[l], flow_in[p in P, n in N],\n sum(f[i] for i in fprod[l] if flocr[l] == n && i == p)\n )\n @expression(transport[l], flow_out[p in P, n in N],\n sum(f[i] for i in fprod[l] if flocs[l] == n && i == p)\n )\n\n # Set objective (penalizing transport costs)\n @objective(transport[l], Max, - sum(fbid[l] * f[j] for j in fprod[l]))\nend","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"With the nodes defined, we can now set the linking constraints for each product. Here, we define a node balance for each product, where, for each product, we have a mass balance (supply - consumption - flow out + flow in - technology consumption). ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"# For each node, do a product balance for each product\nfor n in N\n for p in P\n # Node balance must be equal to zero\n node_balance = (nodes[n][:sum_supplies][p] - nodes[n][:sum_demands][p] +\n nodes[n][:sum_techs][p] + sum(transport[l][:flow_in][p, n] for l in L) -\n sum(transport[l][:flow_out][p, n] for l in L)\n )\n if node_balance != 0\n @linkconstraint(graph, node_balance == 0)\n end\n end\nend","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"The form of this graph network is visualized below, where the (hyper)edges contain the flow constraints defined above: ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"(Image: sc_optigraph)","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Finally, we set the optimizer on the graph and the overall objective on the graph. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"note: Note\nAfter the changes made between Plasmo v0.5.4 and v0.6.0, we must now include the function set_to_node_objectives(graph) in the script below to set the graph's objective to use the individual node objectives. In former versions of Plasmo, the graph used the node objectives automatically. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"set_optimizer(graph, HiGHS.Optimizer)\nset_to_node_objectives(graph)","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"Finally, we can call optimize! and then query the solutions on each node. ","category":"page"},{"location":"tutorials/supply_chain/","page":"Supply Chain Optimization","title":"Supply Chain Optimization","text":"optimize!(graph)\nprintln(objective_value(graph))\nprintln(\"Node 1 demand solutions = \", value.(graph[:nodes][\"n1\"][:d]))\nprintln(\"Node 2 demand solutions = \", value.(graph[:nodes][\"n2\"][:d]))\nprintln(\"Technology conversion = \", value.(graph[:nodes][\"n1\"][:ξ]))","category":"page"},{"location":"documentation/modeling/#Modeling-with-OptiGraphs","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"The primary data structure in Plasmo.jl is the OptiGraph, a mathematical model composed of OptiNodes (which represent self-contained optimization problems) that are connected by OptiEdges (which encapsulate linking constraints that couple optinodes). The optigraph is meant to offer a modular mechanism to create optimization problems and provide methods that can help develop specialized solution strategies, visualize problem structure, or perform graph processing tasks such as partitioning.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"The optigraph ultimately describes the following mathematical representation of an optimization problem:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"beginaligned\n min_x_n_n in mathcalN(mathcalG) quad F(f_n(x_n)_n in mathcalN(mathcalG)) quad (textrmObjective) \n textrmst quad x_n in mathcalX_n quad n in mathcalN(mathcalG) quad (textrmNode Constraints)\n quad g_e(x_n_n in mathcalN(e)) = 0 quad e in mathcalE(mathcalG) (textrmEdge (Link) Constraints)\nendaligned","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"In this formulation, mathcalG represents the optigraph, x_n_n in mathcalN(mathcalG) describes a collection of decision variables over the set of optinodes mathcalN(mathcalG), and x_n is the set of decision variables on optinode n. The objective function for the optigraph F(f_n(x_n)_n in mathcalN(mathcalG)) is given as a composition of optinode objective functions f_n(x_n) (it could be a separable summation of node objectives or any nonlinear function defined over optinode variables). The constraints of an optinode n are represented by the set mathcalX_n while the linking constraints that correspond to an edge e are represented by the vector function g_e(x_n_n in mathcalN(e)).","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"From an implementation standpoint, an optigraph extends much of the modeling functionality and syntax from JuMP. We also sometimes drop the 'opti' prefix and refer to objects as graphs, nodes, and edges throughout the documentation, but we note when the 'opti' distincition is important.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"info: Info\nThe OptiNode and OptiGraph each extend a JuMP.AbstractModel and support JuMP macros such as @variable, @constraint, @expression, and @objective as well as many other JuMP methods that work on a JuMP.Model. The OptiEdge supports most JuMP methods as well but does not support @variable or @objective.","category":"page"},{"location":"documentation/modeling/#Creating-a-New-OptiGraph","page":"Modeling with OptiGraphs","title":"Creating a New OptiGraph","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"An optigraph does not require any arguments to construct but it is recommended to include the optional name argument for tracking and model management purposes. We begin by creating a new optigraph named graph1.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> using Plasmo\n\njulia> graph1 = OptiGraph(;name=:graph1)\nAn OptiGraph\n graph1 #local elements #total elements\n--------------------------------------------------\n Nodes: 0 0\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 0 0\n Constraints: 0 0","category":"page"},{"location":"documentation/modeling/#Add-Variables-and-Constraints-using-OptiNodes","page":"Modeling with OptiGraphs","title":"Add Variables and Constraints using OptiNodes","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Optinodes contain modular groups of optimization variables, constraints, and other model data. The typical way to add optinodes to an graph is by using the @optinode macro where the below snippet adds the node n1 to graph1. ","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> @optinode(graph1, n1)\nn1","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"note: Note\nYou can also use the add_node method to add individual optinodes. In this case, the above snippet would look like: n1 = add_node(graph1)","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"The @optinode macro is more useful for creating containers of optinodes like shown in the below code snippet. Here, we create two more optinodes referred to as nodes1. This macro returns a JuMP.DenseAxisArray which allows us to refer to each optinode using the produced index sets. For example, nodes1[2] and nodes1[3] each return the corresponding optinode.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> @optinode(graph1, nodes1[2:3])\n1-dimensional DenseAxisArray{OptiNode{OptiGraph},1,...} with index sets:\n Dimension 1, 2:3\nAnd data, a 2-element Vector{OptiNode{OptiGraph}}:\n nodes1[2]\n nodes1[3]\n\njulia> nodes1[2]\nnodes1[2]\n\njulia> nodes1[3]\nnodes1[3]","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Each optinode supports adding variables, constraints, expressions, and an objective function. Here we loop through each optinode in graph1 using the local_nodes function and we construct underlying model elements.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> for node in all_nodes(graph1)\n @variable(node,x >= 0)\n @variable(node, y >= 2)\n @constraint(node, node_constraint_1, x + y >= 3)\n @constraint(node, node_constraint_2, x^3 >= 1)\n @objective(node, Min, x + y)\n end\n\njulia> graph1\nAn OptiGraph\n graph1 #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 12 12\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Variables within an optinode can be accessed directly by indexing the associated symbol. This enclosed name-space is useful for referencing variables on different optinodes when creating linking constraints or optigraph objective functions.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> n1[:x]\nn1[:x]\n\njulia> nodes1[2][:x]\nnodes1[2][:x]","category":"page"},{"location":"documentation/modeling/#Add-Linking-Constraints-using-Edges","page":"Modeling with OptiGraphs","title":"Add Linking Constraints using Edges","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"An OptiEdge can be used to store linking constraints that couple variables across optinodes. The simplest way to create a linking constraint is to use the @linkconstraint macro which accepts the same input as the JuMP.@constraint macro, but it requires an expression with at least two variables that exists on two different optinodes. The actual constraint is stored on the edge that connects the optinodes referred to in the linking constraint. This optiedge is created if it does not already exist.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> @linkconstraint(graph1, link_reference, n1[:x] + nodes1[2][:x] + nodes1[3][:x] == 3)\nn1[:x] + nodes1[2][:x] + nodes1[3][:x] = 3\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"note: Note\nSome users may choose to create the edge manually and add the constraint like following:edge = add_edge(graph1, n1, nodes1[2], nodes1[3])\n@constraint(edge, link_reference, n1[:x] + nodes1[2][:x] + nodes1[3][:x] == 3)Both approaches are equivalent.","category":"page"},{"location":"documentation/modeling/#Add-an-Objective-Function","page":"Modeling with OptiGraphs","title":"Add an Objective Function","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"By default, the graph objective is empty even if objective functions exist on nodes. We leave it up to the user to determine what the objective function for the graph should be given its contained nodes. We provide the convenience function set_to_node_objectives which will set the graph objective function to the sum of all the node objectives. We can set the graph objective like following:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> set_to_node_objectives(graph1)\n\njulia> objective_function(graph1)\nn1[:x] + n1[:y] + nodes1[2][:x] + nodes1[2][:y] + nodes1[3][:x] + nodes1[3][:y]\n","category":"page"},{"location":"documentation/modeling/#Solving-and-Querying-Solutions","page":"Modeling with OptiGraphs","title":"Solving and Querying Solutions","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"An optimizer can be specified using the set_optimizer function which supports any MathOptInterface.jl optimizer. For example, we use the Ipopt.Optimizer from the Ipopt.jl to solve the optigraph like following:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> using Ipopt\n\njulia> using Suppressor # suppress complete output\n\njulia> set_optimizer(graph1, Ipopt.Optimizer);\n\njulia> set_optimizer_attribute(graph1, \"print_level\", 0); #suppress Ipopt output\n\njulia> @suppress optimize!(graph1)\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"The solution of an optigraph is stored directly on its optinodes and optiedges. Variables values, constraint duals, objective function values, and solution status codes can be queried just like in JuMP.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> termination_status(graph1) \nLOCALLY_SOLVED::TerminationStatusCode = 4\n\njulia> value(n1[:x]) \n1.0\n\njulia> value(nodes1[2][:x])\n1.0\n\njulia> value(nodes1[3][:x])\n1.0\n\njulia> round(objective_value(graph1))\n9.0\n\njulia> round(dual(link_reference), digits = 2)\n-0.25\n\njulia> round(dual(n1[:node_constraint_1]), digits = 2)\n0.5\n\njulia> round(dual(n1[:node_constraint_2]), digits = 2)\n0.25","category":"page"},{"location":"documentation/modeling/#Plotting-OptiGraphs","page":"Modeling with OptiGraphs","title":"Plotting OptiGraphs","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We can also plot the structure of graph1 using both graph and matrix layouts from the PlasmoPlots package.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"using PlasmoPlots\n\nplt_graph = layout_plot(\n graph1,\n node_labels=true,\n markersize=30,\n labelsize=15,\n linewidth=4,\n layout_options=Dict(\n :tol=>0.01,\n :iterations=>2\n ),\n plt_options=Dict(\n :legend=>false,\n :framestyle=>:box,\n :grid=>false,\n :size=>(400,400),\n :axis => nothing\n )\n);\n\nplt_matrix = matrix_layout(graph1, node_labels=true, markersize=15); ","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"(Image: graph_modeling1) (Image: matrix_modeling1)","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"info: Info\nThe layout_plot and matrix_plot functions both return a Plots.plot object which can be used for further customization and saving using Plots.jl","category":"page"},{"location":"documentation/modeling/#Modeling-with-Subgraphs","page":"Modeling with OptiGraphs","title":"Modeling with Subgraphs","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"A fundamental feature of modeling with optigraphs is the ability to create nested optimization structures using subgraphs (i.e. sub-optigraphs). Subgraphs are created using the add_subgraph method which embeds an optigraph as a subgraph within a higher level optigraph. This is demonstrated in the below snippets. First, we create two new optigraphs in the same fashion as above.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"# create graph2\ngraph2 = OptiGraph(;name=:graph2);\n@optinode(graph2, nodes2[1:3]);\nfor node in all_nodes(graph2)\n @variable(node, x >= 0)\n @variable(node, y >= 2)\n @constraint(node,x + y >= 5)\n @objective(node, Min, y)\nend\n@linkconstraint(graph2, nodes2[1][:x] + nodes2[2][:x] + nodes2[3][:x] == 5);\n\n# create graph3\ngraph3 = OptiGraph(;name=:graph3);\n@optinode(graph3, nodes3[1:3]);\nfor node in all_nodes(graph3)\n @variable(node, x >= 0)\n @variable(node, y >= 2)\n @constraint(node,x + y >= 5)\n @objective(node, Min, y)\nend\n@linkconstraint(graph3, nodes3[1][:x] + nodes3[2][:x] + nodes3[3][:x] == 7);\n\ngraph3\n\n# output\n\nAn OptiGraph\n graph3 #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 1 1\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 10 10\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We now have three optigraphs (graph1,graph2, and graph3), each with their own local optinodes and optiedges. These three optigraphs can be embedded into a higher level optigraph using the following snippet:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> graph0 = OptiGraph(;name=:root_graph)\nAn OptiGraph\n root_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 0\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 0 0\n Constraints: 0 0\n\n\njulia> add_subgraph(graph0, graph1);\n\njulia> graph0\nAn OptiGraph\n root_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 3\n Edges: 0 1\n Subgraphs: 1 1\n Variables: 0 6\n Constraints: 0 13\n\njulia> add_subgraph(graph0, graph2);\n\njulia> graph0\nAn OptiGraph\n root_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 6\n Edges: 0 2\n Subgraphs: 2 2\n Variables: 0 12\n Constraints: 0 23\n\n\njulia> add_subgraph(graph0, graph3);\n\njulia> graph0\nAn OptiGraph\n root_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 9\n Edges: 0 3\n Subgraphs: 3 3\n Variables: 0 18\n Constraints: 0 33\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Here, we see the distinction between local and total graph elements. After we add all three subgraphs to graph0, we see that it contains 0 local optinodes, but contains 9 total optinodes which are elements of its subgraphs. This hierarchical distinction is also made for optiedges and nested subgraphs.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Using this nested approach, linking constraints can be expressed both locally and globally. For instance, we can add a linking constraint to graph0 that connects optinodes across its subgraphs like following:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> @linkconstraint(graph0, nodes1[3][:x] + nodes2[2][:x] + nodes3[1][:x] == 10)\nnodes1[3][:x] + nodes2[2][:x] + nodes3[1][:x] = 10\n\njulia> graph0\nAn OptiGraph\n root_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 9\n Edges: 1 4\n Subgraphs: 3 3\n Variables: 0 18\n Constraints: 1 34\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"graph0 now contains 1 local edge, and 4 total edges (3 from the subgraphs). The higher level edge linking constraint can be thought of as a global constraint that connects subgraphs. This hierarchical construction can be useful for developing optimization problems separately and then coupling them in a higher level optigraph.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We can lastly plot the hierarchical optigraph and see the nested subgraph structure.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"using PlasmoPlots\n\nplt_graph0 = PlasmoPlots.layoutplot(\n graph0,\n node_labels=true,\n markersize=60,\n labelsize=30,\n linewidth=4,\n subgraph_colors=true,\n layout_options = Dict(\n :tol=>0.001,\n :C=>2,\n :K=>4,\n :iterations=>5\n )\n)\n\nplt_matrix0 = PlasmoPlots.matrix_plot(\n graph0,\n node_labels = true,\n subgraph_colors = true,\n markersize = 16\n)","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"(Image: graph_modeling2) (Image: matrix_modeling2)","category":"page"},{"location":"documentation/modeling/#Query-OptiGraph-Attributes","page":"Modeling with OptiGraphs","title":"Query OptiGraph Attributes","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Plasmo.jl offers various methods to inspect the optigraph data structures (see the API Documentation for a full list). We can use local_nodes to retrieve an array of the optinodes contained directly within an optigraph, or we can use all_nodes to recursively retrieve all of the optinodes in an optigraph (which includes the nodes in its subgraphs).","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> local_nodes(graph1)\n3-element Vector{OptiNode{OptiGraph}}:\n n1\n nodes1[2]\n nodes1[3]\n\njulia> local_nodes(graph0)\nOptiNode{OptiGraph}[]\n\njulia> all_nodes(graph0)\n9-element Vector{OptiNode{OptiGraph}}:\n n1\n nodes1[2]\n nodes1[3]\n nodes2[1]\n nodes2[2]\n nodes2[3]\n nodes3[1]\n nodes3[2]\n nodes3[3]\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"It is also possible to query for optiedges in the same way using local_edges and all_edges.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> local_edges(graph1)\n1-element Vector{OptiEdge{OptiGraph}}:\n graph1.e1\n\njulia> local_edges(graph0)\n1-element Vector{OptiEdge{OptiGraph}}:\n root_graph.e1\n\njulia> all_edges(graph0)\n4-element Vector{OptiEdge{OptiGraph}}:\n root_graph.e1\n graph1.e1\n graph2.e1\n graph3.e1\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We can query linking constraints using local_link_constraints and all_link_constraints.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> local_link_constraints(graph1)\n1-element Vector{ConstraintRef}:\n n1[:x] + nodes1[2][:x] + nodes1[3][:x] = 3\n\njulia> local_link_constraints(graph0)\n1-element Vector{ConstraintRef}:\n nodes1[3][:x] + nodes2[2][:x] + nodes3[1][:x] = 10\n\njulia> all_link_constraints(graph0)\n4-element Vector{ConstraintRef}:\n nodes1[3][:x] + nodes2[2][:x] + nodes3[1][:x] = 10\n n1[:x] + nodes1[2][:x] + nodes1[3][:x] = 3\n nodes2[1][:x] + nodes2[2][:x] + nodes2[3][:x] = 5\n nodes3[1][:x] + nodes3[2][:x] + nodes3[3][:x] = 7","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We can lastly query subgraphs using local_subgraphs and all_subgraphs methods.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> local_subgraphs(graph0)\n3-element Vector{OptiGraph}:\n An OptiGraph\n graph1 #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 1 1\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 13 13\n\n An OptiGraph\n graph2 #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 1 1\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 10 10\n\n An OptiGraph\n graph3 #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 1 1\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 10 10\n","category":"page"},{"location":"documentation/modeling/#Managing-Solutions-with-OptiGraphs","page":"Modeling with OptiGraphs","title":"Managing Solutions with OptiGraphs","text":"","category":"section"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"While it is common to use an optigraph as a means to build a singular optimization problem that can be solved with standard solvers, it is also possible to come up with custom solution strategies that consist of solving smaller subgraphs or use optigraph elements to generate new optigraphs we can solve. To demonstrate, assume we first want to solve each subgraph in graph0 in isolation. This can be done like following:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"# optimize each subgraph with Ipopt\nfor subgraph in local_subgraphs(graph0)\n @objective(subgraph, Min, sum(all_variables(subgraph)))\n set_optimizer(subgraph, Ipopt.Optimizer)\n set_optimizer_attribute(subgraph, \"print_level\", 0);\n optimize!(subgraph)\nend\n\n# check termination status of each solve\ntermination_status.(local_subgraphs(graph0))\n\n# output\n\n3-element Vector{MathOptInterface.TerminationStatusCode}:\n LOCALLY_SOLVED::TerminationStatusCode = 4\n LOCALLY_SOLVED::TerminationStatusCode = 4\n LOCALLY_SOLVED::TerminationStatusCode = 4","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"We can query the value of each solution using value like before, but lets instead specify the graph argument for clarity.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> value(graph1, n1[:x])\n1.0\n\njulia> n2 = graph2[1] # get first node on graph2\nnodes2[1]\n\njulia> round(value(graph2, n2[:x]); digits=2)\n1.67\n\njulia> n3 = graph3[1] # get first node on graph3\nnodes3[1]\n\njulia> round(value(graph3, n3[:x]); digits=2)\n2.33","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Now assume we want to use these subgraph solutions to initialize the full graph solution. We could do this using JuMP.set_start_value like following:","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> set_start_value.(all_variables(graph1), value.(graph1, all_variables(graph1)));\n\njulia> set_start_value.(all_variables(graph2), value.(graph2, all_variables(graph2)));\n\njulia> set_start_value.(all_variables(graph3), value.(graph3, all_variables(graph3)));","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"Now that each subgraph has a new initial solution, the total initial solution can be used to optimize graph0 since all of the subgraph attributes will be copied over.","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"julia> @objective(graph0, Min, sum(all_variables(graph0))); # set graph0 objective\n\njulia> set_optimizer(graph0, Ipopt.Optimizer);\n\njulia> set_optimizer_attribute(graph0, \"print_level\", 0);\n\njulia> optimize!(graph0);\n\njulia> termination_status(graph0)\nLOCALLY_SOLVED::TerminationStatusCode = 4\n","category":"page"},{"location":"documentation/modeling/","page":"Modeling with OptiGraphs","title":"Modeling with OptiGraphs","text":"While somewhat simple, this example shows what kinds of model approaches can be taken with Plasmo.jl. Checkout Graph Processing and Analysis for more advanced functionality that makes use of the optigraph structure to define and solve problems.","category":"page"},{"location":"tutorials/gas_pipeline/#Optimal-Control-of-a-Natural-Gas-Network","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"This tutorial shows how to model a natural-gas network optimal control problem by constructing a hierarchical optigraph. We show the resulting structure of the optimization problem and demonstrate how to use Plasmo.jl to partition and decompose the problem. The details of this model and a description of its parameters can be found in this manuscript. The actual implementation of this tutorial can be found in this git repository.","category":"page"},{"location":"tutorials/gas_pipeline/#Problem-Description","page":"Optimal Control of a Natural Gas Network","title":"Problem Description","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"We consider the system of connected pipelines in series shown in the below figure. This linear network includes a gas supply at one end, a time-varying demand at the other end, and twelve compressor stations. The gas junctions connect thirteen pipelines which forms an optigraph with a linear topology.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"(Image: 13pipeline_sketch)","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"We seek to solve an optimal control problem that maximizes revenue over a 24 hour time period given a forecast of gas demand profiles. That is, we wish to obtain a compressor control policy that will meet the gas demand at junction j_25, whilst simultaneously minimizing compressor costs and meeting operational constraints. In the formulation below, alpha_ell and P_ellt are the compression cost ($/kW), and compression power for each compressor ell at time t, and alpha_d and f^target_dt are the demand price and target demand flow for each demand d at time t. This formulation includes physical equations and constraints that describe the network junctions, the pipeline dynamics, and compressors. The network link equations describe how the devices within the topology are coupled together such as conservation of mass and boundary conditions. The sets that describe the elements of the optimization problem are also presented here.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n min_ substack eta_elltf_jdt ell in mathcalL_c d in mathcalD_j j in mathcalJ t in mathcalT quad \n sum_substackell in mathcalL_c t in mathcalT alpha_ell P_ellt -\n sum_substackd in mathcalD_j j in mathcalJ t in mathcalT alpha_jd f_jdt \n st quad textJunction Limits \n textPipeline Dynamics \n textCompressor Equations \n textNetwork Link Equations \nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/#Sets-used-for-optimization-problem","page":"Optimal Control of a Natural Gas Network","title":"Sets used for optimization problem","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Set Description Elements\nmathcalJ Set of gas network nodes j in mathcalJ\nmathcalS Set of gas supplies s in mathcalS\nmathcalD Set of gas demands d in mathcalD\nmathcalD_j Set of gas demands on junction j d in mathcalD_j\nmathcalS_j Set of gas supplies on junction j s in mathcalS_j\nmathcalL Set of gas network links ell in mathcalL\nmathcalL_p Set of network pipeline links mathcalL_p subseteq mathcalL\nmathcalL_c Set of network compressor links mathcalL_c subseteq mathcalL\nmathcalX Set of spatial discretization points k in mathcalX\nmathcalT Set of temporal discretization points t in mathcalT","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The following sections describe each component of the network in further detail.","category":"page"},{"location":"tutorials/gas_pipeline/#Junction-OptiGraph","page":"Optimal Control of a Natural Gas Network","title":"Junction OptiGraph","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The gas junctions in a gas network describe the connection points between pipelines and compressors. The junction model is described by the below equations, where theta_jt is the pressure at junction j and time t. underlinetheta_j is the lower pressure bound for the junction, overlinetheta_j is the upper pressure bound, f_jdt^target is the target demand flow for demand d on junction j and overlinef_js is the available gas generation from supply s on junction j.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n underlinetheta_j le theta_jt le overlinetheta_j quad j in mathcalJ t in mathcalT \n 0 le f_jdt le f_jdt^target quad d in mathcalD_j j in mathcalJ t in mathcalT \n 0 le f_jst le overlinef_js quad s in mathcalS_j j in mathcalJ t in mathcalT\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The optigraph that is used to create the junction model is given by the following julia function. We define the function create_junction_model which accepts junction specific data and the number of time periods nt. We create the optigraph graph, add an optignode for each time interval (using @optinode), and then create the variables and constraints for each node in a loop. We also use the JuMP specific @expression macro to refer to expressions for total gas supplied, total gas delivered, and total cost for convenience. The junction optigraph is finally returned from the function","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"#Define function to create junction model-graph\nfunction create_junction_model(data,nt)\n graph = OptiGraph()\n\n #Add model-node for each time interval\n @optinode(graph, nodes[1:nt])\n\n #query number of supply and demands on the junction\n n_demands = length(data[:demand_values])\n n_supplies = length(data[:supplies])\n\n #Loop and create variables, constraints, and objective for each model-node\n for (i,node) in enumerate(nodes)\n @variable(node, data[:pmin] <= pressure <= data[:pmax], start=60)\n @variable(node, 0 <= fgen[1:n_supplies] <= 200, start=10)\n @variable(node, fdeliver[1:n_demands] >= 0)\n @variable(node, fdemand[1:n_demands] >= 0)\n\n @constraint(node,[d = 1:n_demands],fdeliver[d] <= fdemand[d])\n\n @expression(node, total_supplied, sum(fgen[s] for s = 1:n_supplies))\n @expression(node, total_delivered,sum(fdeliver[d] for d = 1:n_demands))\n @expression(node, total_delivercost,sum(1000*fdeliver[d] for d = 1:n_demands))\n\n @objective(node,Min,total_delivercost)\n end\n\n #Return the junction graph\n return graph\nend","category":"page"},{"location":"tutorials/gas_pipeline/#Compressor-OptiGraph","page":"Optimal Control of a Natural Gas Network","title":"Compressor OptiGraph","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Compressors constitute the primary control decisions in the optimal control problem and are described by the following simple formulation. We use an ideal isentropic compressor model where eta_ellt, p_ellt^in, and p_ellt^out are the compression ratio, suction pressure, and discharge pressure at time t, and P_ellt is power at time t. We also introduceduce the dummy variables f_ellt^in and f_ellt^out to be consistent with the pipeline model in the next section.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n p_ellt^out = eta_ellt p_ellt^in quad ell in mathcalL_cquad t in mathcalT \n P_ellt = c_p cdot T cdot f_ellt left(left(fracp_ellt^outp_ellt^inright)^fracgamma-1gamma-1right)\n quad ell in mathcalL_cquad t in mathcalT\n f_ellt = f_ellt^in = f_ellt^out quad ell in mathcalL_cquad t in mathcalT\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The compressor optigraph construction is straightforward as shown by the following Julia code. Like the above model, we define a function called create_compressor_model to create a compressor optigraph given data and number of time periods nt. We create the compressor optigraph by creating nodes, variables, and constraints, as well as expressions to refer to flow in and out of each compressor. We lastly return the created optigraph from the function.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"function create_compressor_model(data,nt)\n #Create compressor model-graph\n graph = OptiGraph()\n @optinode(graph,nodes[1:nt])\n\n #Setup variables, constraints, and objective\n for node in nodes\n @variable(node, 1 <= psuction <= 100)\n @variable(node, 1 <= pdischarge <= 100)\n @variable(node, 0 <= power <= 1000)\n @variable(node, flow >= 0)\n @variable(node, 1 <= eta <= 2.5)\n @constraint(node, pdischarge == eta*psuction)\n @constraint(node, power == c4*flow*((pdischarge/psuction)^om-1) )\n @objective(node, Min, cost*power*(dt/3600.0))\n end\n\n #Create references for flow in and out\n @expression(graph, fin[t=1:nt], nodes[t][:flow])\n @expression(graph, fout[t=1:nt], nodes[t][:flow])\n\n #Return compressor graph\n return graph\nend","category":"page"},{"location":"tutorials/gas_pipeline/#Pipeline-OptiGraph","page":"Optimal Control of a Natural Gas Network","title":"Pipeline OptiGraph","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"We now implement the pipeline equations to describe the dynamic transport throughout the gas network. For each pipeline model we assume isothermal flow through horizontal segments with constant pipe friction. We ultimately produce the following discretized pipeline model.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n fracp_ellt+1k - p_elltkDelta t = -c_1ell fracf_ellt+1k+1 - f_ellt+1kDelta x_ell ell in mathcalL_pt in mathcalT k in mathcalX_ell \n fracf_ellt+1k - f_elltkDelta t = -c_2ellfracp_ellt+1k+1 - p_ellt+1kDelta x_ell - c_3ellfracf_ellt+1k f_ellt+1kp_ellt+1k ell in mathcalL_p t in mathcalT k in mathcalX_ell \n f_elltN_x = f_ellt^outquad ell in mathcalL_p quad t in mathcalT \n f_ellt1 = f_ellt^inquad ell in mathcalL_p quad t in mathcalT \n p_elltN_x = p_ellt^outquad ell in mathcalL_p quad t in mathcalT \n p_ellt1 = p_ellt^inquad ell in mathcalL_p quad t in mathcalT\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The model contains the transport equations defined in terms of pressure and mass flow rate. It also contains dummy flows and pressures which represent in the inlet and outlet flow and pressure into each pipeline segment. We also express a steady-state initial condition which is typical for this control problem and is given by the following equations.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n fracf_ell1k+1 - f_ell1kDelta x = 0 quad ell in mathcalL_p k in mathcalX_ell \n c_2ellfracp_ell1k - p_ell1kDelta x + c_3fracf_ell1k f_ell1kp_ell1k = 0\n quad ell in mathcalL_p k in mathcalX_ell\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Lastly, we require the total line-pack in each segment (i.e. the inventory of gas) to be refilled at the end of the planning horizon. This is represented by the following approximation of line-pack and constraint for refilling it.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n m_ellt = fracA_ellc^2 sum_k=1^N_x p_elltk Delta x_ellquad ell in mathcalL_p t in mathcalT \n m_ellN_t ge m_ell1 quad ell in mathcalL_p\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"We express the pipeline model with optinodes distributed on a space-time grid. Specifically, the nodes of each pipeline optigraph form a nt x nx grid wherein pressure and flow variables are assigned to each node. Flow dynamics within pipelines are then expressed with linking constraints that describe the discretized PDE equations for mass and momentum using finite differences. We lastly include linking constraints that represent the initial steady-state condition and line-pack constraint.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"function create_pipeline_model(data, nt, nx)\n #unpack data\n c1 = data[:c1]; c2 = data[:c2]; c3 = data[:c3]\n dx = data[:pipe_length] / (nx - 1)\n\n #Create pipeline model-graph\n graph = OptiGraph()\n\n #Create grid of optinodes\n @optinode(graph, grid[1:nt,1:nx])\n\n #Create variables on each node in the grid\n for node in grid\n @variable(node, 1 <= px <= 100)\n @variable(node, 0 <= fx <= 100)\n @variable(node, slack >= 0)\n @constraint(node, slack*px - c3*fx*fx == 0)\n end\n\n # setup useful expressions\n @expression(graph, fin[t=1:nt], grid[:,1][t][:fx])\n @expression(graph, fout[t=1:nt], grid[:,end][t][:fx])\n @expression(graph, pin[t=1:nt], grid[:,1][t][:px])\n @expression(graph, pout[t=1:nt], grid[:,end][t][:px])\n @expression(graph, linepack[t=1:nt], c2/A*sum(grid[t,x][:px]*dx for x in 1:nx-1))\n\n # finite differencing. Backward difference in time from t, forward difference in space from x.\n @linkconstraint(\n graph, \n press[t=2:nt,x=1:nx-1],\n (grid[t,x][:px]-grid[t-1,x][:px])/dt + c1*(grid[t,x+1][:fx] - grid[t,x][:fx])/dx == 0\n )\n\n @linkconstraint(\n graph, \n flow[t=2:nt,x=1:nx-1], \n (grid[t,x][:fx] - grid[t-1,x][:fx])/dt == -c2*(grid[t,x+1][:px] - grid[t,x][:px])/dx - grid[t,x][:slack]\n )\n\n # initially at steady state\n @linkconstraint(graph, ssflow[x=1:nx-1], grid[1,x+1][:fx] - grid[1,x][:fx] == 0)\n @linkconstraint(\n graph, \n sspress[x = 1:nx-1], \n -c2*(grid[1,x+1][:px] - grid[1,x][:px])/dx - grid[1,x][:slack] == 0\n )\n\n # refill pipeline linepack\n @linkconstraint(graph, linepack[end] >= linepack[1])\n return graph\nend","category":"page"},{"location":"tutorials/gas_pipeline/#Network-OptiGraph","page":"Optimal Control of a Natural Gas Network","title":"Network OptiGraph","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The network connections define the topology that connect junctions and equipment links (i.e. pipelines and compressors). Specifically, the network equations express mass conservation around each junction and boundary conditions for pipelines and compressors. Mass conservation around each junction j is given by the following equation.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n sum_ellinmathcalL_rec(j) f^out_ellt - sum_ell inmathcalL_snd(j) f^in_ellt +\n sum_sinmathcalS_jf_jst - sum_din mathcalD_jf_jdt = 0 quad j inmathcalJ\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"where we define mathcalL_rec(j) and mathcalL_snd(j) as the set of receiving and sending links to each junction j respectively.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The next equations define pipeline and compressor link boundary conditions.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"beginaligned\n p_ellt^in = theta_rec(ell)t quad ell in mathcalL t in mathcalT \n p_ellt^out = theta_snd(ell)t quad ell in mathcalL t in mathcalT\nendaligned","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Here, theta_rec(ell)t and theta_snd(ell)t are the receiving and sending junction pressure for each link ell in mathcalL at time t.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The Julia code required to create the network optigraph is a bit more involved, but mostly because we have to define some data structures to capture the network topology. The below piece of code defines the function create_gas_network which accepts a dictionary of network data and calls the above defined functions to create the hierarchical optigraph. That is, the below code creates junction, compressor, and pipeline optigraphs, adds these optigraphs as subgraphs within a higher level network optigraph, and then creates linking constraints that couple the subgraphs to eachother in the form of mass conservation and boundary conditions.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"function create_gas_network(net_data)\n pipe_data = net_data[:pipeline_data]\n comp_data = net_data[:comp_data]\n junc_data = net_data[:junc_data]\n pipe_map = net_data[:pipe_map]; comp_map = net_data[:comp_map]\n\n #Create OptiGraph for entire gas network\n network = OptiGraph()\n network[:pipelines] = [];network[:compressors] = [];network[:junctions] = []\n j_map = Dict()\n\n #Create device OptiGraphs and setup data structures\n for j_data in junc_data\n junc= create_junction_optigraph(j_data)\n add_subgraph(network,junc); push!(network[:junctions],junc)\n j_map[j_data[:id]] = junc\n junc[:devices_in] = []; junc[:devices_out] = []\n end\n for p_data in pipe_data\n pipe = create_pipeline_optigraph(p_data); push!(network[:pipelines],pipe)\n add_subgraph(network,pipe);\n pipe[:junc_from] = j_map[p_data[:junc_from]]\n pipe[:junc_to] = j_map[p_data[:junc_to]]\n push!(pipe[:junc_from][:devices_out],pipe); push!(pipe[:junc_to][:devices_in],pipe)\n end\n for c_data in comp_data\n comp = create_compressor_optigraph(c_data)\n add_subgraph(gas_network,comp); comp[:data] = c_data\n comp[:junc_from] = j_map[c_data[:junc_from]]\n comp[:junc_to] = j_map[c_data[:junc_to]]\n push!(comp[:junc_from][:devices_out],comp); push!(comp[:junc_to][:devices_in],comp)\n end\n\n # link pipelines in gas network\n for pipe in network[:pipelines]\n junc_from,junc_to = [pipe[:junc_from],pipe[:junc_to]]\n @linkconstraint(network,[t = 1:nt],pipe[:pin][t] == junc_from[:pressure][t])\n @linkconstraint(gas_network,[t = 1:nt],pipe[:pout][t] == junc_to[:pressure][t])\n end\n\n # link compressors in gas network\n for comp in network[:compressors]\n junc_from,junc_to = [comp[:junc_from].comp[:junc_to]]\n @linkconstraint(network,[t = 1:nt],comp[:pin][t] == junc_from[:pressure][t])\n @linkconstraint(network,[t = 1:nt],comp[:pout][t] == junc_to[:pressure][t])\n end\n\n # link junctions in gas network\n for junc in network[:junctions]\n devices_in = junc[:devices_in]; devices_out = junc[:devices_out]\n\n flow_in = [sum(device[:fout][t] for device in devices_in) for t = 1:nt]\n flow_out = [sum(device[:fin][t] for device in devices_out) for t = 1:nt]\n\n total_supplied = [junction[:total_supplied][t] for t = 1:nt]\n total_delivered = [junction[:total_delivered][t] for t = 1:nt]\n\n @linkconstraint(\n gas_network,\n [t = 1:nt], \n flow_in[t] - flow_out[t] + total_supplied[t] - total_delivered[t] == 0\n )\n end\n return gas_network\nend","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Using the above function, we can obtain a complete optigraph representation of the optimal control problem. It is now possible to plot the graph layout using Plotting functions or export the graph structure and use another graph visualization tool. Gephi was used to produce the below figure. Here, the green colors correspond to compressor nodes, blue corresponds to junctions, and grey corresponds to pipelines. Notice that the optigraph captures the space-time structure of the optimization problem. We also observe a cylindrical shape to the problem which results from the line-pack constraint which couples the initial and final time optinodes for each pipeline.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"(Image: space_time)","category":"page"},{"location":"tutorials/gas_pipeline/#Partitioning","page":"Optimal Control of a Natural Gas Network","title":"Partitioning","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"Now that we have an optigraph representation of our optimal control problem, we can use hypergraph partitioning to decompose the space-time structure. To do so, we use KaHyPar and the functions described in Graph Partitioning and Processing. the below code creates a hypergraph representation of the optigraph, sets up node and edge weights, partitions the problem, and forms new subgraphs based on the partitions. We also aggregate the subgraphs to produce solvable optinode subproblems which will communicate to our solver.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"# import the KaHyPar interface\nusing KaHyPar\n\n# get the hypergraph representation of the gas network\nprojection = hyper_projection(gas_network)\n\n# setup node and edge weights\nn_vertices = length(vertices(hgraph))\nnode_weights = [num_variables(node) for node in all_nodes(gas_network)]\nedge_weights = [num_constraints(edge) for edge in all_edges(gas_network)]\n\n#Use KaHyPar to partition the hypergraph\nnode_vector = KaHyPar.partition(\n projection,\n 13,\n configuration=:edge_cut,\n imbalance=0.01,\n node_weights=node_weights,\n edge_weights=edge_weights\n)\n\n# create a Partition object\npartition = Partition(projection, node_vector)\n\n# setup subgraphs based on the partition\napply_partition!(gas_network, partition)","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The partitioned optimal control problem is visualized in the below figure and depicts the optimization problem partitioned into 13 distinct partitions.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"(Image: partition)","category":"page"},{"location":"tutorials/gas_pipeline/#Querying-Solutions","page":"Optimal Control of a Natural Gas Network","title":"Querying Solutions","text":"","category":"section"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"The modular construction of an OptiGraph often results in variables with the same names being stored on different subgraphs or nodes, and accessing variables in OptiGraphs with multiple nodes or subgraphs is not always as simple as calling the symbol for the variable. Variable references for variables in a graph can be thought of as being \"nested\" on nodes that are \"nested\" on subgraphs. The \"owning\" subgraph can be accessed by calling getsubgraphs(::OptiGraph). In addition, in the above code, each pipeline, junction, and compressor subgraph was saved on the OptiGraph gas_network using the symbols :pipeline, :junction, and :compressor, respectively. Thus, the subgraphs for each of these objects can be called by using these symbols, such as using gas_network[:pipeline] to get the vector of all pipeline subgraphs. Importantly, the vectors of subgraphs are not in the order they appear in the network because they were formed by iterating through entries to dictionaries, which are order-free. In other words, the pipelines are not in order from 1 - 13. To identify the order the pipelines, junctions, and compressors appear in their respective vectors, the user will have to track the order to which these are added in the for loop.","category":"page"},{"location":"tutorials/gas_pipeline/","page":"Optimal Control of a Natural Gas Network","title":"Optimal Control of a Natural Gas Network","text":"As an example of the \"nested\" nature of the variables, we can consider the problem above. There is a set of nodes called grid (that contain nt times nx nodes) for each pipeline OptiGraph, and each of these nodes contain a variable called px and fx. To access the px variable at t = 1 and x = 2 on the first pipeline subgraph of the vector, we can call gas_network[:pipelines][1][:grid][1, 2][:px]. Here, [:pipelines] acesses the vector of pipelines, [1] accesses the first subgraph of that vector, [:grid] accesses the set of nodes called \"grid\", [1, 2] accesses the node at time t = 1 and x = 2, and [:px] accesses the variable called px. Alternatively, instead of calling gas_network[:pipelines][1], the same pipeline subgraph could be accessed by calling getsubgraphs(gas_network)[26]. Subgraphs appear in getsubgraphs(::OptiGraph) in the order they were added to the OptiGraph; as the junctions are added to gas_network first, followed by pipelines and then compressors in the above code, the first 25 entries of getsubgraphs(gas_network) will correspond to junctions and entries 26 - 38 will contain the pipelines. With the variable references, a user can query solutions to these variables after calling optimize!(gas_network) by using JuMP.value, such as calling value(gas_network[:pipelines][1][:grid][1, 2][:px]).","category":"page"},{"location":"documentation/graph_processing/#Graph-Processing-and-Analysis","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"In Modeling with OptiGraphs we describe how to construct optigraphs using a bottom-up approach that manages problem structure using nodes, edges, and subgraphs. Plasmo.jl also supports managing optigraphs in a more top-down manner using graph analysis functions and interfaces to standard graph partitioning tools such as Metis and KaHyPar. ","category":"page"},{"location":"documentation/graph_processing/#Illustrative-Example:-Dynamic-Optimization","page":"Graph Processing and Analysis","title":"Illustrative Example: Dynamic Optimization","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"To help demonstrate some graph processing capabilities in Plasmo.jl, we construct a simple optimal control problem described by the following equations. In this problem, x is a vector of states and u is a vector of control actions which are both indexed over the set of time indices t in 1T. The objective function minimizes the state trajectory distance from zero with minimal control effort, the second equation describes the state dynamics, and the third equation defines the initial condition. The last two equations define limits on the state and control actions.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"beginaligned\n min_ xu sum_t = 1^T x_t^2 + u_t^2 \n textrmst quad x_t+1 = x_t + u_t + d_t quad t in 1T-1 \n x_1 = 0 \n x_t ge 0 quad t in 1T\n u_t ge -1000 quad t in 1T-1\nendaligned","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"This snippet shows how to construct the optimal control problem in Plasmo.jl. We create an optigraph, we add optinodes which contain states and controls at each time period, we setup objective functions for each node, and we use linking constraints to describe the dynamics (since each node represents a point in time). When we print the newly created optigraph for our optimal control problem, we see it contains about 200 optinodes (one for each state and control) and contains almost 100 linking constraints (which couple the time periods).","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"using Plasmo\n\nT = 100 # number of time points\nd = sin.(1:T) # disturbance vector (a sin wave)\n\ngraph = OptiGraph(;name=:optimal_control)\n@optinode(graph, state[1:T])\n@optinode(graph, control[1:T-1])\n\nfor node in state\n @variable(node, x)\n @constraint(node, x >= 0)\n @objective(node, Min, x^2)\nend\nfor node in control\n @variable(node, u)\n @constraint(node, u >= -1000)\n @objective(node, Min, u^2)\nend\n\n@linkconstraint(graph, [i = 1:T-1], state[i+1][:x] == state[i][:x] + control[i][:u] + d[i])\nJuMP.fix(state[1][:x], 0)\n\ngraph\n\n# output\n\nAn OptiGraph\n optimal_control #local elements #total elements\n--------------------------------------------------\n Nodes: 199 199\n Edges: 99 99\n Subgraphs: 0 0\n Variables: 199 199\n Constraints: 299 299\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We can also plot the resulting optigraph (see [Plotting]) which produces a simple chain of optinodes.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"using PlasmoPlots\n\nplt_chain_layout = layout_plot(\n graph,\n layout_options=Dict(:tol=>0.1,:iterations=>500),\n linealpha = 0.2,\n markersize = 6\n)\n\nplt_chain_matrix = matrix_plot(graph)","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"(Image: partition_layout_1) (Image: partition_matrix_1)","category":"page"},{"location":"documentation/graph_processing/#OptiGraph-Projections","page":"Graph Processing and Analysis","title":"OptiGraph Projections","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Plasmo.jl lets us query optigraph properties such as all_neighbors, induced_subgraph, and incident_edges. Before we can query any of these properties, we need to create a hypergraph representation of the optigraph using a Plasmo.GraphProjection. Specifically, we want to create a hypergraph projection using hyper_projection method.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> projection = hyper_projection(graph)\nGraph Projection: Plasmo.HyperGraphProjectionType()\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"note: Note\nPlasmo.jl contains a few different Graph Projections. The hypergraph is the most natural representation of an optigraph and is used to perform most processing tasks such as querying neighbors and incident edges. Other projections can be useful for various graph analyses, but no examples exist right now beyond graph partitioning. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"The next most useful projection is probably the clique_projection. This projection replaces each hyperedge with a set of standard edges to create a standard graph (where edges strictly connect 2 nodes). This projection internally contains a Graphs.SimpleGraph.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> clique_proj = clique_projection(graph)\nGraph Projection: Plasmo.CliqueGraphProjectionType()\n\njulia> clique_proj.projected_graph\n{199, 297} undirected simple Int64 graph\n","category":"page"},{"location":"documentation/graph_processing/#Querying-Topology","page":"Graph Processing and Analysis","title":"Querying Topology","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Once we have a projection we can run all sorts of methods to query the topology, many of which are extended from Graphs.jl. The below snippet demonstrates some of the primary methods. We first grab two nodes to start some examples. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> node1 = graph[1]\nstate[1]\n\njulia> node2 = graph[2]\nstate[2]","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Remember, state[2] is a function of state[1] and control[1] based on our modeled equations. We can query the neighbors of node1 (state[1]) to confirm this. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> all_neighbors(projection, node1)\n2-element Vector{OptiNode{OptiGraph}}:\n state[2]\n control[1]","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We can query the neighborhood around a node within a given distance (this function also returns the queried node). For this example we use a distance of 1. We can also query a neighborhood given a set of nodes.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> neighborhood(projection, [node1], 1) \n3-element Vector{OptiNode{OptiGraph}}:\n state[1]\n state[2]\n control[1]\n\njulia> neighborhood(projection, [node1, node2], 1)\n5-element Vector{OptiNode{OptiGraph}}:\n state[1]\n state[2]\n control[1]\n state[3]\n control[2]\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We can look at edges incident to a single node or set of nodes. Note that state[1] has one incident edge that connects it to control[1] and state[2]. We verify this by using all_nodes on the returned edge.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> node1_incident = incident_edges(projection, node1) \n1-element Vector{OptiEdge{OptiGraph}}:\n optimal_control.e1\n\njulia> edge1_nodes = all_nodes(node1_incident[1])\n3-element Vector{OptiNode}:\n state[2]\n state[1]\n control[1]\n\njulia> incident_edges(projection, [node1,node2])\n2-element Vector{OptiEdge{OptiGraph}}:\n optimal_control.e1\n optimal_control.e2\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We lastly show how to query the edges induced by a set of nodes. These are all edges that connect the given nodes.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> induced = induced_edges(projection, edge1_nodes)\n1-element Vector{OptiEdge}:\n optimal_control.e1\n","category":"page"},{"location":"documentation/graph_processing/#Assembling-New-OptiGraphs","page":"Graph Processing and Analysis","title":"Assembling New OptiGraphs","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"A key capability that derives from graph topology queries is the ability to create new optigraphs from subsets of nodes and edges. This is primarily done with the assemble_optigraph(@ref) method which some topology functions implicity call. Here we show how to create new optigraphs using some of these methods.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> queried_nodes = neighborhood(projection, [node1], 5)\n11-element Vector{OptiNode{OptiGraph}}:\n state[1]\n state[2]\n control[1]\n state[3]\n control[2]\n state[4]\n control[3]\n state[5]\n control[4]\n state[6]\n control[5]\n\njulia> queried_edges = induced_edges(projection, queried_nodes)\n5-element Vector{OptiEdge}:\n optimal_control.e1\n optimal_control.e2\n optimal_control.e3\n optimal_control.e4\n optimal_control.e5\n\njulia> new_graph = assemble_optigraph(queried_nodes, queried_edges; name=:new_graph)\nAn OptiGraph\n new_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 11 11\n Edges: 5 5\n Subgraphs: 0 0\n Variables: 11 11\n Constraints: 17 17\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"warning: Warning\nYou must pass valid nodes and edges to assemble_optigraph. All of the edges must be connected to the given nodes.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"An easy alternative to using assemble_optigraph is to use induced_subgraph which takes a vector of nodes and does the above operations internally.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> new_graph = induced_subgraph(projection, queried_nodes; name=:induced_graph)\nAn OptiGraph\n induced_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 11 11\n Edges: 5 5\n Subgraphs: 0 0\n Variables: 11 11\n Constraints: 17 17\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We can lastly expand a set of nodes to create a new graph. We can provide either a subgraph (an optigraph) or a set of nodes to expand with. This would look like the following:","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> expanded_graph = expand(projection, new_graph, 1; name=:expanded_graph)\nAn OptiGraph\n expanded_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 13 13\n Edges: 6 6\n Subgraphs: 0 0\n Variables: 13 13\n Constraints: 20 20\n\njulia> expanded_with_nodes = expand(projection, queried_nodes, 1; name=:expanded_nodes)\nAn OptiGraph\n expanded_nodes #local elements #total elements\n--------------------------------------------------\n Nodes: 13 13\n Edges: 6 6\n Subgraphs: 0 0\n Variables: 13 13\n Constraints: 20 20\n","category":"page"},{"location":"documentation/graph_processing/#Partitioning-OptiGraphs","page":"Graph Processing and Analysis","title":"Partitioning OptiGraphs","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Plasmo.jl supports partitioning optigraphs wherein partitions of nodes and edges can be used to assemble optigraphs that contain subgraphs. This allows users to reveal and create nested optigraph structures that would be difficult (or impractical) to formulate otherwise. Plasmo.jl takes care of creating new optigraphs given partition information. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Since the OptiGraph is a hypergraph at its core, it naturally should interface to various partitioning tools (both standard and hypergraph partitioning). To begin however, we show how to partition an optigraph manually by defining vectors of node partitions. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We first define our manual partition as a vector of vectors. Each internal vector contains the optinodes that correspond to a time interval. In this case, we assemble a vector of 5 time intervals. Using our vector we can construct a Partition object which denotes node and edge partitions and how they are connected.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> all_graph_nodes = all_nodes(graph);\n\njulia> node_vectors = [[state[1:20];control[1:20]],[state[21:40];control[21:40]],[state[41:60];control[41:60]],[state[61:80];control[61:80]],[state[81:100];control[81:99]]];\n\njulia> manual_partition = Partition(graph, node_vectors)\nOptiGraph Partition w/ 5 subpartitions\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Once we construct a Partition, we can assemble a new optigraph from the nodes using assemble_optigraph. Notice that the new graph contains few local elements (just the 4 edges that connect the new subgraphs).","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> new_manual_graph = assemble_optigraph(manual_partition; name=:partitioned_graph)\nAn OptiGraph\npartitioned_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 199\n Edges: 4 99\n Subgraphs: 5 5\n Variables: 0 199\n Constraints: 4 299\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"note: Note\nWe can also modify an existing graph using apply_partition! versus creating a new graph. This can be useful for reducing memory requirements but it keep in mind it fundamentally alters the optigraph structure. Also note that this method is somewhat experimental; we suggest using assemble_optigraph if performance is not critical.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We now demonstrate how to use the hypergraph partitioning with KaHyPar.jl using the hyper_projection we created above. The general workflow is straightforward:","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> using KaHyPar\n\njulia> using Suppressor # suppress KaHyPar output\n\njulia> partition_vector = @suppress KaHyPar.partition(projection, 8, configuration=:connectivity, imbalance=0.01);\n\njulia> partition_kahypar = Partition(projection, partition_vector)\nOptiGraph Partition w/ 8 subpartitions\n\njulia> kahypar_graph = assemble_optigraph(partition_kahypar; name=:kahypar_graph)\nAn OptiGraph\n kahypar_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 199\n Edges: 7 99\n Subgraphs: 8 8\n Variables: 0 199\n Constraints: 7 299\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"In this case, we ended up with a similar partition to the manual one (where instead we ask for 8 partitions as KaHyPar makes it easy to do so). In most cases, the best partition is not this obvious.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"warning: Warning\nKaHyPar does not currently build on Windows. If you are interested in using graph partitioning with Plasmo.jl, read on to see how you can use Metis. ","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"note: Note\nPlasmo.jl contains a direct interface to KaHyPar which is used here. In general, a user can always construct the manual partition vector however they wish and generate a Partition object.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"As a final example, we show how one might use Metis to partition this optigraph using the clique_projection presented earlier.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> using Metis\n\njulia> clique_proj\nGraph Projection: Plasmo.CliqueGraphProjectionType()\n\njulia> simple_graph = clique_proj.projected_graph\n{199, 297} undirected simple Int64 graph\n\njulia> metis_vector = Int64.(Metis.partition(simple_graph, 5)); # Plasmo.jl requires Int64 vectors.\n\njulia> partition_metis = Partition(clique_proj, metis_vector)\nOptiGraph Partition w/ 5 subpartitions\n\njulia> metis_graph = assemble_optigraph(partition_metis; name=:metis_graph)\nAn OptiGraph\n metis_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 199\n Edges: 4 99\n Subgraphs: 5 5\n Variables: 0 199\n Constraints: 4 299\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"If we plot and of the above partitioned optigraphs, it reveals five distinct partitions and the coupling between them. The plots show that the partitions are well-balanced and the matrix visualization shows the problem is reordered into a banded structure that is typical of dynamic optimization problems.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"plt_chain_partition_layout = layout_plot(\n kahypar_graph,\n layout_options=Dict(\n :tol=>0.01,\n :iterations=>500\n ),\n linealpha=0.2,\n markersize=6,\n subgraph_colors=true\n)\n )\n\nplt_chain_partition_matrix = matrix_layout(kahypar_graph, subgraph_colors=true)\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"(Image: partition_layout_2) (Image: partition_matrix_2)","category":"page"},{"location":"documentation/graph_processing/#Aggregating-OptiGraphs-(Experimental)","page":"Graph Processing and Analysis","title":"Aggregating OptiGraphs (Experimental)","text":"","category":"section"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"Optigraphs can be converted into stand-alone optinodes using the using the aggregate and aggregate_to_depth functions. This can be helpful when the user models using optigraphs, but they want to represent subproblems using optinodes. In the snippet below, we aggregate our optigraph that contains 5 subgraphs. We include the argument 0 which specifies how many subgraph levels to retain. In this case, 0 means we aggregate subgraphs at the highest level so graph contains only new aggregated optinodes. For hierarchical graphs with many levels, we can define how many subgraph levels we wish to retain. The function returns a new aggregated graph (aggregate_graph), as well as a reference_map which maps elements in aggregate_graph to the original optigraph graph.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"julia> aggregate_graph, reference_map = aggregate_to_depth(kahypar_graph, 0; name=:agg_graph);\n\njulia> aggregate_graph\nAn OptiGraph\n agg_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 8 8\n Edges: 7 7\n Subgraphs: 0 0\n Variables: 199 199\n Constraints: 299 299\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"note: Note\nA user can also use aggregate! to permanently aggregate an existing optigraph. This avoids maintaining a copy of the original optigraph.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"We can lastly plot the aggregated graph structure which simply shows 8 optinodes with 7 linking constraints.","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"plt_chain_aggregate = layout_plot(\n aggregate_graph,\n layout_options=Dict(:tol=>0.01,:iterations=>10),\n node_labels=true,\n markersize=30,\n labelsize=20,\n node_colors=true\n);\n\nplt_chain_matrix_aggregate = matrix_plot(\n aggregate_graph,\n node_labels=true,\n node_colors=true\n);\n","category":"page"},{"location":"documentation/graph_processing/","page":"Graph Processing and Analysis","title":"Graph Processing and Analysis","text":"(Image: partition_layout_3) (Image: partition_matrix_3)","category":"page"},{"location":"documentation/api_docs/#API-Documentation","page":"API Documentation","title":"API Documentation","text":"","category":"section"},{"location":"documentation/api_docs/#OptiGraph-Methods","page":"API Documentation","title":"OptiGraph Methods","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"OptiGraph\nOptiNode\nOptiEdge\n@optinode\n@linkconstraint\n@nodevariables\nset_to_node_objectives\ngraph_backend\ngraph_index\nsource_graph\nadd_subgraph\nlocal_subgraphs\nall_subgraphs\nnum_local_subgraphs\nnum_subgraphs\nadd_node\nget_node\nlocal_nodes\nall_nodes\ncollect_nodes\nnum_local_nodes\nnum_nodes\nnum_local_variables\nadd_edge\nget_edge\nget_edge_by_index\nPlasmo.has_edge\nlocal_edges\nall_edges\nnum_local_edges\nnum_edges\nnum_local_link_constraints\nnum_link_constraints\nlocal_link_constraints\nall_link_constraints\nnum_local_constraints\nlocal_constraints\nlocal_elements\nall_elements\nBase.getindex(::OptiGraph, ::Int)","category":"page"},{"location":"documentation/api_docs/#Plasmo.OptiGraph","page":"API Documentation","title":"Plasmo.OptiGraph","text":"OptiGraph\n\nThe core modeling object of Plasmo.jl. An optigraph represents an optimization model as a set of OptiNode and OptiEdge objects.\n\n\n\n\n\n","category":"type"},{"location":"documentation/api_docs/#Plasmo.OptiNode","page":"API Documentation","title":"Plasmo.OptiNode","text":"OptiNode{GT<:AbstractOptiGraph} <: AbstractOptiNode\n\nA data structure meant to encapsulate variables, constraints, an objective function, and other model data. An optinode is \"lightweight\" in the sense that it does not directly contain model data, but instead acts as an interface that maps to a backend where the model data is stored. This avoids the need to generate memory overhead through container structures in cases when a node contains very little model data.\n\n\n\n\n\n","category":"type"},{"location":"documentation/api_docs/#Plasmo.OptiEdge","page":"API Documentation","title":"Plasmo.OptiEdge","text":"OptiEdge{GT<:AbstractOptiGraph} <: AbstractOptiEdge\n\nA data structure meant to encapsulate linking constraints and other model data. An optiedge is \"lightweight\" in the sense that it does not directly contain model data, but instead acts as an interface that maps to a backend where the model data is stored. This avoids the need to generate memory overhead through container structures in cases when a node contains very little model data.\n\n\n\n\n\n","category":"type"},{"location":"documentation/api_docs/#Plasmo.@optinode","page":"API Documentation","title":"Plasmo.@optinode","text":"@optinode(optigraph, expr...)\n\nAdd a new optinode to optigraph. The expression expr can either be\n\nof the form nodename creating a single optinode with the variable name varname\nof the form nodename[...] or [...] creating a container of optinodes using JuMP Containers\n\n\n\n\n\n","category":"macro"},{"location":"documentation/api_docs/#Plasmo.@linkconstraint","page":"API Documentation","title":"Plasmo.@linkconstraint","text":"@linkconstraint(graph::OptiGraph, expr)\n\nAdd a linking constraint described by the expression expr.\n\n@linkconstraint(graph::OptiGraph, ref[i=..., j=..., ...], expr)\n\nAdd a group of linking constraints described by the expression expr parametrized by i, j, ...\n\nThe @linkconstraint macro works the same way as the JuMP.@constraint macro.\n\n\n\n\n\n","category":"macro"},{"location":"documentation/api_docs/#Plasmo.@nodevariables","page":"API Documentation","title":"Plasmo.@nodevariables","text":"@nodevariables(iterable, expr...)\n\nCall the JuMP.@variable macro for each optinode in a given container\n\n\n\n\n\n","category":"macro"},{"location":"documentation/api_docs/#Plasmo.set_to_node_objectives","page":"API Documentation","title":"Plasmo.set_to_node_objectives","text":"set_to_node_objectives(graph::OptiGraph)\n\nSet the graph objective to the summation of all of its optinode objectives. Assumes the objective sense is an MOI.MIN_SENSE and accounts for the sense of node objectives accordingly. \n\nNote that building nonlinear objective functions is much slower than linear or quadratic because nonlienar expressions cannot be updated in place.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.graph_backend","page":"API Documentation","title":"Plasmo.graph_backend","text":"graph_backend(graph::OptiGraph)\n\nReturn the intermediate backend used to map the optigraph to an optimizer. Plasmo.jl currently only supports a backend to MathOptInterface.jl optimizers, but future versions intend to support GraphOptInterface.jl as a structured backend. \n\n\n\n\n\ngraph_backend(node::OptiNode)\n\nReturn the GraphMOIBackend that holds the associated node model attributes\n\n\n\n\n\ngraph_backend(edge::OptiEdge)\n\nReturn the GraphMOIBackend that holds the associated edge model attributes\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.graph_index","page":"API Documentation","title":"Plasmo.graph_index","text":"graph_index(ref::RT) where {RT<:Union{NodeVariableRef,ConstraintRef}}\n\nReturn the the corresponding variable or constraint index corresponding to a reference.\n\n\n\n\n\ngraph_index(\n backend::GraphMOIBackend, \n ref::RT\n) where {RT<:Union{NodeVariableRef,ConstraintRef}}\n\nReturn the actual variable or constraint index of the backend model that corresponds to the local index of a node or edge.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.source_graph","page":"API Documentation","title":"Plasmo.source_graph","text":"source_graph(node::OptiNode)\n\nReturn the optigraph that contains the optinode. This is the optigraph that defined said node and stores node object dictionary data.\n\n\n\n\n\nsource_graph(edge::OptiEdge)\n\nReturn the optigraph that contains the optiedge. This is the optigraph that defined said edge and stores edge object dictionary data.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.add_subgraph","page":"API Documentation","title":"Plasmo.add_subgraph","text":"add_subgraph(graph::OptiGraph; name::Symbol=Symbol(:sg,gensym()))\n\nCreate and add a new subgraph to the optigraph graph.\n\n\n\n\n\nadd_subgraph(graph::OptiGraph; name::Symbol=Symbol(:sg,gensym()))\n\nAdd an existing subgraph to an optigraph. The subgraph cannot already be part of another optigraph. It also should not have nodes that already exist in the optigraph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_subgraphs","page":"API Documentation","title":"Plasmo.local_subgraphs","text":"local_subgraphs(graph::OptiGraph)::Vector{OptiGraph}\n\nRetrieve the local subgraphs of graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.all_subgraphs","page":"API Documentation","title":"Plasmo.all_subgraphs","text":"all_subgraphs(graph::OptiGraph)::Vector{OptiGraph}\n\nRetrieve all subgraphs of graph. Includes subgraphs within other subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_subgraphs","page":"API Documentation","title":"Plasmo.num_local_subgraphs","text":"num_local_subgraphs(graph::OptiGraph)::Int\n\nRetrieve the number of local subgraphs in graph. Does not include graph in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_subgraphs","page":"API Documentation","title":"Plasmo.num_subgraphs","text":"num_subgraphs(graph::OptiGraph)::Int\n\nRetrieve the total number of subgraphs in graph. Include subgraphs within subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.add_node","page":"API Documentation","title":"Plasmo.add_node","text":"add_node(\n graph::OptiGraph; label=Symbol(graph.label, Symbol(\".n\"), length(graph.optinodes)+1\n)\n\nAdd a new optinode to graph. By default, the node label is set to be \"n\" where \"i\" is the number of nodes in the graph.\n\n\n\n\n\nadd_node(graph::OptiGraph, node::OptiNode)\n\nAdd an existing optinode (created in another optigraph) to graph. This copies model data from the other graph to the new graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.get_node","page":"API Documentation","title":"Plasmo.get_node","text":"get_node(graph::OptiGraph, idx::Int)\n\nRetrieve the optinode in graph at the given index.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_nodes","page":"API Documentation","title":"Plasmo.local_nodes","text":"local_nodes(graph::OptiGraph)::Vector{<:OptiNode}\n\nRetrieve the optinodes defined within the optigraph graph. This does not return nodes that exist in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.all_nodes","page":"API Documentation","title":"Plasmo.all_nodes","text":"all_nodes(graph::OptiGraph)::Vector{<:OptiNode}\n\nRecursively collect all optinodes in graph by traversing each of its subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.collect_nodes","page":"API Documentation","title":"Plasmo.collect_nodes","text":"collect_nodes(jump_func::T where T <: JuMP.AbstractJuMPScalar)\n\nRetrieve the optinodes contained in a JuMP expression.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_nodes","page":"API Documentation","title":"Plasmo.num_local_nodes","text":"num_local_nodes(graph::OptiGraph)::Int\n\nReturn the number of local nodes in the optigraph graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_nodes","page":"API Documentation","title":"Plasmo.num_nodes","text":"num_nodes(graph::OptiGraph)::Int\n\nReturn the total number of nodes in graph by recursively checking subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_variables","page":"API Documentation","title":"Plasmo.num_local_variables","text":"num_local_variables(graph::OptiGraph)\n\nReturn the number of local variables in graph. Does not include variables in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.add_edge","page":"API Documentation","title":"Plasmo.add_edge","text":"add_edge(\n graph::OptiGraph,\n nodes::OptiNode...;\n label=Symbol(graph.label, Symbol(\".e\"), length(graph.optiedges) + 1),\n)\n\nAdd a new optiedge to graph that connects nodes. By default, the edge label is set to be \"e\" where \"i\" is the number of edges in the graph.\n\n\n\n\n\nadd_edge(graph::OptiGraph, edge::OptiEdge)\n\nAdd an existing optiedge (created in another optigraph) to graph. This copies model data from the other graph to the new graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.get_edge","page":"API Documentation","title":"Plasmo.get_edge","text":"get_edge(graph::OptiGraph, nodes::Set{<:OptiNode})\n\nRetrieve the optiedge in graph. that connects nodes.\n\n\n\n\n\nget_edge(graph::OptiGraph, nodes::OptiNode...)\n\nConvenience method. Retrieve the optiedge in graph that connects nodes.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.get_edge_by_index","page":"API Documentation","title":"Plasmo.get_edge_by_index","text":"get_edge_by_index(graph::OptiGraph, idx::Int64)\n\nRetrieve the optiedge in graph that corresponds to the given index.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.has_edge","page":"API Documentation","title":"Plasmo.has_edge","text":"has_edge(graph::OptiGraph, nodes::Set{<:OptiNode})\n\nReturn whether an edge that connects nodes exists in the graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_edges","page":"API Documentation","title":"Plasmo.local_edges","text":"local_edges(graph::OptiGraph)\n\nRetrieve the edges that exists in graph. Does not return edges that exist in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.all_edges","page":"API Documentation","title":"Plasmo.all_edges","text":"all_edges(graph::OptiGraph)::Vector{<:OptiNode}\n\nRecursively collect all optiedges in graph by traversing each of its subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_edges","page":"API Documentation","title":"Plasmo.num_local_edges","text":"num_local_edges(graph::OptiGraph)::Int\n\nReturn the number of local edges in the optigraph graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_edges","page":"API Documentation","title":"Plasmo.num_edges","text":"num_edges(graph::OptiGraph)::Int\n\nReturn the total number of nodes in graph by recursively checking subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_link_constraints","page":"API Documentation","title":"Plasmo.num_local_link_constraints","text":"num_local_link_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve the number of local linking constraints with function func_type and set set_type in graph. Does not include linking constraints in subgraphs.\n\n\n\n\n\nnum_local_link_constraints(graph::OptiGraph)\n\nRetrieve the number of local linking constraints (all constraint types) in graph. Does not include linking constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_link_constraints","page":"API Documentation","title":"Plasmo.num_link_constraints","text":"num_link_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve the total number of linking constraints with function func_type and set set_type in graph. Includes constraints in subgraphs.\n\n\n\n\n\nnum_link_constraints(graph::OptiGraph)\n\nRetrieve the number of local linking constraints (all constraint types) in graph. Does not include constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_link_constraints","page":"API Documentation","title":"Plasmo.local_link_constraints","text":"local_link_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve the local linking constraints with function func_type and set set_type in graph. Does not include linking constraints in subgraphs.\n\n\n\n\n\nlocal_link_constraints(graph::OptiGraph)\n\nRetrieve the local linking constraints (all constraint types) in graph. Does not include constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.all_link_constraints","page":"API Documentation","title":"Plasmo.all_link_constraints","text":"all_link_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve all linking constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.\n\n\n\n\n\nall_link_constraints(graph::OptiGraph)\n\nRetrieve all linking constraints (all constraint types) in graph. Includes linking constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.num_local_constraints","page":"API Documentation","title":"Plasmo.num_local_constraints","text":"num_local_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve the number of local constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.\n\n\n\n\n\nnum_local_constraints(graph::OptiGraph)\n\nRetrieve the number of local constraints (all constraint types) in graph. Does not include constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_constraints","page":"API Documentation","title":"Plasmo.local_constraints","text":"local_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nRetrieve the local constraints with function func_type and set set_type in graph. Does not include constraints in subgraphs.\n\n\n\n\n\nlocal_constraints(graph::OptiGraph)\n\nRetrieve the local constraints (all constraint types) in graph. Does not include constraints in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.local_elements","page":"API Documentation","title":"Plasmo.local_elements","text":"local_elements(graph::OptiGraph)\n\nRetrieve the local elements (nodes and edges) in graph. Does not include elements in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.all_elements","page":"API Documentation","title":"Plasmo.all_elements","text":"local_elements(graph::OptiGraph)\n\nRetrieve all elements (nodes and edges) in graph. Includes elements in subgraphs.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Base.getindex-Tuple{OptiGraph, Int64}","page":"API Documentation","title":"Base.getindex","text":"Base.getindex(graph::OptiGraph, idx::Int)\n\nGet the optinode at the given index.\n\n\n\n\n\n","category":"method"},{"location":"documentation/api_docs/#JuMP.jl-Extended-Methods","page":"API Documentation","title":"JuMP.jl Extended Methods","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"JuMP.name\nJuMP.set_name\nJuMP.index\nJuMP.backend\nJuMP.value\nJuMP.add_variable\nJuMP.num_variables\nJuMP.all_variables\nJuMP.start_value\nJuMP.set_start_value\nJuMP.add_constraint\nJuMP.list_of_constraint_types\nJuMP.num_constraints\nJuMP.all_constraints\nJuMP.objective_value\nJuMP.dual_objective_value\nJuMP.objective_sense\nJuMP.objective_function\nJuMP.objective_function_type\nJuMP.objective_bound\nJuMP.set_objective\nJuMP.set_objective_function\nJuMP.set_objective_sense\nJuMP.set_objective_coefficient\nJuMP.set_optimizer\nJuMP.add_nonlinear_operator\nJuMP.optimize!\nJuMP.termination_status\nJuMP.primal_status\nJuMP.dual_status\nJuMP.relative_gap\nJuMP.constraint_ref_with_index\nJuMP.object_dictionary","category":"page"},{"location":"documentation/api_docs/#JuMP.name","page":"API Documentation","title":"JuMP.name","text":"JuMP.name(graph::OptiGraph)\n\nReturn the name of graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_name","page":"API Documentation","title":"JuMP.set_name","text":"JuMP.set_name(graph::OptiGraph, name::Symbol)\n\nSet the name of graph to name.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.index","page":"API Documentation","title":"JuMP.index","text":"JuMP.index(graph::OptiGraph, nvref::NodeVariableRef)\n\nReturn the backend model index of node variable nvref\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.backend","page":"API Documentation","title":"JuMP.backend","text":"JuMP.backend(graph::OptiGraph)\n\nReturn the backend model object for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.value","page":"API Documentation","title":"JuMP.value","text":"JuMP.value(graph::OptiGraph, nvref::NodeVariableRef; result::Int=1)\n\nReturn the primal value of nvref in graph. Note that this value is specific to the optimizer solution to the graph. The nvref can have different values for different optigraphs it is contained in.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.add_variable","page":"API Documentation","title":"JuMP.add_variable","text":"JuMP.add_variable(node::OptiNode, v::JuMP.AbstractVariable, name::String=\"\")\n\nAdd variable v to optinode node. This function supports use of the @variable JuMP macro. Optionally add a base_name to the variable for printing.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.num_variables","page":"API Documentation","title":"JuMP.num_variables","text":"JuMP.num_variables(graph::OptiGraph)\n\nReturn the total number of variables in graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.all_variables","page":"API Documentation","title":"JuMP.all_variables","text":"JuMP.all_variables(graph::OptiGraph)\n\nReturn all of the variables in graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.start_value","page":"API Documentation","title":"JuMP.start_value","text":"JuMP.start_value(graph::OptiGraph, nvref::NodeVariableRef)\n\nReturn the start value for variable nvref in graph. Note that different graphs can have different start values for node variables.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_start_value","page":"API Documentation","title":"JuMP.set_start_value","text":"JuMP.set_start_value(\n graph::OptiGraph, \n nvref::NodeVariableRef, \n value::Union{Nothing,Real}\n)\n\nSet the start value of variable nvref in graph. Note that different graphs can have different start values for node variables.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.add_constraint","page":"API Documentation","title":"JuMP.add_constraint","text":"JuMP.add_constraint(graph::OptiGraph, con::JuMP.AbstractConstraint, name::String=\"\")\n\nAdd a new constraint to graph. This method is called internall when a user uses the JuMP.@constraint macro.\n\n\n\n\n\nJuMP.add_constraint(node::OptiNode, con::JuMP.AbstractConstraint, name::String=\"\")\n\nAdd a constraint con to optinode node. This function supports use of the @constraint JuMP macro.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.list_of_constraint_types","page":"API Documentation","title":"JuMP.list_of_constraint_types","text":"JuMP.list_of_constraint_types(graph::OptiGraph)::Vector{Tuple{Type,Type}}\n\nList all of the constraint types in graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.num_constraints","page":"API Documentation","title":"JuMP.num_constraints","text":"JuMP.num_constraints(\n graph::OptiGraph,\n func_type::Type{<:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}}},\n set_type::Type{<:MOI.AbstractSet},\n)\n\nReturn all the number of contraints in graph with func_type and set_type.\n\n\n\n\n\nJuMP.num_constraints(graph::OptiGraph; count_variable_in_set_constraints=true)\n\nReturn the total number of constraints in graph. If count_variable_in_set_constraints is set to true, this also includes variable bound constraints. \n\n\n\n\n\nJuMP.num_constraints(\nelement::OptiElement,\nfunction_type::Type{\n <:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}},\n},set_type::Type{<:MOI.AbstractSet})::Int64\n\nReturn the total number of constraints on an element.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.all_constraints","page":"API Documentation","title":"JuMP.all_constraints","text":"JuMP.all_constraints(\n graph::OptiGraph,\n func_type::Type{\n <:Union{JuMP.AbstractJuMPScalar,Vector{<:JuMP.AbstractJuMPScalar}},\n },\n set_type::Type{<:MOI.AbstractSet}\n)\n\nReturn all of the constraints in graph with func_type and set_type.\n\n\n\n\n\nJuMP.all_constraints(graph::OptiGraph)\n\nReturn all of the constraints in graph (all function and set types).\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.objective_value","page":"API Documentation","title":"JuMP.objective_value","text":"JuMP.objective_value(graph::OptiGraph)\n\nRetrieve the current objective value on optigraph graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.dual_objective_value","page":"API Documentation","title":"JuMP.dual_objective_value","text":"JuMP.dual_objective_value(graph::OptiGraph; result::Int=1)\n\nReturn the dual objective value for graph. Specify result for cases when a solver returns multiple results.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.objective_sense","page":"API Documentation","title":"JuMP.objective_sense","text":"JuMP.objective_sense(graph::OptiGraph)\n\nReturn the objective sense for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.objective_function","page":"API Documentation","title":"JuMP.objective_function","text":"JuMP.objective_function(graph::OptiGraph)\n\nReturn the objective function for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.objective_function_type","page":"API Documentation","title":"JuMP.objective_function_type","text":"JuMP.objective_sense(graph::OptiGraph)\n\nReturn the objective function type for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.objective_bound","page":"API Documentation","title":"JuMP.objective_bound","text":"JuMP.objective_bound(graph::OptiGraph)\n\nReturn the objective bound for the current solution for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_objective","page":"API Documentation","title":"JuMP.set_objective","text":"JuMP.set_objective(\n graph::OptiGraph, \n sense::MOI.OptimizationSense, \n func::JuMP.AbstractJuMPScalar\n)\n\nSet the objective function and objective sense for graph. This method is called internally when a user uses the JuMP.@objective macro.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_objective_function","page":"API Documentation","title":"JuMP.set_objective_function","text":"JuMP.set_objective_function(graph::OptiGraph, expr::JuMP.AbstractJuMPScalar)\n\nSet the objective function of graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_objective_sense","page":"API Documentation","title":"JuMP.set_objective_sense","text":"JuMP.set_objective_sense(graph::OptiGraph, sense::MOI.OptimizationSense)\n\nSet the objective sense of graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_objective_coefficient","page":"API Documentation","title":"JuMP.set_objective_coefficient","text":"JuMP.set_objective_coefficient(\n graph::OptiGraph, \n variable::NodeVariableRef, \n coeff::Real\n)\n\nSet the objective function coefficient for variable to coefficient coeff.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.set_optimizer","page":"API Documentation","title":"JuMP.set_optimizer","text":"JuMP.set_optimizer(\n graph::OptiGraph, \n JuMP.@nospecialize(optimizer_constructor); \n add_bridges::Bool=true\n)\n\nSet the optimizer on graph by passing an optimizer_constructor.\n\n\n\n\n\nJuMP.set_optimizer(\n node::OptiNode, \n JuMP.@nospecialize(optimizer_constructor); \n add_bridges::Bool=true\n)\n\nSet the optimizer for an optinode.This internally creates a new optigraph that is used to optimize the node. Calling this method on a node returns the newly created graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.add_nonlinear_operator","page":"API Documentation","title":"JuMP.add_nonlinear_operator","text":"JuMP.add_nonlinear_operator(\n graph::OptiGraph,\n dim::Int,\n f::Function,\n args::Vararg{Function,N};\n name::Symbol=Symbol(f),\n) where {N}\n\nAdd a nonlinear operator to a graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.optimize!","page":"API Documentation","title":"JuMP.optimize!","text":"JuMP.optimize!(\n graph::OptiGraph;\n kwargs...,\n)\n\nOptimize graph using the current set optimizer.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.termination_status","page":"API Documentation","title":"JuMP.termination_status","text":"JuMP.termination_status(graph::OptiGraph)\n\nReturn the solver termination status of graph if a solver has been executed.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.primal_status","page":"API Documentation","title":"JuMP.primal_status","text":"JuMP.primal_status(graph::OptiGraph; result::Int=1)\n\nReturn the primal status of graph if a solver has been executed.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.dual_status","page":"API Documentation","title":"JuMP.dual_status","text":"JuMP.dual_status(graph::OptiGraph; result::Int=1)\n\nReturn the dual status of graph if a solver has been executed.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.relative_gap","page":"API Documentation","title":"JuMP.relative_gap","text":"JuMP.relative_gap(graph::OptiGraph)\n\nReturn the relative gap in the current solution for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.constraint_ref_with_index","page":"API Documentation","title":"JuMP.constraint_ref_with_index","text":"JuMP.constraint_ref_with_index(\nelement::OptiElement, \nidx::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction, <:MOI.AbstractScalarSet}\n)\n\nReturn a ConstraintRef given an optigraph element and MOI.ConstraintIndex. Note that the index is the index corresponding to the graph backend, not the element index.\n\n\n\n\n\nJuMP.constraint_ref_with_index(backend::GraphMOIBackend, idx::MOI.Index)\n\nReturn the constraint reference (or variable reference) associated with the graph index in backend. Returns a JuMP.ConstraintRef (or NodeVariableRef) object.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#JuMP.object_dictionary","page":"API Documentation","title":"JuMP.object_dictionary","text":"JuMP.object_dictionary(graph::OptiGraph)\n\nReturn the object dictionary for graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Interop-with-JuMP.jl","page":"API Documentation","title":"Interop with JuMP.jl","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"set_jump_model","category":"page"},{"location":"documentation/api_docs/#Plasmo.set_jump_model","page":"API Documentation","title":"Plasmo.set_jump_model","text":"Set a JuMP.Model to `node`. This copies the model data over and does not mutate\n\nthe model in any way. \n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Graph-Projections","page":"API Documentation","title":"Graph Projections","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"Plasmo.GraphProjection\nhyper_projection\nedge_hyper_projection\nclique_projection\nedge_clique_projection\nbipartite_projection","category":"page"},{"location":"documentation/api_docs/#Plasmo.GraphProjection","page":"API Documentation","title":"Plasmo.GraphProjection","text":"GraphProjection\n\nA mapping between OptiGraph elements (nodes and edges) and elements in a graph projection. A graph projection can be for example a hypergraph, a bipartite graph or a standard graph.\n\n\n\n\n\n","category":"type"},{"location":"documentation/api_docs/#Plasmo.hyper_projection","page":"API Documentation","title":"Plasmo.hyper_projection","text":"hyper_projection(graph::OptiGraph)\n\nRetrieve a hypergraph representation of the optigraph graph. Returns a GraphProjection that maps elements between the optigraph and the projected graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.edge_hyper_projection","page":"API Documentation","title":"Plasmo.edge_hyper_projection","text":"edge_hyper_projection(graph::OptiGraph)\n\nRetrieve an edge-hypergraph representation of the optigraph graph. This is sometimes called the dual-hypergraph representation of a hypergraph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.clique_projection","page":"API Documentation","title":"Plasmo.clique_projection","text":"clique_projection(graph::OptiGraph)\n\nRetrieve a standard graph representation of graph. The projection contains a standard Graphs.Graph and a mapping between its elements and the given optigraph. This projection works by creating an edge for each pair of nodes in each hyperedge.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.edge_clique_projection","page":"API Documentation","title":"Plasmo.edge_clique_projection","text":"edge_clique_projection(graph::OptiGraph)\n\nRetrieve the edge-graph representation of optigraph graph. This is sometimes called the line graph of a hypergraph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.bipartite_projection","page":"API Documentation","title":"Plasmo.bipartite_projection","text":"bipartite_graph(graph::OptiGraph)\n\nCreate a bipartite graph representation from graph. The bipartite graph contains two sets of vertices corresponding to optinodes and optiedges respectively.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Partitioning-and-Aggregation","page":"API Documentation","title":"Partitioning and Aggregation","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"Partition\nassemble_optigraph\napply_partition!\naggregate\naggregate_to_depth\naggregate_to_depth!","category":"page"},{"location":"documentation/api_docs/#Plasmo.Partition","page":"API Documentation","title":"Plasmo.Partition","text":"Partition\n\nA data structure that describes a (possibly recursive) graph partition.\n\n\n\n\n\n","category":"type"},{"location":"documentation/api_docs/#Plasmo.assemble_optigraph","page":"API Documentation","title":"Plasmo.assemble_optigraph","text":"assemble_optigraph(nodes::Vector{<:OptiNode}, edges::Vector{OptiEdge})\n\nCreate a new optigraph from a collection of nodes and edges.\n\n\n\n\n\nAssemble a new optigraph from a given `Partition`.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.apply_partition!","page":"API Documentation","title":"Plasmo.apply_partition!","text":"apply_partition!(graph::OptiGraph, partition::Partition)\n\nGenerate subgraphs in an optigraph using a partition.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.aggregate","page":"API Documentation","title":"Plasmo.aggregate","text":"aggregate(graph::OptiGraph; name=gensym())\n\nAggregate an optigraph into a graph containing a single optinode. An optional name can be used to name the new optigraph. Returns the new optinode (which points to a new graph with source_graph(node)) and a mapping from the original graph variables and constraints to the new node variables and constraints.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.aggregate_to_depth","page":"API Documentation","title":"Plasmo.aggregate_to_depth","text":"aggregate_to_depth(graph::OptiGraph, max_depth::Int64=0)\n\nAggregate graph by converting subgraphs into optinodes. The max_depth determines how many levels of subgraphs remain in the new aggregated optigraph. For example, a max_depth of 0 signifies there should be no subgraphs in the aggregated optigraph. Return a new aggregated optigraph and reference map that maps elements from the old optigraph to the new aggregate optigraph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.aggregate_to_depth!","page":"API Documentation","title":"Plasmo.aggregate_to_depth!","text":"aggregate_to_depth!(graph::OptiGraph, max_depth::Int64=0)\n\nAggregate graph by converting subgraphs into optinodes. The max_depth determines how many levels of subgraphs remain in the new aggregated optigraph. For example, a max_depth of 0 signifies there should be no subgraphs in the aggregated optigraph. This version of the method modifies the optigraph and transforms it into the aggregated version. \n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Graph-Topology","page":"API Documentation","title":"Graph Topology","text":"","category":"section"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"Graphs.all_neighbors\nGraphs.induced_subgraph\nGraphs.neighborhood\nincident_edges\ninduced_edges\nidentify_edges\nidentify_nodes\nexpand","category":"page"},{"location":"documentation/api_docs/#Graphs.all_neighbors","page":"API Documentation","title":"Graphs.all_neighbors","text":"Graphs.all_neighbors(hyper::HyperGraphProjection, node::OptiNode)\n\nRetrieve the optinode neighbors of node in the optigraph graph. Uses an underlying hypergraph to query for neighbors.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Graphs.induced_subgraph","page":"API Documentation","title":"Graphs.induced_subgraph","text":"Graphs.induced_subgraph(graph::OptiGraph, nodes::Vector{OptiNode})\n\nCreate an induced subgraph of optigraph given a vector of optinodes.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Graphs.neighborhood","page":"API Documentation","title":"Graphs.neighborhood","text":"neighborhood(\n hyper::HyperGraphProjection, \n nodes::Vector{OptiNode}, \n distance::Int64\n)::Vector{OptiNode}\n\nReturn the optinodes within distance of the given nodes in the optigraph graph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.incident_edges","page":"API Documentation","title":"Plasmo.incident_edges","text":"incident_edges(hyper::HyperGraphProjection, nodes::Vector{OptiNode})\n\nRetrieve incident edges to a set of optinodes.\n\nincident_edges(hyper::HyperGraphProjection, node::OptiNode)\n\nRetrieve incident edges to a single optinode.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.induced_edges","page":"API Documentation","title":"Plasmo.induced_edges","text":"induced_edges(graph::OptiGraph, nodes::Vector{OptiNode})\n\nRetrieve induced edges to a set of optinodes.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.identify_edges","page":"API Documentation","title":"Plasmo.identify_edges","text":"identify_edges(hyper::HyperGraphProjection, node_vectors::Vector{Vector{OptiNode}})\n\nIdentify induced edges and edge separators from a vector of optinode partitions.\n\nArguments\n\nhyper::HyperGraphProjection: A HyperGraphProjection obtained from hyper_projection.\nnode_vectors::Vector{Vector{OptiNode}}: A vector of vectors that contain OptiNodes.\n\nReturns\n\npartition_optiedges::Vector{Vector{OptiEdge}}: The OptiEdge vectors for each partition.\ncross_optiedges::Vector{OptiEdge}: A vector of optiedges that cross partitions.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.identify_nodes","page":"API Documentation","title":"Plasmo.identify_nodes","text":"identify_nodes(hyper::HyperGraphProjection, node_vectors::Vector{Vector{OptiEdge}})\n\nIdentify induced nodes and node separators from a vector of optiedge partitions.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/#Plasmo.expand","page":"API Documentation","title":"Plasmo.expand","text":"expand(hyper::HyperGraphProjection, subgraph::OptiGraph, distance::Int64)\n\nReturn a new expanded subgraph given the optigraph graph and an existing subgraph subgraph. The returned subgraph contains the expanded neighborhood within distance of the given subgraph.\n\n\n\n\n\n","category":"function"},{"location":"documentation/api_docs/","page":"API Documentation","title":"API Documentation","text":"","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"(Image: Plasmo logo)","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"CurrentModule = Plasmo\nDocTestSetup = quote\n using Plasmo\nend","category":"page"},{"location":"#Plasmo.jl-Platform-for-Scalable-Modeling-and-Optimization","page":"Introduction","title":"Plasmo.jl - Platform for Scalable Modeling and Optimization","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"Plasmo.jl is a graph-based optimization framework written in Julia that builds upon the JuMP.jl modeling language to offer a modular style to construct and solve optimization problems. The package implements what is called an OptiGraph abstraction to create graph-structured optimization models and facilitate graph-based processing functions. An OptiGraph captures the underlying graph topology of an optimization problem using OptiNodes (which represent stand-alone self-contained optimization models) that are coupled by means of OptiEdges (which represent coupling constraints). The resulting topology can be used for tasks such as visualization, graph partitioning, and interfacing (and developing) decomposition-based solvers.","category":"page"},{"location":"#Installation","page":"Introduction","title":"Installation","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"Plasmo.jl works for Julia versions 1.6 and later. From Julia, Plasmo.jl can be installed using the Pkg module:","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"import Pkg\nPkg.add(\"Plasmo\")","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"or alternatively from the Julia package manager by performing the following:","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"pkg> add Plasmo","category":"page"},{"location":"#Contents","page":"Introduction","title":"Contents","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"Pages = [\n \"documentation/quickstart.md\"\n \"documentation/modeling.md\"\n \"documentation/graph_processing.md\"\n \"documentation/api_docs.md\"\n ]\nDepth = 2","category":"page"},{"location":"#Future-Development","page":"Introduction","title":"Future Development","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"There are currently a few major development avenues for Plasmo.jl. Here is a list of some of the major features we intend to add for future releases:","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"Distributed modeling capabilities\nCustom optigraph partitioning algorithms\nDecomposition-based solver development\nGraphOptInterface.jl development\nInterface with InfiniteOpt.jl","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"We are also looking for help from new contributors. If you would like to contribute to Plasmo.jl, please create a new issue or pull request on the GitHub page","category":"page"},{"location":"#Index","page":"Introduction","title":"Index","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"","category":"page"},{"location":"#Citing-Plasmo.jl","page":"Introduction","title":"Citing Plasmo.jl","text":"","category":"section"},{"location":"","page":"Introduction","title":"Introduction","text":"If you find Plasmo.jl useful for your work, we ask that you cite the manuscript:","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"@article{Jalving2022,\n author = {Jalving, Jordan and Shin, Sungho and Zavala, Victor M.},\n title = {A graph-based modeling abstraction for optimization: concepts and implementation in {Plasmo.jl}},\n journal = {Mathematical Programming Computation},\n volume = {14},\n pages = {699--747},\n year = {2022},\n doi = {10.1007/s12532-022-00223-3}\n}","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"You can access an earlier pre-print of this article.","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"There is also an earlier manuscript where we presented the initial ideas behind Plasmo.jl which you can find here:","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"@article{JalvingCaoZavala2019,\nauthor = {Jalving, Jordan and Cao, Yankai and Zavala, Victor M},\njournal = {Computers {\\&} Chemical Engineering},\npages = {134--154},\ntitle = {Graph-based modeling and simulation of complex systems},\nvolume = {125},\nyear = {2019},\ndoi = {10.1016/j.compchemeng.2019.03.009}\n}","category":"page"},{"location":"","page":"Introduction","title":"Introduction","text":"A pre-print of this paper can also be found here","category":"page"},{"location":"documentation/quickstart/#Quickstart","page":"Quickstart","title":"Quickstart","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"This quickstart gives a brief overview of the functions needed to effectively use Plasmo.jl to build optimization models. If you have used JuMP.jl, much of the functionality here will look familiar. In fact, the primary modeling objects in Plasmo.jl extend the JuMP.AbstractModel and support most JuMP methods. ","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"The below example demonstrates the construction of a simple linear optimization problem that contains two optinodes coupled by a simple linking contraint (which induces an OptiEdge) that is solved with the HiGHS linear optimization solver.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"Once Plasmo.jl has been installed, you can use it from a Julia session as following:","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> using Plasmo","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"For this example we also need to import the HiGHS optimization solver and the PlasmoPlots package which we use to visualize the graph structure.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> using HiGHS","category":"page"},{"location":"documentation/quickstart/#Create-an-OptiGraph","page":"Quickstart","title":"Create an OptiGraph","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"The following command will create the optigraph (referred to as graph). We also see the printed output which denotes the number of optinodes, optiedges, subgraphs, variables, and constraints in the graph.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> graph = OptiGraph(;name=:quickstart_graph)\nAn OptiGraph\nquickstart_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 0 0\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 0 0\n Constraints: 0 0\n","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"note: Note\nAn OptiGraph distinguishes between its local elements (optinodes and optiedges contained directly within the graph) and its total elements (local elements plus elements contained within subgraphs). This distinction helps to describe nested graph structures as described in Modeling with OptiGraphs.","category":"page"},{"location":"documentation/quickstart/#Add-Variables-and-Constraints-(using-OptiNodes)","page":"Quickstart","title":"Add Variables and Constraints (using OptiNodes)","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"An optigraph consists of OptiNode objects which represent stand-alone optimization models. An optinode supports JuMP macros used to create variables, constraints, expressions, and objective functions (i.e. it supports JuMP macros such as @variable, @constraint, @expression and @objective). The simplest way to add optinodes to an optigraph is to use the @optinode macro as shown in the following code snippet. Here we create the optinode n1 and add two variables x and y. We also add a single constraint and an objective function to the node. By default, the name of a node is pre-pended with the name of the graph it was created in.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> @optinode(graph, n1)\nn1\n\njulia> @variable(n1, y >= 2)\nn1[:y]\n\njulia> @variable(n1, x >= 1)\nn1[:x]\n\njulia> @constraint(n1, x + y >= 3)\nn1[:y] + n1[:x] ≥ 3\n\njulia> @objective(n1, Min, y)\nn1[:y]\n\njulia> graph\nAn OptiGraph\nquickstart_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 1 1\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 2 2\n Constraints: 3 3\n","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"We can create more optinodes and add variables, constraints, and objective functions to each of them.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"@optinode(graph, n2);\n@variable(n2, y >= 0);\n@variable(n2, x >= 2);\n@constraint(n2, x + y >= 3);\n@objective(n2, Min, y);\n\n@optinode(graph, n3);\n@variable(n3, y >= 0);\n@variable(n3, x >= 0);\n@constraint(n3, x + y >= 3);\n@objective(n3, Min, y);\n\nprintln(graph)\n\n# output\n\nAn OptiGraph\nquickstart_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 0 0\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 9 9\n","category":"page"},{"location":"documentation/quickstart/#Add-Linking-Constraints-(using-Edges)","page":"Quickstart","title":"Add Linking Constraints (using Edges)","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"A linking constraint can be created by adding a constraint to an OptiEdge. Linking constraints couple variables across nodes (or graphs!) and can take any valid JuMP expression composed of node variables. We add a simple linear equality constraint here between variables that exist on nodes n1, n2, and n3.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> edge = add_edge(graph, n1, n2, n3);\n\njulia> @constraint(edge, n1[:x] + n2[:x] + n3[:x] == 3)\nn1[:x] + n2[:x] + n3[:x] = 3\n\njulia> println(graph)\nAn OptiGraph\nquickstart_graph #local elements #total elements\n--------------------------------------------------\n Nodes: 3 3\n Edges: 1 1\n Subgraphs: 0 0\n Variables: 6 6\n Constraints: 10 10\n","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"info: Info\nYou can also create edges implicitly using the @linkconstraint macro which takes the exact same input as the JuMP.@constraint macro above. The above snippet would correspond to doing:@linkconstraint(graph, n1[:x] + n2[:x] + n3[:x] == 3)This would create a new edge between nodes n1, n2, and n3 (if one does not exist) and add the constraint to it. You can also use the JuMP.@constraint macro directly on an optigraph to generate linking constraints, but the syntax displayed here is preferred to help code readability.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"warning: Warning\nPlasmo.jl no longer supports the legacy nonlinear interface from JuMP.jl. Consequently, @NLconstraint, @NLobjective, and @NLexpression will no longer work. If you have code that uses these macros, you will need to update to the current interface using @constraint, @objective, and @expression.","category":"page"},{"location":"documentation/quickstart/#Solve-the-OptiGraph-and-Query-the-Solution","page":"Quickstart","title":"Solve the OptiGraph and Query the Solution","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"We can set the objective function of an optigraph using either the node objectives or by defining an objective directly using the JuMP.@objective macro on the graph. Since we already defined an objective for each node we can use the set_to_node_objectives function to denote the graph objective.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> set_to_node_objectives(graph)\n\njulia> objective_function(graph)\nn1[:y] + n2[:y] + n3[:y]","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"info: Info\nThe graph objective approach would look like:@objective(graph, Min, n1[:y] + n2[:y] + n3[:y])","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"Plasmo.jl uses MathOptInterface.jl internally to interface with optimization solvers. We can optimize an optigraph using the set_optimizer and optimize! functions just like in JuMP.jl. Here we also set the HiGHS attribute log_to_console to false to suppress the output.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> set_optimizer(graph, HiGHS.Optimizer);\n\njulia> set_optimizer_attribute(graph, MOI.RawOptimizerAttribute(\"log_to_console\"), false)\n\njulia> optimize!(graph)\n","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"After returning from the optimizer we can query the termination status using termination_status. We can also query the solution of variables using value and the objective value of the graph using objective_value","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"julia> termination_status(graph) \nOPTIMAL::TerminationStatusCode = 1\n\njulia> value(graph, n1[:x]) \n1.0\n\njulia> value(graph, n2[:x])\n2.0\n\njulia> value(graph, n3[:x])\n0.0\n\njulia> objective_value(graph)\n6.0\n","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"info: Info\nIt is possible to optimize individual optinodes or different optigraphs that contain the same optinode (nodes can be used in multiple optigraphs). Graph-specific solutions can be accessed using value(node, variable) (if optimizing a single node) or value(graph, variable) (if optimizing an optigraph). \\\nNote that optimizing a node creates a new graph internally; the optimizer interface always goes through a graph. It is also possible to use value(variable) without specifying a graph, but this will always return the value corresponding to the graph that created the node (this graph can be queried using source_graph(node)). Using value(node) is likely fine for most use-cases, but be aware that you should use the value(graph, variable) method when dealing with multiple graphs to avoid grabbing a wrong solution.","category":"page"},{"location":"documentation/quickstart/#Visualize-the-Structure","page":"Quickstart","title":"Visualize the Structure","text":"","category":"section"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"Lastly, it is often useful to visualize the structure of an optigraph. The visualization can lead to insights about an optimization problem and understand its connectivity. Plasmo.jl uses PlasmoPlots.jl (which builds on Plots.jl and NetworkLayout.jl) to visualize the layout of an optigraph. The code here shows how to obtain the graph topology using PlasmoPlots.layout_plot and we plot the corresponding incidence matrix structure using PlasmoPlots.matrix_plot. Both of these functions can accept keyword arguments to customize their layout or appearance. The matrix visualization also encodes information on the number of variables and constraints in each optinode and optiedge. The left figure shows a standard graph visualization where we draw an edge between each pair of nodes if they share an edge, and the right figure shows the matrix representation where labeled blocks correspond to nodes and blue marks represent linking constraints that connect their variables. The node layout helps visualize the overall connectivity of the graph while the matrix layout helps visualize the size of nodes and edges.","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"plt_graph = PlasmoPlots.layout_plot(\n graph,\n node_labels=true, \n markersize=30, \n labelsize=15, \n linewidth=4,\n layout_options=Dict(\n :tol=>0.01, \n :iterations=>2\n ),\n plt_options=Dict(\n :legend=>false, \n :framestyle=>:box, \n :grid=>false,\n :size=>(400,400), \n :axis=>nothing)\n )\n\nplt_matrix = PlasmoPlots.matrix_plot(graph, node_labels=true, markersize=15) ","category":"page"},{"location":"documentation/quickstart/","page":"Quickstart","title":"Quickstart","text":"(Image: graph_quickstart) (Image: matrix_quickstart)","category":"page"},{"location":"tutorials/quadcopter/#Optimal-Control-of-a-Quadcopter","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"","category":"section"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"By: Rishi Mandyam","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"This tutorial notebook is an introduction to the graph-based modeling framework Plasmo.jl (Platform for Scalable Modeling and Optimization) for JuMP (Julia for Mathematical Programming).","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The following problem comes from the paper of Na, Shin, Anitescu, and Zavala (available here).","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"A quadcopter operates in 3-D space with positions (x y z) and angles (gamma, beta, and alpha). g is the graviational constant. The set of state variables at time t are treated as boldsymbolx_t = (x y z dotx doty dotz gamma beta alpha). The input variables at time t are boldsymbolu_t = (a omega_x omega_y omega_z). ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The quadcopter control problem can be written as an optimization problem as:","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"beginalign*\n min phi(t) = int_0^T frac12 (boldsymbolx(t) - boldsymbolx(t)^ref)^top Q (boldsymbolx(t) - boldsymbolx(t)^ref) + boldsymbolu(t)^top R boldsymbolu(t) dt \n textrmst fracd^2xdt^2 = a (cos(gamma) sin( beta) cos (alpha) + sin (gamma) sin (alpha)) \n fracd^2 ydt^2 = a (cos (gamma) sin (beta) sin (alpha) - sin (gamma) cos (alpha)) \n fracd^2 zdt^2 = a cos (gamma) cos (beta) - g \n fracdgammadt = (omega_x cos (gamma) + omega_y sin (gamma)) cos (beta)\nfracdbetadt = -omega_x sin (gamma) + omega_y cos (gamma) \nfracdalphadt = omega_x cos (gamma) tan (beta) + omega_y sin (gamma) tan (beta) + omega_z\nendalign*","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"We will model this problem in Plasmo by discretizing the problem into finite time points and representing each time point with a node. ","category":"page"},{"location":"tutorials/quadcopter/#1.-Import-Packages","page":"Optimal Control of a Quadcopter","title":"1. Import Packages","text":"","category":"section"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"To begin, we will import and use the necessary packages","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"using JuMP\nusing Plasmo\nusing Ipopt\nusing Plots\nusing LinearAlgebra","category":"page"},{"location":"tutorials/quadcopter/#2.-Function-Design","page":"Optimal Control of a Quadcopter","title":"2. Function Design","text":"","category":"section"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"We will define a function called build_quadcopter_graph that will take arguments for the number of nodes and the discretization size (i.e., Delta t), optimize the model, and return the graph and reference values x^ref. ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The function inputs are:","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"number of nodes (N)\ntime discretization (number of seconds between nodes dt)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The function outputs are:","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The objective value of the discretized form of phi\nThe graph\nAn array with the reference values on each node (x^ref)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The build_quadcopter_graph function will use three supporting functions that will add variables, add constraints (both local and linking) and add the objectives to the nodes. These functions will be detailed below before they are used to build the full quadcopter graph. ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"The first function we call add_variables!, which defines each of the decision variables as well as some supporting expressions. Here, we loop through the nodes, and define variables on each node. These variables include expressions that will simplify forming the linking constrints (these are the right hand sides of the derivatives). ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"function add_variables!(nodes)\n grav = 9.8 # m/s^2\n\n for (i, node) in enumerate(nodes)\n # Create state variables\n @variable(node, g)\n @variable(node, b)\n @variable(node, a)\n\n @variable(node, X)\n @variable(node, Y)\n @variable(node, Z)\n\n @variable(node, dXdt)\n @variable(node, dYdt)\n @variable(node, dZdt)\n\n # Create input variables\n @variable(node, C_a)\n @variable(node, wx)\n @variable(node, wy)\n @variable(node, wz)\n\n # These expressions to simplify the linking constraints later\n @expression(node, d2Xdt2, C_a * (cos(g) * sin(b) * cos(a) + sin(g) * sin(a)))\n @expression(node, d2Ydt2, C_a * (cos(g) * sin(b) * sin(a) + sin(g) * cos(a)))\n @expression(node, d2Zdt2, C_a * cos(g) * cos(b) - grav)\n\n @expression(node, dgdt, (wx * cos(g) + wy * sin(g)) / (cos(b)))\n @expression(node, dbdt, - wx * sin(g) + wy * cos(g))\n @expression(node, dadt, wx * cos(g) * tan(b) + wy * sin(g) * tan(b) + wz)\n end\nend","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"Next, we define a function for adding the constraints to the graph. We will set the initial values at time 0 and then define the linking constraints, which are discretized derivatives. Note that both linear and nonlinear constraints are handled in the same way by the user in both the @constraint and @linkconstraint macros. ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"function add_constraints!(graph, nodes, dt)\n N = length(nodes)\n\n @constraint(nodes[1], nodes[1][:X] == 0)\n @constraint(nodes[1], nodes[1][:Y] == 0)\n @constraint(nodes[1], nodes[1][:Z] == 0)\n @constraint(nodes[1], nodes[1][:dXdt] == 0)\n @constraint(nodes[1], nodes[1][:dYdt] == 0)\n @constraint(nodes[1], nodes[1][:dZdt] == 0)\n @constraint(nodes[1], nodes[1][:g] == 0)\n @constraint(nodes[1], nodes[1][:b] == 0)\n @constraint(nodes[1], nodes[1][:a] == 0)\n\n for i in 1:(N-1) # iterate through each node except the last\n @linkconstraint(graph, nodes[i+1][:dXdt] == dt*nodes[i][:d2Xdt2] + nodes[i][:dXdt])\n @linkconstraint(graph, nodes[i+1][:dYdt] == dt*nodes[i][:d2Ydt2] + nodes[i][:dYdt])\n @linkconstraint(graph, nodes[i+1][:dZdt] == dt*nodes[i][:d2Zdt2] + nodes[i][:dZdt])\n\n @linkconstraint(graph, nodes[i+1][:g] == dt*nodes[i][:dgdt] + nodes[i][:g])\n @linkconstraint(graph, nodes[i+1][:b] == dt*nodes[i][:dbdt] + nodes[i][:b])\n @linkconstraint(graph, nodes[i+1][:a] == dt*nodes[i][:dadt] + nodes[i][:a])\n\n @linkconstraint(graph, nodes[i+1][:X] == dt*nodes[i][:dXdt] + nodes[i][:X])\n @linkconstraint(graph, nodes[i+1][:Y] == dt*nodes[i][:dYdt] + nodes[i][:Y])\n @linkconstraint(graph, nodes[i+1][:Z] == dt*nodes[i][:dZdt] + nodes[i][:Z])\n end\nend","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"Next, we set a function for defining the objectives. The quadcopter will fly in a linear upward path in the positive X, Y, and Z directions. We combine these vectors into another vector which we call x^ref, and then define the necessary constants and arrays (this will simplify forming the objective function).","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"function add_objective!(nodes, N, dt)\n X_ref = 0:10/N:10;\n dXdt_ref = zeros(N);\n Y_ref = 0:10/N:10;\n dYdt_ref = zeros(N)\n Z_ref = 0:10/N:10;\n dZdt_ref = zeros(N);\n g_ref = zeros(N);\n b_ref = zeros(N);\n a_ref = zeros(N);\n\n xk_ref = [X_ref, dXdt_ref, Y_ref, dYdt_ref, Z_ref, dZdt_ref, g_ref, b_ref, a_ref];\n\n Q = diagm([1, 0, 1, 0, 1, 0, 1, 1, 1]);\n R = diagm([1/10, 1/10, 1/10, 1/10]);\n\n xk_ref1 = zeros(N,9)\n for i in (1:N)\n for j in 1:length(xk_ref)\n xk_ref1[i,j] = xk_ref[j][i]\n end\n end\n\n for (i, node) in enumerate(nodes)\n xk = [ # Array to hold variables\n node[:X],\n node[:dXdt],\n node[:Y],\n node[:dYdt],\n node[:Z],\n node[:dZdt],\n node[:g],\n node[:b],\n node[:a]\n ]\n\n xk1 = xk .- xk_ref1[i, :] # Array to hold the difference between variable values and their setpoints.\n uk = [node[:C_a], node[:wx], node[:wy], node[:wz]]\n @objective(node, Min, (1 / 2 * (xk1') * Q * (xk1) + 1 / 2 * (uk') * R * (uk)) * dt)\n end\n return xk_ref\nend","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"We can now define the function for building the optigraph. We initialize an OptiGraph and set the optimizer. We then define N nodes on the OptiGraph graph. We then call the three functions above and call set_to_node_objectives to set the graph's objective to the nodes' objectives we have defined. We can then call optimize and return the objective value, the graph, and the reference points. ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"function build_quadcopter_graph(N, dt)\n graph = OptiGraph()\n solver = optimizer_with_attributes(Ipopt.Optimizer, \"max_iter\" => 100)\n set_optimizer(graph, solver)\n @optinode(graph, nodes[1:N])\n\n add_variables!(nodes)\n add_constraints!(graph, nodes, dt)\n xk_ref = add_objective!(nodes, N, dt)\n\n set_to_node_objectives(graph)\n\n optimize!(graph);\n\n return objective_value(graph), graph, xk_ref\nend","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"Now that we have created our function to model the behavior of the quadcopter, we can test it using some example cases.","category":"page"},{"location":"tutorials/quadcopter/#Examples","page":"Optimal Control of a Quadcopter","title":"Examples","text":"","category":"section"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"First, we will run an example with 50 time points (each represented by a node) with a time discretization size of 0.1 seconds","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"N = 50\ndt = 0.1\nobjv, graph, xk_ref = build_quadcopter_graph(N, dt)\nnodes = getnodes(graph)\n# create empty arrays\nCAval_array = zeros(length(nodes))\nxval_array = zeros(length(nodes))\nyval_array = zeros(length(nodes))\nzval_array = zeros(length(nodes))\n\n# add values to arrays\nfor (i, node) in enumerate(nodes)\n CAval_array[i] = value(node[:C_a])\n xval_array[i] = value(node[:X])\n yval_array[i] = value(node[:Y])\n zval_array[i] = value(node[:Z])\nend\n\nxarray = Array{Array}(undef, 2)\nxarray[1] = xval_array\nxarray[2] = 0:10/(N-1):10\n\nyarray = Array{Array}(undef, 2)\nyarray[1] = yval_array\nyarray[2] = 0:10/(N-1):10\n\nzarray = Array{Array}(undef, 2)\nzarray[1] = zval_array\nzarray[2] = 0:10/(N-1):10","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"Now, let's visualize the position of the quadcopter in relation to its setpoint in each dimension. Below is the code for doing so in the x-dimension, and the code can be adapted for the y and z dimensions. ","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"plot(\n 1:length(xval_array),\n xarray[1:end], \n title = \"X value over time\", \n xlabel = \"Node (N)\", \n ylabel = \"X Value\", \n label = [\"Current X position\" \"X Setpoint\"]\n)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"(Image: QuadcopterXpos) (Image: QuadcopterYpos) (Image: QuadcopterZpos)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"Now that we have solved for the optimal solution, let's explore a correlation. Let's see how increasing the number of nodes changes the objective value of the system. In the code snippet below, we keep the time horizon the same (10 seconds) while changing the number of nodes (i.e., the discretization intervals)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"time_steps = 2:4:50\n\nN = length(time_steps)\ndt = .5\nobj_val_N = zeros(N)\n\nfor i in 1:length(time_steps)\n timing = @elapsed begin\n objval, graph, xk_ref = build_quadcopter_graph(time_steps[i], 10 / time_steps[i]);\n obj_val_N[i] = objval\n end\n println(\"Done with iteration $i after \", timing, \" seconds\")\nend\n\nQuad_Obj_NN = plot(\n time_steps,\n obj_val_N, \n title=\"Objective Value vs Number of Nodes (N)\", \n xlabel=\"Number of Nodes (N)\", \n ylabel=\"Objective Value\",\n label=\"Objective Value\"\n)","category":"page"},{"location":"tutorials/quadcopter/","page":"Optimal Control of a Quadcopter","title":"Optimal Control of a Quadcopter","text":"(Image: Quadcopter_Ojb)","category":"page"}] } diff --git a/dev/tutorials/gas_pipeline/index.html b/dev/tutorials/gas_pipeline/index.html index f000e7c..a69c28a 100644 --- a/dev/tutorials/gas_pipeline/index.html +++ b/dev/tutorials/gas_pipeline/index.html @@ -227,4 +227,4 @@ partition = Partition(projection, node_vector) # setup subgraphs based on the partition -apply_partition!(gas_network, partition)

The partitioned optimal control problem is visualized in the below figure and depicts the optimization problem partitioned into 13 distinct partitions.

partition

Querying Solutions

The modular construction of an OptiGraph often results in variables with the same names being stored on different subgraphs or nodes, and accessing variables in OptiGraphs with multiple nodes or subgraphs is not always as simple as calling the symbol for the variable. Variable references for variables in a graph can be thought of as being "nested" on nodes that are "nested" on subgraphs. The "owning" subgraph can be accessed by calling getsubgraphs(::OptiGraph). In addition, in the above code, each pipeline, junction, and compressor subgraph was saved on the OptiGraph gas_network using the symbols :pipeline, :junction, and :compressor, respectively. Thus, the subgraphs for each of these objects can be called by using these symbols, such as using gas_network[:pipeline] to get the vector of all pipeline subgraphs. Importantly, the vectors of subgraphs are not in the order they appear in the network because they were formed by iterating through entries to dictionaries, which are order-free. In other words, the pipelines are not in order from 1 - 13. To identify the order the pipelines, junctions, and compressors appear in their respective vectors, the user will have to track the order to which these are added in the for loop.

As an example of the "nested" nature of the variables, we can consider the problem above. There is a set of nodes called grid (that contain nt $\times$ nx nodes) for each pipeline OptiGraph, and each of these nodes contain a variable called px and fx. To access the px variable at $t = 1$ and $x = 2$ on the first pipeline subgraph of the vector, we can call gas_network[:pipelines][1][:grid][1, 2][:px]. Here, [:pipelines] acesses the vector of pipelines, [1] accesses the first subgraph of that vector, [:grid] accesses the set of nodes called "grid", [1, 2] accesses the node at time $t = 1$ and $x = 2$, and [:px] accesses the variable called px. Alternatively, instead of calling gas_network[:pipelines][1], the same pipeline subgraph could be accessed by calling getsubgraphs(gas_network)[26]. Subgraphs appear in getsubgraphs(::OptiGraph) in the order they were added to the OptiGraph; as the junctions are added to gas_network first, followed by pipelines and then compressors in the above code, the first 25 entries of getsubgraphs(gas_network) will correspond to junctions and entries 26 - 38 will contain the pipelines. With the variable references, a user can query solutions to these variables after calling optimize!(gas_network) by using JuMP.value, such as calling value(gas_network[:pipelines][1][:grid][1, 2][:px]).

+apply_partition!(gas_network, partition)

The partitioned optimal control problem is visualized in the below figure and depicts the optimization problem partitioned into 13 distinct partitions.

partition

Querying Solutions

The modular construction of an OptiGraph often results in variables with the same names being stored on different subgraphs or nodes, and accessing variables in OptiGraphs with multiple nodes or subgraphs is not always as simple as calling the symbol for the variable. Variable references for variables in a graph can be thought of as being "nested" on nodes that are "nested" on subgraphs. The "owning" subgraph can be accessed by calling getsubgraphs(::OptiGraph). In addition, in the above code, each pipeline, junction, and compressor subgraph was saved on the OptiGraph gas_network using the symbols :pipeline, :junction, and :compressor, respectively. Thus, the subgraphs for each of these objects can be called by using these symbols, such as using gas_network[:pipeline] to get the vector of all pipeline subgraphs. Importantly, the vectors of subgraphs are not in the order they appear in the network because they were formed by iterating through entries to dictionaries, which are order-free. In other words, the pipelines are not in order from 1 - 13. To identify the order the pipelines, junctions, and compressors appear in their respective vectors, the user will have to track the order to which these are added in the for loop.

As an example of the "nested" nature of the variables, we can consider the problem above. There is a set of nodes called grid (that contain nt $\times$ nx nodes) for each pipeline OptiGraph, and each of these nodes contain a variable called px and fx. To access the px variable at $t = 1$ and $x = 2$ on the first pipeline subgraph of the vector, we can call gas_network[:pipelines][1][:grid][1, 2][:px]. Here, [:pipelines] acesses the vector of pipelines, [1] accesses the first subgraph of that vector, [:grid] accesses the set of nodes called "grid", [1, 2] accesses the node at time $t = 1$ and $x = 2$, and [:px] accesses the variable called px. Alternatively, instead of calling gas_network[:pipelines][1], the same pipeline subgraph could be accessed by calling getsubgraphs(gas_network)[26]. Subgraphs appear in getsubgraphs(::OptiGraph) in the order they were added to the OptiGraph; as the junctions are added to gas_network first, followed by pipelines and then compressors in the above code, the first 25 entries of getsubgraphs(gas_network) will correspond to junctions and entries 26 - 38 will contain the pipelines. With the variable references, a user can query solutions to these variables after calling optimize!(gas_network) by using JuMP.value, such as calling value(gas_network[:pipelines][1][:grid][1, 2][:px]).

diff --git a/dev/tutorials/quadcopter/index.html b/dev/tutorials/quadcopter/index.html index 5777acf..45bb59c 100644 --- a/dev/tutorials/quadcopter/index.html +++ b/dev/tutorials/quadcopter/index.html @@ -181,4 +181,4 @@ xlabel="Number of Nodes (N)", ylabel="Objective Value", label="Objective Value" -)

Quadcopter_Ojb

+)

Quadcopter_Ojb

diff --git a/dev/tutorials/supply_chain/index.html b/dev/tutorials/supply_chain/index.html index ea64854..c4ddb7f 100644 --- a/dev/tutorials/supply_chain/index.html +++ b/dev/tutorials/supply_chain/index.html @@ -118,4 +118,4 @@ println(objective_value(graph)) println("Node 1 demand solutions = ", value.(graph[:nodes]["n1"][:d])) println("Node 2 demand solutions = ", value.(graph[:nodes]["n2"][:d])) -println("Technology conversion = ", value.(graph[:nodes]["n1"][:ξ])) +println("Technology conversion = ", value.(graph[:nodes]["n1"][:ξ]))