gemseo-multi-fidelity 0.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.
- gemseo_multi_fidelity/__init__.py +17 -0
- gemseo_multi_fidelity/core/MFMapperAdapter_input.json +22 -0
- gemseo_multi_fidelity/core/MFMapperAdapter_output.json +22 -0
- gemseo_multi_fidelity/core/MFMapperLinker_input.json +22 -0
- gemseo_multi_fidelity/core/MFMapperLinker_output.json +22 -0
- gemseo_multi_fidelity/core/MFScenarioAdapter_input.json +39 -0
- gemseo_multi_fidelity/core/MFScenarioAdapter_output.json +23 -0
- gemseo_multi_fidelity/core/__init__.py +16 -0
- gemseo_multi_fidelity/core/boxed_domain.py +242 -0
- gemseo_multi_fidelity/core/corr_function.py +411 -0
- gemseo_multi_fidelity/core/criticality.py +124 -0
- gemseo_multi_fidelity/core/ds_mapper.py +307 -0
- gemseo_multi_fidelity/core/errors.py +42 -0
- gemseo_multi_fidelity/core/eval_mapper.py +188 -0
- gemseo_multi_fidelity/core/id_mapper_adapter.py +61 -0
- gemseo_multi_fidelity/core/mapper_adapter.py +126 -0
- gemseo_multi_fidelity/core/mapper_linker.py +72 -0
- gemseo_multi_fidelity/core/mf_formulation.py +635 -0
- gemseo_multi_fidelity/core/mf_logger.py +216 -0
- gemseo_multi_fidelity/core/mf_opt_problem.py +480 -0
- gemseo_multi_fidelity/core/mf_scenario.py +205 -0
- gemseo_multi_fidelity/core/noise_criterion.py +94 -0
- gemseo_multi_fidelity/core/projpolytope.out +0 -0
- gemseo_multi_fidelity/core/scenario_adapter.py +568 -0
- gemseo_multi_fidelity/core/stop_criteria.py +201 -0
- gemseo_multi_fidelity/core/strict_chain.py +75 -0
- gemseo_multi_fidelity/core/utils_model_quality.py +74 -0
- gemseo_multi_fidelity/corrections/__init__.py +16 -0
- gemseo_multi_fidelity/corrections/add_corr_function.py +80 -0
- gemseo_multi_fidelity/corrections/correction_factory.py +65 -0
- gemseo_multi_fidelity/corrections/mul_corr_function.py +86 -0
- gemseo_multi_fidelity/drivers/__init__.py +16 -0
- gemseo_multi_fidelity/drivers/mf_algo_factory.py +38 -0
- gemseo_multi_fidelity/drivers/mf_driver_lib.py +462 -0
- gemseo_multi_fidelity/drivers/refinement.py +234 -0
- gemseo_multi_fidelity/drivers/settings/__init__.py +16 -0
- gemseo_multi_fidelity/drivers/settings/base_mf_driver_settings.py +59 -0
- gemseo_multi_fidelity/drivers/settings/mf_refine_settings.py +50 -0
- gemseo_multi_fidelity/formulations/__init__.py +16 -0
- gemseo_multi_fidelity/formulations/refinement.py +144 -0
- gemseo_multi_fidelity/mapping/__init__.py +16 -0
- gemseo_multi_fidelity/mapping/identity_mapper.py +74 -0
- gemseo_multi_fidelity/mapping/interp_mapper.py +422 -0
- gemseo_multi_fidelity/mapping/mapper_factory.py +70 -0
- gemseo_multi_fidelity/mapping/mapping_errors.py +46 -0
- gemseo_multi_fidelity/mapping/subset_mapper.py +122 -0
- gemseo_multi_fidelity/mf_rosenbrock/__init__.py +16 -0
- gemseo_multi_fidelity/mf_rosenbrock/delayed_disc.py +136 -0
- gemseo_multi_fidelity/mf_rosenbrock/refact_rosen_testcase.py +46 -0
- gemseo_multi_fidelity/mf_rosenbrock/rosen_mf_case.py +284 -0
- gemseo_multi_fidelity/mf_rosenbrock/rosen_mf_funcs.py +350 -0
- gemseo_multi_fidelity/models/__init__.py +16 -0
- gemseo_multi_fidelity/models/fake_updater.py +112 -0
- gemseo_multi_fidelity/models/model_updater.py +91 -0
- gemseo_multi_fidelity/models/rbf/__init__.py +16 -0
- gemseo_multi_fidelity/models/rbf/kernel_factory.py +66 -0
- gemseo_multi_fidelity/models/rbf/kernels/__init__.py +16 -0
- gemseo_multi_fidelity/models/rbf/kernels/gaussian.py +93 -0
- gemseo_multi_fidelity/models/rbf/kernels/matern_3_2.py +101 -0
- gemseo_multi_fidelity/models/rbf/kernels/matern_5_2.py +101 -0
- gemseo_multi_fidelity/models/rbf/kernels/rbf_kernel.py +172 -0
- gemseo_multi_fidelity/models/rbf/rbf_model.py +422 -0
- gemseo_multi_fidelity/models/sparse_rbf_updater.py +96 -0
- gemseo_multi_fidelity/models/taylor/__init__.py +16 -0
- gemseo_multi_fidelity/models/taylor/taylor.py +212 -0
- gemseo_multi_fidelity/models/taylor_updater.py +66 -0
- gemseo_multi_fidelity/models/updater_factory.py +62 -0
- gemseo_multi_fidelity/settings/__init__.py +16 -0
- gemseo_multi_fidelity/settings/drivers.py +22 -0
- gemseo_multi_fidelity/settings/formulations.py +16 -0
- gemseo_multi_fidelity-0.0.1.dist-info/METADATA +99 -0
- gemseo_multi_fidelity-0.0.1.dist-info/RECORD +76 -0
- gemseo_multi_fidelity-0.0.1.dist-info/WHEEL +5 -0
- gemseo_multi_fidelity-0.0.1.dist-info/entry_points.txt +2 -0
- gemseo_multi_fidelity-0.0.1.dist-info/licenses/LICENSE.txt +165 -0
- gemseo_multi_fidelity-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
|
|
2
|
+
#
|
|
3
|
+
# This program is free software; you can redistribute it and/or
|
|
4
|
+
# modify it under the terms of the GNU Lesser General Public
|
|
5
|
+
# License version 3 as published by the Free Software Foundation.
|
|
6
|
+
#
|
|
7
|
+
# This program is distributed in the hope that it will be useful,
|
|
8
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
9
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
10
|
+
# Lesser General Public License for more details.
|
|
11
|
+
#
|
|
12
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
13
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
14
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
15
|
+
|
|
16
|
+
# Copyright (c) 2019 AIRBUS OPERATIONS
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# Contributors:
|
|
20
|
+
# INITIAL AUTHORS - API and implementation and/or documentation
|
|
21
|
+
# :author: Romain Olivanti
|
|
22
|
+
# OTHER AUTHORS - MACROSCOPIC CHANGES
|
|
23
|
+
# Francois Gallard: portage to GEMSEO 6
|
|
24
|
+
"""Multi-fidelity scenario adapter."""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import contextlib
|
|
29
|
+
import logging
|
|
30
|
+
from collections.abc import Iterable
|
|
31
|
+
from typing import TYPE_CHECKING
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from gemseo.algos.database import Database
|
|
35
|
+
from gemseo.core.discipline.discipline import Discipline
|
|
36
|
+
from gemseo.scenarios.mdo_scenario import MDOScenario
|
|
37
|
+
|
|
38
|
+
from gemseo_multi_fidelity.core.corr_function import MDOCorrectedFunction
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from collections.abc import Callable
|
|
42
|
+
from collections.abc import Mapping
|
|
43
|
+
|
|
44
|
+
from gemseo.core._process_flow.base_process_flow import BaseProcessFlow
|
|
45
|
+
from gemseo.core.mdo_functions.mdo_function import MDOFunction
|
|
46
|
+
from gemseo.typing import StrKeyMapping
|
|
47
|
+
from numpy.typing import NDArray
|
|
48
|
+
|
|
49
|
+
LOGGER = logging.getLogger(__name__)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CallbackReturnError(Exception):
|
|
53
|
+
"""Error to raise if a return is requested at runtime."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, msg: str | None = None) -> None:
|
|
56
|
+
"""Constructor.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
msg: explanation for the error.
|
|
60
|
+
"""
|
|
61
|
+
super().__init__()
|
|
62
|
+
self.msg = msg
|
|
63
|
+
|
|
64
|
+
def __str__(self) -> str:
|
|
65
|
+
"""Custom string representation."""
|
|
66
|
+
return repr(self.msg)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class MFScenarioAdapter(Discipline):
|
|
70
|
+
"""An adapter class to run a scenario in a multi-fidelity context."""
|
|
71
|
+
|
|
72
|
+
X_START = "x_start"
|
|
73
|
+
X_BEST = "x_best"
|
|
74
|
+
|
|
75
|
+
INPUT_OPTIONS = "input_options"
|
|
76
|
+
OUTPUT_OPTIONS = "output_options"
|
|
77
|
+
|
|
78
|
+
X_REF = "x_reference"
|
|
79
|
+
OUTPUT_REF = "output_reference"
|
|
80
|
+
|
|
81
|
+
ON_ENTRY_CALLBACKS = "on_entry_callbacks"
|
|
82
|
+
RETURN_CALLBACK = "return_callback"
|
|
83
|
+
ON_EXIT_CALLBACKS = "on_exit_callbacks"
|
|
84
|
+
|
|
85
|
+
OUTPUT_STATUS = "output_status"
|
|
86
|
+
NOT_STARTED = "not started"
|
|
87
|
+
EARLY_RETURN = "asked to return"
|
|
88
|
+
RUN_COMPLETED = "completed"
|
|
89
|
+
N_ITER_RUN = "n_iters"
|
|
90
|
+
|
|
91
|
+
MAX_CORR_RAD = "max_corr_rad"
|
|
92
|
+
HOTSTART_OPTIONS = "hotstart_options"
|
|
93
|
+
HESS_PAIRS = "input_hess_pairs"
|
|
94
|
+
PRECOND = "input_precond"
|
|
95
|
+
|
|
96
|
+
DEF_HOTSTART_OPTIONS_GETTER = "get_hotstart_options"
|
|
97
|
+
|
|
98
|
+
scenario: MDOScenario
|
|
99
|
+
"""The wrapped scenario"""
|
|
100
|
+
|
|
101
|
+
auto_detect_grammar_files = True
|
|
102
|
+
|
|
103
|
+
def __init__(self, scenario: MDOScenario) -> None:
|
|
104
|
+
"""Constructor.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
scenario: The scenario to run.
|
|
108
|
+
"""
|
|
109
|
+
if not isinstance(scenario, MDOScenario):
|
|
110
|
+
msg = "input scenario must be an MDOScenario"
|
|
111
|
+
raise TypeError(msg)
|
|
112
|
+
|
|
113
|
+
super().__init__(name=scenario.name)
|
|
114
|
+
|
|
115
|
+
self.scenario = scenario
|
|
116
|
+
|
|
117
|
+
self._opt_status = self.NOT_STARTED
|
|
118
|
+
self._return_callback: callable | None = None
|
|
119
|
+
self._hotstart_options_set: bool = False
|
|
120
|
+
self._init_return_callback()
|
|
121
|
+
|
|
122
|
+
def _init_return_callback(self):
|
|
123
|
+
"""Initialize return callback.
|
|
124
|
+
|
|
125
|
+
Initializes a return callback linked to the database of the optimization
|
|
126
|
+
problem. By default, the callback has no effect.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
def must_return(x_vect=None):
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
self._return_callback = must_return
|
|
133
|
+
self.scenario.formulation.optimization_problem.database.add_new_iter_listener(
|
|
134
|
+
self._return_callback
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def check_callback(callback: Callable) -> None:
|
|
139
|
+
"""Check if the callback provided is valid.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
callback: The callback to check.
|
|
143
|
+
"""
|
|
144
|
+
if not callable(callback):
|
|
145
|
+
msg = f"{callback} must be callable"
|
|
146
|
+
raise TypeError(msg)
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def _check_return_callback(callback: Callable) -> None:
|
|
150
|
+
"""Check return callback.
|
|
151
|
+
|
|
152
|
+
Checks that the callback provided is a valid return callback, i.e. that it only
|
|
153
|
+
returns a bool as output and that it should not already order to return at check
|
|
154
|
+
time.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
callback: The callback to check.
|
|
158
|
+
"""
|
|
159
|
+
MFScenarioAdapter.check_callback(callback)
|
|
160
|
+
|
|
161
|
+
# Specific checks
|
|
162
|
+
return_flag = callback()
|
|
163
|
+
if not isinstance(return_flag, bool):
|
|
164
|
+
msg = "return_callback must return a bool"
|
|
165
|
+
raise TypeError(msg)
|
|
166
|
+
|
|
167
|
+
if return_flag:
|
|
168
|
+
LOGGER.warning(
|
|
169
|
+
"return_callback is asking to return before the "
|
|
170
|
+
"beginning of the optimization"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _link_return_callback(self, return_callback: Callable) -> None:
|
|
174
|
+
"""Link return callback.
|
|
175
|
+
|
|
176
|
+
Implements the return callback using a listener linked to the optimization
|
|
177
|
+
database of the scenario. ``return_callback`` must return a bool and take the
|
|
178
|
+
design vector as argument.
|
|
179
|
+
At least two stores are allowed to be performed irrespectively to the return
|
|
180
|
+
callback in order to avoid deadlocks during optimization (overrides the return
|
|
181
|
+
flag).
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
return_callback: The callback allowing an early return.
|
|
185
|
+
"""
|
|
186
|
+
self._check_return_callback(return_callback)
|
|
187
|
+
|
|
188
|
+
# Delete the current listener
|
|
189
|
+
opt_prob = self.scenario.formulation.optimization_problem
|
|
190
|
+
# TODO access should be cleaner than this, add a method in GEMSEO
|
|
191
|
+
new_it_listeners = opt_prob.database._Database__new_iter_listeners
|
|
192
|
+
with contextlib.suppress(ValueError):
|
|
193
|
+
new_it_listeners.remove(self._return_callback)
|
|
194
|
+
# Make sure at least one real optimization iteration can be performed by the
|
|
195
|
+
# MDOScenario, build a specific callback.
|
|
196
|
+
it_init = len(opt_prob.database)
|
|
197
|
+
it_return_allowed = it_init + 1
|
|
198
|
+
|
|
199
|
+
def must_return(x_vect=None) -> None:
|
|
200
|
+
"""Callback making sure that at least on real optimization is performed."""
|
|
201
|
+
asked_to_return = return_callback()
|
|
202
|
+
curr_it = len(opt_prob.database)
|
|
203
|
+
if asked_to_return and (curr_it > it_return_allowed):
|
|
204
|
+
msg = "Ordered to return by the callback"
|
|
205
|
+
raise CallbackReturnError(msg)
|
|
206
|
+
|
|
207
|
+
# Add the new callback
|
|
208
|
+
self._return_callback = must_return
|
|
209
|
+
opt_prob.database.add_new_iter_listener(self._return_callback)
|
|
210
|
+
|
|
211
|
+
def run_callbacks_from_local_data(self, callback_type: str) -> list:
|
|
212
|
+
"""Run the specified callback type from the local data.
|
|
213
|
+
|
|
214
|
+
Also checks that the callbacks defined in the default_inputs are also called if
|
|
215
|
+
different from the ones defined in the local_data.
|
|
216
|
+
This is explained by the fact that the behavior expected here is not to override
|
|
217
|
+
but to append.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
callback_type: The key in the local data.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
The list ??? # TODO
|
|
224
|
+
"""
|
|
225
|
+
callbacks = self.io.data.get(callback_type)
|
|
226
|
+
out = []
|
|
227
|
+
|
|
228
|
+
if callbacks is not None:
|
|
229
|
+
# Make sure that the callbacks defined in the default_inputs are
|
|
230
|
+
# not simply overridden
|
|
231
|
+
if callable(callbacks):
|
|
232
|
+
# Make sure it is a list to ease the check
|
|
233
|
+
callbacks = [callbacks]
|
|
234
|
+
# Add them if there are not there yet
|
|
235
|
+
callbacks_in_default = self.default_input_data.get(callback_type, [])
|
|
236
|
+
if callable(callbacks_in_default):
|
|
237
|
+
callbacks_in_default = [callbacks_in_default]
|
|
238
|
+
for callback in callbacks_in_default:
|
|
239
|
+
if callback not in callbacks:
|
|
240
|
+
callbacks.append(callback)
|
|
241
|
+
out = self.run_callbacks(callbacks, self.io.data)
|
|
242
|
+
return out
|
|
243
|
+
|
|
244
|
+
@staticmethod
|
|
245
|
+
def run_callbacks(
|
|
246
|
+
callbacks: Iterable[Callable] | Callable,
|
|
247
|
+
*args: list[Any],
|
|
248
|
+
**kwargs: Mapping[str, Any],
|
|
249
|
+
) -> list[Callable]:
|
|
250
|
+
"""Run the specified callbacks with the same args sequentially.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
callbacks: The callbacks to run.
|
|
254
|
+
args: The args.
|
|
255
|
+
kwargs: The keyword arguments.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
The list of outputs.
|
|
259
|
+
"""
|
|
260
|
+
if isinstance(callbacks, Iterable):
|
|
261
|
+
return [callback(*args, **kwargs) for callback in callbacks]
|
|
262
|
+
|
|
263
|
+
return [callbacks(*args, **kwargs)]
|
|
264
|
+
|
|
265
|
+
def get_all_nonproc_funcs(self) -> list[MDOFunction]:
|
|
266
|
+
"""Get all non-processed functions.
|
|
267
|
+
|
|
268
|
+
Convenience method to get all the non-processed functions even when the
|
|
269
|
+
functions have not been preprocessed yet.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
The list of the non-processed functions.
|
|
273
|
+
"""
|
|
274
|
+
opt_prob = self.scenario.formulation.optimization_problem
|
|
275
|
+
|
|
276
|
+
if opt_prob._functions_are_preprocessed:
|
|
277
|
+
return opt_prob.functions
|
|
278
|
+
return opt_prob.original_functions
|
|
279
|
+
|
|
280
|
+
def get_algo_lib(self) -> str:
|
|
281
|
+
"""Convenience method to get the algo lib specified to run the scenario.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
The lib.
|
|
285
|
+
"""
|
|
286
|
+
# The algo name is either set by the input data or already defined in the
|
|
287
|
+
# default inputs of the scenario
|
|
288
|
+
tag_algo = "algo_name"
|
|
289
|
+
default_algo = None
|
|
290
|
+
if self.scenario._settings is not None:
|
|
291
|
+
default_algo = self.scenario._settings.algo_name
|
|
292
|
+
algo_name = self.io.data.get(tag_algo, default_algo)
|
|
293
|
+
if algo_name is None:
|
|
294
|
+
msg = "No algorithm specified to execute the scenario"
|
|
295
|
+
raise RuntimeError(msg)
|
|
296
|
+
|
|
297
|
+
return self.scenario._algo_factory.create(algo_name)
|
|
298
|
+
|
|
299
|
+
def _set_func_reference(
|
|
300
|
+
self, func: MDOCorrectedFunction, x_ref: NDArray, output_ref: dict
|
|
301
|
+
) -> None:
|
|
302
|
+
"""Set function reference.
|
|
303
|
+
|
|
304
|
+
Sets the reference data for the specified function.
|
|
305
|
+
Nothing is done if no data are provided for the specified function.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
func: The MDOCorrectedFunction to update.
|
|
309
|
+
x_ref: The reference point.
|
|
310
|
+
output_ref: The reference data to set.
|
|
311
|
+
"""
|
|
312
|
+
max_norm_corr = self.io.data.get(self.MAX_CORR_RAD)
|
|
313
|
+
func_name = func.name
|
|
314
|
+
|
|
315
|
+
val_ref = output_ref.get(func_name)
|
|
316
|
+
if val_ref is not None:
|
|
317
|
+
grad_ref = output_ref.get(Database.GRAD_TAG + func_name)
|
|
318
|
+
if func.original is not None and isinstance(
|
|
319
|
+
func.original, MDOCorrectedFunction
|
|
320
|
+
):
|
|
321
|
+
func = func.original
|
|
322
|
+
func.set_reference(
|
|
323
|
+
x_ref, val_ref, grad_ref=grad_ref, max_norm_thr=max_norm_corr
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def _set_reference(self) -> None:
|
|
327
|
+
"""Set the reference data provided."""
|
|
328
|
+
x_ref = self.io.data.get(self.X_REF)
|
|
329
|
+
output_ref = self.io.data.get(self.OUTPUT_REF)
|
|
330
|
+
|
|
331
|
+
if x_ref is None or output_ref is None:
|
|
332
|
+
# Nothing to do
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
opt_prob = self.scenario.formulation.optimization_problem
|
|
336
|
+
|
|
337
|
+
# Correction will be set, therefore, clear the current optimization data as it
|
|
338
|
+
# may no longer be consistent
|
|
339
|
+
opt_prob.database.clear()
|
|
340
|
+
|
|
341
|
+
to_array = self.scenario.design_space.convert_dict_to_array
|
|
342
|
+
# Convert to arrays
|
|
343
|
+
if isinstance(x_ref, dict):
|
|
344
|
+
x_ref = to_array(x_ref)
|
|
345
|
+
for func, jac in output_ref.items():
|
|
346
|
+
if isinstance(jac, dict):
|
|
347
|
+
output_ref[func] = to_array(x_ref)
|
|
348
|
+
|
|
349
|
+
# Apply the reference data
|
|
350
|
+
for func in self.get_all_nonproc_funcs():
|
|
351
|
+
self._set_func_reference(func, x_ref, output_ref)
|
|
352
|
+
|
|
353
|
+
def _filter_subprocess_inputs(self) -> dict:
|
|
354
|
+
"""Filter subprocess inputs.
|
|
355
|
+
|
|
356
|
+
Filters self.local_data to keep only the data required to run the MDOScenario.
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
The filtered inputs.
|
|
360
|
+
"""
|
|
361
|
+
LOGGER.warning(
|
|
362
|
+
"ScenarioAdapter._filter_subprocess_inputs: Deactivated subprocess inputs!"
|
|
363
|
+
)
|
|
364
|
+
return {}
|
|
365
|
+
input_data = {}
|
|
366
|
+
|
|
367
|
+
key_algo = "algo_name"
|
|
368
|
+
key_algo_opt = "algo_options"
|
|
369
|
+
key_max_iter = "max_iter"
|
|
370
|
+
|
|
371
|
+
# Check if the algo specified in self.local_data is the same as the one defined
|
|
372
|
+
# in the default inputs
|
|
373
|
+
def_algo = (
|
|
374
|
+
self.scenario._settings.algo_name
|
|
375
|
+
if self.scenario._settings is not None
|
|
376
|
+
else None
|
|
377
|
+
)
|
|
378
|
+
algo = self.io.data.get(key_algo)
|
|
379
|
+
#
|
|
380
|
+
new_algo_options = self.io.data.get(key_algo_opt, {})
|
|
381
|
+
#
|
|
382
|
+
if def_algo is not None and (algo == def_algo or algo is None):
|
|
383
|
+
# Same algo Try to load the default inputs in the input data as only a
|
|
384
|
+
# limited number of options may have been updated
|
|
385
|
+
algo_options = self.scenario._settings.model_dump()
|
|
386
|
+
algo_options.update(new_algo_options)
|
|
387
|
+
else:
|
|
388
|
+
# The new algo differs from the default one
|
|
389
|
+
# Use only new options
|
|
390
|
+
algo_options = new_algo_options
|
|
391
|
+
|
|
392
|
+
max_iter = self.io.data.get(key_max_iter)
|
|
393
|
+
|
|
394
|
+
# Handle preconditioner and hess approx pairs inputs
|
|
395
|
+
other_algo_options = self._proccess_lib_options()
|
|
396
|
+
algo_options.update(other_algo_options)
|
|
397
|
+
|
|
398
|
+
input_data = {
|
|
399
|
+
key: data
|
|
400
|
+
for key, data in zip(
|
|
401
|
+
[key_algo, key_max_iter], [algo, max_iter], strict=False
|
|
402
|
+
)
|
|
403
|
+
if data is not None
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
input_data.update(algo_options)
|
|
407
|
+
|
|
408
|
+
return input_data
|
|
409
|
+
|
|
410
|
+
def _proccess_lib_options(self) -> dict:
|
|
411
|
+
"""Process lib-specific options.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
The processed options.
|
|
415
|
+
"""
|
|
416
|
+
lib_options = {}
|
|
417
|
+
# Preconditioner
|
|
418
|
+
precond = self.io.data.get(self.PRECOND)
|
|
419
|
+
algo_lib = self.get_algo_lib()
|
|
420
|
+
if precond is not None:
|
|
421
|
+
try:
|
|
422
|
+
precond_lib_name = algo_lib.PRECOND
|
|
423
|
+
lib_options[precond_lib_name] = precond
|
|
424
|
+
except AttributeError:
|
|
425
|
+
pass
|
|
426
|
+
# Hessian approx pairs
|
|
427
|
+
pairs = self.io.data.get(self.HESS_PAIRS)
|
|
428
|
+
if pairs is not None:
|
|
429
|
+
try:
|
|
430
|
+
pairs_lib_name = algo_lib.HESS_PAIRS
|
|
431
|
+
lib_options[pairs_lib_name] = pairs
|
|
432
|
+
except AttributeError:
|
|
433
|
+
pass
|
|
434
|
+
return lib_options
|
|
435
|
+
|
|
436
|
+
def _set_inputs(self) -> dict:
|
|
437
|
+
"""Tasks to perform before executing self.scenario.
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
The input data to run the MDOScenario.
|
|
441
|
+
"""
|
|
442
|
+
self._opt_status = self.NOT_STARTED
|
|
443
|
+
x_start = self.io.data[self.X_START]
|
|
444
|
+
self.scenario.design_space.set_current_value(x_start)
|
|
445
|
+
|
|
446
|
+
# Unpack forwarded options
|
|
447
|
+
input_options = self.io.data.get(self.INPUT_OPTIONS)
|
|
448
|
+
if input_options is not None:
|
|
449
|
+
for option, value in input_options.items():
|
|
450
|
+
self.io.data[option] = value
|
|
451
|
+
|
|
452
|
+
# Set the reference data
|
|
453
|
+
self._set_reference()
|
|
454
|
+
|
|
455
|
+
# Link the update callback
|
|
456
|
+
return_callback = self.io.data.get(self.RETURN_CALLBACK)
|
|
457
|
+
if return_callback is not None:
|
|
458
|
+
self._link_return_callback(return_callback)
|
|
459
|
+
|
|
460
|
+
# Filter scenario inputs
|
|
461
|
+
return self._filter_subprocess_inputs()
|
|
462
|
+
|
|
463
|
+
def _set_outputs(self) -> None:
|
|
464
|
+
"""Store the outputs from the optimization."""
|
|
465
|
+
# Output the best data expressed in the upper design space
|
|
466
|
+
opt_prob = self.scenario.formulation.optimization_problem
|
|
467
|
+
|
|
468
|
+
# Get the best point
|
|
469
|
+
x_best = opt_prob.optimum[1]
|
|
470
|
+
x_best = opt_prob.design_space.convert_array_to_dict(x_best)
|
|
471
|
+
|
|
472
|
+
# Update the output data
|
|
473
|
+
self.io.data[self.X_BEST] = x_best
|
|
474
|
+
|
|
475
|
+
# Update the output status
|
|
476
|
+
self.io.data[self.OUTPUT_STATUS] = self._opt_status
|
|
477
|
+
|
|
478
|
+
# Set the potential hotstart options in the event of a restart
|
|
479
|
+
# At this level
|
|
480
|
+
self._set_hotstart_options()
|
|
481
|
+
|
|
482
|
+
def _set_hotstart_options(self) -> None:
|
|
483
|
+
"""Set hotstart options.
|
|
484
|
+
|
|
485
|
+
Either tries to recover hotstart options from the lib itself, using a specific
|
|
486
|
+
getter if a string is provided in self.local_data[self.HOTSTART_OPTIONS] or uses
|
|
487
|
+
the options provided if a dict is provided.
|
|
488
|
+
If nothing is provided, a default getter is called.
|
|
489
|
+
If the latter is not recognized by the lib, nothing is done.
|
|
490
|
+
"""
|
|
491
|
+
hotstart_options = self.io.data.get(
|
|
492
|
+
self.HOTSTART_OPTIONS, self.DEF_HOTSTART_OPTIONS_GETTER
|
|
493
|
+
)
|
|
494
|
+
options = None
|
|
495
|
+
self._hotstart_options_set = False
|
|
496
|
+
if isinstance(hotstart_options, dict):
|
|
497
|
+
# Simply use the options provided
|
|
498
|
+
options = hotstart_options
|
|
499
|
+
elif isinstance(hotstart_options, str):
|
|
500
|
+
# Try to recover the options from the specified getter
|
|
501
|
+
algo_lib = self.get_algo_lib()
|
|
502
|
+
try:
|
|
503
|
+
hotstart_options_getter = getattr(algo_lib, hotstart_options)
|
|
504
|
+
opt_prob = self.scenario.formulation.optimization_problem
|
|
505
|
+
options = hotstart_options_getter(
|
|
506
|
+
opt_prob.database, opt_prob.objective.name
|
|
507
|
+
)
|
|
508
|
+
except: # noqa: E722
|
|
509
|
+
# Nothing can be done
|
|
510
|
+
pass
|
|
511
|
+
|
|
512
|
+
if options is not None:
|
|
513
|
+
# Set the options
|
|
514
|
+
try:
|
|
515
|
+
algo_options = self.io.data["algo_options"]
|
|
516
|
+
algo_options.update(options)
|
|
517
|
+
except KeyError:
|
|
518
|
+
# No algo options have defined yet
|
|
519
|
+
self.io.data["algo_options"] = options
|
|
520
|
+
self._hotstart_options_set = True
|
|
521
|
+
|
|
522
|
+
def _run_subprocess(self, input_data: StrKeyMapping) -> None:
|
|
523
|
+
"""Run the subprocess, here the optimization of the scenario.
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
input_data: The input data.
|
|
527
|
+
"""
|
|
528
|
+
self.scenario.execute(**input_data)
|
|
529
|
+
|
|
530
|
+
def _run(self, input_data: StrKeyMapping) -> None:
|
|
531
|
+
"""Run.
|
|
532
|
+
|
|
533
|
+
Processes the input data potentially:
|
|
534
|
+
- setting the reference data
|
|
535
|
+
- setting the return callback
|
|
536
|
+
then runs:
|
|
537
|
+
- the on entry callbacks
|
|
538
|
+
- the scenario
|
|
539
|
+
- the on exit callbacks.
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
input_data: The input data.
|
|
543
|
+
"""
|
|
544
|
+
# Run the on entry callback(s)
|
|
545
|
+
self.run_callbacks_from_local_data(self.ON_ENTRY_CALLBACKS)
|
|
546
|
+
|
|
547
|
+
inputs_subprocess = self._set_inputs()
|
|
548
|
+
# Run the subprocess
|
|
549
|
+
try:
|
|
550
|
+
self._run_subprocess(inputs_subprocess)
|
|
551
|
+
self._opt_status = self.RUN_COMPLETED
|
|
552
|
+
except CallbackReturnError:
|
|
553
|
+
# Asked to return by the callback
|
|
554
|
+
self._opt_status = self.EARLY_RETURN
|
|
555
|
+
|
|
556
|
+
# Set the output data
|
|
557
|
+
self._set_outputs()
|
|
558
|
+
|
|
559
|
+
# Run the on exit callback(s)
|
|
560
|
+
self.run_callbacks_from_local_data(self.ON_EXIT_CALLBACKS)
|
|
561
|
+
|
|
562
|
+
def get_process_flow(self) -> BaseProcessFlow:
|
|
563
|
+
"""Get the process flow.
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
The process flow.
|
|
567
|
+
"""
|
|
568
|
+
return self.scenario.get_process_flow()
|