A Simple Example: Optimizing a Paraboloid
We're going to duplicate the Paraboloid example from the OpenMDAO documentation, but implement the single ExplicitComponent in Julia instead of Python. The goal of this tutorial is to minimize the paraboloid
\[f(x,y) = (x - 3.0)^2 + xy + (y + 4.0)^2 - 3.0\]
with respect to $x$ and $y$. The OpenMDAO docs say the answer is $x = \frac{20}{3} \approx 6.667$ and $y = -\frac{22}{3} \approx -7.333$. Let's find out!
The Python Implementation
One possible Python implementation of the above paraboloid is this:
import openmdao.api as om
class Paraboloid(om.ExplicitComponent):
"""
Evaluates the equation f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3.
"""
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('f_xy', val=0.0)
# Finite difference all partials.
self.declare_partials('*', '*', method='fd')
def compute(self, inputs, outputs):
"""
f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3
Minimum at: x = 6.6667; y = -7.3333
"""
x = inputs['x']
y = inputs['y']
outputs['f_xy'] = (x - 3.0)**2 + x * y + (y + 4.0)**2 - 3.0Not too bad. How do we do it in Julia?
The Julia Implementation
Like this, using OpenMDAOCore.jl:
using OpenMDAOCore: OpenMDAOCore
struct Paraboloid <: OpenMDAOCore.AbstractExplicitComp
end
function OpenMDAOCore.setup(self::Paraboloid)
inputs = [OpenMDAOCore.VarData("x", val=0.0), OpenMDAOCore.VarData("y", val=0.0)]
outputs = [OpenMDAOCore.VarData("f_xy", val=0.0)]
partials = [OpenMDAOCore.PartialsData("*", "*", method="fd")]
return inputs, outputs, partials
end
function OpenMDAOCore.compute!(self::Paraboloid, inputs, outputs)
x = inputs["x"][1]
y = inputs["y"][1]
outputs["f_xy"][1] = (x - 3.0)^2 + x * y + (y + 4.0)^2 - 3.0
return nothing
endWhat does all that mean? We'll go through it step by step.
Step 1: Preamble
using OpenMDAOCore: OpenMDAOCoreThis line loads the OpenMDAOCore.jl Julia package. Julia uses two different keywords for loading code from Julia modules: using and import. The official Julia docs on Modules do a good job of explaining the difference. I like doing using Foo: Foo because it brings the module name Foo into the current scope, but not any of the names inside of Foo, so it doesn't clutter the namespace. (The statement using Foo: Foo is kind of like Julia's version of import foo in Python, while just plain using Foo is like Python's from foo import *.)
Step 2: The Paraboloid struct
struct Paraboloid <: OpenMDAOCore.AbstractExplicitComp
endThis bit of code defines a new type in Julia named Paraboloid. The <: is the subtype operator in Julia, so we are telling Julia that our new Paraboloid type is a subtype of the AbstractExplicitComp type defined in OpenMDAOCore. This is the Julian equivalent of
class Paraboloid(om.ExplicitComponent):in Python.
Step 3: OpenMDAOCore.setup
function OpenMDAOCore.setup(self::Paraboloid)
inputs = [OpenMDAOCore.VarData("x", val=0.0), OpenMDAOCore.VarData("y", val=0.0)]
outputs = [OpenMDAOCore.VarData("f_xy", val=0.0)]
partials = [OpenMDAOCore.PartialsData("*", "*", method="fd")]
return inputs, outputs, partials
endThis OpenMDAOCore.setup method is the Julian equivalent of the ExplicitComponent.setup method from the Python version of the paraboloid. The job of OpenMDAOCore.setup is to take a single argument (an OpenMDAOCore.AbstractExplicitComp or OpenMDAOCore.AbstractImplicitComp) and return three things:
- A
VectorofVarDatastructscontaining metadata for the inputs to the component - A
VectorofVarDatastructscontaining metadata for the outputs of the component - A
VectorofPartialsDatastructscontaining metadata for the partial derivatives of the component
These Julia Vectors must always be returned in that order: inputs, outputs, partials. OpenMDAO.jl uses the VarData entries in the inputs and outputs Vectors to construct arguments to the Component.add_input and Component.add_output, respectively. And OpenMDAO.jl uses the PartialsData entries in the partials Vector to construct arguments to Component.declare_partials. The OpenMDAOCore.VarData and OpenMDAOCore.PartialsData docstrings have all the details.
Step 4: OpenMDAOCore.compute!
function OpenMDAOCore.compute!(self::Paraboloid, inputs, outputs)
x = inputs["x"][1]
y = inputs["y"][1]
outputs["f_xy"][1] = (x - 3.0)^2 + x * y + (y + 4.0)^2 - 3.0
return nothing
endThis OpenMDAOCore.compute! method is the equivalent of the Paraboloid.compute method from the Python version of the Paraboloid. Its job is to take a Paraboloid struct and a Dict of inputs, calculate the outputs, and then store these outputs in the outputs Dict. The inputs and outputs Dict entries are Julia arrays, similar to the NumPy arrays that OpenMDAO uses. (They are actually PyArrays from the PythonCall package, which are wrappers around the NumPy arrays that OpenMDAO creates for us.)
Now we need to figure out how to get that Julia code into OpenMDAO. How we do that depends on whether we're following the Python-Centric Approach or Julia-Centric Approach.
The Python-Centric Run Script
We'll use JuliaCall, provided by the PythonCall package, to import the Julia code from the previous section into Python. Then we can use the omjlcomps Python package to create an OpenMDAO ExplicitComponent from the Paraboloid Julia struct, and write a run script as usual.
import openmdao.api as om
# Create a new Julia module that will hold all the Julia code imported into this Python module.
import juliacall; jl = juliacall.newmodule("ParaboloidExample")
# This assumes the file with the Julia Paraboloid implementation is in the current directory and is named `paraboloid.jl`.
jl.include("paraboloid.jl")
# Now we have access to everything in `paraboloid.jl`.
# omjlcomps knows how to create an OpenMDAO ExplicitComponent from an OpenMDAOCore.AbstractExplicitComp
from omjlcomps import JuliaExplicitComp
comp = JuliaExplicitComp(jlcomp=jl.Paraboloid())
# Now everything else is the same as https://openmdao.org/newdocs/versions/latest/basic_user_guide/single_disciplinary_optimization/first_analysis.html
model = om.Group()
model.add_subsystem('parab_comp', 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()
prob.set_val('parab_comp.x', 3.0)
prob.set_val('parab_comp.y', -4.0)
prob.run_model()
print(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()
print(prob.get_val('parab_comp.f_xy')) # Should print `[-5.]`
prob.run_driver()
print(f"f_xy = {prob.get_val('parab_comp.f_xy')}") # Should print `[-27.33333333]`
print(f"x = {prob.get_val('parab_comp.x')}") # Should print `[6.66666633]`
print(f"y = {prob.get_val('parab_comp.y')}") # Should print `[-7.33333367]`The above Python run script should look pretty familiar if you have experience using OpenMDAO. The only difference from a pure-Python version is the little bit at the top that we use to create the JuliaExplicitComp.
The Julia-Centric Run Script
Now let's see if we can write a Julia run script:
using OpenMDAO: om, make_component
prob = om.Problem()
# omjlcomps knows how to create an OpenMDAO ExplicitComponent from an OpenMDAOCore.AbstractExplicitComp
comp = make_component(Paraboloid())
model = om.Group()
model.add_subsystem("parab_comp", 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()
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.]`
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]`[-15.]
[-5.]
Optimization terminated successfully (Exit mode 0)
Current function value: -27.333333333333336
Iterations: 4
Function evaluations: 5
Gradient evaluations: 4
Optimization Complete
-----------------------------------
=======
circuit
=======
NL: Newton 0 ; 0.00141407214 1
| LS: AG 1 ; 6.9707252e+152 1
| LS: AG 2 ; 8.51199079e+68 0.5
| LS: AG 3 ; 9.40605982e+26 0.25
| LS: AG 4 ; 988771.709 0.125
| LS: AG 5 ; 0.00130322117 0.0625
NL: Newton 1 ; 0.00130322117 0.921608691
| LS: AG 1 ; 5580826.07 1
| LS: AG 2 ; 13.3748871 0.5
| LS: AG 3 ; 0.0198015756 0.25
| LS: AG 4 ; 0.000828003297 0.125
NL: Newton 2 ; 0.000828003297 0.585545301
NL: Newton 3 ; 7.02986117e-06 0.00497135964
NL: Newton 4 ; 2.64550002e-08 1.87083809e-05
NL: Newton 5 ; 3.78431444e-13 2.67618202e-10
NL: Newton Converged
------------------------------------
Component: JuliaExplicitComp 'ecomp'
------------------------------------
ecomp: 'y' wrt 'x'
Max Tolerance Violation (Jfwd - Jfd) - (atol + rtol * Jfd) : (0.000000e+00)
abs error: 0.000000e+00
rel error: inf
fwd value @ max viol: 0.000000e+00
fd value @ max viol: 0.000000e+00 (cs:None)
Raw Forward Derivative (Jfwd)
[[ 4.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 8.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 1.200000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 1.600000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 2.000000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 2.400000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 2.800000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 3.200000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 3.600000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 4.000000000000e+01 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 4.400000000000e+01 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 4.800000000000e+01]]
Raw CS Derivative (Jfd)
[[ 4.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 8.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 1.200000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 1.600000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 2.000000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 2.400000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 2.800000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 3.200000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 3.600000000000e+01 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 4.000000000000e+01 0.000000000000e+00 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 4.400000000000e+01 0.000000000000e+00]
[ 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 4.800000000000e+01]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
f_xy = [-27.33333333]
x = [6.66666633]
y = [-7.33333367](This example assumes that the definition of the Paraboloid struct is included in the same file. So concatenate those two code blocks if you'd like to run this yourself.) Good news—we got the expected answer!
Adding Derivatives
In the previous example we used OpenMDAO's finite difference method to approximate the paraboloid's partial derivatives. We can calculate them ourselves, though, just like in a Python OpenMDAO Component. Here's the implementation:
using OpenMDAOCore: OpenMDAOCore
struct ParaboloidUserPartials <: OpenMDAOCore.AbstractExplicitComp
end
function OpenMDAOCore.setup(self::ParaboloidUserPartials)
inputs = [OpenMDAOCore.VarData("x", val=0.0), OpenMDAOCore.VarData("y", val=0.0)]
outputs = [OpenMDAOCore.VarData("f_xy", val=0.0)]
partials = [OpenMDAOCore.PartialsData("*", "*")]
return inputs, outputs, partials
end
function OpenMDAOCore.compute!(self::ParaboloidUserPartials, inputs, outputs)
x = inputs["x"][1]
y = inputs["y"][1]
outputs["f_xy"][1] = (x - 3.0)^2 + x * y + (y + 4.0)^2 - 3.0
return nothing
end
function OpenMDAOCore.compute_partials!(self::ParaboloidUserPartials, inputs, partials)
x = inputs["x"][1]
y = inputs["y"][1]
partials["f_xy", "x"][1] = 2*(x - 3.0) + y
partials["f_xy", "y"][1] = x + 2*(y + 4.0)
return nothing
endThe implementation of ParaboloidUserPartials is almost the same as Paraboloid. The are only two differences:
- We've removed the
method="fd"argument from the call to thePartialsDataconstructor. This means themethodargument will default to"exact"(as shown in the docstring above), and OpenMDAO will expect we'll calculate the derivatives of this component ourselves. - We've implemented a
compute_partials!method for our newParaboloidUserPartialsstruct. This is just like anExplicitComponent.compute_partialsmethod in a Python OpenMDAO component. Its job is to calculate the the derivatives of the outputs with respect to the inputs, of course.
So, we implemented a compute_partials! method. But how do we know if they're right? The OpenMDAO Problem class has a method called check_partials that compares the user-defined partial derivatives to the finite difference method. Can we use that with an OpenMDAOCore.AbstractExplicitComp? Let's try!
using OpenMDAO: om, make_component
prob = om.Problem()
# omjlcomps knows how to create an OpenMDAO ExplicitComponent from an OpenMDAOCore.AbstractExplicitComp
comp = make_component(ParaboloidUserPartials())
model = om.Group()
model.add_subsystem("parab_comp", 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)
prob.set_val("parab_comp.x", 3.0)
prob.set_val("parab_comp.y", -4.0)
prob.run_model()
println(prob.check_partials(method="fd")){'parab_comp': {('f_xy', 'x'): {'J_fwd': array([[-4.]]), 'J_fd': array([[-3.999999]]), 'rows': None, 'cols': None, 'tol violation': _ErrorData(forward=-3.000469211755785e-06, reverse=None, fwd_rev=None), 'magnitude': _MagnitudeData(forward=4.0, reverse=0.0, fd=3.9999990004702113), 'vals_at_max_error': _ErrorData(forward=(np.float64(-4.0), np.float64(-3.9999990004702113)), reverse=None, fwd_rev=None), 'abs error': _ErrorData(forward=9.99529788714426e-07, reverse=None, fwd_rev=None), 'rel error': _ErrorData(forward=2.498825096198595e-07, reverse=None, fwd_rev=None)}, ('f_xy', 'y'): {'J_fwd': array([[3.]]), 'J_fd': array([[3.000001]]), 'rows': None, 'cols': None, 'tol violation': _ErrorData(forward=-2.000380943556479e-06, reverse=None, fwd_rev=None), 'magnitude': _MagnitudeData(forward=3.0, reverse=0.0, fd=3.000000999620056), 'vals_at_max_error': _ErrorData(forward=(np.float64(3.0), np.float64(3.000000999620056)), reverse=None, fwd_rev=None), 'abs error': _ErrorData(forward=9.99620056063577e-07, reverse=None, fwd_rev=None), 'rel error': _ErrorData(forward=3.332065743278675e-07, reverse=None, fwd_rev=None)}}}It worked! And the error is quite small.
What about the complex step method?
println(prob.check_partials(method="cs")){'parab_comp': {('f_xy', 'x'): {'J_fwd': array([[-4.]]), 'J_fd': array([[-4.]]), 'rows': None, 'cols': None, 'tol violation': _ErrorData(forward=-4e-06, reverse=None, fwd_rev=None), 'magnitude': _MagnitudeData(forward=4.0, reverse=0.0, fd=4.0), 'vals_at_max_error': _ErrorData(forward=(np.float64(-4.0), np.float64(-4.0)), reverse=None, fwd_rev=None), 'abs error': _ErrorData(forward=0.0, reverse=None, fwd_rev=None), 'rel error': _ErrorData(forward=0.0, reverse=None, fwd_rev=None)}, ('f_xy', 'y'): {'J_fwd': array([[3.]]), 'J_fd': array([[3.]]), 'rows': None, 'cols': None, 'tol violation': _ErrorData(forward=-3e-06, reverse=None, fwd_rev=None), 'magnitude': _MagnitudeData(forward=3.0, reverse=0.0, fd=3.0), 'vals_at_max_error': _ErrorData(forward=(np.float64(3.0), np.float64(3.0)), reverse=None, fwd_rev=None), 'abs error': _ErrorData(forward=0.0, reverse=None, fwd_rev=None), 'rel error': _ErrorData(forward=0.0, reverse=None, fwd_rev=None)}}}It works! (The error is zero since the complex-step method is second-order accurate and we're differentiating a second-order polynomial.) Complex numbers are no problem for Julia, but just like Python, we need to be careful to write our compute_partials! function in a complex-step-safe manner.
The Julia library FLOWMath has a collection of complex-step-safe functions.
Now, let's try an optimization:
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: -27.333333333333005
Iterations: 4
Function evaluations: 5
Gradient evaluations: 4
Optimization Complete
-----------------------------------
-----------------------------------------
Component: JuliaExplicitComp 'parab_comp'
-----------------------------------------
parab_comp: 'f_xy' wrt 'x'
Max Tolerance Violation (Jfwd - Jfd) - (atol + rtol * Jfd) : (-3.000469e-06)
abs error: 9.995298e-07
rel error: 2.498825e-07
fwd value @ max viol: -4.000000e+00
fd value @ max viol: -3.999999e+00 (fd:forward)
Raw Forward Derivative (Jfwd)
[[-4.000000000000e+00]]
Raw FD Derivative (Jfd)
[[-3.999999000470e+00]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
parab_comp: 'f_xy' wrt 'y'
Max Tolerance Violation (Jfwd - Jfd) - (atol + rtol * Jfd) : (-2.000381e-06)
abs error: 9.996201e-07
rel error: 3.332066e-07
fwd value @ max viol: 3.000000e+00
fd value @ max viol: 3.000001e+00 (fd:forward)
Raw Forward Derivative (Jfwd)
[[ 3.000000000000e+00]]
Raw FD Derivative (Jfd)
[[ 3.000000999620e+00]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-----------------------------------------
Component: JuliaExplicitComp 'parab_comp'
-----------------------------------------
parab_comp: 'f_xy' wrt 'x'
Max Tolerance Violation (Jfwd - Jfd) - (atol + rtol * Jfd) : (-4.000000e-06)
abs error: 0.000000e+00
rel error: 0.000000e+00
fwd value @ max viol: -4.000000e+00
fd value @ max viol: -4.000000e+00 (cs:None)
Raw Forward Derivative (Jfwd)
[[-4.000000000000e+00]]
Raw CS Derivative (Jfd)
[[-4.000000000000e+00]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
parab_comp: 'f_xy' wrt 'y'
Max Tolerance Violation (Jfwd - Jfd) - (atol + rtol * Jfd) : (-3.000000e-06)
abs error: 0.000000e+00
rel error: 0.000000e+00
fwd value @ max viol: 3.000000e+00
fd value @ max viol: 3.000000e+00 (cs:None)
Raw Forward Derivative (Jfwd)
[[ 3.000000000000e+00]]
Raw CS Derivative (Jfd)
[[ 3.000000000000e+00]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
f_xy = [-27.33333333]
x = [6.66666667]
y = [-7.33333333]Still works, and we got the right answer.