Quickstart

This quickstart gives a brief overview of the functions needed for using PlasmoSchwarz to solve optimization problems defined in Plasmo.

Once PlasmoSchwarz has been installed, you can use it from a Julia session using

julia> using PlasmoSchwarz

PlasmoSchwarz also requires defining a graph with Plasmo, and we will need a solver for the subproblems. We will use Ipopt. These packages will also need to be loaded

julia> using Plasmo, Ipopt

PlasmoSchwarz Overview

PlasmoSchwarz is an iterative algorithm that solves overlapping subproblems and then shares information between the subproblems to help converge to the overall solution. The structure that PlasmoSchwarz "sees" is either defined by the user by partitioning into subgraphs or the partitioning can be done automatically using packages like Metis.jl or KaHyPar. The graph is passed to the SchwarzAlgorithm function which overlaps the graphs and updates the graphs as needed to apply Schwarz Decomposition. Schwarz Decomposition can then be applied by calling run_algorithm!.

Optimal Control Example

To show how PlasmoSchwarz can be applied for solving a problem, we will show a simple example of an optimal control problem over 200 time steps. The problem can be written as

\[\begin{align*} \min &\; \sum_{t \in \mathcal{T}} x_t^2 + u_t^2\\ \textrm{s.t.} &\; x_{t+1} = x_t + u_t + d_t\\ &\; x_0 = \bar{x}_o \\ &\; x_t \ge 0 \\ &\; u_t \ge \underline{u} \end{align*}\]

Here, $x$ are the states, $u$ are the inputs, and $d$ are disturbances.

We will start by importing the required packages and defining problem parameters

using Plasmo, Ipopt
using PlasmoSchwarz

T = 200             # number of time points
d = sin.(1:T)       # a disturbance vector
imbalance = 0.1     # partition imbalance
distance = 2        # expansion distance
n_parts = 10        # number of partitions

Next, we will create the OptiGraph and nodes. Each state and input variable will be represented by a node.

# create the optigraph
graph = Plasmo.OptiGraph()
@optinode(graph, state[1:T])
@optinode(graph, control[1:(T - 1)])
for (i, node) in enumerate(state)
    @variable(node, x)
    @constraint(node, x >= 0)
    @objective(node, Min, x^2)
end
for node in control
    @variable(node, u)
    @constraint(node, u >= -1000)
    @objective(node, Min, u^2)
end

Next, we will set the initial value of the state and then link the state variables to the previous state and inputs.

# initial condition
n1 = state[1]
@constraint(n1, n1[:x] == 0)

# dynamics
for i in 1:(T - 1)
    @linkconstraint(graph, state[i + 1][:x] == state[i][:x] + control[i][:u] + d[i])
end

Now, we can define the sub-solver that will be used on the individual subproblem subgraphs, and we can then construct the SchwarzAlgorithm object.

# subproblem optimizer
sub_optimizer = Plasmo.optimizer_with_attributes(Ipopt.Optimizer, "print_level" => 0)

# optimize using overlapping schwarz decomposition
optimizer = SchwarzAlgorithm(
    graph;
    n_partitions=n_parts,
    overlap_distance=1,
    subproblem_optimizer=sub_optimizer,
    max_iterations=100,
    mu=10.0,
)

Here, we have not defined the partitions to be used, so the SchwarzAlgorithm function will do the partitioning internally using Metis.jl and then overlap each subgraph by a distance of 1. Now, we can run the algorithm by calling

run_algorithm!(optimizer)

Querying Solutions

PlasmoSchwarz.jl provides API functions for querying solutions and information from the SchwarzAlgorithm object. To query the objective value, termination status, or solve time, we can use

Plasmo.objective_value(optimizer)
Plasmo.termination_status(optimizer)
Plasmo.solve_time(optimizer)

The primal and dual variables can be queried by calling value or dual with the first argument being the SchwarzAlgorithm object

Plasmo.value(optmizer, state[1][:x])
cons = all_constraints(graph)
Plasmo.dual(optimizer, cons[1])

You can also access the primal and dual feasibility vectors by viewing the algorithm methods.

prf = PlasmoSchwarz.calculate_primal_feasibility(optimizer)
duf = PlasmoSchwarz.calculate_dual_feasibility(optimizer)