Welcome to pygradflow’s documentation!
The pygradflow package is designed to solve nonlinear optimization problems given by an objective \(f : \mathbb{R}^{n} \to \mathbb{R}\), a set of smooth nonlinear constraints \(c : \mathbb{R}^{n} \to \mathbb{R}^{m}\) bounded by \(l, u \in \mathbb{R}^{m}\), and variable bounds \(x_l, x_u \in \mathbb{R}^{n}\), yielding the problem
\[\begin{split}\begin{align} \min_{x \in \mathbb{R}^{n}} \quad & f(x) \\ \text{s.t.} \quad & l \leq c(x) \leq u \\ & l^x \leq x \leq u^x \end{align}\end{split}\]
The method works by iterating towards a primal / dual solution approximately satisfying the KKT conditions.
Examples
Consider the problem of minimizing the Rosenbrock function
\[f(x_0, x_1) := (a - x_0)^{2} + b(x_1 - x_0^{2})^{2}\]
over \(\mathbb{R}^{2}\) with parameters \((a, b) = (1, 100)\).
This problem in unconstrained
in the sense that it incorporates neither variable bounds
not nonlinear constraints. To solve the problem, we
first create a subclass of the pygradflow.problem.Problem
class, overriding the required methods to evaluate \(f\),
\(\nabla f\), and
\(\nabla_{xx} \mathcal{L}(x, y) = \nabla_{xx} f(x)\):
import numpy as np
import scipy as sp
from pygradflow.problem import Problem
class Rosenbrock(Problem):
def __init__(self):
lb = np.array([-np.inf, -np.inf])
ub = np.array([np.inf, np.inf])
super().__init__(lb, ub)
self.a = 1.0
self.b = 100.0
def obj(self, x):
[x0, x1] = x
a = self.a
b = self.b
return (a - x0) ** 2 + b * (x1 - x0**2) ** 2
def obj_grad(self, x):
[x0, x1] = x
a = self.a
b = self.b
return np.array(
[4 * (x0**2 - x1) * b * x0 - 2 * a + 2 * x0, -2 * (x0**2 - x1) * b]
)
def cons(self, x):
return np.array([])
def cons_jac(self, x):
return sp.sparse.coo_matrix(np.zeros((0, 2)))
def lag_hess(self, x, _):
[x0, x1] = x
b = self.b
h = np.array(
[
[8 * b * x0**2 + 4 * (x0**2 - x1) * b + 2, -4 * b * x0],
[-4 * b * x0, 2 * b],
]
)
return sp.sparse.coo_matrix(h)
To solve the problem, we create a pygradflow.solver.Solver
based on the problem definition and call its pygradflow.solver.Solver.solve()
method:
import logging
import numpy as np
from pygradflow.solver import Solver
logging.basicConfig(level=logging.INFO)
rosenbrock = Rosenbrock()
solver = Solver(problem=rosenbrock)
solution = solver.solve()
print(solution.x)
The resulting pygradflow.solver.SolverResult contains a status, indicating
whether or not the solution attempt was successful, along with the primal
solution corresponding, which in this case is close to the known optimum
of \((x_0^{*}, x_1^{*}) = (1, 1)\):
INFO:gradflow:Solving problem with 2 variables, 0 constraints
INFO:gradflow: Iter Aug Lag Cons inf Dual inf Primal step Dual step Lambda Type
INFO:gradflow: 9 6.76695301e-01 0.00000000e+00 1.73055080e+00 5.24163592e-02 0.00000000e+00 1.82519554e+01 accepted
INFO:gradflow: Status: Convergence achieved
INFO:gradflow: Iterations: 30
INFO:gradflow: Accepted steps: 25
INFO:gradflow: Distance factor: 1.045707e+00
INFO:gradflow: Objective: 1.723984e-13
INFO:gradflow: Aug Lag violation: 0.000000e+00
INFO:gradflow: Aug Lag dual: 0.000000e+00
INFO:gradflow: Bound violation: 0.000000e+00
INFO:gradflow: Constraint violation: 0.000000e+00
INFO:gradflow: Dual violation: 3.320413e-07
[0.99999959 0.99999917]