(Experimental) Automatic Matrix-Free AD

OpenMDAOCore.jl can create explicit components that are differentiated automatically by the AD packages supported by DifferentiationInterface.jl using Jacobian-vector or vector-Jacobian products instead of assembling the complete Jacobian. The resulting components will use the OpenMDAO Matrix-Free API.

The User-Defined Function for Matrix-Free AD

The requirements for the user-defined function for the matrix-free AD functionality is identical to those for Automatic Dense AD and Automatic Sparse AD, and the interface is much the same, as well. We'll still need to provide a function that expects a ComponentVector for its input, and either write its outputs to a ComponentVector (for the in-place form) or return a ComponentVector with outputs (for the out-of-place form). The only significant difference is that users will create a MatrixFreeADExplicitComp instead of a SparseADExplicitComp.

MatrixFreeADExplicitComp Paraboloid

Let's do the good old Paraboloid example yet again, this time with the MatrixFreeADExplicitComp. We'll load the same packages as we did for the sparse AD example (except we don't need SparseMatrixColorings since we won't be doing sparsity):

using ADTypes: ADTypes
using ComponentArrays: ComponentVector
using OpenMDAOCore: OpenMDAOCore
using OpenMDAO: make_component

We'll be using the same paraboloid function, but this time let's create an out-of-place function:

function f_paraboloid(X_ca, params)
    # Get the inputs:
    # Using @view with ReverseDiff.jl doesn't work for some reason.
    # x = @view(X_ca[:x])
    # y = @view(X_ca[:y])
    # Could also do this:
    x = X_ca.x
    y = X_ca.y
    # or even this
    # (; x, y) = X_ca

    # Do the calculation:
    f_xy = @. (x - 3.0)^2 + x*y + (y + 4.0)^2 - 3.0

    return ComponentVector(f_xy=f_xy)
end

(For some reason the @view macro doesn't work with ReverseDiff.jl, the AD library we'll use for this example.)

Now we'll create the input ComponentVector. No need to create the output ComponentVector for the out-of-place callback function.

X_ca = ComponentVector(x=1.0, y=1.0)

And finally we'll decide which AD library we'll use. For this one, let's try ReverseDiff.jl:

using ReverseDiff: ReverseDiff
ad_backend = ADTypes.AutoReverseDiff()

Now we can create the component:

comp = OpenMDAOCore.MatrixFreeADExplicitComp(ad_backend, f_paraboloid, X_ca)
parab_comp = make_component(comp)

As before, make_component will convert the MatrixFreeADExplicitComp into a OpenMDAO Python component that we can use with OpenMDAO. So now we just need to proceed with the paraboloid example as usual. But! We need to make sure to tell OpenMDAO that we need to calculate total derivatives in reverse mode, not forward, since we're using reverse AD for our paraboloid component. We do that by supplying the mode="rev" argument to Problem.setup().

using OpenMDAO: om

prob = om.Problem()

model = om.Group()
model.add_subsystem("parab_comp", parab_comp)

prob = om.Problem(model)

prob.driver = om.ScipyOptimizeDriver()
prob.driver.options["optimizer"] = "SLSQP"

prob.model.add_design_var("parab_comp.x")
prob.model.add_design_var("parab_comp.y")
prob.model.add_objective("parab_comp.f_xy")

prob.setup(force_alloc_complex=true, mode="rev")

prob.set_val("parab_comp.x", 3.0)
prob.set_val("parab_comp.y", -4.0)

prob.run_model()
println(prob["parab_comp.f_xy"])  # Should print `[-15.]`

prob.set_val("parab_comp.x", 5.0)
prob.set_val("parab_comp.y", -2.0)

prob.run_model()
println(prob.get_val("parab_comp.f_xy"))  # Should print `[-5.]`
/home/runner/work/OpenMDAO.jl/OpenMDAO.jl/docs/.CondaPkg/.pixi/envs/default/lib/python3.14/site-packages/openmdao/visualization/n2_viewer/n2_viewer.py:115: OpenMDAOWarning:'float' object has no attribute 'shape'
-15.0
-5.0

Looks good. Let's check our derivatives using finite difference:

println(prob.check_partials(method="fd"))
┌ Warning: mode = "fwd" not supported for AD backend ADTypes.AutoReverseDiff(), preparation DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}(Val{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}()), derivatives for OpenMDAOCore.MatrixFreeADExplicitComp{false, ADTypes.AutoReverseDiff{false}, OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Nothing, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}, DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}, ComponentArrays.ComponentVector{ComplexF64, Vector{ComplexF64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Nothing, Dict{String, Nothing}}(ADTypes.AutoReverseDiff(), OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}(Main.f_paraboloid, nothing), (x = 5.0, y = -2.0), nothing, (x = 5.0e-324, y = 1.0e-323), (f_xy = 6.9107278880297e-310), "", false, DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}(Val{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}()), Dict{Symbol, String}(), Dict{Symbol, Vector{String}}(), Dict{Symbol, Bool}(), Dict{Symbol, Symbol}(), (x = 6.91071678141025e-310 + 6.9107948835431e-310im, y = 6.910728876976e-310 + 6.9107288769823e-310im), nothing, Dict{Symbol, String}(), Dict{Symbol, String}(), Dict{String, Nothing}()) will be incorrect
└ @ OpenMDAOCore ~/work/OpenMDAO.jl/OpenMDAO.jl/julia/OpenMDAOCore.jl/src/matrix_free_ad.jl:575
┌ Warning: mode = "fwd" not supported for AD backend ADTypes.AutoReverseDiff(), preparation DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}(Val{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}()), derivatives for OpenMDAOCore.MatrixFreeADExplicitComp{false, ADTypes.AutoReverseDiff{false}, OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Nothing, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}, DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}, ComponentArrays.ComponentVector{ComplexF64, Vector{ComplexF64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Nothing, Dict{String, Nothing}}(ADTypes.AutoReverseDiff(), OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}(Main.f_paraboloid, nothing), (x = 5.0, y = -2.0), nothing, (x = 5.0e-324, y = 1.0e-323), (f_xy = 6.9107278880297e-310), "", false, DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}(Val{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}()), Dict{Symbol, String}(), Dict{Symbol, Vector{String}}(), Dict{Symbol, Bool}(), Dict{Symbol, Symbol}(), (x = 6.91071678141025e-310 + 6.9107948835431e-310im, y = 6.910728876976e-310 + 6.9107288769823e-310im), nothing, Dict{Symbol, String}(), Dict{Symbol, String}(), Dict{String, Nothing}()) will be incorrect
└ @ OpenMDAOCore ~/work/OpenMDAO.jl/OpenMDAO.jl/julia/OpenMDAOCore.jl/src/matrix_free_ad.jl:575
{'parab_comp': {('f_xy', 'x'): {'J_fwd': array([[0.]]), 'J_rev': array([[2.]]), 'J_fd': array([[2.000001]]), 'rows': None, 'cols': None, 'tol violation': _ErrorData(forward=1.9999990003674561, reverse=-9.99632543852158e-07, fwd_rev=1.999998), 'magnitude': _MagnitudeData(forward=0.0, reverse=2.0, fd=2.0000010003684565), 'vals_at_max_error': _ErrorData(forward=(np.float64(0.0), np.float64(2.0000010003684565)), reverse=(np.float64(2.0), np.float64(2.0000010003684565)), fwd_rev=(np.float64(0.0), np.float64(2.0))), 'abs error': _ErrorData(forward=2.0000010003684565, reverse=1.0003684565162985e-06, fwd_rev=2.0), 'rel error': _ErrorData(forward=1.0, reverse=5.001839780740122e-07, fwd_rev=1.0), 'matrix_free': True}, ('f_xy', 'y'): {'J_fwd': array([[0.]]), 'J_rev': array([[9.]]), 'J_fd': array([[9.000001]]), 'rows': None, 'cols': None, 'tol violation': _ErrorData(forward=8.999992000457723, reverse=-7.999542276593274e-06, fwd_rev=8.999991), 'magnitude': _MagnitudeData(forward=0.0, reverse=9.0, fd=9.000001000458724), 'vals_at_max_error': _ErrorData(forward=(np.float64(0.0), np.float64(9.000001000458724)), reverse=(np.float64(9.0), np.float64(9.000001000458724)), fwd_rev=(np.float64(0.0), np.float64(9.0))), 'abs error': _ErrorData(forward=9.000001000458724, reverse=1.0004587238654494e-06, fwd_rev=9.0), 'rel error': _ErrorData(forward=1.0, reverse=1.1116206807248763e-07, fwd_rev=1.0), 'matrix_free': True}}}

Now, some of the derivatives don't look so great! Why? There's a hint at the beginning of the output above in the form of a warning from OpenMDAOCore.jl. When checking the partial derivatives of a matrix-free component, OpenMDAO runs the component in both forward and reverse mode, showing the results in the output above as Jfor for forward, Jrev for reverse (and Jfd for the finite difference approximation to the derivatives). ReverseDiff.jl only supports reverse mode, however, so the derivatives calculated by our paraboloid component will be incorrect when run in forward mode (as the warning message tells us). Similarly, if we had chosen ForwardDiff.jl, the reverse-mode derivatives would have been incorrect. So, when looking at the output above, we need to ignore any result that involves Jfor, and just look at the comparisons between Jrev and Jfd. With that in mind, the derivatives look quite good.

We can also check the derivatives with the finite difference method:

println(prob.check_partials(method="cs"))
┌ Warning: mode = "fwd" not supported for AD backend ADTypes.AutoReverseDiff(), preparation DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}(Val{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}()), derivatives for OpenMDAOCore.MatrixFreeADExplicitComp{false, ADTypes.AutoReverseDiff{false}, OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Nothing, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}, DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}, ComponentArrays.ComponentVector{ComplexF64, Vector{ComplexF64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Nothing, Dict{String, Nothing}}(ADTypes.AutoReverseDiff(), OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}(Main.f_paraboloid, nothing), (x = 5.0, y = -2.0), nothing, (x = 2.0, y = 9.0), (f_xy = 1.0), "", false, DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}(Val{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}()), Dict{Symbol, String}(), Dict{Symbol, Vector{String}}(), Dict{Symbol, Bool}(), Dict{Symbol, Symbol}(), (x = 6.91071678141025e-310 + 6.9107948835431e-310im, y = 6.910728876976e-310 + 6.9107288769823e-310im), nothing, Dict{Symbol, String}(), Dict{Symbol, String}(), Dict{String, Nothing}()) will be incorrect
└ @ OpenMDAOCore ~/work/OpenMDAO.jl/OpenMDAO.jl/julia/OpenMDAOCore.jl/src/matrix_free_ad.jl:575
┌ Warning: mode = "fwd" not supported for AD backend ADTypes.AutoReverseDiff(), preparation DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}(Val{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}()), derivatives for OpenMDAOCore.MatrixFreeADExplicitComp{false, ADTypes.AutoReverseDiff{false}, OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Nothing, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}, DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}, ComponentArrays.ComponentVector{ComplexF64, Vector{ComplexF64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Nothing, Dict{String, Nothing}}(ADTypes.AutoReverseDiff(), OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}(Main.f_paraboloid, nothing), (x = 5.0, y = -2.0), nothing, (x = 2.0, y = 9.0), (f_xy = 1.0), "", false, DifferentiationInterface.NoPullbackPrep{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}(Val{Tuple{OpenMDAOCore.var"#64#65"{typeof(Main.f_paraboloid), Nothing}, ADTypes.AutoReverseDiff{false}, ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(x = 1, y = 2)}}}, Tuple{ComponentArrays.ComponentVector{Float64, Vector{Float64}, Tuple{ComponentArrays.Axis{(f_xy = 1,)}}}}, Tuple{}}}()), Dict{Symbol, String}(), Dict{Symbol, Vector{String}}(), Dict{Symbol, Bool}(), Dict{Symbol, Symbol}(), (x = 6.91071678141025e-310 + 6.9107948835431e-310im, y = 6.910728876976e-310 + 6.9107288769823e-310im), nothing, Dict{Symbol, String}(), Dict{Symbol, String}(), Dict{String, Nothing}()) will be incorrect
└ @ OpenMDAOCore ~/work/OpenMDAO.jl/OpenMDAO.jl/julia/OpenMDAOCore.jl/src/matrix_free_ad.jl:575
{'parab_comp': {('f_xy', 'x'): {'J_fwd': array([[0.]]), 'J_rev': array([[2.]]), 'J_fd': array([[2.]]), 'rows': None, 'cols': None, 'tol violation': _ErrorData(forward=1.999998, reverse=-2e-06, fwd_rev=1.999998), 'magnitude': _MagnitudeData(forward=0.0, reverse=2.0, fd=2.0), 'vals_at_max_error': _ErrorData(forward=(np.float64(0.0), np.float64(2.0)), reverse=(np.float64(2.0), np.float64(2.0)), fwd_rev=(np.float64(0.0), np.float64(2.0))), 'abs error': _ErrorData(forward=2.0, reverse=0.0, fwd_rev=2.0), 'rel error': _ErrorData(forward=1.0, reverse=0.0, fwd_rev=1.0), 'matrix_free': True}, ('f_xy', 'y'): {'J_fwd': array([[0.]]), 'J_rev': array([[9.]]), 'J_fd': array([[9.]]), 'rows': None, 'cols': None, 'tol violation': _ErrorData(forward=8.999991, reverse=-9e-06, fwd_rev=8.999991), 'magnitude': _MagnitudeData(forward=0.0, reverse=9.0, fd=9.0), 'vals_at_max_error': _ErrorData(forward=(np.float64(0.0), np.float64(9.0)), reverse=(np.float64(9.0), np.float64(9.0)), fwd_rev=(np.float64(0.0), np.float64(9.0))), 'abs error': _ErrorData(forward=9.0, reverse=0.0, fwd_rev=9.0), 'rel error': _ErrorData(forward=1.0, reverse=0.0, fwd_rev=1.0), 'matrix_free': True}}}

Same story: the derivatives look good, assuming we just look at the comparisons between Jrev and Jfd. So we're ready to actually run the optimization to verify everything is working properly:

prob.run_driver()
println("f_xy = $(prob.get_val("parab_comp.f_xy"))")  # Should print `[-27.33333333]`
println("x = $(prob.get_val("parab_comp.x"))")  # Should print `[6.66666633]`
println("y = $(prob.get_val("parab_comp.y"))")  # Should print `[-7.33333367]`
Optimization terminated successfully    (Exit mode 0)
            Current function value: 18.016167304638333
            Iterations: 24
            Function evaluations: 24
            Gradient evaluations: 24
Optimization Complete
-----------------------------------
-----------------------------------------
Component: JuliaExplicitComp 'parab_comp'
-----------------------------------------

  parab_comp: 'f_xy' wrt 'x'

    Max Tolerance Violation (Jfwd - Jfd) - (atol + rtol * Jfd) : 1.999999e+00 *
      abs error: 2.000001e+00
      rel error: 1.000000e+00
      fwd value @ max viol: 0.000000e+00
      fd value @ max viol: 2.000001e+00 (fd:forward)

    Max Tolerance Violation (Jrev - Jfd) - (atol + rtol * Jfd) : (-9.996325e-07)
      abs error: 1.000368e-06
      rel error: 5.001840e-07
      rev value @ max viol: 2.000000e+00
      fd value @ max viol: 2.000001e+00 (fd:forward)

    Max Tolerance Violation (Jrev - Jfwd) - (atol + rtol * Jfwd) : 1.999998e+00 *
      abs error: 2.000000e+00
      rel error: 1.000000e+00
      rev value @ max viol: 0.000000e+00
      fwd value @ max viol: 2.000000e+00

    Raw Forward Derivative (Jfwd)
    [[ 0.000000000000e+00]]

    Raw Reverse Derivative (Jrev)
    [[ 2.000000000000e+00]]

    Raw FD Derivative (Jfd)
    [[ 2.000001000368e+00]]

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  parab_comp: 'f_xy' wrt 'y'

    Max Tolerance Violation (Jfwd - Jfd) - (atol + rtol * Jfd) : 8.999992e+00 *
      abs error: 9.000001e+00
      rel error: 1.000000e+00
      fwd value @ max viol: 0.000000e+00
      fd value @ max viol: 9.000001e+00 (fd:forward)

    Max Tolerance Violation (Jrev - Jfd) - (atol + rtol * Jfd) : (-7.999542e-06)
      abs error: 1.000459e-06
      rel error: 1.111621e-07
      rev value @ max viol: 9.000000e+00
      fd value @ max viol: 9.000001e+00 (fd:forward)

    Max Tolerance Violation (Jrev - Jfwd) - (atol + rtol * Jfwd) : 8.999991e+00 *
      abs error: 9.000000e+00
      rel error: 1.000000e+00
      rev value @ max viol: 0.000000e+00
      fwd value @ max viol: 9.000000e+00

    Raw Forward Derivative (Jfwd)
    [[ 0.000000000000e+00]]

    Raw Reverse Derivative (Jrev)
    [[ 9.000000000000e+00]]

    Raw FD Derivative (Jfd)
    [[ 9.000001000459e+00]]

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-----------------------------------------
Component: JuliaExplicitComp 'parab_comp'
-----------------------------------------

  parab_comp: 'f_xy' wrt 'x'

    Max Tolerance Violation (Jfwd - Jfd) - (atol + rtol * Jfd) : 1.999998e+00 *
      abs error: 2.000000e+00
      rel error: 1.000000e+00
      fwd value @ max viol: 0.000000e+00
      fd value @ max viol: 2.000000e+00 (cs:None)

    Max Tolerance Violation (Jrev - Jfd) - (atol + rtol * Jfd) : (-2.000000e-06)
      abs error: 0.000000e+00
      rel error: 0.000000e+00
      rev value @ max viol: 2.000000e+00
      fd value @ max viol: 2.000000e+00 (cs:None)

    Max Tolerance Violation (Jrev - Jfwd) - (atol + rtol * Jfwd) : 1.999998e+00 *
      abs error: 2.000000e+00
      rel error: 1.000000e+00
      rev value @ max viol: 0.000000e+00
      fwd value @ max viol: 2.000000e+00

    Raw Forward Derivative (Jfwd)
    [[ 0.000000000000e+00]]

    Raw Reverse Derivative (Jrev)
    [[ 2.000000000000e+00]]

    Raw CS Derivative (Jfd)
    [[ 2.000000000000e+00]]

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  parab_comp: 'f_xy' wrt 'y'

    Max Tolerance Violation (Jfwd - Jfd) - (atol + rtol * Jfd) : 8.999991e+00 *
      abs error: 9.000000e+00
      rel error: 1.000000e+00
      fwd value @ max viol: 0.000000e+00
      fd value @ max viol: 9.000000e+00 (cs:None)

    Max Tolerance Violation (Jrev - Jfd) - (atol + rtol * Jfd) : (-9.000000e-06)
      abs error: 0.000000e+00
      rel error: 0.000000e+00
      rev value @ max viol: 9.000000e+00
      fd value @ max viol: 9.000000e+00 (cs:None)

    Max Tolerance Violation (Jrev - Jfwd) - (atol + rtol * Jfwd) : 8.999991e+00 *
      abs error: 9.000000e+00
      rel error: 1.000000e+00
      rev value @ max viol: 0.000000e+00
      fwd value @ max viol: 9.000000e+00

    Raw Forward Derivative (Jfwd)
    [[ 0.000000000000e+00]]

    Raw Reverse Derivative (Jrev)
    [[ 9.000000000000e+00]]

    Raw CS Derivative (Jfd)
    [[ 9.000000000000e+00]]

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

f_xy = -27.333333333333336
x = [6.66666667]
y = [-7.33333333]

We got the right answer, so everything's good!