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).
Compared to MATPOWER or PowerModels, Argos comes with two major differences in the formulation.
- Only a subset of the power flow equations is considered, leading to a total of $n_x$ instead of $2 n_{bus}$ equations.
- 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
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
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