surrogates_interface 4.0.2__tar.gz → 4.1.0__tar.gz
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-4.0.2 → surrogates_interface-4.1.0}/PKG-INFO +3 -1
- {surrogates_interface-4.0.2 → surrogates_interface-4.1.0}/pyproject.toml +6 -1
- {surrogates_interface-4.0.2 → surrogates_interface-4.1.0}/surrogates_interface/surrogates.py +173 -2
- {surrogates_interface-4.0.2 → surrogates_interface-4.1.0}/surrogates_interface/transformers.py +39 -0
- surrogates_interface-4.0.2/surrogates_interface/test/__init__.py +0 -0
- surrogates_interface-4.0.2/surrogates_interface/test/test_domains.py +0 -44
- surrogates_interface-4.0.2/surrogates_interface/test/test_init.py +0 -12
- surrogates_interface-4.0.2/surrogates_interface/test/test_pca_jacobian.py +0 -137
- surrogates_interface-4.0.2/surrogates_interface/test/test_smt.py +0 -258
- surrogates_interface-4.0.2/surrogates_interface/test/test_surrogates.py +0 -22
- surrogates_interface-4.0.2/surrogates_interface/test/test_tensorflow_and_openmdao.py +0 -1220
- surrogates_interface-4.0.2/surrogates_interface/test/test_transformers.py +0 -617
- {surrogates_interface-4.0.2 → surrogates_interface-4.1.0}/LICENSE +0 -0
- {surrogates_interface-4.0.2 → surrogates_interface-4.1.0}/README.md +0 -0
- {surrogates_interface-4.0.2 → surrogates_interface-4.1.0}/surrogates_interface/__init__.py +0 -0
- {surrogates_interface-4.0.2 → surrogates_interface-4.1.0}/surrogates_interface/domains.py +0 -0
- {surrogates_interface-4.0.2 → surrogates_interface-4.1.0}/surrogates_interface/openmdao.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: surrogates_interface
|
|
3
|
-
Version: 4.0
|
|
3
|
+
Version: 4.1.0
|
|
4
4
|
Summary: Interface for surrogate models.
|
|
5
5
|
Home-page: https://gitlab.windenergy.dtu.dk/surrogate-models/surrogate-models-interface
|
|
6
6
|
License: MIT
|
|
@@ -19,6 +19,7 @@ Provides-Extra: om
|
|
|
19
19
|
Provides-Extra: smt
|
|
20
20
|
Provides-Extra: test
|
|
21
21
|
Provides-Extra: tf
|
|
22
|
+
Provides-Extra: torch
|
|
22
23
|
Requires-Dist: black ; extra == "code-style"
|
|
23
24
|
Requires-Dist: codespell ; extra == "code-style"
|
|
24
25
|
Requires-Dist: flake8 ; extra == "code-style"
|
|
@@ -43,6 +44,7 @@ Requires-Dist: tensorflow-intel (>=2.10.0,<3.0.0) ; (sys_platform == "win32") an
|
|
|
43
44
|
Requires-Dist: tensorflow-io-gcs-filesystem (<0.32.0) ; (python_version >= "3.9" and python_version < "3.12" and platform_system == "Windows") and (extra == "test" or extra == "tf")
|
|
44
45
|
Requires-Dist: tensorflow-io-gcs-filesystem (>=0.23.1) ; (platform_machine != "arm64" and python_version >= "3.9" and python_version < "3.12" or platform_system != "Darwin" and python_version >= "3.9" and python_version < "3.12") and (extra == "test" or extra == "tf")
|
|
45
46
|
Requires-Dist: tomli (>=2.0.1,<3.0.0) ; (python_version < "3.11") and (extra == "test")
|
|
47
|
+
Requires-Dist: torch (>=2.0.0,<3.0.0) ; extra == "test" or extra == "torch"
|
|
46
48
|
Project-URL: Repository, https://gitlab.windenergy.dtu.dk/surrogate-models/surrogate-models-interface
|
|
47
49
|
Description-Content-Type: text/markdown
|
|
48
50
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "surrogates_interface"
|
|
3
|
-
version = "4.0
|
|
3
|
+
version = "4.1.0"
|
|
4
4
|
description = "Interface for surrogate models."
|
|
5
5
|
authors = ["DTU Wind and Energy Systems"]
|
|
6
6
|
maintainers = [
|
|
@@ -45,6 +45,7 @@ tensorflow-io-gcs-filesystem = [
|
|
|
45
45
|
tensorflow-intel = {version="^2.10.0", platform = "win32", optional=true}
|
|
46
46
|
smt = {version="^2.0.1", optional=true}
|
|
47
47
|
openmdao = {version="^3.25", optional=true}
|
|
48
|
+
torch = { version = "^2.0.0", optional=true }
|
|
48
49
|
|
|
49
50
|
[tool.poetry.extras]
|
|
50
51
|
|
|
@@ -74,6 +75,7 @@ test = ["matplotlib", # Needed by SMT.
|
|
|
74
75
|
"tensorflow-io-gcs-filesystem",
|
|
75
76
|
"tensorflow-intel",
|
|
76
77
|
"openmdao",
|
|
78
|
+
"torch",
|
|
77
79
|
]
|
|
78
80
|
|
|
79
81
|
smt = ["matplotlib", # Needed by SMT.
|
|
@@ -89,6 +91,9 @@ tf = ["scikit-learn",
|
|
|
89
91
|
|
|
90
92
|
om = ["openmdao"]
|
|
91
93
|
|
|
94
|
+
torch = ["torch"]
|
|
95
|
+
|
|
96
|
+
|
|
92
97
|
|
|
93
98
|
[build-system]
|
|
94
99
|
build-backend = "poetry.core.masonry.api"
|
{surrogates_interface-4.0.2 → surrogates_interface-4.1.0}/surrogates_interface/surrogates.py
RENAMED
|
@@ -452,6 +452,8 @@ class SurrogateModel:
|
|
|
452
452
|
x_transformed = x.copy()
|
|
453
453
|
# The input transformation Jacobian starts as an identity matrix,
|
|
454
454
|
# repeated over all points.
|
|
455
|
+
if x.ndim == 1:
|
|
456
|
+
x = x.reshape(1, -1)
|
|
455
457
|
input_transformation_jacobian = np.tile(np.eye(x.shape[1]), (x.shape[0], 1, 1))
|
|
456
458
|
for transformer in self.input_transformers:
|
|
457
459
|
# Get Jacobian of current input transformation.
|
|
@@ -736,9 +738,9 @@ class TensorFlowModel(SurrogateModel):
|
|
|
736
738
|
IOError
|
|
737
739
|
In case of an invalid savefile.
|
|
738
740
|
"""
|
|
739
|
-
|
|
741
|
+
import tensorflow as tf
|
|
740
742
|
|
|
741
|
-
return load_model(filepath, custom_objects, compile, options)
|
|
743
|
+
return tf.keras.models.load_model(filepath, custom_objects, compile, options)
|
|
742
744
|
|
|
743
745
|
def _save_model(self, filepath, overwrite=True, save_format="h5"):
|
|
744
746
|
"""
|
|
@@ -1025,3 +1027,172 @@ class SMTModel(SurrogateModel):
|
|
|
1025
1027
|
|
|
1026
1028
|
class SurrogateModelFamily:
|
|
1027
1029
|
pass
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
class PyTorchModel(SurrogateModel):
|
|
1033
|
+
import torch
|
|
1034
|
+
import torch._dynamo # fmt: skip
|
|
1035
|
+
torch._dynamo.config.suppress_errors = True
|
|
1036
|
+
import torch.func as torch_func # fmt: skip
|
|
1037
|
+
|
|
1038
|
+
# from torch.func import jacfwd, jacrev, jvp, vjp, vmap # fmt: skip
|
|
1039
|
+
|
|
1040
|
+
def __init__(self, *args, dtype=torch.float16, max_batch=50_000, **kwargs):
|
|
1041
|
+
super().__init__(*args, **kwargs)
|
|
1042
|
+
self.model.eval()
|
|
1043
|
+
self.device = self.torch.device(
|
|
1044
|
+
"cuda" if self.torch.cuda.is_available() else "cpu"
|
|
1045
|
+
)
|
|
1046
|
+
self.model.to(self.device)
|
|
1047
|
+
self.dtype = dtype
|
|
1048
|
+
self.model.to(dtype)
|
|
1049
|
+
self.max_batch = max_batch
|
|
1050
|
+
|
|
1051
|
+
def __to_numpy(self, _x):
|
|
1052
|
+
try:
|
|
1053
|
+
import cupy as cp
|
|
1054
|
+
except ImportError: # pragma: no cover
|
|
1055
|
+
cp = np
|
|
1056
|
+
lalib = (
|
|
1057
|
+
cp if self.device == self.torch.device("cuda") else np
|
|
1058
|
+
) # pragma: no cover
|
|
1059
|
+
return lalib.asarray(_x).astype(lalib.float32)
|
|
1060
|
+
|
|
1061
|
+
def __to_tensor(self, _x):
|
|
1062
|
+
if len(_x.shape) < 2:
|
|
1063
|
+
_x = _x.reshape(1, -1)
|
|
1064
|
+
return self.torch.as_tensor(_x, device=self.device, dtype=self.dtype)
|
|
1065
|
+
|
|
1066
|
+
def __record_in_dtype(self, _x): # pragma: no cover
|
|
1067
|
+
# TODO: could be used to retain the input/output dtype the same
|
|
1068
|
+
self.in_dtype = _x.dtype
|
|
1069
|
+
|
|
1070
|
+
def _predict_model_output(self, x):
|
|
1071
|
+
x = self.__to_tensor(x)
|
|
1072
|
+
|
|
1073
|
+
with self.torch.no_grad():
|
|
1074
|
+
output = self.torch.zeros(
|
|
1075
|
+
(x.shape[0], self.n_outputs),
|
|
1076
|
+
device=self.device,
|
|
1077
|
+
dtype=self.dtype,
|
|
1078
|
+
)
|
|
1079
|
+
for i in range(0, x.shape[0], self.max_batch):
|
|
1080
|
+
inter_out = self.model(x[i : i + self.max_batch])
|
|
1081
|
+
output[i : i + self.max_batch].copy_(
|
|
1082
|
+
inter_out[: x[i : i + self.max_batch].shape[0]], non_blocking=True
|
|
1083
|
+
)
|
|
1084
|
+
|
|
1085
|
+
return self.__to_numpy(output)
|
|
1086
|
+
|
|
1087
|
+
def _predict_model_jacobian(self, x, ad_mode=None):
|
|
1088
|
+
if ad_mode is None:
|
|
1089
|
+
ad_mode = ADMode.REV if (self.n_outputs <= self.n_inputs) else ADMode.FWD
|
|
1090
|
+
|
|
1091
|
+
if ad_mode is ADMode.REV:
|
|
1092
|
+
y, jacobian = self._predict_model_output_and_jacobian_rev(x)
|
|
1093
|
+
elif ad_mode is ADMode.FWD:
|
|
1094
|
+
y, jacobian = self._predict_model_output_and_jacobian_fwd(x)
|
|
1095
|
+
|
|
1096
|
+
return y, jacobian
|
|
1097
|
+
|
|
1098
|
+
def predict_output(self, x):
|
|
1099
|
+
y = super().predict_output(x)
|
|
1100
|
+
return self.__to_numpy(y)
|
|
1101
|
+
|
|
1102
|
+
def _save_model(self, filepath):
|
|
1103
|
+
self.torch.save(self.model.state_dict(), filepath)
|
|
1104
|
+
|
|
1105
|
+
@staticmethod
|
|
1106
|
+
def _load_model(filepath, model_cls):
|
|
1107
|
+
import torch # fmt: skip
|
|
1108
|
+
model_cls.load_state_dict(torch.load(filepath, weights_only=True))
|
|
1109
|
+
return model_cls
|
|
1110
|
+
|
|
1111
|
+
def _predict_model_jvp(self, x, dx):
|
|
1112
|
+
if not hasattr(self, "batched_jvp_func"):
|
|
1113
|
+
def g(_x): # fmt: skip
|
|
1114
|
+
return self.model(_x)
|
|
1115
|
+
def single_jvp(_x, _v): # fmt: skip
|
|
1116
|
+
return self.torch_func.jvp(g, (_x,), (_v,))
|
|
1117
|
+
self.batched_jvp_func = self.torch_func.vmap(single_jvp)
|
|
1118
|
+
|
|
1119
|
+
x = self.__to_tensor(x)
|
|
1120
|
+
dx = self.__to_tensor(dx)
|
|
1121
|
+
output, jvp_res = self.batched_jvp_func(x, dx)
|
|
1122
|
+
return self.__to_numpy(output.detach()), self.__to_numpy(jvp_res.detach())
|
|
1123
|
+
|
|
1124
|
+
def _predict_model_vjp(self, x, dy):
|
|
1125
|
+
if not hasattr(self, "batched_vjp_func"):
|
|
1126
|
+
def g(_x): # fmt: skip
|
|
1127
|
+
return self.model(_x)
|
|
1128
|
+
def single_vjp(_x, _v): # fmt: skip
|
|
1129
|
+
out, f = self.torch_func.vjp(g, _x)
|
|
1130
|
+
return out, f(_v)
|
|
1131
|
+
self.batched_vjp_func = self.torch_func.vmap(single_vjp)
|
|
1132
|
+
|
|
1133
|
+
x = self.__to_tensor(x)
|
|
1134
|
+
dy = self.__to_tensor(dy)
|
|
1135
|
+
output, vjp_res = self.batched_vjp_func(x, dy)
|
|
1136
|
+
return self.__to_numpy(output.detach()), self.__to_numpy(vjp_res[0].detach())
|
|
1137
|
+
|
|
1138
|
+
def _predict_model_output_and_jacobian_fwd(self, x):
|
|
1139
|
+
if not hasattr(self, "y_jac_fwd_func"):
|
|
1140
|
+
def g(_x): # fmt: skip
|
|
1141
|
+
result = self.model(_x)
|
|
1142
|
+
return (result, result)
|
|
1143
|
+
self.y_jac_fwd_func = self.torch_func.vmap(
|
|
1144
|
+
self.torch_func.jacfwd(g, has_aux=True)
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1147
|
+
x = self.__to_tensor(x)
|
|
1148
|
+
jac, output = self.y_jac_fwd_func(x)
|
|
1149
|
+
return self.__to_numpy(output.detach()), self.__to_numpy(jac.detach())
|
|
1150
|
+
|
|
1151
|
+
def _predict_model_output_and_jacobian_rev(self, x):
|
|
1152
|
+
if not hasattr(self, "y_jac_rev_func"):
|
|
1153
|
+
def g(_x): # fmt: skip
|
|
1154
|
+
result = self.model(_x)
|
|
1155
|
+
return (result, result)
|
|
1156
|
+
self.y_jac_rev_func = self.torch_func.vmap(
|
|
1157
|
+
self.torch_func.jacrev(g, has_aux=True)
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
x = self.__to_tensor(x)
|
|
1161
|
+
jac, output = self.y_jac_rev_func(x)
|
|
1162
|
+
return self.__to_numpy(output.detach()), self.__to_numpy(jac.detach())
|
|
1163
|
+
|
|
1164
|
+
def _predict_model_jacobian_sbatch(self, x): # pragma: no cover
|
|
1165
|
+
"""Not used for now, but left as a reference for future if GPU memory issues arise."""
|
|
1166
|
+
if not hasattr(self, "y_jac_func"):
|
|
1167
|
+
def g(_x): # fmt: skip
|
|
1168
|
+
result = self.model(_x)
|
|
1169
|
+
return (result, result)
|
|
1170
|
+
self.y_jac_func = self.torch_func.vmap(
|
|
1171
|
+
self.torch_func.jacrev(g, has_aux=True)
|
|
1172
|
+
)
|
|
1173
|
+
|
|
1174
|
+
x = self.__to_tensor(x)
|
|
1175
|
+
batch_size = x.shape[0]
|
|
1176
|
+
y = self.torch.zeros(
|
|
1177
|
+
(batch_size, self.n_outputs),
|
|
1178
|
+
device=self.device,
|
|
1179
|
+
dtype=self.dtype,
|
|
1180
|
+
)
|
|
1181
|
+
jac = self.torch.zeros(
|
|
1182
|
+
(batch_size, self.n_outputs, self.n_inputs),
|
|
1183
|
+
device=self.device,
|
|
1184
|
+
dtype=self.dtype,
|
|
1185
|
+
)
|
|
1186
|
+
for i in range(0, batch_size, self.max_batch):
|
|
1187
|
+
x_mb = x[i : i + self.max_batch]
|
|
1188
|
+
inter_jac, inter_y = self.y_jac_func(x_mb)
|
|
1189
|
+
y[i : i + self.max_batch].copy_(
|
|
1190
|
+
inter_y[: x_mb.shape[0]],
|
|
1191
|
+
non_blocking=True,
|
|
1192
|
+
)
|
|
1193
|
+
jac[i : i + self.max_batch].copy_(
|
|
1194
|
+
inter_jac[: x_mb.shape[0]],
|
|
1195
|
+
non_blocking=True,
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
return self.__to_numpy(y.detach()), self.__to_numpy(jac.detach())
|
{surrogates_interface-4.0.2 → surrogates_interface-4.1.0}/surrogates_interface/transformers.py
RENAMED
|
@@ -814,3 +814,42 @@ class PowerTransformerYeoJohnson(Transformer):
|
|
|
814
814
|
if isinstance(other, self.__class__):
|
|
815
815
|
return np.array_equal(self.lambdas_, other.lambdas_)
|
|
816
816
|
return False
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
class StandardScalerTorch(StandardScaler):
|
|
820
|
+
import torch
|
|
821
|
+
|
|
822
|
+
def __init__(self, scale, mean):
|
|
823
|
+
super().__init__(scale, mean)
|
|
824
|
+
device = self.torch.device("cuda" if self.torch.cuda.is_available() else "cpu")
|
|
825
|
+
self.device = device
|
|
826
|
+
def __np2torch(v): # fmt: skip
|
|
827
|
+
return self.torch.as_tensor(v).to(device)
|
|
828
|
+
self.gain_ = __np2torch(self.gain_)
|
|
829
|
+
self.inv_gain_ = __np2torch(self.inv_gain_)
|
|
830
|
+
self.offset_ = __np2torch(self.offset_)
|
|
831
|
+
|
|
832
|
+
def transform(self, x, inplace=True):
|
|
833
|
+
if inplace:
|
|
834
|
+
raise ValueError("No inplace...")
|
|
835
|
+
x = self.torch.as_tensor(x, device=self.device, dtype=self.torch.float32)
|
|
836
|
+
return super().transform(x, inplace)
|
|
837
|
+
|
|
838
|
+
def inverse_transform(self, x, inplace=True):
|
|
839
|
+
if inplace:
|
|
840
|
+
raise ValueError("No inplace...")
|
|
841
|
+
x = self.torch.as_tensor(x, device=self.device, dtype=self.torch.float32)
|
|
842
|
+
return super().inverse_transform(x, inplace)
|
|
843
|
+
|
|
844
|
+
def transform_gradient(self, x):
|
|
845
|
+
return self.torch.broadcast_to(self.gain_, x.shape).numpy()
|
|
846
|
+
|
|
847
|
+
def inverse_transform_gradient(self, x):
|
|
848
|
+
return self.torch.broadcast_to(self.inv_gain_, x.shape).numpy()
|
|
849
|
+
|
|
850
|
+
def __eq__(self, other):
|
|
851
|
+
if isinstance(other, self.__class__):
|
|
852
|
+
test_gain = self.torch.equal(self.gain_, other.gain_)
|
|
853
|
+
test_offset = self.torch.equal(self.offset_, other.offset_)
|
|
854
|
+
return test_gain and test_offset
|
|
855
|
+
return False
|
|
File without changes
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
import numpy as np
|
|
3
|
-
import numpy.testing as npt
|
|
4
|
-
import pytest
|
|
5
|
-
|
|
6
|
-
from surrogates_interface.domains import BoxDomain, GenericDomain
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TestBoxDomain:
|
|
10
|
-
@staticmethod
|
|
11
|
-
def test_in_domain():
|
|
12
|
-
min = np.array([-1.0, +2.0, -15.0])
|
|
13
|
-
max = np.array([+1.0, +7.0, +8.0])
|
|
14
|
-
x = np.array(
|
|
15
|
-
[[0.0, 0.0, 0.0], [0.0, 4.0, 9.0], [0.0, 4.0, 0.0], [3.0, 4.0, 6.0]]
|
|
16
|
-
)
|
|
17
|
-
box = BoxDomain(min, max)
|
|
18
|
-
out = box.in_domain(x)
|
|
19
|
-
npt.assert_array_equal(out, np.array([False, False, True, False]))
|
|
20
|
-
|
|
21
|
-
@staticmethod
|
|
22
|
-
def test_bad_box():
|
|
23
|
-
min = np.array([-1.0, +7.0])
|
|
24
|
-
max = np.array([+1.0, -15.0])
|
|
25
|
-
with pytest.raises(AssertionError):
|
|
26
|
-
BoxDomain(min, max)
|
|
27
|
-
|
|
28
|
-
@staticmethod
|
|
29
|
-
def test_eq():
|
|
30
|
-
box1 = BoxDomain([-1.0, +2.0, -15.0], [+1.0, +7.0, +8.0])
|
|
31
|
-
box2 = BoxDomain([-1.0, -5.0, -15.0], [+1.0, +7.0, +8.0])
|
|
32
|
-
assert not box1 == box2
|
|
33
|
-
|
|
34
|
-
@staticmethod
|
|
35
|
-
def test_eq_different_type():
|
|
36
|
-
class DummyDomain(GenericDomain):
|
|
37
|
-
fields = []
|
|
38
|
-
|
|
39
|
-
def in_domain():
|
|
40
|
-
pass
|
|
41
|
-
|
|
42
|
-
box1 = BoxDomain([-1.0, +2.0, -15.0], [+1.0, +7.0, +8.0])
|
|
43
|
-
box2 = DummyDomain()
|
|
44
|
-
assert not box1 == box2
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
try:
|
|
2
|
-
import tomllib # From Python 3.11.
|
|
3
|
-
except ModuleNotFoundError:
|
|
4
|
-
import tomli as tomllib # Before Python 3.11.
|
|
5
|
-
|
|
6
|
-
import surrogates_interface
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def test_version():
|
|
10
|
-
with open("pyproject.toml", "rb") as fid:
|
|
11
|
-
data = tomllib.load(fid)
|
|
12
|
-
assert surrogates_interface.__version__ == data["tool"]["poetry"]["version"]
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
import numpy.testing as npt
|
|
5
|
-
import openmdao.api as om
|
|
6
|
-
import pytest
|
|
7
|
-
from sklearn.decomposition import PCA
|
|
8
|
-
from sklearn.preprocessing import MinMaxScaler, PowerTransformer
|
|
9
|
-
|
|
10
|
-
from surrogates_interface.openmdao import InputOutputType, SurrogateModelComp
|
|
11
|
-
from surrogates_interface.surrogates import SurrogateModel
|
|
12
|
-
from surrogates_interface.transformers import Transformer
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class Paraboloid(SurrogateModel):
|
|
16
|
-
"""Define a paraboloid."""
|
|
17
|
-
|
|
18
|
-
coef_1 = np.array([0.2, 0.5, 0.3])
|
|
19
|
-
coef_2 = np.array([1.2, 1.5, 1.3])
|
|
20
|
-
|
|
21
|
-
def _predict_model_output(self, x):
|
|
22
|
-
y = np.column_stack(
|
|
23
|
-
(np.sum(self.coef_1 * x**2, axis=1), np.sum(self.coef_2 * x**2, axis=1))
|
|
24
|
-
)
|
|
25
|
-
return y
|
|
26
|
-
|
|
27
|
-
def _predict_model_jacobian(self, x):
|
|
28
|
-
y = self._predict_model_output(x)
|
|
29
|
-
jac = np.stack((2.0 * self.coef_1 * x, 2.0 * self.coef_2 * x), axis=1)
|
|
30
|
-
return y, jac
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@pytest.fixture
|
|
34
|
-
def make_data():
|
|
35
|
-
rng = np.random.default_rng(seed=123)
|
|
36
|
-
n_points = 100
|
|
37
|
-
n_inputs_original = 5
|
|
38
|
-
n_outputs_original = 4
|
|
39
|
-
input = rng.uniform(-1.0, +1.0, size=(n_points, n_inputs_original))
|
|
40
|
-
# Introduce linear correlation between some input features.
|
|
41
|
-
input[:, 1] = 2.0 * input[:, 0]
|
|
42
|
-
input[:, 2] = 3.0 * input[:, 0] - 5.0
|
|
43
|
-
# Build input transformers.
|
|
44
|
-
sklearn_input_transformers = [
|
|
45
|
-
PowerTransformer(standardize=False),
|
|
46
|
-
PCA(n_components=3, whiten=False),
|
|
47
|
-
]
|
|
48
|
-
input_transformed = input.copy()
|
|
49
|
-
for transformer in sklearn_input_transformers:
|
|
50
|
-
input_transformed = transformer.fit_transform(input_transformed)
|
|
51
|
-
si_input_transformers = [
|
|
52
|
-
Transformer.from_sklearn(t) for t in sklearn_input_transformers
|
|
53
|
-
]
|
|
54
|
-
# Make a dummy output. We only need it to create the transformers.
|
|
55
|
-
output = np.zeros((n_points, n_outputs_original))
|
|
56
|
-
output[:, 0] = np.sin(input[:, 0]) * np.cos(input[:, 4])
|
|
57
|
-
output[:, 1] = np.sum(input**2, axis=1)
|
|
58
|
-
# Introduce linear correlation between some output features.
|
|
59
|
-
output[:, 2] = 1.1 * output[:, 0]
|
|
60
|
-
output[:, 3] = 0.5 * output[:, 1]
|
|
61
|
-
# Build output transformers.
|
|
62
|
-
sklearn_output_transformers = [
|
|
63
|
-
MinMaxScaler(feature_range=(-1.0, +1.0), clip=False),
|
|
64
|
-
PCA(n_components=2, whiten=False),
|
|
65
|
-
]
|
|
66
|
-
output_transformed = output.copy()
|
|
67
|
-
for transformer in sklearn_output_transformers:
|
|
68
|
-
output_transformed = transformer.fit_transform(output_transformed)
|
|
69
|
-
si_output_transformers = [
|
|
70
|
-
Transformer.from_sklearn(t) for t in sklearn_output_transformers
|
|
71
|
-
]
|
|
72
|
-
# Build the surrogate model.
|
|
73
|
-
si_model = Paraboloid(
|
|
74
|
-
[], # model is dummy.
|
|
75
|
-
input_transformers=si_input_transformers,
|
|
76
|
-
output_transformers=si_output_transformers,
|
|
77
|
-
n_inputs=n_inputs_original,
|
|
78
|
-
n_outputs=n_outputs_original,
|
|
79
|
-
)
|
|
80
|
-
# Evaluate it on the input, and overwrite the transformed output.
|
|
81
|
-
output_transformed = si_model._predict_model_output(input_transformed)
|
|
82
|
-
# Apply inverse output transformations and overwrite the original output.
|
|
83
|
-
output = output_transformed.copy()
|
|
84
|
-
for transformer in reversed(si_output_transformers):
|
|
85
|
-
output = transformer.inverse_transform(output, inplace=False)
|
|
86
|
-
return (
|
|
87
|
-
si_model,
|
|
88
|
-
input,
|
|
89
|
-
output,
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def test_jacobian(make_data):
|
|
94
|
-
si_model, input, _ = make_data
|
|
95
|
-
# Compute exact Jacobian.
|
|
96
|
-
jacobian_si = si_model.predict_jacobian(input)
|
|
97
|
-
# Compute Jacobian using central finite differences.
|
|
98
|
-
jacobian_fd = np.zeros_like(jacobian_si)
|
|
99
|
-
h = 1e-6
|
|
100
|
-
for i in range(input.shape[1]):
|
|
101
|
-
xm = input.copy()
|
|
102
|
-
xp = input.copy()
|
|
103
|
-
xm[:, i] -= h
|
|
104
|
-
xp[:, i] += h
|
|
105
|
-
jacobian_fd[:, :, i] = si_model.predict_output(xp) - si_model.predict_output(xm)
|
|
106
|
-
jacobian_fd /= 2.0 * h
|
|
107
|
-
# Test.
|
|
108
|
-
npt.assert_allclose(jacobian_si, jacobian_fd, rtol=2e-7)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def test_om_compute_partials_joined_joined(make_data):
|
|
112
|
-
# Get the data.
|
|
113
|
-
si_model, input, _ = make_data
|
|
114
|
-
# Make the OpenMDAO problem.
|
|
115
|
-
problem = om.Problem()
|
|
116
|
-
component = SurrogateModelComp(
|
|
117
|
-
model=si_model,
|
|
118
|
-
input_type=InputOutputType.JOINED,
|
|
119
|
-
output_type=InputOutputType.JOINED,
|
|
120
|
-
n_points=input.shape[0],
|
|
121
|
-
)
|
|
122
|
-
problem.model.add_subsystem(
|
|
123
|
-
"surrogate", component, promotes_inputs=["x"], promotes_outputs=["y"]
|
|
124
|
-
)
|
|
125
|
-
problem.setup()
|
|
126
|
-
# Set the input.
|
|
127
|
-
problem.set_val("x", input)
|
|
128
|
-
# Check partial derivatives.
|
|
129
|
-
om_partials = problem.check_partials(
|
|
130
|
-
out_stream=None, step=1e-6, step_calc="abs", form="central"
|
|
131
|
-
)
|
|
132
|
-
# Jacobian computed by finite differences.
|
|
133
|
-
jacobian_fd = om_partials["surrogate"][("y", "x")]["J_fd"]
|
|
134
|
-
# Exact Jacobian.
|
|
135
|
-
jacobian_exact = om_partials["surrogate"][("y", "x")]["J_fwd"]
|
|
136
|
-
# Test.
|
|
137
|
-
npt.assert_allclose(jacobian_fd, jacobian_exact, rtol=2e-7)
|