Demonstration on how to use the programmatically defined options
In this demonstration we show how to create a component and group that allow a user to programmatically define options. We also show how to access the default options of a component using the required_options class method, and the default options of a group using its full_options instance method once the group has been initialized.
We finish the demo with showing how to get the current values of all the options required to set up a group using the current_options instance method.
import numpy as np
import pandas as pd
import openmdao.api as om
from standard_evaluator import StandardBase, StandardGroup, OptionsDictionaryUnit
matlabengine not available.
We start by defining the Paraboloid class which inherits from both the StandardBase class as well as from the ExplicitComponent class from OpenMDAO. Note that in the future this dual inheritance would not be required if this functionality is moved into OpenMDAO.
We define the _define_options class method to define the required options for this class, which in this case is a single integer option with a default value of 40 and a lower bound of 1. In the setup method we then use this option to specify the dimension of the x input to the calculation. We also tell the system that the x input depends on the option dimension.
Note that the order when defining dual inheritance is important. The StandardBase class must come first.
class Paraboloid(StandardBase, om.ExplicitComponent):
"""
Evaluates the equation f(x,y) = (|x|-3)^2 + |x|y + (y+4)^2 - 3.
"""
@classmethod
def _define_options(cls) -> OptionsDictionaryUnit:
"""Abstract method that allows a developer to define information about the parameters this class uses.
This method is called by a class method that is adding options to the `class_options` option.
Returns:
OptionsDictionaryUnit -- The required options for this class, their default values, and, if required, their units.
"""
options = OptionsDictionaryUnit()
options.declare("dimensions", default=40, lower=1, types=int)
return options
def setup(self):
self.add_standard_input('x', val=np.ones(self.lookup_option_value("dimensions")), options=["dimensions"],
look_up=False,)
self.add_input('y', val=0.0)
self.add_output('f_xy', val=0.0)
def setup_partials(self):
# 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']
print(f"Dimension of x: {len(x)}")
outputs['f_xy'] = (np.linalg.norm(x) - 3.0)**2 + np.linalg.norm(x) * y + (y + 4.0)**2 - 3.0
Next we create a group which in this case only contains the Paraboloid component. Note that we have to inherit from both the StandardGroup as well as from the OpenMDAO Group class. Note that the order when defining dual inheritance is important. The StandardGroup class must come first. We also need to pass the class_option to the Paraboliod class when we add that as a subsystem.
class MyGroup(StandardGroup, om.Group):
"""
Prepare derived values of aircraft geometry for aerodynamics analysis.
"""
def setup(self):
class_options = self.options["class_options"]
self.add_subsystem(
"parab_comp",
Paraboloid(class_options=class_options),
promotes_inputs=["*"],
)
Afyer defining the component and the group within it we can show how we can leverage the new functionality.
First, we will show that we can now programmatically get the required options of the Paraboloid class without needing to instantiate it. Calling the same method on the group even after instantiationg it does not return anything since the setup method has not been called, which means no components have been added to the group yet.
print(Paraboloid.required_options())
model = MyGroup()
print(model.required_options())
========== ======= ================= ================ =========== ========
Option Default Acceptable Values Acceptable Types Description Units
========== ======= ================= ================ =========== ========
dimensions 40 N/A ['int'] unitless
========== ======= ================= ================ =========== ========
====== ======= ================= ================ =========== =====
Option Default Acceptable Values Acceptable Types Description Units
====== ======= ================= ================ =========== =====
====== ======= ================= ================ =========== =====
Next we add the group to an OpenMDAO problem and run the setup method. Now things have been initalized, and the component has been added to the group. That means the group now knows the options required, and we can get them with the full_options method. After getting the options we can set the dimension to a value that is not the default. We can now instantiate the group with the modified options, and create an OpenMDAO problem. After again using the setup method we can show the options have been set correctly, and when we run the problem we see the output from the compute step showing the correct dimension of the variable x.
prob = om.Problem(model)
prob.setup()
my_options = model.full_options()
my_options.set(dimensions=100)
model = MyGroup(class_options=my_options)
prob = om.Problem(model)
prob.setup()
print(model.full_options())
print(model.current_options())
prob.set_val('parab_comp.x', 3.0)
prob.set_val('parab_comp.y', -4.0)
prob.run_model()
print(prob.get_val('parab_comp.f_xy'))
print(prob.get_val('parab_comp.x'))
Looking up value for dimensions from the full options method
{'dimensions'}
{'dimensions'}
========== ======= ================= ================ =========== ========
Option Default Acceptable Values Acceptable Types Description Units
========== ======= ================= ================ =========== ========
dimensions 40 N/A ['int'] unitless
========== ======= ================= ================ =========== ========
========== ======= ================= ================ =========== ========
Option Default Acceptable Values Acceptable Types Description Units
========== ======= ================= ================ =========== ========
dimensions 100 N/A ['int'] unitless
========== ======= ================= ================ =========== ========
Dimension of x: 100
[606.]
[3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3.
3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3.
3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3.
3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3. 3.
3. 3. 3. 3.]