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
|
@@ -3,18 +3,18 @@ from typing import Iterable
|
|
|
3
3
|
|
|
4
4
|
# built-in
|
|
5
5
|
import os
|
|
6
|
+
import inspect
|
|
6
7
|
|
|
7
8
|
# 3rd-party
|
|
8
|
-
import numpy as np
|
|
9
9
|
import optuna
|
|
10
10
|
from optuna.trial import TrialState
|
|
11
11
|
from optuna.study import MaxTrialsCallback
|
|
12
12
|
|
|
13
13
|
# pyfemtet relative
|
|
14
|
-
from pyfemtet.opt._femopt_core import OptimizationStatus, generate_lhs
|
|
14
|
+
from pyfemtet.opt._femopt_core import OptimizationStatus, generate_lhs
|
|
15
15
|
from pyfemtet.opt.optimizer import AbstractOptimizer, logger, OptimizationMethodChecker
|
|
16
16
|
from pyfemtet.core import MeshError, ModelError, SolveError
|
|
17
|
-
from pyfemtet.
|
|
17
|
+
from pyfemtet._message import Msg
|
|
18
18
|
|
|
19
19
|
# filter warnings
|
|
20
20
|
import warnings
|
|
@@ -37,6 +37,38 @@ class OptunaMethodChecker(OptimizationMethodChecker):
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class OptunaOptimizer(AbstractOptimizer):
|
|
40
|
+
"""Optimizer using ```optuna```.
|
|
41
|
+
|
|
42
|
+
This class provides an interface for the optimization
|
|
43
|
+
engine using Optuna. For more details, please refer to
|
|
44
|
+
the Optuna documentation.
|
|
45
|
+
|
|
46
|
+
See Also:
|
|
47
|
+
https://optuna.readthedocs.io/en/stable/reference/index.html
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
sampler_class (optuna.samplers.BaseSampler, optional):
|
|
51
|
+
A sampler class from Optuna. If not specified,
|
|
52
|
+
```optuna.samplers.TPESampler``` is specified.
|
|
53
|
+
This defines the sampling strategy used during
|
|
54
|
+
optimization. Defaults to None.
|
|
55
|
+
sampler_kwargs (dict, optional):
|
|
56
|
+
A dictionary of keyword arguments to be passed to
|
|
57
|
+
the sampler class. This allows for customization
|
|
58
|
+
of the sampling process. Defaults to None.
|
|
59
|
+
add_init_method (str or Iterable[str], optional):
|
|
60
|
+
A method or a collection of methods to be added
|
|
61
|
+
during initialization. This can be used to specify
|
|
62
|
+
additional setup procedures.
|
|
63
|
+
Currently, the only valid value is 'LHS'
|
|
64
|
+
(using Latin Hypercube Sampling).
|
|
65
|
+
Defaults to None.
|
|
66
|
+
|
|
67
|
+
Warnings:
|
|
68
|
+
Do not include ```constraints_func``` in ```sampler_kwargs```.
|
|
69
|
+
It is generated and provided by :func:`FEMOpt.add_constraint`.
|
|
70
|
+
|
|
71
|
+
"""
|
|
40
72
|
|
|
41
73
|
def __init__(
|
|
42
74
|
self,
|
|
@@ -54,17 +86,24 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
54
86
|
self.additional_initial_parameter = []
|
|
55
87
|
self.additional_initial_methods = add_init_method if hasattr(add_init_method, '__iter__') else [add_init_method]
|
|
56
88
|
self.method_checker = OptunaMethodChecker(self)
|
|
57
|
-
self._do_monkey_patch = False
|
|
58
89
|
|
|
59
90
|
def _objective(self, trial):
|
|
60
91
|
|
|
92
|
+
logger.info('')
|
|
93
|
+
if self._retry_counter == 0:
|
|
94
|
+
logger.info(f'===== trial {1 + len(self.history.get_df())} start =====')
|
|
95
|
+
else:
|
|
96
|
+
logger.info(f'===== trial {1 + len(self.history.get_df())} (retry {self._retry_counter}) start =====')
|
|
97
|
+
|
|
61
98
|
# 中断の確認 (FAIL loop に陥る対策)
|
|
62
99
|
if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
|
|
63
100
|
self.worker_status.set(OptimizationStatus.INTERRUPTING)
|
|
64
101
|
trial.study.stop() # 現在実行中の trial を最後にする
|
|
102
|
+
self._retry_counter = 0
|
|
65
103
|
return None # set TrialState FAIL
|
|
66
104
|
|
|
67
105
|
# candidate x and update parameters
|
|
106
|
+
logger.info('Searching new parameter set...')
|
|
68
107
|
for prm in self.variables.get_variables(format='raw', filter_parameter=True):
|
|
69
108
|
value = trial.suggest_float(
|
|
70
109
|
name=prm.name,
|
|
@@ -94,9 +133,11 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
94
133
|
if cns.ub is not None:
|
|
95
134
|
feasible = feasible and (cns.ub >= cns_value)
|
|
96
135
|
if not feasible:
|
|
136
|
+
logger.info('----- Out of constraint! -----')
|
|
97
137
|
logger.info(Msg.INFO_INFEASIBLE)
|
|
98
138
|
logger.info(f'Constraint: {cns.name}')
|
|
99
139
|
logger.info(self.variables.get_variables('dict', filter_parameter=True))
|
|
140
|
+
self._retry_counter += 1
|
|
100
141
|
raise optuna.TrialPruned() # set TrialState PRUNED because FAIL causes similar candidate loop.
|
|
101
142
|
|
|
102
143
|
# 計算
|
|
@@ -110,6 +151,15 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
110
151
|
trial.study.stop() # 現在実行中の trial を最後にする
|
|
111
152
|
return None # set TrialState FAIL
|
|
112
153
|
|
|
154
|
+
logger.warning('----- Infeasible! -----')
|
|
155
|
+
logger.warning(Msg.INFO_INFEASIBLE)
|
|
156
|
+
logger.warning(f'Hidden Constraint ({type(e).__name__})')
|
|
157
|
+
logger.warning(self.variables.get_variables('dict', filter_parameter=True))
|
|
158
|
+
logger.warning('Please consider to determine the cause'
|
|
159
|
+
'of the above error and modify the model'
|
|
160
|
+
'or analysis.')
|
|
161
|
+
|
|
162
|
+
self._retry_counter += 1
|
|
113
163
|
raise optuna.TrialPruned() # set TrialState PRUNED because FAIL causes similar candidate loop.
|
|
114
164
|
|
|
115
165
|
# 拘束 attr の更新
|
|
@@ -127,9 +177,11 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
127
177
|
if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
|
|
128
178
|
self.worker_status.set(OptimizationStatus.INTERRUPTING)
|
|
129
179
|
trial.study.stop() # 現在実行中の trial を最後にする
|
|
180
|
+
self._retry_counter = 0
|
|
130
181
|
return None # set TrialState FAIL
|
|
131
182
|
|
|
132
183
|
# 結果
|
|
184
|
+
self._retry_counter = 0
|
|
133
185
|
return tuple(_y)
|
|
134
186
|
|
|
135
187
|
def _constraint(self, trial):
|
|
@@ -263,10 +315,20 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
263
315
|
self.sampler_kwargs.update(
|
|
264
316
|
seed=seed
|
|
265
317
|
)
|
|
318
|
+
parameters = inspect.signature(self.sampler_class.__init__).parameters
|
|
319
|
+
sampler_kwargs = dict()
|
|
320
|
+
for k, v in self.sampler_kwargs.items():
|
|
321
|
+
if k in parameters.keys():
|
|
322
|
+
sampler_kwargs.update({k: v})
|
|
266
323
|
sampler = self.sampler_class(
|
|
267
|
-
**
|
|
324
|
+
**sampler_kwargs
|
|
268
325
|
)
|
|
269
326
|
|
|
327
|
+
from pyfemtet.opt.optimizer._optuna._pof_botorch import PoFBoTorchSampler
|
|
328
|
+
if isinstance(sampler, PoFBoTorchSampler):
|
|
329
|
+
sampler._pyfemtet_constraints = [cns for cns in self.constraints.values() if cns.strict]
|
|
330
|
+
sampler._pyfemtet_optimizer = self
|
|
331
|
+
|
|
270
332
|
# load study
|
|
271
333
|
study = optuna.load_study(
|
|
272
334
|
study_name=self.study_name,
|
|
@@ -274,21 +336,62 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
274
336
|
sampler=sampler,
|
|
275
337
|
)
|
|
276
338
|
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
339
|
+
# 一時的な実装。
|
|
340
|
+
# TPESampler の場合、リスタート時などの場合で、
|
|
341
|
+
# Pruned が多いとエラーを起こす挙動があるので、
|
|
342
|
+
# Pruned な Trial は remove したい。
|
|
343
|
+
# study.remove_trial がないので、一度ダミー
|
|
344
|
+
# study を作成して最適化終了後に結果をコピーする。
|
|
345
|
+
if isinstance(sampler, optuna.samplers.TPESampler):
|
|
346
|
+
tmp_db = f"tmp{self.subprocess_idx}.db"
|
|
347
|
+
if os.path.exists(tmp_db):
|
|
348
|
+
os.remove(tmp_db)
|
|
349
|
+
|
|
350
|
+
_study = optuna.create_study(
|
|
351
|
+
study_name="tmp",
|
|
352
|
+
storage=f"sqlite:///{tmp_db}",
|
|
353
|
+
sampler=sampler,
|
|
354
|
+
directions=['minimize']*len(self.objectives),
|
|
355
|
+
load_if_exists=False,
|
|
356
|
+
)
|
|
280
357
|
|
|
281
|
-
|
|
358
|
+
# 既存の trials のうち COMPLETE のものを取得
|
|
359
|
+
existing_trials = study.get_trials(states=(optuna.trial.TrialState.COMPLETE,))
|
|
360
|
+
_study.add_trials(existing_trials)
|
|
282
361
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
self.
|
|
286
|
-
self
|
|
362
|
+
# run
|
|
363
|
+
_study.optimize(
|
|
364
|
+
self._objective,
|
|
365
|
+
timeout=self.timeout,
|
|
366
|
+
callbacks=self.optimize_callbacks,
|
|
287
367
|
)
|
|
288
368
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
369
|
+
# trial.number と trial_id は _study への add_trials 時に
|
|
370
|
+
# 振りなおされるため重複したものをフィルタアウトするために
|
|
371
|
+
# datetime_start を利用。
|
|
372
|
+
added_trials = []
|
|
373
|
+
for _trial in _study.get_trials():
|
|
374
|
+
if _trial.datetime_start not in [t.datetime_start for t in existing_trials]:
|
|
375
|
+
added_trials.append(_trial)
|
|
376
|
+
|
|
377
|
+
# Write back added trials to the existing study.
|
|
378
|
+
study.add_trials(added_trials)
|
|
379
|
+
|
|
380
|
+
# clean up
|
|
381
|
+
from optuna.storages import get_storage
|
|
382
|
+
storage = get_storage(f"sqlite:///{tmp_db}")
|
|
383
|
+
storage.remove_session()
|
|
384
|
+
del _study
|
|
385
|
+
del storage
|
|
386
|
+
import gc
|
|
387
|
+
gc.collect()
|
|
388
|
+
if os.path.exists(tmp_db):
|
|
389
|
+
os.remove(tmp_db)
|
|
390
|
+
|
|
391
|
+
else:
|
|
392
|
+
# run
|
|
393
|
+
study.optimize(
|
|
394
|
+
self._objective,
|
|
395
|
+
timeout=self.timeout,
|
|
396
|
+
callbacks=self.optimize_callbacks,
|
|
397
|
+
)
|