Overview

Argos acts as a layer built on top of ExaPF to formulate the OPF problem in a form suitable for optimization solvers (i.e. by formulating the problems with nonlinear callbacks). Depending on whether we are working in the full- or the reduced-space, the two principal evaluators are the FullSpaceEvaluator and the ReducedSpaceEvaluator.

Abstraction

Argos formulates the OPF problem in abstract form as

\[\min_{x, u } \; f(x, u) \quad \text{subject to}\quad \left\{ \begin{aligned} & g(x, u) = 0 \\ & h_l \leq h(x, u) \leq h_u . \end{aligned} \right.\]

By design, the control variable $u$ (voltage magnitude at PV and REF nodes, active power generations) is dissociated from the state variable $x$ (voltage angle, voltage magnitudes at PQ nodes). The function $f$ encodes the objective function, the function $g$ is associated with the power flow equations](https://exanauts.github.io/ExaPF.jl/stable/lib/formulations/#ExaPF.PowerFlowBalance) and $h$ encodes the remaining operational constraints (bounds on reactive power generations, line flow constraints).

Note

Compared to MATPOWER or PowerModels, Argos comes with two major differences in the formulation.

  1. Only a subset of the power flow equations is considered, leading to a total of $n_x$ instead of $2 n_{bus}$ equations.
  2. The reactive power generations are considered only implicitly, through the remaining power flow equations.

Hence, the problem formulated by Argos has a smaller size as the ones obtained through PowerModels or MATPOWER, at the expense of robustness.

Usage

For demonstration purposes, we instantiate a new FullSpaceEvaluator object associated with case9:

using ExaPF, Argos
datafile = joinpath(INSTANCES_DIR, "case9.m")
flp = Argos.FullSpaceEvaluator(datafile)
A FullSpaceEvaluator object
    * device: KernelAbstractions.CPU(false)
    * #vars: 19
    * #cons: 36

We get the number of variables and constraints simply as

(n, m) = Argos.n_variables(flp), Argos.n_constraints(flp)
(19, 36)

Network variables

Internally, each evaluator stores the current state of the network in a ExaPF.NetworkStack object. We can query the current state of the network as:

stack = flp.stack
# Query current voltage magnitude and angle values
[stack.vmag stack.vang]
9×2 Matrix{Float64}:
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0

The function update! updates the values in the cache:

x = rand(n)
Argos.update!(flp, x)
# The values in the cache are modified accordingly
[stack.vmag stack.vang]
9×2 Matrix{Float64}:
 0.848416   0.0
 0.0805513  0.141918
 0.59004    0.384708
 0.302376   0.153779
 0.457351   0.361448
 0.491978   0.584548
 0.298669   0.468811
 0.919493   0.925342
 0.529845   0.515877
Note

Every time we have a new variable x, it is important to refresh the cache by calling explicitly Argos.update!(flp, x) before calling the other callbacks.

Callbacks

Now the cache has been refreshed by calling update!, one can query the different callbacks to evaluate the objective, the constraints and the derivatives:

Objective:

obj = Argos.objective(flp, x)
2557.5763249374195

Gradient:

g = zeros(n)
Argos.gradient!(flp, g, x)
g
19-element Vector{Float64}:
    0.0
    0.0
 4405.061166663853
    0.0
    0.0
    0.0
    0.0
    0.0
 2258.096736511352
    0.0
    0.0
    0.0
    0.0
    0.0
  804.7874540050929
    0.0
    0.0
 1106.9134779683984
 2154.860282702878

Constraints:

cons = zeros(m)
Argos.constraint!(flp, cons, x)
cons
36-element Vector{Float64}:
 -1.4168474784045788
 -1.822086998005177
 -0.4410101904562455
  1.0517630620103455
  1.5675708506630845
 -1.1442785553765182
  5.09550661584778
  0.7310041848822406
 -3.8352350383430722
  0.9733440092619392
  ⋮
  8.38360039543063
  0.6923941358279246
  0.09473624401679183
  1.4922010815228346
  0.35065614637926124
 70.03819024784804
  1.2408188908806044
  2.4884211065537105
  0.9149204782766154
Note

All the callbacks are written to modify the data (constraints, gradient) inplace, to avoid unneeded allocations. In addition, Argos.jl provides a version allocating automatically the return values:

g = Argos.gradient(flp, x)
c = Argos.constraint(flp, x)
36-element Vector{Float64}:
 -1.4168474784045788
 -1.822086998005177
 -0.4410101904562455
  1.0517630620103455
  1.5675708506630845
 -1.1442785553765182
  5.09550661584778
  0.7310041848822406
 -3.8352350383430722
  0.9733440092619392
  ⋮
  8.38360039543063
  0.6923941358279246
  0.09473624401679183
  1.4922010815228346
  0.35065614637926124
 70.03819024784804
  1.2408188908806044
  2.4884211065537105
  0.9149204782766154

Eventually, one can reset the evaluator to its original state by using reset!:

Argos.reset!(flp)
[stack.vmag stack.vang]
9×2 Matrix{Float64}:
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0
 1.0  0.0