pyfemtet 0.5.3__py3-none-any.whl → 0.6.0__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.
Potentially problematic release.
This version of pyfemtet might be problematic. Click here for more details.
- pyfemtet/__init__.py +1 -1
- pyfemtet/{message → _message}/locales/ja/LC_MESSAGES/messages.po +89 -77
- pyfemtet/{message → _message}/locales/messages.pot +88 -76
- pyfemtet/{message → _message}/messages.py +1 -1
- pyfemtet/_warning.py +23 -0
- pyfemtet/dispatch_extensions/__init__.py +12 -0
- pyfemtet/{dispatch_extensions.py → dispatch_extensions/_impl.py} +45 -43
- pyfemtet/logger/__init__.py +3 -0
- pyfemtet/{logger.py → logger/_impl.py} +12 -6
- pyfemtet/opt/__init__.py +3 -0
- pyfemtet/opt/_femopt.py +265 -68
- pyfemtet/opt/_femopt_core.py +111 -68
- pyfemtet/opt/_test_utils/record_history.py +1 -1
- pyfemtet/opt/interface/__init__.py +0 -1
- pyfemtet/opt/interface/_base.py +3 -3
- pyfemtet/opt/interface/_femtet.py +116 -59
- pyfemtet/opt/interface/_femtet_with_nx/_interface.py +35 -12
- pyfemtet/opt/interface/_femtet_with_sldworks.py +22 -2
- pyfemtet/opt/optimizer/__init__.py +5 -1
- pyfemtet/opt/optimizer/_base.py +81 -55
- pyfemtet/opt/optimizer/{_optuna_botorchsampler_parameter_constraint_helper.py → _optuna/_botorch_patch/enable_nonlinear_constraint.py} +10 -127
- pyfemtet/opt/optimizer/{_optuna.py → _optuna/_optuna.py} +122 -19
- pyfemtet/opt/optimizer/_optuna/_pof_botorch.py +1833 -0
- pyfemtet/opt/optimizer/_scipy.py +20 -5
- pyfemtet/opt/optimizer/_scipy_scalar.py +20 -5
- pyfemtet/opt/prediction/{base.py → _base.py} +3 -2
- pyfemtet/opt/prediction/single_task_gp.py +10 -5
- pyfemtet/opt/samples/femprj_sample/constrained_pipe.py +2 -2
- pyfemtet/opt/samples/femprj_sample/her_ex40_parametric.py +2 -2
- pyfemtet/opt/visualization/{base.py → _base.py} +1 -1
- pyfemtet/opt/visualization/{complex_components → _complex_components}/alert_region.py +2 -2
- pyfemtet/opt/visualization/{complex_components → _complex_components}/control_femtet.py +3 -3
- pyfemtet/opt/visualization/{complex_components → _complex_components}/main_figure_creator.py +1 -1
- pyfemtet/opt/visualization/{complex_components → _complex_components}/main_graph.py +5 -5
- pyfemtet/opt/visualization/{complex_components → _complex_components}/pm_graph.py +5 -5
- pyfemtet/opt/visualization/{complex_components → _complex_components}/pm_graph_creator.py +2 -2
- pyfemtet/opt/visualization/_create_wrapped_components.py +2 -2
- pyfemtet/opt/visualization/_process_monitor/__init__.py +0 -0
- pyfemtet/opt/visualization/{process_monitor → _process_monitor}/application.py +3 -3
- pyfemtet/opt/visualization/{process_monitor → _process_monitor}/pages.py +10 -10
- pyfemtet/opt/visualization/_wrapped_components/__init__.py +0 -0
- pyfemtet/opt/visualization/{wrapped_components → _wrapped_components}/dbc.py +1 -1
- pyfemtet/opt/visualization/{wrapped_components → _wrapped_components}/dcc.py +1 -1
- pyfemtet/opt/visualization/{wrapped_components → _wrapped_components}/html.py +1 -1
- pyfemtet/opt/visualization/result_viewer/application.py +4 -4
- pyfemtet/opt/visualization/result_viewer/pages.py +9 -9
- {pyfemtet-0.5.3.dist-info → pyfemtet-0.6.0.dist-info}/METADATA +2 -2
- {pyfemtet-0.5.3.dist-info → pyfemtet-0.6.0.dist-info}/RECORD +60 -56
- {pyfemtet-0.5.3.dist-info → pyfemtet-0.6.0.dist-info}/WHEEL +1 -1
- pyfemtet/message/locales/ja/LC_MESSAGES/messages.mo +0 -0
- pyfemtet/opt/samples/femprj_sample/.gitignore +0 -2
- /pyfemtet/{message → _message}/1. make_pot.bat +0 -0
- /pyfemtet/{message → _message}/2. make_mo.bat +0 -0
- /pyfemtet/{message → _message}/__init__.py +0 -0
- /pyfemtet/{message → _message}/babel.cfg +0 -0
- /pyfemtet/opt/{visualization/complex_components → optimizer/_optuna}/__init__.py +0 -0
- /pyfemtet/opt/{visualization/process_monitor → optimizer/_optuna/_botorch_patch}/__init__.py +0 -0
- /pyfemtet/opt/{parameter.py → optimizer/parameter.py} +0 -0
- /pyfemtet/opt/visualization/{wrapped_components → _complex_components}/__init__.py +0 -0
- /pyfemtet/opt/visualization/{wrapped_components → _wrapped_components}/str_enum.py +0 -0
- {pyfemtet-0.5.3.dist-info → pyfemtet-0.6.0.dist-info}/LICENSE +0 -0
- {pyfemtet-0.5.3.dist-info → pyfemtet-0.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -10,10 +10,30 @@ from pythoncom import CoInitialize, CoUninitialize
|
|
|
10
10
|
|
|
11
11
|
from pyfemtet.core import ModelError
|
|
12
12
|
from pyfemtet.opt.interface import FemtetInterface, logger
|
|
13
|
-
from pyfemtet.
|
|
13
|
+
from pyfemtet._message import Msg
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class FemtetWithSolidworksInterface(FemtetInterface):
|
|
17
|
+
"""Control Femtet and Solidworks.
|
|
18
|
+
|
|
19
|
+
Using this class, you can import CAD files created
|
|
20
|
+
in Solidworks through the Parasolid format into a
|
|
21
|
+
Femtet project. It allows you to pass design
|
|
22
|
+
variables to Solidworks, update the model, and
|
|
23
|
+
perform analysis using the updated model in Femtet.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
sldprt_path (str):
|
|
28
|
+
The path to .sldprt file containing the
|
|
29
|
+
CAD data from which the import is made.
|
|
30
|
+
**kwargs:
|
|
31
|
+
For other arguments, please refer to the
|
|
32
|
+
:class:`FemtetInterface` class.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
17
37
|
# 定数の宣言
|
|
18
38
|
swThisConfiguration = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swInConfigurationOpts_e.html
|
|
19
39
|
swAllConfiguration = 2
|
|
@@ -76,7 +96,7 @@ class FemtetWithSolidworksInterface(FemtetInterface):
|
|
|
76
96
|
CoInitialize()
|
|
77
97
|
self.initialize_sldworks_connection()
|
|
78
98
|
|
|
79
|
-
def update_model(self, parameters: pd.DataFrame):
|
|
99
|
+
def update_model(self, parameters: pd.DataFrame, with_warning=False):
|
|
80
100
|
"""Update .x_t"""
|
|
81
101
|
|
|
82
102
|
self.parameters = parameters.copy()
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
from pyfemtet.opt.optimizer._base import AbstractOptimizer, logger, OptimizationMethodChecker
|
|
2
|
-
from pyfemtet.opt.optimizer._optuna import OptunaOptimizer
|
|
2
|
+
from pyfemtet.opt.optimizer._optuna._optuna import OptunaOptimizer
|
|
3
3
|
from pyfemtet.opt.optimizer._scipy import ScipyOptimizer
|
|
4
4
|
from pyfemtet.opt.optimizer._scipy_scalar import ScipyScalarOptimizer
|
|
5
5
|
|
|
6
|
+
from pyfemtet.opt.optimizer._optuna._pof_botorch import PoFBoTorchSampler, PoFConfig
|
|
7
|
+
|
|
6
8
|
__all__ = [
|
|
7
9
|
'ScipyScalarOptimizer',
|
|
8
10
|
'ScipyOptimizer',
|
|
9
11
|
'OptunaOptimizer',
|
|
10
12
|
'AbstractOptimizer',
|
|
11
13
|
'logger',
|
|
14
|
+
'PoFBoTorchSampler',
|
|
15
|
+
'PoFConfig',
|
|
12
16
|
]
|
pyfemtet/opt/optimizer/_base.py
CHANGED
|
@@ -8,13 +8,12 @@ from time import sleep
|
|
|
8
8
|
|
|
9
9
|
# 3rd-party
|
|
10
10
|
import numpy as np
|
|
11
|
-
import pandas as pd
|
|
12
11
|
|
|
13
12
|
# pyfemtet relative
|
|
14
|
-
from pyfemtet.opt.interface import
|
|
13
|
+
from pyfemtet.opt.interface import FEMInterface
|
|
15
14
|
from pyfemtet.opt._femopt_core import OptimizationStatus, Objective, Constraint
|
|
16
|
-
from pyfemtet.
|
|
17
|
-
from pyfemtet.opt.parameter import ExpressionEvaluator
|
|
15
|
+
from pyfemtet._message import Msg
|
|
16
|
+
from pyfemtet.opt.optimizer.parameter import ExpressionEvaluator, Parameter
|
|
18
17
|
|
|
19
18
|
# logger
|
|
20
19
|
import logging
|
|
@@ -117,17 +116,11 @@ class AbstractOptimizer(ABC):
|
|
|
117
116
|
fem (FEMInterface): The finite element method object.
|
|
118
117
|
fem_class (type): The class of the finite element method object.
|
|
119
118
|
fem_kwargs (dict): The keyword arguments used to instantiate the finite element method object.
|
|
120
|
-
|
|
121
|
-
objectives (dict): A dictionary containing the objective functions used in the optimization.
|
|
122
|
-
constraints (dict): A dictionary containing the constraint functions used in the optimization.
|
|
123
|
-
entire_status (OptimizationStatus): The status of the entire optimization process.
|
|
119
|
+
variables (ExpressionEvaluator): The variables using optimization process including parameters.
|
|
120
|
+
objectives (dict[str, Objective]): A dictionary containing the objective functions used in the optimization.
|
|
121
|
+
constraints (dict[str, Constraint]): A dictionary containing the constraint functions used in the optimization.
|
|
124
122
|
history (History): An actor object that records the history of each iteration in the optimization process.
|
|
125
|
-
worker_status (OptimizationStatus): The status of each worker in a distributed computing environment.
|
|
126
|
-
message (str): A message associated with the current state of the optimization process.
|
|
127
123
|
seed (int or None): The random seed used for random number generation during the optimization process.
|
|
128
|
-
timeout (float or int or None): The maximum time allowed for each iteration of the optimization process. If exceeded, it will be interrupted and terminated early.
|
|
129
|
-
n_trials (int or None): The maximum number of trials allowed for each iteration of the optimization process. If exceeded, it will be interrupted and terminated early.
|
|
130
|
-
is_cluster (bool): Flag indicating if running on a distributed computing cluster.
|
|
131
124
|
|
|
132
125
|
"""
|
|
133
126
|
|
|
@@ -149,23 +142,46 @@ class AbstractOptimizer(ABC):
|
|
|
149
142
|
self.subprocess_idx = None
|
|
150
143
|
self._exception = None
|
|
151
144
|
self.method_checker: OptimizationMethodChecker = OptimizationMethodChecker(self)
|
|
145
|
+
self._retry_counter = 0
|
|
146
|
+
|
|
147
|
+
# ===== algorithm specific methods =====
|
|
148
|
+
@abstractmethod
|
|
149
|
+
def run(self) -> None:
|
|
150
|
+
"""Start optimization."""
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
# ----- FEMOpt interfaces -----
|
|
154
|
+
@abstractmethod
|
|
155
|
+
def _setup_before_parallel(self, *args, **kwargs):
|
|
156
|
+
"""Setup before parallel processes are launched."""
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
# ===== calc =====
|
|
160
|
+
def f(self, x: np.ndarray) -> list[np.ndarray]:
|
|
161
|
+
"""Calculate objectives and constraints.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
x (np.ndarray): Optimization parameters.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
list[np.ndarray]:
|
|
168
|
+
The list of internal objective values,
|
|
169
|
+
un-normalized objective values and
|
|
170
|
+
constraint values.
|
|
171
|
+
|
|
172
|
+
"""
|
|
152
173
|
|
|
153
|
-
def f(self, x):
|
|
154
|
-
"""Get x, update fem analysis, return objectives (and constraints)."""
|
|
155
|
-
# interruption の実装は具象クラスに任せる
|
|
156
174
|
|
|
157
175
|
if isinstance(x, np.float64):
|
|
158
176
|
x = np.array([x])
|
|
159
177
|
|
|
160
178
|
# Optimizer の x の更新
|
|
161
179
|
self.set_parameter_values(x)
|
|
162
|
-
|
|
163
|
-
logger.info('---------------------')
|
|
164
180
|
logger.info(f'input: {x}')
|
|
165
181
|
|
|
166
182
|
# FEM の更新
|
|
167
|
-
logger.debug('fem.update() start')
|
|
168
183
|
try:
|
|
184
|
+
logger.info(f'Solving FEM...')
|
|
169
185
|
df_to_fem = self.variables.get_variables(
|
|
170
186
|
format='df',
|
|
171
187
|
filter_pass_to_fem=True
|
|
@@ -176,25 +192,20 @@ class AbstractOptimizer(ABC):
|
|
|
176
192
|
logger.info(f'{type(e).__name__} : {e}')
|
|
177
193
|
logger.info(Msg.INFO_EXCEPTION_DURING_FEM_ANALYSIS)
|
|
178
194
|
logger.info(x)
|
|
179
|
-
raise e # may be just a ModelError, etc.
|
|
195
|
+
raise e # may be just a ModelError, etc. Handling them in Concrete classes.
|
|
180
196
|
|
|
181
197
|
# y, _y, c の更新
|
|
182
|
-
logger.debug('calculate y start')
|
|
183
198
|
y = [obj.calc(self.fem) for obj in self.objectives.values()]
|
|
184
199
|
|
|
185
|
-
logger.debug('calculate _y start')
|
|
186
200
|
_y = [obj.convert(value) for obj, value in zip(self.objectives.values(), y)]
|
|
187
201
|
|
|
188
|
-
logger.debug('calculate c start')
|
|
189
202
|
c = [cns.calc(self.fem) for cns in self.constraints.values()]
|
|
190
203
|
|
|
191
|
-
|
|
192
|
-
|
|
204
|
+
# register to history
|
|
193
205
|
df_to_opt = self.variables.get_variables(
|
|
194
206
|
format='df',
|
|
195
207
|
filter_parameter=True,
|
|
196
208
|
)
|
|
197
|
-
|
|
198
209
|
self.history.record(
|
|
199
210
|
df_to_opt,
|
|
200
211
|
self.objectives,
|
|
@@ -202,36 +213,29 @@ class AbstractOptimizer(ABC):
|
|
|
202
213
|
y,
|
|
203
214
|
c,
|
|
204
215
|
self.message,
|
|
205
|
-
postprocess_func=self.fem.
|
|
206
|
-
postprocess_args=self.fem.
|
|
216
|
+
postprocess_func=self.fem._postprocess_func,
|
|
217
|
+
postprocess_args=self.fem._create_postprocess_args(),
|
|
207
218
|
)
|
|
208
219
|
|
|
209
|
-
logger.
|
|
210
|
-
|
|
211
|
-
logger.info(f'output: {_y}')
|
|
220
|
+
logger.info(f'output: {y}')
|
|
212
221
|
|
|
213
222
|
return np.array(y), np.array(_y), np.array(c)
|
|
214
223
|
|
|
215
|
-
|
|
216
|
-
"""Reconstruct FEMInterface in a subprocess."""
|
|
217
|
-
# reconstruct fem
|
|
218
|
-
if not skip_reconstruct:
|
|
219
|
-
self.fem = self.fem_class(**self.fem_kwargs)
|
|
220
|
-
|
|
221
|
-
# COM 定数の restore
|
|
222
|
-
for obj in self.objectives.values():
|
|
223
|
-
obj._restore_constants()
|
|
224
|
-
for cns in self.constraints.values():
|
|
225
|
-
cns._restore_constants()
|
|
226
|
-
|
|
224
|
+
# ===== parameter processing =====
|
|
227
225
|
def get_parameter(self, format='dict'):
|
|
228
226
|
"""Returns the parameters in the specified format.
|
|
229
227
|
|
|
230
228
|
Args:
|
|
231
|
-
format (str, optional):
|
|
229
|
+
format (str, optional):
|
|
230
|
+
The desired format of the parameters.
|
|
231
|
+
Can be 'df' (DataFrame),
|
|
232
|
+
'values' (np.ndarray),
|
|
233
|
+
'dict' or
|
|
234
|
+
'raw' (list of Variable object).
|
|
235
|
+
Defaults to 'dict'.
|
|
232
236
|
|
|
233
237
|
Returns:
|
|
234
|
-
|
|
238
|
+
The parameters in the specified format.
|
|
235
239
|
|
|
236
240
|
Raises:
|
|
237
241
|
ValueError: If an invalid format is provided.
|
|
@@ -239,11 +243,14 @@ class AbstractOptimizer(ABC):
|
|
|
239
243
|
"""
|
|
240
244
|
return self.variables.get_variables(format=format, filter_parameter=True)
|
|
241
245
|
|
|
242
|
-
def set_parameter(self, params: dict) -> None:
|
|
246
|
+
def set_parameter(self, params: dict[str, float]) -> None:
|
|
243
247
|
"""Update parameter.
|
|
244
248
|
|
|
245
249
|
Args:
|
|
246
|
-
params (dict):
|
|
250
|
+
params (dict):
|
|
251
|
+
Key is the name of parameter and
|
|
252
|
+
the value is the value of it.
|
|
253
|
+
The partial set is available.
|
|
247
254
|
|
|
248
255
|
"""
|
|
249
256
|
for name, value in params.items():
|
|
@@ -255,11 +262,25 @@ class AbstractOptimizer(ABC):
|
|
|
255
262
|
|
|
256
263
|
Args:
|
|
257
264
|
values (np.ndarray): Values of all parameters.
|
|
265
|
+
|
|
258
266
|
"""
|
|
259
267
|
prm_names = self.variables.get_parameter_names()
|
|
260
268
|
assert len(values) == len(prm_names)
|
|
261
269
|
self.set_parameter({k: v for k, v in zip(prm_names, values)})
|
|
262
270
|
|
|
271
|
+
# ===== FEMOpt interfaces =====
|
|
272
|
+
def _reconstruct_fem(self, skip_reconstruct=False):
|
|
273
|
+
"""Reconstruct FEMInterface in a subprocess."""
|
|
274
|
+
# reconstruct fem
|
|
275
|
+
if not skip_reconstruct:
|
|
276
|
+
self.fem = self.fem_class(**self.fem_kwargs)
|
|
277
|
+
|
|
278
|
+
# COM 定数の restore
|
|
279
|
+
for obj in self.objectives.values():
|
|
280
|
+
obj._restore_constants()
|
|
281
|
+
for cns in self.constraints.values():
|
|
282
|
+
cns._restore_constants()
|
|
283
|
+
|
|
263
284
|
def _check_interruption(self):
|
|
264
285
|
""""""
|
|
265
286
|
if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
|
|
@@ -275,6 +296,7 @@ class AbstractOptimizer(ABC):
|
|
|
275
296
|
if not self.worker_status.get() == OptimizationStatus.CRASHED:
|
|
276
297
|
self.worker_status.set(OptimizationStatus.TERMINATED)
|
|
277
298
|
|
|
299
|
+
# run via FEMOpt (considering parallel processing)
|
|
278
300
|
def _run(
|
|
279
301
|
self,
|
|
280
302
|
subprocess_idx,
|
|
@@ -331,12 +353,16 @@ class AbstractOptimizer(ABC):
|
|
|
331
353
|
|
|
332
354
|
return self._exception
|
|
333
355
|
|
|
334
|
-
@abstractmethod
|
|
335
|
-
def run(self) -> None:
|
|
336
|
-
"""Start calculation using optimization library."""
|
|
337
|
-
pass
|
|
338
356
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
pass
|
|
357
|
+
if __name__ == '__main__':
|
|
358
|
+
class Optimizer(AbstractOptimizer):
|
|
359
|
+
def run(self): pass
|
|
360
|
+
def _setup_before_parallel(self, *args, **kwargs): pass
|
|
361
|
+
|
|
362
|
+
opt = Optimizer()
|
|
363
|
+
opt.set_parameter(
|
|
364
|
+
dict(
|
|
365
|
+
prm1=0.,
|
|
366
|
+
prm2=1.,
|
|
367
|
+
)
|
|
368
|
+
)
|
|
@@ -15,48 +15,14 @@ from botorch.optim.initializers import gen_batch_initial_conditions
|
|
|
15
15
|
|
|
16
16
|
from pyfemtet.opt._femopt_core import Constraint
|
|
17
17
|
from pyfemtet.opt.optimizer import OptunaOptimizer, logger
|
|
18
|
-
from pyfemtet.
|
|
18
|
+
from pyfemtet._message import Msg
|
|
19
19
|
|
|
20
|
-
from time import time
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
__all__ = ['do_patch']
|
|
24
20
|
|
|
25
21
|
|
|
26
22
|
BotorchConstraint = Callable[[Tensor], Tensor]
|
|
27
23
|
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
study: Study,
|
|
31
|
-
constraints: dict[str, Constraint],
|
|
32
|
-
opt: OptunaOptimizer,
|
|
33
|
-
):
|
|
34
|
-
"""BoTorchSampler の optimize_acqf をパッチし、パラメータ拘束が実施できるようにします。
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
study (Study): Optuna study. Use to calculate bounds.
|
|
38
|
-
constraints (dict[str, Constraint]): Constraints.
|
|
39
|
-
opt (OptunaOptimizer): OptunaOptimizer.
|
|
40
|
-
"""
|
|
41
|
-
import optuna_integration
|
|
42
|
-
|
|
43
|
-
from optuna_integration import version
|
|
44
|
-
if int(version.__version__.split('.')[0]) >= 4:
|
|
45
|
-
target_fun = optuna_integration.botorch.botorch.optimize_acqf
|
|
46
|
-
else:
|
|
47
|
-
target_fun = optuna_integration.botorch.optimize_acqf
|
|
48
|
-
|
|
49
|
-
new_fun: callable = OptimizeReplacedACQF(target_fun)
|
|
50
|
-
new_fun.set_constraints(list(constraints.values()))
|
|
51
|
-
new_fun.set_study(study)
|
|
52
|
-
new_fun.set_opt(opt)
|
|
53
|
-
|
|
54
|
-
if int(version.__version__.split('.')[0]) >= 4:
|
|
55
|
-
optuna_integration.botorch.botorch.optimize_acqf = new_fun
|
|
56
|
-
else:
|
|
57
|
-
optuna_integration.botorch.optimize_acqf = new_fun
|
|
58
|
-
|
|
59
|
-
|
|
25
|
+
# 拘束関数に pytorch の自動微分機能を適用するためのクラス
|
|
60
26
|
class GeneralFunctionWithForwardDifference(torch.autograd.Function):
|
|
61
27
|
"""自作関数を pytorch で自動微分するためのクラスです。
|
|
62
28
|
|
|
@@ -85,6 +51,8 @@ class GeneralFunctionWithForwardDifference(torch.autograd.Function):
|
|
|
85
51
|
return None, diff
|
|
86
52
|
|
|
87
53
|
|
|
54
|
+
# ユーザー定義関数 (pyfemtet.opt.Constraint) を受け取り、
|
|
55
|
+
# botorch で処理できる callable オブジェクトを作成するクラス
|
|
88
56
|
class ConvertedConstraint:
|
|
89
57
|
"""ユーザーが定義した Constraint を botorch で処理できる形式に変換します。
|
|
90
58
|
|
|
@@ -116,6 +84,7 @@ class ConvertedConstraint:
|
|
|
116
84
|
return Tensor([self._constraint.ub - c])
|
|
117
85
|
|
|
118
86
|
|
|
87
|
+
# list[pyfemtet.opt.Constraint] について、正規化された入力に対し、 feasible or not を返す関数
|
|
119
88
|
def is_feasible(study: Study, constraints: list[Constraint], norm_x: np.ndarray, opt: OptunaOptimizer) -> bool:
|
|
120
89
|
feasible = True
|
|
121
90
|
cns: Constraint
|
|
@@ -132,6 +101,7 @@ def is_feasible(study: Study, constraints: list[Constraint], norm_x: np.ndarray,
|
|
|
132
101
|
return feasible
|
|
133
102
|
|
|
134
103
|
|
|
104
|
+
# 正規化された入力を受けて pyfemtet.opt.Constraint を評価する関数
|
|
135
105
|
def evaluate_pyfemtet_cns(study: Study, cns: Constraint, norm_x: np.ndarray, opt: OptunaOptimizer) -> float:
|
|
136
106
|
"""Evaluate given constraint function by given NORMALIZED x.
|
|
137
107
|
|
|
@@ -163,6 +133,9 @@ def evaluate_pyfemtet_cns(study: Study, cns: Constraint, norm_x: np.ndarray, opt
|
|
|
163
133
|
return cns.calc(opt.fem)
|
|
164
134
|
|
|
165
135
|
|
|
136
|
+
# botorch の optimize_acqf で非線形拘束を使えるようにするクラス。以下を備える。
|
|
137
|
+
# - 渡すパラメータ nonlinear_constraints を作成する
|
|
138
|
+
# - gen_initial_conditions で feasible なものを返すラッパー関数
|
|
166
139
|
class NonlinearInequalityConstraints:
|
|
167
140
|
"""botorch の optimize_acqf に parameter constraints を設定するための引数を作成します。"""
|
|
168
141
|
|
|
@@ -191,7 +164,7 @@ class NonlinearInequalityConstraints:
|
|
|
191
164
|
feasible_q_list = []
|
|
192
165
|
for each_q in each_num_restarts:
|
|
193
166
|
norm_x: np.ndarray = each_q.numpy() # normalized parameters
|
|
194
|
-
|
|
167
|
+
|
|
195
168
|
if is_feasible(self._study, self._constraints, norm_x, self._opt):
|
|
196
169
|
feasible_q_list.append(each_q) # Keep only feasible rows
|
|
197
170
|
|
|
@@ -242,93 +215,3 @@ class NonlinearInequalityConstraints:
|
|
|
242
215
|
nonlinear_inequality_constraints=self._nonlinear_inequality_constraints,
|
|
243
216
|
ic_generator=self._generate_feasible_initial_conditions,
|
|
244
217
|
)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
class AcquisitionFunctionWithPenalty(AcquisitionFunction):
|
|
248
|
-
"""獲得関数に infeasible 項を追加します。"""
|
|
249
|
-
|
|
250
|
-
# noinspection PyAttributeOutsideInit
|
|
251
|
-
def set_acqf(self, acqf):
|
|
252
|
-
self._acqf = acqf
|
|
253
|
-
|
|
254
|
-
# noinspection PyAttributeOutsideInit
|
|
255
|
-
def set_constraints(self, constraints: list[Constraint]):
|
|
256
|
-
self._constraints: list[Constraint] = constraints
|
|
257
|
-
|
|
258
|
-
# noinspection PyAttributeOutsideInit
|
|
259
|
-
def set_study(self, study: Study):
|
|
260
|
-
self._study: Study = study
|
|
261
|
-
|
|
262
|
-
# noinspection PyAttributeOutsideInit
|
|
263
|
-
def set_opt(self, opt: OptunaOptimizer):
|
|
264
|
-
self._opt = opt
|
|
265
|
-
|
|
266
|
-
def forward(self, X: "Tensor") -> "Tensor":
|
|
267
|
-
"""
|
|
268
|
-
|
|
269
|
-
Args:
|
|
270
|
-
X (Tensor): batch_size x 1 x n_params tensor.
|
|
271
|
-
|
|
272
|
-
Returns:
|
|
273
|
-
Tensor: batch_size tensor.
|
|
274
|
-
|
|
275
|
-
"""
|
|
276
|
-
base = self._acqf.forward(X)
|
|
277
|
-
|
|
278
|
-
norm_x: np.ndarray
|
|
279
|
-
for i, _norm_x in enumerate(X.detach().numpy()):
|
|
280
|
-
|
|
281
|
-
cns: Constraint
|
|
282
|
-
for cns in self._constraints:
|
|
283
|
-
feasible = is_feasible(self._study, [cns], _norm_x[0], self._opt)
|
|
284
|
-
if not feasible:
|
|
285
|
-
base[i] = base[i] * 0. # ペナルティ
|
|
286
|
-
break
|
|
287
|
-
|
|
288
|
-
return base
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
class OptimizeReplacedACQF(partial):
|
|
292
|
-
"""optimize_acqf をこの partial 関数に置き換えます。"""
|
|
293
|
-
|
|
294
|
-
# noinspection PyAttributeOutsideInit
|
|
295
|
-
def set_constraints(self, constraints: list[Constraint]):
|
|
296
|
-
self._constraints: list[Constraint] = constraints
|
|
297
|
-
|
|
298
|
-
# noinspection PyAttributeOutsideInit
|
|
299
|
-
def set_study(self, study: Study):
|
|
300
|
-
self._study: Study = study
|
|
301
|
-
|
|
302
|
-
# noinspection PyAttributeOutsideInit
|
|
303
|
-
def set_opt(self, opt: OptunaOptimizer):
|
|
304
|
-
self._opt = opt
|
|
305
|
-
|
|
306
|
-
def __call__(self, *args, **kwargs):
|
|
307
|
-
"""置き換え先の関数の処理内容です。
|
|
308
|
-
|
|
309
|
-
kwargs を横入りして追記することで拘束を実現します。
|
|
310
|
-
"""
|
|
311
|
-
|
|
312
|
-
logger.info(Msg.START_CANDIDATE_WITH_PARAMETER_CONSTRAINT)
|
|
313
|
-
|
|
314
|
-
# FEM の更新が必要な場合、時間がかかることが多いので警告を出す
|
|
315
|
-
if any([cns.using_fem for cns in self._constraints]):
|
|
316
|
-
logger.warning(Msg.WARN_UPDATE_FEM_PARAMETER_TOOK_A_LONG_TIME)
|
|
317
|
-
|
|
318
|
-
# 獲得関数に infeasible な場合のペナルティ項を追加します。
|
|
319
|
-
acqf = kwargs['acq_function']
|
|
320
|
-
new_acqf = AcquisitionFunctionWithPenalty(...)
|
|
321
|
-
new_acqf.set_acqf(acqf)
|
|
322
|
-
new_acqf.set_constraints(self._constraints)
|
|
323
|
-
new_acqf.set_study(self._study)
|
|
324
|
-
new_acqf.set_opt(self._opt)
|
|
325
|
-
kwargs['acq_function'] = new_acqf
|
|
326
|
-
|
|
327
|
-
# optimize_acqf の探索に parameter constraints を追加します。
|
|
328
|
-
nlic = NonlinearInequalityConstraints(self._study, self._constraints, self._opt)
|
|
329
|
-
kwargs.update(nlic.create_kwargs())
|
|
330
|
-
|
|
331
|
-
# replace other arguments
|
|
332
|
-
...
|
|
333
|
-
|
|
334
|
-
return super().__call__(*args, **kwargs)
|