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]

Indices and tables