-
Notifications
You must be signed in to change notification settings - Fork 240
/
add_subtract_comp.py
253 lines (222 loc) · 11.7 KB
/
add_subtract_comp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
"""
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
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.
"""
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
def initialize(self):
"""
Declare options.
"""
self.options.declare('complex', types=bool, default=False,
desc="Allocate as complex (e.g. for complex-step verification)")
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 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)
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}
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}.')
def add_output(self):
"""
Use add_equation instead of add_output to define equation systems.
"""
raise NotImplementedError(self.msginfo + ': Use add_equation method, not add_output '
'method to create an addition/subtraction relation')
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