Previous topic

Comparison and Combination of Forward and Reverse Mode

Next topic

Minimization of the Rosenbrock Function

This Page

Derivatives for OptimizationΒΆ

Optimization algorithms often require:

  • Hessian of the Lagrangian
  • Diagonal elements of the Lagrangian
  • etc.

The following code shows how these derivatives can be evaluated in AlgoPy:

import algopy
import numpy


class Model:

    # Function evaluations
    def eval_f(self, x):
        return (x[0]-10)**2 + 5*(x[1]-12)**2 + x[2]**4 + 3*(x[3]-11)**2 + 10*x[4]**6
        + 7*x[5]**2 + x[6]**4 - 4*x[5]*x[6] - 10*x[5] - 8*x[6]

    def eval_g(self, x):
        out = algopy.zeros(3, dtype=x)
        out[0] = 2*x[0]**2 + 3*x[1]**4 + x[2] + 4*x[3]**2 + 5*x[4]
        out[1] = 7*x[0] + 3*x[1] + 10*x[2]**2 + x[3] - x[4]
        out[2] = 23*x[0] + x[1]**2 + 6*x[5]**2 - 8*x[6]  -4*x[0]**2 - x[1]**2 + 3*x[0]*x[1] -2*x[2]**2 - 5*x[5]+11*x[6]
        return out

    def eval_Lagrangian(self, lam,x):
        return self.eval_f(x) + algopy.dot(lam, self.eval_g(x))


    # Forward Mode Derivative Evaluations
    def eval_grad_f_forward(self, x):
        x = algopy.UTPM.init_jacobian(x)
        return algopy.UTPM.extract_jacobian(self.eval_f(x))

    def eval_jac_g_forward(self, x):
        x = algopy.UTPM.init_jacobian(x)
        return algopy.UTPM.extract_jacobian(self.eval_g(x))

    def eval_jac_vec_g_forward(self, x, v):
        x = algopy.UTPM.init_jac_vec(x, v)
        return algopy.UTPM.extract_jac_vec(self.eval_g(x))

    def eval_grad_Lagrangian_forward(self, lam, x):
        return self.eval_grad_f_forward(x) + algopy.dot(lam, self.eval_jac_g_forward(x))

    def eval_hess_Lagrangian_forward(self, lam, x):
        x = algopy.UTPM.init_hessian(x)
        return algopy.UTPM.extract_hessian(x.size, self.eval_Lagrangian(lam, x))

    def eval_vec_hess_g_forward(self, w, x):
        x = algopy.UTPM.init_hessian(x)
        tmp = algopy.dot(w, self.eval_g(x))
        return algopy.UTPM.extract_hessian(x.size, tmp)

    # Reverse Mode Derivative Evaluations
    def trace_eval_f(self, x):
        cg = algopy.CGraph()
        x = algopy.Function(x)
        y = self.eval_f(x)
        cg.trace_off()
        cg.independentFunctionList = [x]
        cg.dependentFunctionList = [y]
        self.cg = cg

    def trace_eval_g(self, x):
        cg2 = algopy.CGraph()
        x = algopy.Function(x)
        y = self.eval_g(x)
        cg2.trace_off()
        cg2.independentFunctionList = [x]
        cg2.dependentFunctionList = [y]
        self.cg2 = cg2

    def eval_grad_f_reverse(self, x):
        return self.cg.gradient(x)

    def eval_jac_g_reverse(self, x):
        return self.cg2.jacobian(x)

    def eval_hess_f_reverse(self, x):
        return self.cg.hessian(x)

    def eval_hess_vec_f_reverse(self, x, v):
        return self.cg.hess_vec(x,v)

    def eval_vec_hess_g_reverse(self, w, x):
        return self.cg2.vec_hess(w, x)

    def eval_grad_Lagrangian_reverse(self, lam, x):
        return self.cg.gradient(x) + self.cg2.vec_jac(lam, x)

    def eval_hess_Lagrangian_reverse(self, lam, x):
        return self.cg.hessian(x) + self.cg2.vec_hess(lam, x)


lam = numpy.array([1,1,1],dtype=float)
x = numpy.array([1,2,3,4,0,1,1],dtype=float)
v = numpy.array([1,1,1,1,1,1,1],dtype=float)
lagra = numpy.array([1,2,0],dtype=float)
V = numpy.eye(7)

m = Model()

print('normal function evaluation')
m.eval_f(x)
m.eval_g(x)

print('Forward Mode')

grad_f_forward          = m.eval_grad_f_forward(x)
jac_g_forward           = m.eval_jac_g_forward(x)
jac_vec_g_forward       = m.eval_jac_vec_g_forward(x,[1,0,0,0,0,0,0])
grad_Lagrangian_forward = m.eval_grad_Lagrangian_forward(lam, x)
hess_Lagrangian_forward = m.eval_hess_Lagrangian_forward(lam, x)
vec_hess_g_forward      = m.eval_vec_hess_g_forward(lagra, x)

print(grad_f_forward)
print(jac_g_forward)
print(jac_vec_g_forward)
print(grad_Lagrangian_forward)
print(hess_Lagrangian_forward)
print(vec_hess_g_forward)

print('Reverse Mode')
m.trace_eval_f(x)
m.trace_eval_g(x)
grad_f_reverse           = m.eval_grad_f_reverse(x)
jac_g_reverse            = m.eval_jac_g_reverse(x)
hess_f_reverse           = m.eval_hess_f_reverse(x)
hess_vec_f_reverse       = m.eval_hess_vec_f_reverse(x,v)
vec_hess_g_reverse       = m.eval_vec_hess_g_reverse(lagra, x)
grad_Lagrangian_reverse  = m.eval_grad_Lagrangian_reverse(lam, x)
hess_Lagrangian_reverse  = m.eval_hess_Lagrangian_reverse(lam, x)

print(grad_f_reverse)
print(jac_g_reverse)
print(hess_f_reverse)
print(hess_vec_f_reverse)
print(vec_hess_g_reverse)
print(grad_Lagrangian_reverse)

from numpy.testing import assert_almost_equal
assert_almost_equal(grad_f_forward, grad_f_reverse)
assert_almost_equal(jac_g_forward, jac_g_reverse)
assert_almost_equal(grad_Lagrangian_forward, grad_Lagrangian_reverse)
assert_almost_equal(hess_Lagrangian_forward, hess_Lagrangian_reverse)
assert_almost_equal(vec_hess_g_forward, vec_hess_g_reverse)