Source code for openconcept.utilities.math.multiply_divide_comp

"""Definition of the Element Multiply Component."""

import collections
import numpy as np
from scipy import sparse as sp
from six import string_types

from openmdao.core.explicitcomponent import ExplicitComponent


[docs]class ElementMultiplyDivideComp(ExplicitComponent): r""" Compute a vectorized element-wise multiplication and/or division. Use the add_equation method to define any number of mult/div relations User defines the names of the input and output variables using add_equation(output_name='my_output', input_names=['a','b', 'c', ...], divide=[False,False,True,...]) .. math:: result = (a * b / c ....) * \textrm{scaling factor} 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. Attributes ---------- _add_systems : list List of equation systems to be initialized with the system. """
[docs] def __init__(self, output_name=None, input_names=None, vec_size=1, length=1, val=1.0, scaling_factor=1, divide=None, input_units=None, **kwargs): """ Allow user to create an multiplication system with one-liner. 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,) scaling_factor : numeric Scaling factor to apply to the whole system Default is 1 divide : iterable of bool or None True to use division operator, False to use mult operator Default is None which results in mult of every input Length is same as number of inputs val : float or list or tuple or ndarray The initial value of the variable being added in user-defined units. Default is 1.0. input_units : iterable of str Units for each of the input vectors in order. Output units will be dimensionally consistent. **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) """ complexify = kwargs.pop('complex', False) super(ElementMultiplyDivideComp, self).__init__(complex=complexify) self._add_systems = [] if isinstance(output_name, string_types): self._add_systems.append((output_name, input_names, vec_size, length, val, scaling_factor, divide, input_units, kwargs)) elif isinstance(output_name, collections.Iterable): raise NotImplementedError('Declaring multiple systems ' 'on initiation is not implemented.' 'Use a string to name a single addition relationship or use ' 'multiple add_equation calls') elif output_name is None: pass else: raise ValueError( "first argument to init must be either of type " "`str' or 'None'")
[docs] def initialize(self): """ Declare options. Parameters ---------- complex : Boolean Set True to enable complex math (e.g. for complex step verification) """ self.options.declare('complex', 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, res_units=None, desc='', lower=None, upper=None, ref=1.0, ref0=0.0, res_ref=None, var_set=0, scaling_factor=1, divide=None, input_units=None): """ Add a multiplication relation. 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,) scaling_factor : numeric Scaling factor to apply to the whole system Default is 1 divide : iterable of bool or None True to use division operator, False to use mult operator Default is None which results in mult of every input Length is same as number of inputs val : float or list or tuple or ndarray The initial value of the variable being added in user-defined units. Default is 1.0. input_units : iterable of str Units for each of the input vectors in order. Output units will be dimensionally consistent. 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. var_set : hashable object For advanced users only. ID or color for this variable, relevant for reconfigurability. Default is 0. """ kwargs = {'res_units': res_units, 'desc': desc, 'lower': lower, 'upper': upper, 'ref': ref, 'ref0': ref0, 'res_ref': res_ref, 'var_set': var_set} self._add_systems.append((output_name, input_names, vec_size, length, val, scaling_factor, divide, input_units, kwargs))
[docs] def add_output(self): """ Use add_equation instead of add_output to define equation systems. """ raise NotImplementedError('Use add_equation method, not add_output method' 'to create an multliplication/division relation')
[docs] def setup(self): """ Set up the addition/subtraction system at run time. """ for (output_name, input_names, vec_size, length, val, scaling_factor, divide, input_units, kwargs) in self._add_systems: if isinstance(input_names, string_types): input_names = [input_names] desc = kwargs.get('desc', '') var_set = kwargs.get('var_set', 0) if divide is None: divide = [False for k in range(len(input_names))] if input_units is None: input_units = [None for k in range(len(input_names))] if len(divide) != len(input_names): raise ValueError('Division bool list needs to be same length as input names') if len(input_units) != len(input_names): raise ValueError('Input units list needs to be same length as input names') if length == 1: shape = (vec_size,) else: shape = (vec_size, length) output_units_assemble = [] for i, input_name in enumerate(input_names): self.add_input(input_name, shape=shape, units=input_units[i], desc=desc + '_inp_' + input_name, var_set=var_set) self.declare_partials([output_name], [input_name], rows=np.arange(0, vec_size * length), cols=np.arange(0, vec_size * length)) # derive the units of the output vector from the inputs if input_units[i] is not None: if divide[i]: if i == 0: output_units_assemble.append('(' + input_units[i] + ')**-1 ') else: output_units_assemble.append('/ (' + input_units[i] + ') ') else: if i == 0: output_units_assemble.append(input_units[i] + ' ') else: output_units_assemble.append('* (' + input_units[i] + ') ') output_units = ''.join(output_units_assemble) if len(output_units_assemble) == 0: output_units = None super(ElementMultiplyDivideComp, self).add_output(output_name, val, shape=shape, units=output_units, **kwargs)
[docs] def compute(self, inputs, outputs): """ Compute the element wise multiplication or division of inputs using numpy. 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_factor, divide, input_units, kwargs) in self._add_systems: if isinstance(input_names, string_types): input_names = [input_names] if divide is None: divide = [False for k in range(len(input_names))] if length == 1: shape = (vec_size,) else: shape = (vec_size, length) if complexify: temp = np.ones(shape, dtype=np.complex_) else: temp = np.ones(shape) for i, input_name in enumerate(input_names): if divide[i]: temp = temp / inputs[input_name] else: temp = temp * inputs[input_name] outputs[output_name] = temp * scaling_factor
def compute_partials(self, inputs, J): for (output_name, input_names, vec_size, length, val, scaling_factor, divide, input_units, kwargs) in self._add_systems: if isinstance(input_names, string_types): input_names = [input_names] if divide is None: divide = [False for k in range(len(input_names))] if length == 1: shape = (vec_size,) else: shape = (vec_size, length) for j, input_name in enumerate(input_names): temp = np.ones(shape) for i, input_name_partial in enumerate(input_names): if input_name_partial != input_name: if divide[i]: temp = temp / inputs[input_name_partial] else: temp = temp * inputs[input_name_partial] else: # if i is the differentiated variable if divide[i]: temp = - temp / inputs[input_name_partial] ** 2 else: pass temp = temp * scaling_factor J[output_name, input_name] = temp.flatten()