Plasmo.jl - Platform for Scalable Modeling and Optimization
Plasmo.jl is a graph-based optimization framework written in Julia that adopts a modular modeling style to construct and solve optimization problems. The package builds upon the modeling framework JuMP.jl to create graph-structured optimization models and works at a higher level of abstraction which facilitates hierarchical modeling and graph-based operations such as partitioning. More specifically, Plasmo.jl implements what is called the OptiGraph
abstraction to construct optimization models. An OptiGraph
captures the underlying topology of an optimization problem using OptiNodes
(which represent stand-alone optimization models) that are coupled by means of OptiEdges
(which correspond to coupling constraints). The resulting graph topology enables systematic model construction and can be exploited for various modeling tasks and the development of distributed optimization algorithms.
Installation
The Plasmo.jl package works for Julia versions 1.0 and later. From Julia, Plasmo.jl can be installed using the built-in package manager:
import Pkg
Pkg.add("Plasmo")
or alternatively from the Julia 1.0 package manager, one can simply do:
] add Plasmo
Quickstart Example
This quickstart example gives a brief overview of the functions needed to effectively use Plasmo.jl to build optimization models. If you are familiar with JuMP, much of the functionality you see here will be equivalent. In fact, the primary OptiGraph
object is an extension of the JuMP.AbstractModel
, as well as its contained OptiNodes
.
The below example demonstrates the construction of a simple nonlinear optimization problem that contains two OptiNodes
coupled by a simple LinkConstraint
(which creates an OptiEdge
) and solved with the linear optimization solver GLPK. More detailed examples can be found in the examples folder.
Once Plasmo.jl has been installed, you can use it from a Julia session as following:
julia> using Plasmo
For this example we also need to import the GLPK optimization solver and the Plots package which we use to visualize graph structure.
julia> using GLPK
julia> using Plots
We highlight that it is possible to use any solver that works with JuMP. By default, when using a standard optimization solver available through JuMP, Plasmo.jl will aggregate the OptiGraph
into a single node to solve (hence ignoring the graph structure). While it is useful having such granular control to build optimization models with an OptiGraph
, we note that this aggregation step introduces additional model-building time when using standard optimization solvers (such as GLPK and Ipopt).
Create an OptiGraph
The following command will create an OptiGraph
model. We also see the printed output which denotes the number of optinodes, linking constraints, and subgraphs within the OptiGraph
.
julia> graph = OptiGraph()
OptiGraph:
local nodes: 0, total nodes: 0
local link constraints: 0, total link constraints 0
local subgraphs: 0, total subgraphs 0
An OptiGraph
distinguishes between local and total entities (i.e. nodes, edges, link constraints, and subgraphs). This distinction between local and total is used to describe hierarchical graph structures which are introduced in Hierarchical Modeling.
Add OptiNodes
An OptiGraph
consists of OptiNodes
which contain stand-alone optimization models. An OptiNode
extends a JuMP.AbstractModel
(and also a wraps a JuMP.Model
) and supports the same macros to create variables, constraints, and add objective functions (using @variable
, @constraint
, and @objective
). To add optinodes to a graph, one can simply use the @optinode
macro as shown in the following code snippet. For this example, we create the OptiNode
n1
, we create two variables x
and y
, and add a single constraint and an objective function.
julia> @optinode(graph,n1)
OptiNode w/ 0 Variable(s)
julia> @variable(n1, y >= 2)
y
julia> @variable(n1, x >= 0)
x
julia> @constraint(n1,x + y >= 3)
x + y >= 3
julia> @objective(n1, Min, y)
y
We can create more OptiNodes
and add variables, constraints, and objective functions to each node in the graph.
julia> @optinode(graph,n2);
julia> @variable(n2, y >= 0);
julia> @variable(n2,x >= 0);
julia> @constraint(n2,x + y >= 3);
julia> @objective(n2, Min, y);
julia> @optinode(graph,n3);
julia> @variable(n3, y >= 0);
julia> @variable(n3,x >= 0);
julia> @constraint(n3,x + y >= 3);
julia> @objective(n3, Min, y);
julia> println(graph)
OptiGraph:
local nodes: 3, total nodes: 3
local link constraints: 0, total link constraints 0
local subgraphs: 0, total subgraphs 0
Create LinkConstraints (OptiEdges)
Linking constraints can be used to couple variables between optinodes. Beneath the modeling surface, creating a linking constraint induces an OptiEdge
in the OptiGraph
which describes its connectivity. Linking constraints are created using the @linkconstraint
macro which takes the exact same input as the JuMP.@constraint
macro. The following code creates a linking constraint between variables on the three optinodes.
julia> @linkconstraint(graph, n1[:x] + n2[:x] + n3[:x] == 3)
LinkConstraintRef(1, OptiEdge w/ 1 Constraint(s))
julia> println(graph)
OptiGraph:
local nodes: 3, total nodes: 3
local link constraints: 1, total link constraints 1
local subgraphs: 0, total subgraphs 0
Nonlinear linking constraints are not yet supported
Solve and Query Solution
When using a JuMP/MOI enabled optimization solver, we can optimize an OptiGraph
using the optimize!
function extended from JuMP. As mentioned earlier, Plasmo.jl aggregates the graph into a single model (an optinode), hands off the problem to JuMP and the chosen solver, and then populates the OptiGraph
solution.
julia> optimize!(graph,GLPK.Optimizer)
Converting OptiGraph to OptiNode...
Optimizing OptiNode
Found Solution
After finding a solution, we can query it using value(::OptiNode,::VariableRef)
extended from JuMP. We can also query the objective value of the graph using objective_value(::OptiGraph)
julia> value(n1,n1[:x])
1.0
julia> value(n2,n2[:x])
2.0
julia> value(n3,n3[:x])
0.0
julia> objective_value(graph)
6.0
Plasmo.jl assumes the objective function of each optinode is added by default. The objective function for an optigraph can be changed using the @objective
macro on the optigraph itself.
Visualize the Structure
Lastly, it is often useful to be able to visualize the structure of an OptiGraph
object. Doing such a visualization can lead to physical insights about an optimization problem (such as space-time dependencies), but it is also helpful just to see the connectivity of the problem. Plasmo.jl uses Plots.jl and NetworkLayout.jl to visualize the layout of an OptiGraph
. The code here shows how to obtain the graph topology using Plots.plot(::OptiGraph)
and we plot the underlying adjacency matrix structure using Plots.spy
function. 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 node and edge. 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 rightfigure 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.
julia> plt_graph = Plots.plot(graph,node_labels = true, markersize = 30,labelsize = 15, linewidth = 4,layout_options = Dict(:tol => 0.01,:iterations => 2),plt_options = Dict(:legend => false,:framestyle => :box,:grid => false,:size => (400,400),:axis => nothing));
julia> Plots.savefig(plt_graph,"graph_layout.svg");
julia> plt_matrix = Plots.spy(graph,node_labels = true,markersize = 15);
julia> Plots.savefig(plt_matrix,"matrix_layout.svg");
Contents
- Modeling
- Creating an OptiGraph
- Adding OptiNodes
- Adding Linking Constraints (OptiEdges)
- Hierarchical Modeling
- Query OptiGraph Attributes
- Methods
- Partitioning and Graph Operations
- Solvers
- Plotting
Future Development
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:
- Parallel modeling capabilities
- Nonlinear linking constraints
- Graph metrics and custom partitioning algorithms
- More distributed solver support
Index
Plasmo.HyperGraph
Plasmo.LinkConstraint
Plasmo.OptiEdge
Plasmo.OptiGraph
Plasmo.OptiNode
Plasmo.Partition
Base.getindex
Base.getindex
JuMP.all_variables
JuMP.num_constraints
JuMP.num_variables
JuMP.objective_function
JuMP.set_optimizer
JuMP.value
Plasmo.add_node!
Plasmo.add_subgraph!
Plasmo.aggregate
Plasmo.all_edges
Plasmo.all_linkconstraints
Plasmo.all_nodes
Plasmo.all_subgraphs
Plasmo.expand
Plasmo.find_node
Plasmo.getedge
Plasmo.getedges
Plasmo.gethypergraph
Plasmo.getlinkconstraints
Plasmo.getnode
Plasmo.getnodes
Plasmo.getsubgraphs
Plasmo.is_node_variable
Plasmo.make_subgraphs!
Plasmo.neighborhood
Plasmo.nodevalue
Plasmo.set_model
Plots.spy
RecipesBase.plot
Plasmo.@NLnodeconstraint
Plasmo.@linkconstraint
Plasmo.@optinode
Citing Plasmo.jl
If you find Plasmo.jl useful for your work, you may cite the current pre-print:
@misc{JalvingShinZavala2020,
title = {A Graph-Based Modeling Abstraction for Optimization: Concepts and Implementation in Plasmo.jl},
author = {Jordan Jalving and Sungho Shin and Victor M. Zavala},
year = {2020},
eprint = {2006.05378},
archivePrefix = {arXiv},
primaryClass = {math.OC}
}
There is also an earlier manuscript where we presented the initial ideas behind Plasmo.jl which you can find here:
@article{JalvingCaoZavala2019,
author = {Jalving, Jordan and Cao, Yankai and Zavala, Victor M},
journal = {Computers {\&} Chemical Engineering},
pages = {134--154},
title = {Graph-based modeling and simulation of complex systems},
volume = {125},
year = {2019},
doi = {https://doi.org/10.1016/j.compchemeng.2019.03.009}
}
A pre-print of this paper can also be found here