Discrete Variables

There may be times when it’s necessary to pass variables that are not floats or float arrays between components. These variables can be declared as discrete variables. A discrete variable can be any picklable python object.

In explicit and implicit components, the user must call add_discrete_input and add_discrete_output to declare discrete variables in the setup method.

Methods for Adding Discrete Variables

Here are the methods used to add discrete variables to components.

Component.add_discrete_input(self, name, val, desc='', tags=None)[source]

Add a discrete input variable to the component.

Parameters
namestr

name of the variable in this component’s namespace.

vala picklable object

The initial value of the variable being added.

descstr

description of the variable

tagsstr or list of strs

User defined tags that can be used to filter what gets listed when calling list_inputs and list_outputs.

Returns
dict

metadata for added variable

Component.add_discrete_output(self, name, val, desc='', tags=None)[source]

Add an output variable to the component.

Parameters
namestr

name of the variable in this component’s namespace.

vala picklable object

The initial value of the variable being added.

descstr

description of the variable.

tagsstr or list of strs or set of strs

User defined tags that can be used to filter what gets listed when calling list_inputs and list_outputs.

Returns
dict

metadata for added variable

Discrete Variable Considerations

Discrete variables, like continuous ones, can be connected to each other using the connect function or by promoting an input and an output to the same name. The type of the output must be a valid subclass of the type of the input or the connection will raise an exception.

Warning

If a model computes derivatives and any of those derivatives depend on the value of a discrete output variable, an exception will be raised.

If a component or group contains discrete variables, then the discrete inputs and/or outputs will be passed to the relevant API functions. In general, if nonlinear inputs are passed to a function, then a discrete inputs argument will be added. If nonlinear outputs are passed, then a discrete outputs argument will be added. The signatures of the affected functions are shown below:

ExplicitComponent.compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None)[source]

Compute outputs given inputs. The model is assumed to be in an unscaled state.

Parameters
inputsVector

unscaled, dimensional input variables read via inputs[key]

outputsVector

unscaled, dimensional output variables read via outputs[key]

discrete_inputsdict or None

If not None, dict containing discrete input values.

discrete_outputsdict or None

If not None, dict containing discrete output values.

ExplicitComponent.compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode, discrete_inputs=None)[source]

Compute jac-vector product. The model is assumed to be in an unscaled state.

If mode is:

‘fwd’: d_inputs |-> d_outputs

‘rev’: d_outputs |-> d_inputs

Parameters
inputsVector

unscaled, dimensional input variables read via inputs[key]

d_inputsVector

see inputs; product must be computed only if var_name in d_inputs

d_outputsVector

see outputs; product must be computed only if var_name in d_outputs

modestr

either ‘fwd’ or ‘rev’

discrete_inputsdict or None

If not None, dict containing discrete input values.

ExplicitComponent.compute_partials(self, inputs, partials, discrete_inputs=None)[source]

Compute sub-jacobian parts. The model is assumed to be in an unscaled state.

Parameters
inputsVector

unscaled, dimensional input variables read via inputs[key]

partialsJacobian

sub-jac components written to partials[output_name, input_name]

discrete_inputsdict or None

If not None, dict containing discrete input values.

ImplicitComponent.apply_nonlinear(self, inputs, outputs, residuals, discrete_inputs=None, discrete_outputs=None)[source]

Compute residuals given inputs and outputs.

The model is assumed to be in an unscaled state.

Parameters
inputsVector

unscaled, dimensional input variables read via inputs[key]

outputsVector

unscaled, dimensional output variables read via outputs[key]

residualsVector

unscaled, dimensional residuals written to via residuals[key]

discrete_inputsdict or None

If not None, dict containing discrete input values.

discrete_outputsdict or None

If not None, dict containing discrete output values.

ImplicitComponent.guess_nonlinear(self, inputs, outputs, residuals, discrete_inputs=None, discrete_outputs=None)[source]

Provide initial guess for states.

Override this method to set the initial guess for states.

Parameters
inputsVector

unscaled, dimensional input variables read via inputs[key]

outputsVector

unscaled, dimensional output variables read via outputs[key]

residualsVector

unscaled, dimensional residuals written to via residuals[key]

discrete_inputsdict or None

If not None, dict containing discrete input values.

discrete_outputsdict or None

If not None, dict containing discrete output values.

ImplicitComponent.linearize(self, inputs, outputs, jacobian, discrete_inputs=None, discrete_outputs=None)[source]

Compute sub-jacobian parts and any applicable matrix factorizations.

The model is assumed to be in an unscaled state.

Parameters
inputsVector

unscaled, dimensional input variables read via inputs[key]

outputsVector

unscaled, dimensional output variables read via outputs[key]

jacobianJacobian

sub-jac components written to jacobian[output_name, input_name]

discrete_inputsdict or None

If not None, dict containing discrete input values.

discrete_outputsdict or None

If not None, dict containing discrete output values.

Group.guess_nonlinear(self, inputs, outputs, residuals, discrete_inputs=None, discrete_outputs=None)[source]

Provide initial guess for states.

Override this method to set the initial guess for states.

Parameters
inputsVector

unscaled, dimensional input variables read via inputs[key]

outputsVector

unscaled, dimensional output variables read via outputs[key]

residualsVector

unscaled, dimensional residuals written to via residuals[key]

discrete_inputsdict or None

If not None, dict containing discrete input values.

discrete_outputsdict or None

If not None, dict containing discrete output values.

Discrete Variable Examples

An example is given below that shows an explicit component that has a discrete input along with continuous inputs and outputs.

import numpy as np
import openmdao.api as om

class BladeSolidity(om.ExplicitComponent):
    def setup(self):

        # Continuous Inputs
        self.add_input('r_m', 1.0, units="ft", desc="Mean radius")
        self.add_input('chord', 1.0, units="ft", desc="Chord length")

        # Discrete Inputs
        self.add_discrete_input('num_blades', 2, desc="Number of blades")

        # Continuous Outputs
        self.add_output('blade_solidity', 0.0, desc="Blade solidity")

    def compute(self, inputs, outputs, discrete_inputs, discrete_outputs):

        num_blades = discrete_inputs['num_blades']
        chord = inputs['chord']
        r_m = inputs['r_m']

        outputs['blade_solidity'] = chord / (2.0 * np.pi * r_m / num_blades)

# build the model
prob = om.Problem()
indeps = prob.model.add_subsystem('indeps', om.IndepVarComp(), promotes=['*'])
indeps.add_output('r_m', 3.2, units="ft")
indeps.add_output('chord', .3, units='ft')
indeps.add_discrete_output('num_blades', 2)

prob.model.add_subsystem('SolidityComp', BladeSolidity(),
                         promotes_inputs=['r_m', 'chord', 'num_blades'])

prob.setup()
prob.run_model()

# minimum value
print(prob['SolidityComp.blade_solidity'])
[0.02984155]

Similarly, discrete variables can be added to implicit components.

import openmdao.api as om

class ImpWithInitial(om.ImplicitComponent):
    """
    An implicit component to solve the quadratic equation: x^2 - 4x + 3
    (solutions at x=1 and x=3)
    """
    def setup(self):
        self.add_input('a', val=1.)
        self.add_input('b', val=-4.)
        self.add_discrete_input('c', val=3)
        self.add_output('x', val=5.)

        self.declare_partials(of='*', wrt='*')

    def apply_nonlinear(self, inputs, outputs, residuals, discrete_inputs, discrete_outputs):
        a = inputs['a']
        b = inputs['b']
        c = discrete_inputs['c']
        x = outputs['x']
        residuals['x'] = a * x ** 2 + b * x + c

    def linearize(self, inputs, outputs, partials, discrete_inputs, discrete_outputs):
        a = inputs['a']
        b = inputs['b']
        x = outputs['x']

        partials['x', 'a'] = x ** 2
        partials['x', 'b'] = x
        partials['x', 'x'] = 2 * a * x + b

    def guess_nonlinear(self, inputs, outputs, resids, discrete_inputs, discrete_outputs):
        # Default initial state of zero for x takes us to x=1 solution.
        # Here we set it to a value that will take us to the x=3 solution.
        outputs['x'] = 5

prob = om.Problem()
model = prob.model

model.add_subsystem('comp', ImpWithInitial())

model.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)
model.linear_solver = om.ScipyKrylov()

prob.setup()
prob.run_model()
NL: Newton Converged in 6 iterations
print(prob['comp.x'])
[3.]