-
Notifications
You must be signed in to change notification settings - Fork 240
/
ks_comp.py
258 lines (209 loc) · 8.67 KB
/
ks_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
254
255
256
257
258
"""
KS Function Component.
"""
import numpy as np
from openmdao.core.explicitcomponent import ExplicitComponent
from openmdao.utils.units import valid_units
CITATIONS = """
@conference {Martins:2005:SOU,
title = {On Structural Optimization Using Constraint Aggregation},
booktitle = {Proceedings of the 6th World Congress on Structural and Multidisciplinary
Optimization},
year = {2005},
month = {May},
address = {Rio de Janeiro, Brazil},
author = {Joaquim R. R. A. Martins and Nicholas M. K. Poon}
}
"""
def check_option(option, value):
"""
Check option for validity.
Parameters
----------
option : str
The name of the option.
value : any
The value of the option.
Raises
------
ValueError
"""
if option == 'units' and value is not None and not valid_units(value):
raise ValueError("The units '%s' are invalid." % value)
class KSfunction(object):
"""
Helper class for KSComp.
Helper class that can be used to aggregate constraint vectors with a
Kreisselmeier-Steinhauser Function.
"""
@staticmethod
def _compute_values(g, rho):
"""
Compute values needed by the KS function for the given array of constraints.
Parameters
----------
g : ndarray
Array of constraint values, where negative means satisfied and positive means violated.
rho : float
Constraint Aggregation Factor.
Returns
-------
tuple
g_max, g_diff, exponents and summation as needed by compute and derivates functions.
"""
g_max = np.max(np.atleast_2d(g), axis=-1)[:, np.newaxis]
g_diff = g - g_max
exponents = np.exp(rho * g_diff)
summation = np.sum(exponents, axis=-1)[:, np.newaxis]
return g_max, g_diff, exponents, summation
@staticmethod
def compute(g, rho=50.0):
"""
Compute the value of the KS function for the given array of constraints.
Parameters
----------
g : ndarray
Array of constraint values, where negative means satisfied and positive means violated.
rho : float
Constraint Aggregation Factor.
Returns
-------
float
Value of KS function.
"""
g_max, g_diff, exponents, summation = KSfunction._compute_values(g, rho)
KS = g_max + 1.0 / rho * np.log(summation)
return KS
@staticmethod
def derivatives(g, rho=50.0):
"""
Compute elements of [dKS_gd, dKS_drho] for the given array of constraints.
Parameters
----------
g : ndarray
Array of constraint values, where negative means satisfied and positive means violated.
rho : float
Constraint Aggregation Factor.
Returns
-------
ndarray
Derivative of KS function with respect to parameter values.
"""
g_max, g_diff, exponents, summation = KSfunction._compute_values(g, rho)
dsum_dg = rho * exponents
dKS_dsum = 1.0 / (rho * summation)
dKS_dg = dKS_dsum * dsum_dg
dsum_drho = np.sum(g_diff * exponents, axis=-1)[:, np.newaxis]
dKS_drho = dKS_dsum * dsum_drho
return dKS_dg, dKS_drho
class KSComp(ExplicitComponent):
"""
KS function component.
Component that aggregates a number of functions to a single value via the
Kreisselmeier-Steinhauser Function. This new constraint is satisfied when it
is less than or equal to zero.
Parameters
----------
**kwargs : dict of keyword arguments
Keyword arguments that will be mapped into the Component options.
Attributes
----------
cite : str
Listing of relevant citations that should be referenced when publishing
work that uses this class.
"""
def __init__(self, **kwargs):
"""
Initialize the KS component.
"""
super().__init__(**kwargs)
self.cite = CITATIONS
self._no_check_partials = True
def initialize(self):
"""
Declare options.
"""
self.options.declare('width', types=int, default=1, desc='Width of constraint vector.')
self.options.declare('vec_size', types=int, default=1,
desc='The number of rows to independently aggregate.')
self.options.declare('lower_flag', types=bool, default=False,
desc="Set to True to reverse sign of input constraints.")
self.options.declare('rho', 50.0, desc="Constraint Aggregation Factor.")
self.options.declare('upper', 0.0, desc="Upper bound for constraint, default is zero.")
self.options.declare('add_constraint', types=bool, default=False,
desc='If True, add a constraint on the resulting output of the KSComp.'
' If False, the user will be expected to add a constraint '
'explicitly.')
self.options.declare('units', types=str, allow_none=True, default=None,
desc='Units to be assigned to all variables in this component. '
'Default is None, which means variables are unitless.',
check_valid=check_option)
self.options.declare('scaler', types=(int, float), allow_none=True, default=None,
desc="Scaler for constraint, if added, default is one.")
self.options.declare('adder', types=(int, float), allow_none=True, default=None,
desc="Adder for constraint, if added, default is zero.")
self.options.declare('ref0', types=(int, float), allow_none=True, default=None,
desc="Zero-reference for constraint, if added, default is zero.")
self.options.declare('ref', types=(int, float), allow_none=True, default=None,
desc="Unit reference for constraint, if added, default is one.")
self.options.declare('parallel_deriv_color', types=str, allow_none=True, default=None,
desc='If specified, this design var will be grouped for parallel '
'derivative calculations with other variables sharing the same '
'parallel_deriv_color.')
def setup(self):
"""
Declare inputs, outputs, and derivatives for the KS component.
"""
opts = self.options
width = opts['width']
vec_size = opts['vec_size']
units = opts['units']
# Inputs
self.add_input('g', shape=(vec_size, width), units=units,
desc="Array of function values to be aggregated")
# Outputs
self.add_output('KS', shape=(vec_size, 1), units=units,
desc="Value of the aggregate KS function")
if opts['add_constraint']:
self.add_constraint(name='KS', upper=0.0, scaler=opts['scaler'], adder=opts['adder'],
ref0=opts['ref0'], ref=opts['ref'],
parallel_deriv_color=opts['parallel_deriv_color'])
rows = np.zeros(width, dtype=int)
cols = range(width)
rows = np.tile(rows, vec_size) + np.repeat(np.arange(vec_size), width)
cols = np.tile(cols, vec_size) + np.repeat(np.arange(vec_size), width) * width
self.declare_partials(of='KS', wrt='g', rows=rows, cols=cols)
def compute(self, inputs, outputs):
"""
Compute the output of the KS function.
Parameters
----------
inputs : `Vector`
`Vector` containing inputs.
outputs : `Vector`
`Vector` containing outputs.
"""
opt = self.options
con_val = inputs['g'] - opt['upper']
if opt['lower_flag']:
con_val = -con_val
outputs['KS'] = KSfunction.compute(con_val, opt['rho'])
def compute_partials(self, inputs, partials):
"""
Compute sub-jacobian parts. The model is assumed to be in an unscaled state.
Parameters
----------
inputs : Vector
Unscaled, dimensional input variables read via inputs[key].
partials : Jacobian
Sub-jac components written to partials[output_name, input_name].
"""
opt = self.options
width = opt['width']
con_val = inputs['g'] - opt['upper']
if opt['lower_flag']:
con_val = -con_val
derivs = KSfunction.derivatives(con_val, opt['rho'])[0]
if self.options['lower_flag']:
derivs = -derivs
partials['KS', 'g'] = derivs.flatten()