pyfemtet 0.7.0__py3-none-any.whl → 0.8.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/locales/ja/LC_MESSAGES/messages.mo +0 -0
- pyfemtet/_message/locales/ja/LC_MESSAGES/messages.po +112 -90
- pyfemtet/_message/locales/messages.pot +105 -89
- pyfemtet/_message/messages.py +6 -2
- pyfemtet/_util/dask_util.py +10 -0
- pyfemtet/_util/excel_macro_util.py +16 -4
- pyfemtet/_util/excel_parse_util.py +138 -0
- pyfemtet/_util/sample.xlsx +0 -0
- pyfemtet/brep/__init__.py +0 -3
- pyfemtet/brep/_impl.py +7 -3
- pyfemtet/opt/_femopt.py +69 -31
- pyfemtet/opt/_femopt_core.py +100 -36
- pyfemtet/opt/advanced_samples/excel_ui/(ref) original_project.femprj +0 -0
- pyfemtet/opt/advanced_samples/excel_ui/femtet-macro.xlsm +0 -0
- pyfemtet/opt/advanced_samples/excel_ui/pyfemtet-core.py +291 -0
- pyfemtet/opt/advanced_samples/excel_ui/test-pyfemtet-core.cmd +22 -0
- pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_create_training_data.py +60 -0
- pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_create_training_data_jp.py +57 -0
- pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate.py +100 -0
- pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate_jp.py +90 -0
- pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_parametric.femprj +0 -0
- pyfemtet/opt/interface/__init__.py +2 -0
- pyfemtet/opt/interface/_base.py +3 -0
- pyfemtet/opt/interface/_excel_interface.py +565 -204
- pyfemtet/opt/interface/_femtet.py +26 -29
- pyfemtet/opt/interface/_surrogate/__init__.py +5 -0
- pyfemtet/opt/interface/_surrogate/_base.py +85 -0
- pyfemtet/opt/interface/_surrogate/_chaospy.py +71 -0
- pyfemtet/opt/interface/_surrogate/_singletaskgp.py +70 -0
- pyfemtet/opt/optimizer/_base.py +30 -19
- pyfemtet/opt/optimizer/_optuna/_optuna.py +20 -8
- pyfemtet/opt/optimizer/_optuna/_pof_botorch.py +60 -18
- pyfemtet/opt/prediction/_base.py +8 -0
- pyfemtet/opt/prediction/single_task_gp.py +85 -62
- pyfemtet/opt/visualization/_complex_components/main_figure_creator.py +5 -5
- pyfemtet/opt/visualization/_complex_components/main_graph.py +7 -1
- pyfemtet/opt/visualization/_complex_components/pm_graph.py +1 -1
- pyfemtet/opt/visualization/_process_monitor/application.py +2 -2
- pyfemtet/opt/visualization/_process_monitor/pages.py +1 -1
- pyfemtet/opt/visualization/result_viewer/pages.py +1 -1
- {pyfemtet-0.7.0.dist-info → pyfemtet-0.8.0.dist-info}/METADATA +3 -2
- {pyfemtet-0.7.0.dist-info → pyfemtet-0.8.0.dist-info}/RECORD +46 -29
- {pyfemtet-0.7.0.dist-info → pyfemtet-0.8.0.dist-info}/WHEEL +1 -1
- {pyfemtet-0.7.0.dist-info → pyfemtet-0.8.0.dist-info}/LICENSE +0 -0
- {pyfemtet-0.7.0.dist-info → pyfemtet-0.8.0.dist-info}/entry_points.txt +0 -0
|
@@ -49,6 +49,10 @@ def _post_activate_message(hwnd):
|
|
|
49
49
|
win32gui.PostMessage(hwnd, win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
class FailedToPostProcess(Exception):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
52
56
|
class FemtetInterface(FEMInterface):
|
|
53
57
|
"""Control Femtet from optimizer.
|
|
54
58
|
|
|
@@ -86,10 +90,6 @@ class FemtetInterface(FEMInterface):
|
|
|
86
90
|
it will be None and no parametric outputs are used
|
|
87
91
|
as objectives.
|
|
88
92
|
|
|
89
|
-
confirm_before_exit (bool):
|
|
90
|
-
Whether to confirm before (abnormal) termination.
|
|
91
|
-
Default is True.
|
|
92
|
-
|
|
93
93
|
**kwargs: Additional arguments from inherited classes.
|
|
94
94
|
|
|
95
95
|
Warning:
|
|
@@ -115,7 +115,6 @@ class FemtetInterface(FEMInterface):
|
|
|
115
115
|
allow_without_project: bool = False, # main でのみ True を許容したいので super() の引数にしない。
|
|
116
116
|
open_result_with_gui: bool = True,
|
|
117
117
|
parametric_output_indexes_use_as_objective: dict[int, str or float] = None,
|
|
118
|
-
confirm_before_exit: bool = True,
|
|
119
118
|
**kwargs # 継承されたクラスからの引数
|
|
120
119
|
):
|
|
121
120
|
|
|
@@ -145,7 +144,6 @@ class FemtetInterface(FEMInterface):
|
|
|
145
144
|
self.parametric_output_indexes_use_as_objective = parametric_output_indexes_use_as_objective
|
|
146
145
|
self._original_autosave_enabled = _get_autosave_enabled()
|
|
147
146
|
_set_autosave_enabled(False)
|
|
148
|
-
self.confirm_before_exit = confirm_before_exit
|
|
149
147
|
|
|
150
148
|
# dask サブプロセスのときは femprj を更新し connect_method を new にする
|
|
151
149
|
try:
|
|
@@ -183,14 +181,11 @@ class FemtetInterface(FEMInterface):
|
|
|
183
181
|
open_result_with_gui=self.open_result_with_gui,
|
|
184
182
|
parametric_output_indexes_use_as_objective=self.parametric_output_indexes_use_as_objective,
|
|
185
183
|
save_pdt=self.save_pdt,
|
|
186
|
-
confirm_before_exit=self.confirm_before_exit,
|
|
187
184
|
**kwargs
|
|
188
185
|
)
|
|
189
186
|
|
|
190
187
|
def __del__(self):
|
|
191
|
-
|
|
192
|
-
if self.quit_when_destruct:
|
|
193
|
-
self.quit()
|
|
188
|
+
self.quit()
|
|
194
189
|
# CoUninitialize() # Win32 exception occurred releasing IUnknown at 0x0000022427692748
|
|
195
190
|
|
|
196
191
|
def _connect_new_femtet(self):
|
|
@@ -253,11 +248,9 @@ class FemtetInterface(FEMInterface):
|
|
|
253
248
|
cmd = f'{sys.executable} -m win32com.client.makepy FemtetMacro'
|
|
254
249
|
os.system(cmd)
|
|
255
250
|
message = Msg.ERR_NO_MAKEPY
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if self.confirm_before_exit:
|
|
260
|
-
input('Press enter to finish...')
|
|
251
|
+
logger.error('================')
|
|
252
|
+
logger.error(message)
|
|
253
|
+
logger.error('================')
|
|
261
254
|
raise RuntimeError(message)
|
|
262
255
|
|
|
263
256
|
if self.Femtet is None:
|
|
@@ -541,24 +534,20 @@ class FemtetInterface(FEMInterface):
|
|
|
541
534
|
try:
|
|
542
535
|
variable_names = self.Femtet.GetVariableNames_py()
|
|
543
536
|
except AttributeError as e:
|
|
544
|
-
|
|
537
|
+
logger.error('================')
|
|
545
538
|
logger.error(Msg.ERR_CANNOT_ACCESS_API + 'GetVariableNames_py')
|
|
546
539
|
logger.error(Msg.CERTIFY_MACRO_VERSION)
|
|
547
|
-
|
|
548
|
-
if self.confirm_before_exit:
|
|
549
|
-
input(Msg.ENTER_TO_QUIT)
|
|
540
|
+
logger.error('================')
|
|
550
541
|
raise e
|
|
551
542
|
|
|
552
543
|
if variable_names is not None:
|
|
553
544
|
if param_name in variable_names:
|
|
554
545
|
return self.Femtet.GetVariableValue(param_name)
|
|
555
546
|
message = Msg.ERR_NO_SUCH_PARAMETER_IN_FEMTET
|
|
556
|
-
|
|
547
|
+
logger.error('================')
|
|
557
548
|
logger.error(message)
|
|
558
549
|
logger.error(f'`{param_name}` not in {variable_names}')
|
|
559
|
-
|
|
560
|
-
if self.confirm_before_exit:
|
|
561
|
-
input(Msg.ENTER_TO_QUIT)
|
|
550
|
+
logger.error('================')
|
|
562
551
|
raise RuntimeError(message)
|
|
563
552
|
else:
|
|
564
553
|
return None
|
|
@@ -739,7 +728,8 @@ class FemtetInterface(FEMInterface):
|
|
|
739
728
|
|
|
740
729
|
_set_autosave_enabled(self._original_autosave_enabled)
|
|
741
730
|
|
|
742
|
-
|
|
731
|
+
if self.quit_when_destruct:
|
|
732
|
+
_exit_or_force_terminate(timeout=timeout, Femtet=self.Femtet, force=True)
|
|
743
733
|
|
|
744
734
|
def _setup_before_parallel(self, client):
|
|
745
735
|
client.upload_file(
|
|
@@ -751,8 +741,15 @@ class FemtetInterface(FEMInterface):
|
|
|
751
741
|
return _version(Femtet=self.Femtet)
|
|
752
742
|
|
|
753
743
|
def _create_postprocess_args(self):
|
|
754
|
-
|
|
755
|
-
|
|
744
|
+
try:
|
|
745
|
+
file_content = self._create_result_file_content()
|
|
746
|
+
except FailedToPostProcess:
|
|
747
|
+
file_content = None
|
|
748
|
+
|
|
749
|
+
try:
|
|
750
|
+
jpg_content = self._create_jpg_content()
|
|
751
|
+
except FailedToPostProcess:
|
|
752
|
+
jpg_content = None
|
|
756
753
|
|
|
757
754
|
out = dict(
|
|
758
755
|
original_femprj_path=self.original_femprj_path,
|
|
@@ -800,7 +797,7 @@ class FemtetInterface(FEMInterface):
|
|
|
800
797
|
fun=self.Femtet.SavePDT,
|
|
801
798
|
args=(pdt_path, True),
|
|
802
799
|
return_value_if_failed=False,
|
|
803
|
-
if_error=
|
|
800
|
+
if_error=FailedToPostProcess,
|
|
804
801
|
error_message=Msg.ERR_FAILED_TO_SAVE_PDT,
|
|
805
802
|
is_Gaudi_method=False,
|
|
806
803
|
)
|
|
@@ -828,10 +825,10 @@ class FemtetInterface(FEMInterface):
|
|
|
828
825
|
self.Femtet.RedrawMode = True # 逐一の描画をオン
|
|
829
826
|
|
|
830
827
|
if not succeed:
|
|
831
|
-
raise
|
|
828
|
+
raise FailedToPostProcess(Msg.ERR_FAILED_TO_SAVE_JPG)
|
|
832
829
|
|
|
833
830
|
if not os.path.exists(jpg_path):
|
|
834
|
-
raise
|
|
831
|
+
raise FailedToPostProcess(Msg.ERR_JPG_NOT_FOUND)
|
|
835
832
|
|
|
836
833
|
with open(jpg_path, 'rb') as f:
|
|
837
834
|
content = f.read()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from typing import Optional, List, Any
|
|
2
|
+
from abc import ABC
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from pyfemtet.logger import get_module_logger
|
|
8
|
+
from pyfemtet.opt._femopt_core import History, Objective
|
|
9
|
+
from pyfemtet.opt.interface._base import FEMInterface
|
|
10
|
+
from pyfemtet.opt.optimizer._base import AbstractOptimizer
|
|
11
|
+
|
|
12
|
+
logger = get_module_logger('opt.interface', __name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SurrogateModelInterfaceBase(FEMInterface, ABC):
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
history_path: str = None,
|
|
19
|
+
history: History = None,
|
|
20
|
+
):
|
|
21
|
+
|
|
22
|
+
self.history: History
|
|
23
|
+
self.model: Any
|
|
24
|
+
self.prm: dict[str, float] = dict()
|
|
25
|
+
self.obj: dict[str, float] = dict()
|
|
26
|
+
self.df_prm: pd.DataFrame
|
|
27
|
+
self.df_obj: pd.DataFrame
|
|
28
|
+
|
|
29
|
+
# history_path が与えられた場合、history をコンストラクトする
|
|
30
|
+
if history_path is not None:
|
|
31
|
+
history = History(history_path=history_path)
|
|
32
|
+
|
|
33
|
+
# history が与えられるかコンストラクトされている場合
|
|
34
|
+
if history is not None:
|
|
35
|
+
# 学習データを準備する
|
|
36
|
+
df_prm = history.get_df()[history.prm_names]
|
|
37
|
+
df_obj = history.get_df()[history.obj_names]
|
|
38
|
+
|
|
39
|
+
# obj の名前を作る
|
|
40
|
+
for obj_name in history.obj_names:
|
|
41
|
+
self.obj[obj_name] = np.nan
|
|
42
|
+
|
|
43
|
+
# prm の名前を作る
|
|
44
|
+
for prm_name in history.prm_names:
|
|
45
|
+
self.prm[prm_name] = np.nan
|
|
46
|
+
|
|
47
|
+
self.history = history
|
|
48
|
+
|
|
49
|
+
# history から作らない場合、引数チェック
|
|
50
|
+
else:
|
|
51
|
+
# assert len(train_x) == len(train_y)
|
|
52
|
+
raise NotImplementedError
|
|
53
|
+
|
|
54
|
+
self.df_prm = df_prm
|
|
55
|
+
self.df_obj = df_obj
|
|
56
|
+
|
|
57
|
+
FEMInterface.__init__(
|
|
58
|
+
self,
|
|
59
|
+
history=history, # コンストラクト済み history を渡せば並列計算時も何もしなくてよい
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def filter_feasible(self, x: np.ndarray, y: np.ndarray, return_feasibility=False):
|
|
63
|
+
feasible_idx = np.where(~np.isnan(y.sum(axis=1)))
|
|
64
|
+
if return_feasibility:
|
|
65
|
+
# calculated or not
|
|
66
|
+
y = np.zeros_like(y)
|
|
67
|
+
y[feasible_idx] = 1.
|
|
68
|
+
# satisfy weak feasibility or not
|
|
69
|
+
infeasible_idx = np.where(~self.history.get_df()['feasible'].values)
|
|
70
|
+
y[infeasible_idx] = .0
|
|
71
|
+
return x, y.reshape((-1, 1))
|
|
72
|
+
else:
|
|
73
|
+
return x[feasible_idx], y[feasible_idx]
|
|
74
|
+
|
|
75
|
+
def _setup_after_parallel(self, *args, **kwargs):
|
|
76
|
+
|
|
77
|
+
opt: AbstractOptimizer = kwargs['opt']
|
|
78
|
+
obj: Objective
|
|
79
|
+
for obj_name, obj in opt.objectives.items():
|
|
80
|
+
obj.fun = lambda: self.obj[obj_name]
|
|
81
|
+
|
|
82
|
+
def update_parameter(self, parameters: pd.DataFrame, with_warning=False) -> Optional[List[str]]:
|
|
83
|
+
for i, row in parameters.iterrows():
|
|
84
|
+
name, value = row['name'], row['value']
|
|
85
|
+
self.prm[name] = value
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
from pyfemtet.logger import get_module_logger
|
|
5
|
+
from pyfemtet.opt.interface._surrogate._base import SurrogateModelInterfaceBase
|
|
6
|
+
from pyfemtet.opt.prediction.polynomial_expansion import PolynomialExpansionModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = get_module_logger('opt.interface', __name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PolynomialChaosInterface(SurrogateModelInterfaceBase):
|
|
13
|
+
|
|
14
|
+
model: PolynomialExpansionModel # surrogate_func_expansion
|
|
15
|
+
|
|
16
|
+
def train(self):
|
|
17
|
+
x, y = self.filter_feasible(self.df_prm.values, self.df_obj.values)
|
|
18
|
+
assert len(x) > 0, 'No feasible results in training data.'
|
|
19
|
+
self.model.fit(x, y)
|
|
20
|
+
|
|
21
|
+
def _setup_after_parallel(self, *args, **kwargs):
|
|
22
|
+
|
|
23
|
+
# # update objectives
|
|
24
|
+
# SurrogateModelInterfaceBase._setup_after_parallel(
|
|
25
|
+
# self, *args, **kwargs
|
|
26
|
+
# )
|
|
27
|
+
|
|
28
|
+
# train model
|
|
29
|
+
self.model = PolynomialExpansionModel()
|
|
30
|
+
self.model.set_bounds_from_history(self.history)
|
|
31
|
+
self.train()
|
|
32
|
+
|
|
33
|
+
def update(self, parameters: pd.DataFrame) -> None:
|
|
34
|
+
# self.prm 更新
|
|
35
|
+
SurrogateModelInterfaceBase.update_parameter(
|
|
36
|
+
self, parameters
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# history.prm_name 順に並べ替え
|
|
40
|
+
x = np.array([self.prm[k] for k in self.history.prm_names])
|
|
41
|
+
|
|
42
|
+
# prediction
|
|
43
|
+
dist_mean, _ = self.model.predict(x)
|
|
44
|
+
|
|
45
|
+
# 目的関数の更新
|
|
46
|
+
self.obj = {obj_name: value for obj_name, value in zip(self.history.obj_names, dist_mean)}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == '__main__':
|
|
50
|
+
import os
|
|
51
|
+
from pyfemtet.opt._femopt_core import History
|
|
52
|
+
|
|
53
|
+
os.chdir(os.path.dirname(__file__))
|
|
54
|
+
|
|
55
|
+
history = History(history_path='sample.csv')
|
|
56
|
+
fem = PolynomialChaosInterface(history=history)
|
|
57
|
+
|
|
58
|
+
import pandas as pd
|
|
59
|
+
df = history.get_df()
|
|
60
|
+
parameters = pd.DataFrame(
|
|
61
|
+
dict(
|
|
62
|
+
name=history.prm_names,
|
|
63
|
+
value=[1. for _ in history.prm_names]
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
fem._setup_after_parallel()
|
|
68
|
+
|
|
69
|
+
fem.update(
|
|
70
|
+
parameters
|
|
71
|
+
)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from scipy.stats.distributions import norm
|
|
4
|
+
|
|
5
|
+
from pyfemtet.core import SolveError
|
|
6
|
+
from pyfemtet.logger import get_module_logger
|
|
7
|
+
from pyfemtet.opt.interface._surrogate._base import SurrogateModelInterfaceBase
|
|
8
|
+
from pyfemtet.opt.prediction.single_task_gp import SingleTaskGPModel
|
|
9
|
+
|
|
10
|
+
from pyfemtet._message.messages import Message as Msg
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = get_module_logger('opt.interface', __name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PoFBoTorchInterface(SurrogateModelInterfaceBase):
|
|
17
|
+
model_f: SingleTaskGPModel
|
|
18
|
+
model: SingleTaskGPModel
|
|
19
|
+
threshold = 0.5
|
|
20
|
+
|
|
21
|
+
def train(self):
|
|
22
|
+
# df そのまま用いて training する
|
|
23
|
+
x, y = self.filter_feasible(self.df_prm.values, self.df_obj.values)
|
|
24
|
+
assert len(x) > 0, 'No feasible results in training data.'
|
|
25
|
+
self.model.fit(x, y)
|
|
26
|
+
|
|
27
|
+
def train_f(self):
|
|
28
|
+
# df そのまま用いて training する
|
|
29
|
+
x, y = self.filter_feasible(self.df_prm.values, self.df_obj.values, return_feasibility=True)
|
|
30
|
+
if y.min() == 1:
|
|
31
|
+
self.model_f.predict = lambda *args, **kwargs: (1., 0.001)
|
|
32
|
+
self.model_f.fit(x, y)
|
|
33
|
+
|
|
34
|
+
def _setup_after_parallel(self, *args, **kwargs):
|
|
35
|
+
|
|
36
|
+
# update objectives
|
|
37
|
+
SurrogateModelInterfaceBase._setup_after_parallel(
|
|
38
|
+
self, *args, **kwargs
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# model training
|
|
42
|
+
self.model = SingleTaskGPModel()
|
|
43
|
+
self.model.set_bounds_from_history(self.history)
|
|
44
|
+
self.train()
|
|
45
|
+
|
|
46
|
+
# model_f training
|
|
47
|
+
self.model_f = SingleTaskGPModel(is_noise_free=False)
|
|
48
|
+
self.model_f.set_bounds_from_history(self.history)
|
|
49
|
+
self.train_f()
|
|
50
|
+
|
|
51
|
+
def update(self, parameters: pd.DataFrame) -> None:
|
|
52
|
+
# self.prm 更新
|
|
53
|
+
SurrogateModelInterfaceBase.update_parameter(
|
|
54
|
+
self, parameters
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# history.prm_name 順に並べ替え
|
|
58
|
+
x = np.array([self.prm[k] for k in self.history.prm_names])
|
|
59
|
+
|
|
60
|
+
# feasibility の計算
|
|
61
|
+
mean_f, std_f = self.model_f.predict(np.array([x]))
|
|
62
|
+
pof = 1. - norm.cdf(self.threshold, loc=mean_f, scale=std_f)
|
|
63
|
+
if pof < self.threshold:
|
|
64
|
+
raise SolveError(Msg.INFO_POF_IS_LESS_THAN_THRESHOLD)
|
|
65
|
+
|
|
66
|
+
# 実際の計算(mean は history.obj_names 順)
|
|
67
|
+
mean, _ = self.model.predict(np.array([x]))
|
|
68
|
+
|
|
69
|
+
# 目的関数の更新
|
|
70
|
+
self.obj = {obj_name: value for obj_name, value in zip(self.history.obj_names, mean)}
|
pyfemtet/opt/optimizer/_base.py
CHANGED
|
@@ -155,12 +155,18 @@ class AbstractOptimizer(ABC):
|
|
|
155
155
|
"""Setup before parallel processes are launched."""
|
|
156
156
|
pass
|
|
157
157
|
|
|
158
|
+
def generate_infeasible_result(self):
|
|
159
|
+
y = np.full_like(np.zeros(len(self.objectives)), np.nan)
|
|
160
|
+
c = np.full_like(np.zeros(len(self.constraints)), np.nan)
|
|
161
|
+
return y, y, c
|
|
162
|
+
|
|
158
163
|
# ===== calc =====
|
|
159
|
-
def f(self, x: np.ndarray) -> list[np.ndarray]:
|
|
164
|
+
def f(self, x: np.ndarray, _record_infeasible=False) -> list[np.ndarray]:
|
|
160
165
|
"""Calculate objectives and constraints.
|
|
161
166
|
|
|
162
167
|
Args:
|
|
163
168
|
x (np.ndarray): Optimization parameters.
|
|
169
|
+
_record_infeasible (bool): If True, skip fem.update() and record self.generate_invalid_results().
|
|
164
170
|
|
|
165
171
|
Returns:
|
|
166
172
|
list[np.ndarray]:
|
|
@@ -178,27 +184,31 @@ class AbstractOptimizer(ABC):
|
|
|
178
184
|
self.set_parameter_values(x)
|
|
179
185
|
logger.info(f'input: {x}')
|
|
180
186
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
if not _record_infeasible:
|
|
188
|
+
# FEM の更新
|
|
189
|
+
try:
|
|
190
|
+
logger.info(f'Solving FEM...')
|
|
191
|
+
df_to_fem = self.variables.get_variables(
|
|
192
|
+
format='df',
|
|
193
|
+
filter_pass_to_fem=True
|
|
194
|
+
)
|
|
195
|
+
self.fem.update(df_to_fem)
|
|
189
196
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logger.info(f'{type(e).__name__} : {e}')
|
|
199
|
+
logger.info(Msg.INFO_EXCEPTION_DURING_FEM_ANALYSIS)
|
|
200
|
+
logger.info(x)
|
|
201
|
+
raise e # may be just a ModelError, etc. Handling them in Concrete classes.
|
|
195
202
|
|
|
196
|
-
|
|
197
|
-
|
|
203
|
+
# y, _y, c の更新
|
|
204
|
+
y = [obj.calc(self.fem) for obj in self.objectives.values()]
|
|
198
205
|
|
|
199
|
-
|
|
206
|
+
_y = [obj.convert(value) for obj, value in zip(self.objectives.values(), y)]
|
|
200
207
|
|
|
201
|
-
|
|
208
|
+
c = [cns.calc(self.fem) for cns in self.constraints.values()]
|
|
209
|
+
|
|
210
|
+
else:
|
|
211
|
+
y, _y, c = self.generate_infeasible_result()
|
|
202
212
|
|
|
203
213
|
# register to history
|
|
204
214
|
df_to_opt = self.variables.get_variables(
|
|
@@ -302,6 +312,7 @@ class AbstractOptimizer(ABC):
|
|
|
302
312
|
worker_status_list, # 他の worker の status オブジェクト
|
|
303
313
|
wait_setup, # 他の worker の status が ready になるまで待つか
|
|
304
314
|
skip_reconstruct=False, # reconstruct fem を行うかどうか
|
|
315
|
+
space_dir=None, # 特定の space_dir を使うかどうか
|
|
305
316
|
) -> Optional[Exception]:
|
|
306
317
|
|
|
307
318
|
# 自分の worker_status の取得
|
|
@@ -314,7 +325,7 @@ class AbstractOptimizer(ABC):
|
|
|
314
325
|
|
|
315
326
|
# set_fem をはじめ、終了したらそれを示す
|
|
316
327
|
self._reconstruct_fem(skip_reconstruct)
|
|
317
|
-
self.fem._setup_after_parallel(opt=self)
|
|
328
|
+
self.fem._setup_after_parallel(opt=self, space_dir=space_dir)
|
|
318
329
|
self.worker_status.set(OptimizationStatus.WAIT_OTHER_WORKERS)
|
|
319
330
|
|
|
320
331
|
# wait_setup or not
|
|
@@ -4,6 +4,7 @@ from typing import Iterable
|
|
|
4
4
|
# built-in
|
|
5
5
|
import os
|
|
6
6
|
import inspect
|
|
7
|
+
import gc
|
|
7
8
|
|
|
8
9
|
# 3rd-party
|
|
9
10
|
import optuna
|
|
@@ -92,10 +93,7 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
92
93
|
def _objective(self, trial):
|
|
93
94
|
|
|
94
95
|
logger.info('')
|
|
95
|
-
|
|
96
|
-
logger.info(f'===== trial {1 + len(self.history.get_df())} start =====')
|
|
97
|
-
else:
|
|
98
|
-
logger.info(f'===== trial {1 + len(self.history.get_df())} (retry {self._retry_counter}) start =====')
|
|
96
|
+
logger.info(f'===== trial {1 + len(self.history.get_df())} ({len(self.history.get_df(valid_only=True))} succeeded trials) start =====')
|
|
99
97
|
|
|
100
98
|
# 中断の確認 (FAIL loop に陥る対策)
|
|
101
99
|
if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
|
|
@@ -124,6 +122,7 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
124
122
|
# fem 経由で変数を取得して constraint を計算する時のためにアップデート
|
|
125
123
|
df_fem = self.variables.get_variables(format='df', filter_pass_to_fem=True)
|
|
126
124
|
self.fem.update_parameter(df_fem)
|
|
125
|
+
x = self.variables.get_variables(format='values', filter_parameter=True)
|
|
127
126
|
|
|
128
127
|
# strict 拘束
|
|
129
128
|
strict_constraints = [cns for cns in self.constraints.values() if cns.strict]
|
|
@@ -140,10 +139,11 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
140
139
|
logger.info(f'Constraint: {cns.name}')
|
|
141
140
|
logger.info(self.variables.get_variables('dict', filter_parameter=True))
|
|
142
141
|
self._retry_counter += 1
|
|
142
|
+
self.message = f'Failed to calculate objectives because of the constraint violation: {cns.name}'
|
|
143
|
+
self.f(x, _record_infeasible=True)
|
|
143
144
|
raise optuna.TrialPruned() # set TrialState PRUNED because FAIL causes similar candidate loop.
|
|
144
145
|
|
|
145
146
|
# 計算
|
|
146
|
-
x = self.variables.get_variables(format='values', filter_parameter=True)
|
|
147
147
|
try:
|
|
148
148
|
_, _y, c = self.f(x) # f の中で info は出している
|
|
149
149
|
except (ModelError, MeshError, SolveError) as e:
|
|
@@ -162,6 +162,8 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
162
162
|
'or analysis.')
|
|
163
163
|
|
|
164
164
|
self._retry_counter += 1
|
|
165
|
+
self.message = f'Failed to calculate objectives because of the parameter broke the FEM model.'
|
|
166
|
+
self.f(x, _record_infeasible=True)
|
|
165
167
|
raise optuna.TrialPruned() # set TrialState PRUNED because FAIL causes similar candidate loop.
|
|
166
168
|
|
|
167
169
|
# 拘束 attr の更新
|
|
@@ -284,8 +286,19 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
284
286
|
from optuna.samplers import TPESampler
|
|
285
287
|
if issubclass(self.sampler_class, TPESampler):
|
|
286
288
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
+
import re
|
|
290
|
+
pattern = r'_\d+$'
|
|
291
|
+
|
|
292
|
+
while os.path.exists(self._temporary_storage_path):
|
|
293
|
+
name, ext = os.path.splitext(self._temporary_storage_path)
|
|
294
|
+
|
|
295
|
+
if bool(re.search(pattern, name)):
|
|
296
|
+
base = '_'.join(name.split('_')[:-1])
|
|
297
|
+
n = int(name.split('_')[-1])
|
|
298
|
+
self._temporary_storage_path = name + '_' + str(n+1) + ext
|
|
299
|
+
|
|
300
|
+
else:
|
|
301
|
+
self._temporary_storage_path = name + '_2' + ext
|
|
289
302
|
|
|
290
303
|
self._temporary_storage = optuna.integration.dask.DaskStorage(
|
|
291
304
|
f'sqlite:///{self._temporary_storage_path}',
|
|
@@ -403,7 +416,6 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
403
416
|
self._temporary_storage.remove_session()
|
|
404
417
|
del self._temporary_storage
|
|
405
418
|
del _study
|
|
406
|
-
import gc
|
|
407
419
|
gc.collect()
|
|
408
420
|
if os.path.exists(self._temporary_storage_path):
|
|
409
421
|
os.remove(self._temporary_storage_path)
|