surrogates_interface 4.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- surrogates_interface/__init__.py +3 -0
- surrogates_interface/domains.py +107 -0
- surrogates_interface/openmdao.py +389 -0
- surrogates_interface/surrogates.py +1027 -0
- surrogates_interface/test/__init__.py +0 -0
- surrogates_interface/test/test_domains.py +44 -0
- surrogates_interface/test/test_init.py +12 -0
- surrogates_interface/test/test_pca_jacobian.py +137 -0
- surrogates_interface/test/test_smt.py +258 -0
- surrogates_interface/test/test_surrogates.py +22 -0
- surrogates_interface/test/test_tensorflow_and_openmdao.py +1220 -0
- surrogates_interface/test/test_transformers.py +617 -0
- surrogates_interface/transformers.py +816 -0
- surrogates_interface-4.0.1.dist-info/LICENSE +25 -0
- surrogates_interface-4.0.1.dist-info/METADATA +66 -0
- surrogates_interface-4.0.1.dist-info/RECORD +17 -0
- surrogates_interface-4.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
The classes contained in this module allow to find when a set of points is contained in the domain.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
|
|
8
|
+
import h5py
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
# String type for h5py.
|
|
12
|
+
H5_STR = h5py.special_dtype(vlen=str)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GenericDomain(ABC):
|
|
16
|
+
"""Base class for all domains."""
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def fields(self):
|
|
21
|
+
"""List of fields in hdf5 group and GenericDomain object."""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def in_domain(self, x):
|
|
25
|
+
"""Check that the input points are inside the domain."""
|
|
26
|
+
|
|
27
|
+
def _save_h5(self, group):
|
|
28
|
+
"""Save this domain to a h5py `Group`.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
group : h5py.Group
|
|
33
|
+
Group where this object will be saved.
|
|
34
|
+
"""
|
|
35
|
+
group.create_dataset("type", data=self.__class__.__name__, dtype=H5_STR)
|
|
36
|
+
for f in self.fields:
|
|
37
|
+
group.create_dataset(f, data=getattr(self, f))
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def _load_h5(cls, group):
|
|
41
|
+
"""
|
|
42
|
+
Load this domain from a h5py `Group`.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
group : h5py.Group
|
|
47
|
+
Group where this object has been saved.
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
obj : GenericDomain
|
|
52
|
+
The saved domain.
|
|
53
|
+
"""
|
|
54
|
+
# Protected because this function should be called by SurrogateModel.load_h5.
|
|
55
|
+
return cls(*[group[f][:] for f in cls.fields])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class BoxDomain(GenericDomain):
|
|
59
|
+
"""Implements a n-dimensional box domain."""
|
|
60
|
+
|
|
61
|
+
fields = ["min", "max"]
|
|
62
|
+
|
|
63
|
+
def __init__(self, min, max):
|
|
64
|
+
"""Create a new `BoxDomain`, based on the given min and max for each feature.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
min : array-like of shape (n_features)
|
|
69
|
+
Minimum for each feature.
|
|
70
|
+
max : array-like of shape (n_features)
|
|
71
|
+
Maximum for each feature.
|
|
72
|
+
"""
|
|
73
|
+
self.min = np.asarray(min)
|
|
74
|
+
self.max = np.asarray(max)
|
|
75
|
+
assert np.all(np.greater_equal(self.max, self.min))
|
|
76
|
+
|
|
77
|
+
def in_domain(self, x):
|
|
78
|
+
"""Check that the input points are inside the domain.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
x : array-like of shape (n_points, n_features)
|
|
83
|
+
Input points.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
y : array-like of shape (n_points, )
|
|
88
|
+
Array with `True` if a point is inside the domain and `False` otherwise.
|
|
89
|
+
"""
|
|
90
|
+
# Test x >= min for each dimension.
|
|
91
|
+
test_min = np.greater_equal(x, self.min)
|
|
92
|
+
|
|
93
|
+
# Test x <= max for each dimension.
|
|
94
|
+
test_max = np.less_equal(x, self.max)
|
|
95
|
+
|
|
96
|
+
# Test min <= x <= max for each dimension.
|
|
97
|
+
test_minmax = np.logical_and(test_min, test_max)
|
|
98
|
+
|
|
99
|
+
# Test min <= x <= max for all dimensions.
|
|
100
|
+
return np.all(test_minmax, axis=1)
|
|
101
|
+
|
|
102
|
+
def __eq__(self, other):
|
|
103
|
+
if isinstance(other, self.__class__):
|
|
104
|
+
test_min = np.array_equal(self.min, other.min)
|
|
105
|
+
test_max = np.array_equal(self.max, other.max)
|
|
106
|
+
return test_min and test_max
|
|
107
|
+
return False
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
In this module, we wrap the surrogate models into OpenMDAO components.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from enum import IntEnum, auto
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import openmdao.api as om
|
|
10
|
+
|
|
11
|
+
# from surrogates_interface.surrogates import SurrogateModel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InputOutputType(IntEnum):
|
|
15
|
+
"""
|
|
16
|
+
Enumeration used by `SurrogateModelComp` to determine the type of the input and output.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
#: The input or output dimensions of a surrogate model are combined into one.
|
|
20
|
+
JOINED = auto()
|
|
21
|
+
#: The input or output dimensions of a surrogate model are kept separate.
|
|
22
|
+
SPLIT = auto()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SurrogateModelCompFD(om.ExplicitComponent):
|
|
26
|
+
"""
|
|
27
|
+
Wraps `SurrogateModel` into an OpenMDAO explicit component.
|
|
28
|
+
The gradient is approximated via finite differences.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
model : subclass of SurrogateModel
|
|
33
|
+
Surrogate model to be wrapped.
|
|
34
|
+
n_points : int
|
|
35
|
+
Number of points over which to evaluate the surrogate model.
|
|
36
|
+
input_type : InputOutputType
|
|
37
|
+
Type of the input.
|
|
38
|
+
- If `InputOutputType.JOINED` there will be only 1 input named `input_name`.
|
|
39
|
+
- If `InputOutputType.SPLIT` there will be 1 input per input channel. Their names will be taken from `model.input_names`.
|
|
40
|
+
output_type : InputOutputType
|
|
41
|
+
Type of the output.
|
|
42
|
+
- If `InputOutputType.JOINED` there will be only 1 output named `output_name`.
|
|
43
|
+
- If `InputOutputType.SPLIT` there will be 1 input per output channel. Their names will be taken from `model.output_names`.
|
|
44
|
+
input_name : str, default 'x'
|
|
45
|
+
Name of the input, that is used when `input_type = InputOutputType.JOINED`.
|
|
46
|
+
output_name : str, default 'y'
|
|
47
|
+
Name of the output, that is used when `output_type = InputOutputType.JOINED`.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _check_in_out_type(name, value):
|
|
52
|
+
"""
|
|
53
|
+
Check that `value` is defined in `InputOutputType`.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
name : str
|
|
58
|
+
Name of the option.
|
|
59
|
+
value : InputOutputType
|
|
60
|
+
Value of the option.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
None.
|
|
65
|
+
|
|
66
|
+
Raises
|
|
67
|
+
------
|
|
68
|
+
ValueError
|
|
69
|
+
If value is not valid for option.
|
|
70
|
+
"""
|
|
71
|
+
if value not in iter(InputOutputType):
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"Option {name}, here {value}, must be defined in `InputOutputType`."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def initialize(self):
|
|
77
|
+
self.options.declare(
|
|
78
|
+
"model",
|
|
79
|
+
# types=SurrogateModel,
|
|
80
|
+
desc="Surrogate model to be wrapped.",
|
|
81
|
+
recordable=False,
|
|
82
|
+
) # There is nothing to record here.
|
|
83
|
+
|
|
84
|
+
self.options.declare(
|
|
85
|
+
"n_points",
|
|
86
|
+
types=int,
|
|
87
|
+
desc="Number of points over which to evaluate the surrogate model.",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self.options.declare(
|
|
91
|
+
"input_type",
|
|
92
|
+
desc="""Type of the input.
|
|
93
|
+
- If `InputOutputType.JOINED` there will be only 1 input named `input_name`.
|
|
94
|
+
- If `InputOutputType.SPLIT` there will be 1 input per input channel. Their names will be taken from `model.input_names`.
|
|
95
|
+
""",
|
|
96
|
+
check_valid=self._check_in_out_type,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
self.options.declare(
|
|
100
|
+
"output_type",
|
|
101
|
+
desc="""Type of the output.
|
|
102
|
+
- If `InputOutputType.JOINED` there will be only 1 output named `output_name`.
|
|
103
|
+
- If `InputOutputType.SPLIT` there will be 1 input per output channel. Their names will be taken from `model.output_names`.
|
|
104
|
+
""",
|
|
105
|
+
check_valid=self._check_in_out_type,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
self.options.declare(
|
|
109
|
+
"input_name",
|
|
110
|
+
default="x",
|
|
111
|
+
desc="Name of the input, that is used when `input_type = InputOutputType.JOINED`.",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
self.options.declare(
|
|
115
|
+
"output_name",
|
|
116
|
+
default="y",
|
|
117
|
+
desc="Name of the output, that is used when `output_type = InputOutputType.JOINED`.",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def setup(self):
|
|
121
|
+
if self.options["input_type"] is InputOutputType.JOINED:
|
|
122
|
+
self.add_input(
|
|
123
|
+
self.options["input_name"],
|
|
124
|
+
shape=(self.options["n_points"], self.options["model"].n_inputs),
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
for name in self.options["model"].input_names:
|
|
128
|
+
self.add_input(name, shape=(self.options["n_points"]))
|
|
129
|
+
|
|
130
|
+
if self.options["output_type"] is InputOutputType.JOINED:
|
|
131
|
+
self.add_output(
|
|
132
|
+
self.options["output_name"],
|
|
133
|
+
shape=(self.options["n_points"], self.options["model"].n_outputs),
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
for name in self.options["model"].output_names:
|
|
137
|
+
self.add_output(name, shape=(self.options["n_points"]))
|
|
138
|
+
|
|
139
|
+
def setup_partials(self):
|
|
140
|
+
"""
|
|
141
|
+
Setup the partial derivatives.
|
|
142
|
+
"""
|
|
143
|
+
self.declare_partials("*", "*", method="fd") # pragma: no cover
|
|
144
|
+
|
|
145
|
+
def compute(self, inputs, outputs):
|
|
146
|
+
"""
|
|
147
|
+
Wrapper of `SurrogateModel.predict_output()`.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
inputs : dict
|
|
152
|
+
Inputs.
|
|
153
|
+
- If `input_type is InputOutputType.JOINED` we expect one input named `input_name` of shape `(n_points, n_inputs)`.
|
|
154
|
+
- If `input_type is InputOutputType.SPLIT` we expect one input per feature of shape `(n_points,)`.
|
|
155
|
+
The inputs will be stacked according to `input_names`.
|
|
156
|
+
outputs : dict
|
|
157
|
+
Outputs.
|
|
158
|
+
- If `output_type is InputOutputType.JOINED` we expect one output named `output_name` of shape `(n_points, n_outputs)`.
|
|
159
|
+
The outputs will be stacked according to `output_names`.
|
|
160
|
+
- If `output_type is InputOutputType.SPLIT` we expect one output per feature of shape `(n_points,)`.
|
|
161
|
+
|
|
162
|
+
Returns
|
|
163
|
+
-------
|
|
164
|
+
None.
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
# Compose the input.
|
|
168
|
+
if self.options["input_type"] is InputOutputType.JOINED:
|
|
169
|
+
x = inputs[self.options["input_name"]]
|
|
170
|
+
|
|
171
|
+
elif self.options["input_type"] is InputOutputType.SPLIT:
|
|
172
|
+
x = np.column_stack(
|
|
173
|
+
[inputs[key] for key in self.options["model"].input_names]
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Evaluate the model.
|
|
177
|
+
y = self.options["model"].predict_output(x)
|
|
178
|
+
|
|
179
|
+
# Compose the output.
|
|
180
|
+
if self.options["output_type"] is InputOutputType.JOINED:
|
|
181
|
+
outputs[self.options["output_name"]] = y
|
|
182
|
+
|
|
183
|
+
elif self.options["output_type"] is InputOutputType.SPLIT:
|
|
184
|
+
for i in range(self.options["model"].n_outputs):
|
|
185
|
+
outputs[self.options["model"].output_names[i]] = y[:, i]
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class SurrogateModelComp(SurrogateModelCompFD):
|
|
189
|
+
"""
|
|
190
|
+
Wraps `SurrogateModel` into an OpenMDAO explicit component.
|
|
191
|
+
The exact gradient is provided via `compute_partials`.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
def setup_partials(self):
|
|
195
|
+
"""
|
|
196
|
+
Setup the partial derivatives.
|
|
197
|
+
|
|
198
|
+
The format of the Jacobian depends if the user selected `JOINED` or `SPLIT` for the input and output.
|
|
199
|
+
Since the points are independent, the Jacobian is a block diagonal matrix
|
|
200
|
+
with shape `(n_points * n_outputs, n_points * n_inputs)`,
|
|
201
|
+
where each block has shape `(n_outputs, n_inputs)`.
|
|
202
|
+
If the user selected `SPLIT` than there is only 1 feature, while with `JOINED` all features are kept.
|
|
203
|
+
|
|
204
|
+
Note that in a time series model the points are dependent, and this code will be incorrect.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
# The following code defines the sparsity pattern in COO format.
|
|
208
|
+
|
|
209
|
+
# Shorthand for the number of points.
|
|
210
|
+
ns = self.options["n_points"]
|
|
211
|
+
|
|
212
|
+
# Set the number of inputs.
|
|
213
|
+
if self.options["input_type"] is InputOutputType.JOINED:
|
|
214
|
+
ni = self.options["model"].n_inputs
|
|
215
|
+
|
|
216
|
+
elif self.options["input_type"] is InputOutputType.SPLIT:
|
|
217
|
+
ni = 1
|
|
218
|
+
|
|
219
|
+
# Set the number of outputs.
|
|
220
|
+
if self.options["output_type"] is InputOutputType.JOINED:
|
|
221
|
+
no = self.options["model"].n_outputs
|
|
222
|
+
|
|
223
|
+
elif self.options["output_type"] is InputOutputType.SPLIT:
|
|
224
|
+
no = 1
|
|
225
|
+
|
|
226
|
+
# Define the block-diagonal matrix.
|
|
227
|
+
self.declare_partials(
|
|
228
|
+
"*",
|
|
229
|
+
"*",
|
|
230
|
+
method="exact",
|
|
231
|
+
rows=np.arange(ns * no).repeat(ni),
|
|
232
|
+
cols=np.arange(ns * ni).reshape((ns, ni)).repeat(no, 0).flatten(),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def compute_partials(self, inputs, partials, discrete_inputs=None):
|
|
236
|
+
"""
|
|
237
|
+
Wrapper of `SurrogateModel.predict_jacobian()`.
|
|
238
|
+
|
|
239
|
+
Parameters
|
|
240
|
+
----------
|
|
241
|
+
inputs : dict
|
|
242
|
+
Inputs.
|
|
243
|
+
- If `input_type is InputOutputType.JOINED` we expect one input named `input_name` of shape `(n_points, n_inputs)`.
|
|
244
|
+
- If `input_type is InputOutputType.SPLIT` we expect one input per feature of shape `(n_points,)`.
|
|
245
|
+
The inputs will be stacked according to `input_names`.
|
|
246
|
+
partials : dict
|
|
247
|
+
Jacobian.
|
|
248
|
+
- If `output_type is InputOutputType.JOINED` we expect one Jacobian of shape `(n_points, n_outputs)`.
|
|
249
|
+
- If `output_type is InputOutputType.SPLIT` we expect `n_outputs` Jacobians of shape `(n_points,)`.
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
-------
|
|
253
|
+
None.
|
|
254
|
+
|
|
255
|
+
"""
|
|
256
|
+
# Compose the input.
|
|
257
|
+
if self.options["input_type"] is InputOutputType.JOINED:
|
|
258
|
+
x = inputs[self.options["input_name"]]
|
|
259
|
+
elif self.options["input_type"] is InputOutputType.SPLIT:
|
|
260
|
+
x = np.column_stack(
|
|
261
|
+
[inputs[key] for key in self.options["model"].input_names]
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Evaluate the model.
|
|
265
|
+
jacobian = self.options["model"].predict_jacobian(x)
|
|
266
|
+
|
|
267
|
+
# Compose the output.
|
|
268
|
+
if self.options["output_type"] is InputOutputType.JOINED:
|
|
269
|
+
if self.options["input_type"] is InputOutputType.JOINED:
|
|
270
|
+
partials[self.options["output_name"], self.options["input_name"]] = (
|
|
271
|
+
jacobian.ravel()
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
elif self.options["input_type"] is InputOutputType.SPLIT:
|
|
275
|
+
for j in range(self.options["model"].n_inputs):
|
|
276
|
+
partials[
|
|
277
|
+
self.options["output_name"],
|
|
278
|
+
self.options["model"].input_names[j],
|
|
279
|
+
] = jacobian[:, :, j].ravel()
|
|
280
|
+
|
|
281
|
+
elif self.options["output_type"] is InputOutputType.SPLIT:
|
|
282
|
+
if self.options["input_type"] is InputOutputType.JOINED:
|
|
283
|
+
for i in range(self.options["model"].n_outputs):
|
|
284
|
+
partials[
|
|
285
|
+
self.options["model"].output_names[i],
|
|
286
|
+
self.options["input_name"],
|
|
287
|
+
] = jacobian[:, i, :].ravel()
|
|
288
|
+
|
|
289
|
+
elif self.options["input_type"] is InputOutputType.SPLIT:
|
|
290
|
+
for i in range(self.options["model"].n_outputs):
|
|
291
|
+
for j in range(self.options["model"].n_inputs):
|
|
292
|
+
partials[
|
|
293
|
+
self.options["model"].output_names[i],
|
|
294
|
+
self.options["model"].input_names[j],
|
|
295
|
+
] = jacobian[:, i, j]
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class SurrogateModelCompMatrixFree(SurrogateModelCompFD):
|
|
299
|
+
"""
|
|
300
|
+
Wraps `SurrogateModel` into an OpenMDAO explicit component that uses the matrix-free API.
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
def setup_partials(self):
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
def compute_jacvec_product(
|
|
307
|
+
self, inputs, d_inputs, d_outputs, mode, discrete_inputs=None
|
|
308
|
+
):
|
|
309
|
+
# Compose the input.
|
|
310
|
+
if self.options["input_type"] is InputOutputType.JOINED:
|
|
311
|
+
x = inputs[self.options["input_name"]]
|
|
312
|
+
elif self.options["input_type"] is InputOutputType.SPLIT:
|
|
313
|
+
x = np.column_stack(
|
|
314
|
+
[inputs[key] for key in self.options["model"].input_names]
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Check for forward or reverse mode.
|
|
318
|
+
if mode == "fwd":
|
|
319
|
+
if self.options["output_type"] is InputOutputType.JOINED:
|
|
320
|
+
if self.options["input_type"] is InputOutputType.JOINED:
|
|
321
|
+
dx = d_inputs[self.options["input_name"]]
|
|
322
|
+
dy = self.options["model"].predict_jvp(x, dx)
|
|
323
|
+
d_outputs[self.options["output_name"]] = dy
|
|
324
|
+
|
|
325
|
+
elif self.options["input_type"] is InputOutputType.SPLIT:
|
|
326
|
+
for in_name in d_inputs:
|
|
327
|
+
i_in = self.options["model"].input_names.index(in_name)
|
|
328
|
+
dx = np.zeros(
|
|
329
|
+
(
|
|
330
|
+
self.options["n_points"],
|
|
331
|
+
self.options["model"].n_inputs,
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
dx[:, i_in] = d_inputs[in_name]
|
|
335
|
+
dy = self.options["model"].predict_jvp(x, dx)
|
|
336
|
+
d_outputs[self.options["output_name"]] += dy
|
|
337
|
+
|
|
338
|
+
elif self.options["output_type"] is InputOutputType.SPLIT:
|
|
339
|
+
if self.options["input_type"] is InputOutputType.JOINED:
|
|
340
|
+
dx = d_inputs[self.options["input_name"]]
|
|
341
|
+
dy = self.options["model"].predict_jvp(x, dx)
|
|
342
|
+
for out_name in d_outputs:
|
|
343
|
+
i_out = self.options["model"].output_names.index(out_name)
|
|
344
|
+
d_outputs[out_name] += dy[:, i_out]
|
|
345
|
+
|
|
346
|
+
elif self.options["input_type"] is InputOutputType.SPLIT:
|
|
347
|
+
for in_name in d_inputs:
|
|
348
|
+
i_in = self.options["model"].input_names.index(in_name)
|
|
349
|
+
dx = np.zeros(
|
|
350
|
+
(
|
|
351
|
+
self.options["n_points"],
|
|
352
|
+
self.options["model"].n_inputs,
|
|
353
|
+
)
|
|
354
|
+
)
|
|
355
|
+
dx[:, i_in] = d_inputs[in_name]
|
|
356
|
+
dy = self.options["model"].predict_jvp(x, dx)
|
|
357
|
+
for out_name in d_outputs:
|
|
358
|
+
i_out = self.options["model"].output_names.index(out_name)
|
|
359
|
+
d_outputs[out_name] += dy[:, i_out]
|
|
360
|
+
|
|
361
|
+
else: # mode == 'rev'
|
|
362
|
+
if self.options["output_type"] is InputOutputType.JOINED:
|
|
363
|
+
dy = d_outputs[self.options["output_name"]]
|
|
364
|
+
dx = self.options["model"].predict_vjp(x, dy)
|
|
365
|
+
|
|
366
|
+
if self.options["input_type"] is InputOutputType.JOINED:
|
|
367
|
+
d_inputs[self.options["input_name"]] += dx
|
|
368
|
+
|
|
369
|
+
elif self.options["input_type"] is InputOutputType.SPLIT:
|
|
370
|
+
for in_name in d_inputs:
|
|
371
|
+
i_in = self.options["model"].input_names.index(in_name)
|
|
372
|
+
d_inputs[in_name] += dx[:, i_in]
|
|
373
|
+
|
|
374
|
+
elif self.options["output_type"] is InputOutputType.SPLIT:
|
|
375
|
+
for out_name in d_outputs:
|
|
376
|
+
i_out = self.options["model"].output_names.index(out_name)
|
|
377
|
+
dy = np.zeros(
|
|
378
|
+
(self.options["n_points"], self.options["model"].n_outputs)
|
|
379
|
+
)
|
|
380
|
+
dy[:, i_out] = d_outputs[out_name]
|
|
381
|
+
dx = self.options["model"].predict_vjp(x, dy)
|
|
382
|
+
|
|
383
|
+
if self.options["input_type"] is InputOutputType.JOINED:
|
|
384
|
+
d_inputs[self.options["input_name"]] += dx
|
|
385
|
+
|
|
386
|
+
elif self.options["input_type"] is InputOutputType.SPLIT:
|
|
387
|
+
for in_name in d_inputs:
|
|
388
|
+
i_in = self.options["model"].input_names.index(in_name)
|
|
389
|
+
d_inputs[in_name] += dx[:, i_in]
|