Source code for openmdao.components.add_subtract_comp

"""
Definition of the Add/Subtract Component.
"""
from collections.abc import Iterable

import numpy as np
from scipy import sparse as sp

from openmdao.core.explicitcomponent import ExplicitComponent
from openmdao.utils.om_warnings import issue_warning


[docs]class AddSubtractComp(ExplicitComponent): r""" Compute a vectorized element-wise addition or subtraction. Use the add_equation method to define any number of add/subtract relations User defines the names of the input and output variables using add_equation(output_name='my_output', input_names=['a','b', 'c', ...]) .. math:: result = a * \textrm{scaling factor}_a + b * \textrm{scaling factor}_b + c * \textrm{scaling factor}_c + ... where all inputs shape (vec_size, n) b is of shape (vec_size, n) c is of shape (vec_size, n) result is of shape (vec_size, n) All input vectors must be of the same shape, specified by the options 'vec_size' and 'length'. Use scaling factor -1 for subtraction. Parameters ---------- output_name : str (Required) name of the result variable in this component's namespace. input_names : iterable of str (Required) names of the input variables for this system. vec_size : int Length of the first dimension of the input and output vectors (i.e number of rows, or vector length for a 1D vector) Default is 1. length : int Length of the second dimension of the input and ouptut vectors (i.e. number of columns) Default is 1 which results in input/output vectors of size (vec_size,). val : float or list or tuple or ndarray The initial value of the variable being added in user-defined units. Default is 1.0. scaling_factors : iterable of numeric Scaling factors to apply to each input. Use [1,1,...] for addition, [1,-1,...] for subtraction Must be same length as input_names Default is None which results in a scaling factor of 1 on each input (element-wise addition). **kwargs : str Any other arguments to pass to the addition system (same as add_output method for ExplicitComponent) Examples include units (str or None), desc (str). Attributes ---------- _equations : list List of equation systems to be initialized with the system. _input_names : dict Dictionary of input names and key associated options for inputs so that a given input name can be used in multiple equations. """
[docs] def __init__(self, output_name=None, input_names=None, vec_size=1, length=1, val=1.0, scaling_factors=None, **kwargs): """ Allow user to create an addition/subtracton system with one-liner. """ super().__init__() # Add systems is used to store those systems provided upon initialization self._equations = [] # Input names will store the names of inputs and their key properties which must be # the same across all equations in which they are used. self._input_names = {} if isinstance(output_name, str): self.add_equation(output_name, input_names, vec_size, length, val, scaling_factors=scaling_factors, **kwargs) elif isinstance(output_name, Iterable): raise NotImplementedError(self.msginfo + ': Declaring multiple addition systems ' 'on initiation is not implemented.' 'Use a string to name a single addition relationship or use ' 'multiple add_output calls') elif output_name is None: pass else: raise ValueError(self.msginfo + ": first argument to adder init must be either of " "type `str' or 'None'") self._no_check_partials = True
[docs] def initialize(self): """ Declare options. """ self.options.declare('complex', types=bool, default=False, desc="Allocate as complex (e.g. for complex-step verification)")
[docs] def add_equation(self, output_name, input_names, vec_size=1, length=1, val=1.0, units=None, res_units=None, desc='', lower=None, upper=None, ref=1.0, ref0=0.0, res_ref=None, scaling_factors=None, tags=None): """ Add an addition/subtraction relation. Parameters ---------- output_name : str (required) Name of the result variable in this component's namespace. input_names : iterable (required) names of the input variables for this system. vec_size : int Length of the first dimension of the input and output vectors (i.e number of rows, or vector length for a 1D vector) Default is 1. length : int Length of the second dimension of the input and output vectors (i.e. number of columns) Default is 1 which results in input/output vectors of size (vec_size,). val : float or list or tuple or ndarray The initial value of the variable being added in user-defined units. Default is 1.0. units : str or None Units in which the output variables will be provided to the component during execution. Default is None, which means it has no units. res_units : str or None Units in which the residuals of this output will be given to the user when requested. Default is None, which means it has no units. desc : str Description of the variable. lower : float or list or tuple or ndarray or Iterable or None Lower bound(s) in user-defined units. It can be (1) a float, (2) an array_like consistent with the shape arg (if given), or (3) an array_like matching the shape of val, if val is array_like. A value of None means this output has no lower bound. Default is None. upper : float or list or tuple or ndarray or or Iterable None Upper bound(s) in user-defined units. It can be (1) a float, (2) an array_like consistent with the shape arg (if given), or (3) an array_like matching the shape of val, if val is array_like. A value of None means this output has no upper bound. Default is None. ref : float or ndarray Scaling parameter. The value in the user-defined units of this output variable when the scaled value is 1. Default is 1. ref0 : float or ndarray Scaling parameter. The value in the user-defined units of this output variable when the scaled value is 0. Default is 0. res_ref : float or ndarray Scaling parameter. The value in the user-defined res_units of this output's residual when the scaled value is 1. Default is 1. scaling_factors : iterable of numeric Scaling factors to apply to each input. Use [1,1,...] for addition, [1,-1,...] for subtraction Must be same length as input_names Default is None which results in a scaling factor of 1 on each input (element-wise addition). tags : str or list of strs User defined tags that can be used to filter what gets listed when calling list_inputs and list_outputs and also when listing results from case recorders. """ kwargs = {'units': units, 'res_units': res_units, 'desc': desc, 'lower': lower, 'upper': upper, 'ref': ref, 'ref0': ref0, 'res_ref': res_ref, 'tags': tags} if (not isinstance(input_names, (list, tuple))) or len(input_names) < 2: raise ValueError(self.msginfo + ': must specify more than one input name for ' 'an equation, but only one given') if scaling_factors is None: scaling_factors = np.ones(len(input_names)) elif len(scaling_factors) != len(input_names): raise ValueError(self.msginfo + ': Scaling factors list needs to be same length ' 'as input names') if len(input_names) != len(set(input_names)): issue_warning(f"Duplicate inputs are connected to '{output_name}'. This will " "double count the same value, which may cause unexpected behavior.") if length == 1: shape = (vec_size,) else: shape = (vec_size, length) super().add_output(output_name, val, shape=shape, **kwargs) self._equations.append((output_name, input_names, vec_size, length, val, scaling_factors, kwargs)) for i, input_name in enumerate(input_names): if input_name not in self._input_names: self.add_input(input_name, shape=shape, units=units, desc=desc + '_inp_' + input_name) else: # Verify that the input is consistent with that added for a previous equation prev_vec_size = self._input_names[input_name]['vec_size'] prev_length = self._input_names[input_name]['length'] prev_units = self._input_names[input_name]['units'] if vec_size != prev_vec_size: raise ValueError(self.msginfo + f': Input {input_name} was added in a previous ' f'equation but had a different vec_size ' f'({prev_vec_size} vs. {vec_size}.') if length != prev_length: raise ValueError(self.msginfo + f': Input {input_name} was added in a previous ' f'equation but had a different length ' f'({prev_length} vs. {length}.') if units != prev_units: raise ValueError(self.msginfo + f': Input {input_name} was added in a previous ' f'equation but had different units ' f'({prev_units} vs. {units}.') sf = scaling_factors[i] self.declare_partials([output_name], [input_name], val=sf * sp.eye(vec_size * length, format='csc')) self._input_names[input_name] = {'vec_size': vec_size, 'length': length, 'units': units}
[docs] def add_output(self, name, val=1.0, **kwargs): """ Use add_equation instead of add_output to define equation systems. Parameters ---------- name : str Name of the variable in this component's namespace. val : float or ndarray The initial value of the variable being added in user-defined units. Default is 1.0. **kwargs : dict Keyword arguments. """ raise NotImplementedError(self.msginfo + ': Use add_equation method, not add_output ' 'method to create an addition/subtraction relation')
[docs] def compute(self, inputs, outputs): """ Compute the element wise addition or subtraction of inputs using numpy + operator. Parameters ---------- inputs : Vector Unscaled, dimensional input variables read via inputs[key]. outputs : Vector Unscaled, dimensional output variables read via outputs[key]. """ complexify = self.options['complex'] for (output_name, input_names, vec_size, length, val, scaling_factors, kwargs) in self._equations: if isinstance(input_names, str): input_names = [input_names] if scaling_factors is None: scaling_factors = np.ones(len(input_names)) if length == 1: shape = (vec_size,) else: shape = (vec_size, length) if complexify: temp = np.zeros(shape, dtype=complex) else: temp = np.zeros(shape) for i, input_name in enumerate(input_names): sf = scaling_factors[i] temp = temp + inputs[input_name] * sf outputs[output_name] = temp