-
Notifications
You must be signed in to change notification settings - Fork 240
/
indepvarcomp.py
307 lines (263 loc) · 11.9 KB
/
indepvarcomp.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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
"""Define the IndepVarComp class."""
import numpy as np
from openmdao.core.explicitcomponent import ExplicitComponent
from openmdao.utils.array_utils import shape_to_len
from openmdao.utils.general_utils import make_set, ensure_compatible
from openmdao.recorders.recording_iteration_stack import Recording
class IndepVarComp(ExplicitComponent):
"""
Class to use when all output variables are independent.
Parameters
----------
name : str or None
Name of the variable.
If None, variables should be defined external to this class by calling add_output.
val : float or ndarray
Value of the variable if a single variable is being defined.
**kwargs : dict
Keyword arguments.
"""
def __init__(self, name=None, val=1.0, **kwargs):
"""
Initialize all attributes.
"""
super().__init__(**kwargs)
if 'tags' not in kwargs:
kwargs['tags'] = {'openmdao:indep_var', 'openmdao:allow_desvar'}
else:
kwargs['tags'] = make_set(kwargs['tags'], name='tags') | {'openmdao:indep_var',
'openmdao:allow_desvar'}
# A single variable is declared during instantiation
if isinstance(name, str):
super().add_output(name, val, **kwargs)
elif name is None:
pass
else:
raise ValueError(
"first argument to IndepVarComp init must be either of type "
"`str` or an iterable of tuples of the form (name, value) or "
"(name, value, keyword_dict).")
for illegal in ('promotes', 'promotes_inputs', 'promotes_outputs'):
if illegal in kwargs:
raise ValueError("IndepVarComp init: '%s' is not supported "
"in IndepVarComp." % illegal)
self._no_check_partials = True
def initialize(self):
"""
Declare options.
"""
opt = self.options
opt.declare('name', types=str,
desc="Name of the variable in this component's namespace.")
opt.declare('val', types=(float, list, tuple, np.ndarray), default=1.0,
desc="The initial value of the variable being added in user-defined units.")
opt.declare('shape', types=(int, tuple, list), default=None,
desc="Shape of this variable, only required if val is not an array.")
opt.declare('units', types=str, default=None,
desc="Units in which the output variables will be provided to the "
"component during execution.")
opt.declare('res_units', types=str, default=None,
desc="Units in which the residuals of this output will be given to "
"the user when requested.")
opt.declare('desc', types=str, default=None,
desc="Description of the variable")
opt.declare('lower', types=(int, float, list, tuple, np.ndarray), default=None,
desc="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.")
opt.declare('upper', types=(int, float, list, tuple, np.ndarray), default=None,
desc="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.")
opt.declare('ref', types=float, default=1.,
desc="Scaling parameter. The value in the user-defined units of this output "
"variable when the scaled value is 1")
opt.declare('ref0', types=float, default=0.,
desc="Scaling parameter. The value in the user-defined units of this output "
"variable when the scaled value is 0.")
opt.declare('res_ref', types=float, default=None,
desc="Scaling parameter. The value in the user-defined res_units of this "
"output's residual when the scaled value is 1. Default is None, which "
"means residual scaling matches output scaling.")
opt.declare('tags', types=(str, list), default=None,
desc="User defined tags that can be used to filter what gets listed when "
"calling list_outputs.")
def _configure_check(self):
"""
Do any error checking on i/o configuration.
"""
errs = self._get_saved_errors()
if len(self._static_var_rel2meta) + len(self._var_rel2meta) == 0 and not errs:
raise RuntimeError(f"{self.msginfo}: No outputs (independent variables) have been "
"declared. They must either be declared during instantiation or "
"by calling add_output or add_discrete_output afterwards.")
super()._configure_check()
def add_input(self, name, val=1.0, **kwargs):
"""
Add an input variable to the component.
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 : named args
Remaining args.
"""
raise RuntimeError(f"Can't add input '{name}' to IndepVarComp '{self.name}'. IndepVarComps "
"are not allowed to have inputs. If you want IndepVarComp-like behavior"
" for some outputs of a component that has inputs, you can tag those "
"outputs with 'openmdao:indep_var' and 'openmdao:allow_desvar' and they "
"will be treated as independent variables.")
def add_output(self, name, val=1.0, **kwargs):
"""
Add an independent variable to this component.
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 : named args
Remaining args passed to the base class add_output.
Returns
-------
dict
Metadata for added variable.
"""
tags = {'openmdao:indep_var', 'openmdao:allow_desvar'}
if 'tags' in kwargs and kwargs['tags'] is not None:
tags.update(make_set(kwargs['tags'], name='tags'))
kwargs['tags'] = tags
return super().add_output(name, val=val, **kwargs)
def add_discrete_output(self, name, val, desc='', tags=None):
"""
Add an output variable to the component.
Parameters
----------
name : str
Name of the variable in this component's namespace.
val : float or list or tuple or ndarray
The initial value of the variable being added in user-defined units. Default is 1.0.
desc : str
Description of the variable.
tags : str or list of strs
User defined tags that can be used to filter what gets listed when calling list_outputs.
Returns
-------
dict
Metadata for added variable.
"""
if tags is None:
tags = {'openmdao:indep_var'}
else:
tags = make_set(tags, name='tags') | {'openmdao:indep_var'}
kwargs = {'desc': desc, 'tags': tags}
return super().add_discrete_output(name, val, **kwargs)
def _linearize(self, jac=None, sub_do_ln=False):
"""
Compute jacobian / factorization. The model is assumed to be in a scaled state.
Parameters
----------
jac : Jacobian or None
If None, use local jacobian, else use assembled jacobian jac.
sub_do_ln : bool
Flag indicating if the children should call linearize on their linear solvers.
"""
# define this for IndepVarComp to avoid overhead of ExplicitComponent._linearize.
pass
def _apply_nonlinear(self):
"""
Compute residuals. The model is assumed to be in a scaled state.
"""
# define this for IndepVarComp to avoid overhead of ExplicitComponent._apply_nonlinear.
self.iter_count_apply += 1
def _solve_nonlinear(self):
"""
Compute outputs. The model is assumed to be in a scaled state.
"""
# define this for IndepVarComp to avoid overhead of ExplicitComponent._solve_nonlinear.
with Recording(self.pathname + '._solve_nonlinear', self.iter_count, self):
pass
class _AutoIndepVarComp(IndepVarComp):
"""
IndepVarComp whose outputs are automatically connected to all unconnected inputs.
Attributes
----------
_remotes : set
Set of var names connected to remote inputs.
"""
def __init__(self, name=None, val=1.0, **kwargs):
"""
Initialize all attributes.
Parameters
----------
name : str or None
name of the variable.
If None, variables should be defined external to this class by calling add_output.
val : float or ndarray
value of the variable if a single variable is being defined.
**kwargs : dict
keyword arguments.
"""
super().__init__(name, val, **kwargs)
self._remotes = set()
def _add_remote(self, name):
self._remotes.add(name)
def _set_vector_class(self):
if self.comm.size > 1:
all_remotes = set()
for remotes in self.comm.allgather(self._remotes):
all_remotes.update(remotes)
if all_remotes:
self._has_distrib_vars = True
self._remotes = all_remotes
for name in all_remotes:
self._static_var_rel2meta[name]['distributed'] = True
super()._set_vector_class()
def add_output(self, name, val=1.0, units=None):
"""
Add an independent variable to this component.
This should never be called by a user, as it skips all checks.
Parameters
----------
name : str
Name of the variable in this component's namespace.
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.
Returns
-------
dict
Metadata for added variable.
"""
# Add the output quickly.
# We don't need to check for errors because we get the value straight from a
# source, and ivc metadata is minimal.
value, shape = ensure_compatible(name, val, None)
metadata = {
'val': value,
'shape': shape,
'size': shape_to_len(shape),
'units': units,
'res_units': None,
'desc': '',
'distributed': False,
'tags': set(['openmdao:allow_desvar', 'openmdao:indep_var']),
'ref': 1.0,
'ref0': 0.0,
'res_ref': 1.0,
'lower': None,
'upper': None,
'shape_by_conn': False,
'compute_shape': None,
'copy_shape': None,
}
self._static_var_rel2meta[name] = metadata
self._static_var_rel_names['output'].append(name)
self._var_added(name)
return metadata