Source code for openconcept.utilities.math.sum_comp

"""Definition of the Element Summation 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 SumComp(ExplicitComponent): r""" Compute a vectorized summation. Use the add_equation method to define any number of summations User defines the names of the input and output variables using add_equation(output_name='my_output', input_name='my_input') Use option axis = None to sum over all array elements. Default behavior sums along the columns. .. math:: \textrm{result}_j = \sum_{i=1} ^\text{vec_size} a_{ij} * \textrm{scaling factor} where - a is shape (vec_size, n) - b is of shape (vec_size, n) - c is of shape (vec_size, n) Result is of shape (1, n) or (1, ) Attributes ---------- _add_systems : list List of equation systems to be initialized with the system. """
[docs] def __init__(self, output_name=None, input_name=None, vec_size=1, length=1, val=1.0, scaling_factor=1, **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_name : str (required) name of the input variable 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 val : float or list or tuple or ndarray The initial value of the variable being added in user-defined units. Default is 1.0. **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) """ axis = kwargs.pop('axis', 0) super(SumComp, self).__init__(axis=axis) self._add_systems = [] if isinstance(output_name, string_types): self._add_systems.append((output_name, input_name, vec_size, length, val, scaling_factor, 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 ---------- axis : int or None Sum along this axis. Default 0 sums along first dimension. None sums all elements into a scalar. 1 sums along rows. """ self.options.declare('axis', default=0, desc="Axis along which to sum")
[docs] def add_equation(self, output_name, input_name, 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, var_set=0, scaling_factor=1): """ Add a multiplication relation. Parameters ---------- output_name : str (required) name of the result variable in this component's namespace. input_name : 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 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. var_set : hashable object For advanced users only. ID or color for this variable, relevant for reconfigurability. Default is 0. """ kwargs = {'units': units, '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_name, vec_size, length, val, scaling_factor, 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. """ axis = self.options['axis'] for (output_name, input_name, vec_size, length, val, scaling_factor, kwargs) in self._add_systems: units = kwargs.get('units', None) desc = kwargs.get('desc', '') var_set = kwargs.get('var_set', 0) if length == 1: shape = (vec_size,) else: shape = (vec_size, length) self.add_input(input_name, shape=shape, units=units, desc=desc + '_inp_' + input_name, var_set=var_set) if axis is None: rowidx = np.zeros(vec_size * length) output_shape = (1,) elif axis == 0: output_arange = np.arange(0, length) rowidx = np.tile(output_arange, vec_size) if length == 1: output_shape = (1,) else: output_shape = (1, length) elif axis == 1: output_arange = np.arange(0, vec_size) rowidx = np.repeat(output_arange, length) output_shape = (vec_size,) else: raise ValueError('Summation is allowed only over axis=0, 1 or None') colidx = np.arange(0, vec_size * length) self.declare_partials([output_name], [input_name], rows=rowidx, cols=colidx, val=scaling_factor * np.ones(vec_size * length)) super(SumComp, self).add_output(output_name, val, shape=output_shape, **kwargs)
[docs] def compute(self, inputs, outputs): """ Compute the summation using numpy. Parameters ---------- inputs : Vector unscaled, dimensional input variables read via inputs[key] outputs : Vector unscaled, dimensional output variables read via outputs[key] """ axis = self.options['axis'] for (output_name, input_name, vec_size, length, val, scaling_factor, kwargs) in self._add_systems: if axis is None: output_shape = (1,) elif axis == 0: if length == 1: output_shape = (1,) else: output_shape = (1, length) elif axis == 1: output_shape = (vec_size,) result = np.sum(inputs[input_name], axis=axis) * scaling_factor outputs[output_name] = result.reshape(output_shape)