pyfemtet 0.7.1__py3-none-any.whl → 0.8.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.
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/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 +42 -14
- pyfemtet/opt/_femopt_core.py +93 -34
- 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 +296 -124
- pyfemtet/opt/interface/_femtet.py +19 -9
- 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 +28 -18
- 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.1.dist-info → pyfemtet-0.8.1.dist-info}/METADATA +8 -8
- {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.1.dist-info}/RECORD +44 -28
- {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.1.dist-info}/WHEEL +1 -1
- {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.1.dist-info}/LICENSE +0 -0
- {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.1.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
|
|
|
@@ -181,9 +185,7 @@ class FemtetInterface(FEMInterface):
|
|
|
181
185
|
)
|
|
182
186
|
|
|
183
187
|
def __del__(self):
|
|
184
|
-
|
|
185
|
-
if self.quit_when_destruct:
|
|
186
|
-
self.quit()
|
|
188
|
+
self.quit()
|
|
187
189
|
# CoUninitialize() # Win32 exception occurred releasing IUnknown at 0x0000022427692748
|
|
188
190
|
|
|
189
191
|
def _connect_new_femtet(self):
|
|
@@ -726,7 +728,8 @@ class FemtetInterface(FEMInterface):
|
|
|
726
728
|
|
|
727
729
|
_set_autosave_enabled(self._original_autosave_enabled)
|
|
728
730
|
|
|
729
|
-
|
|
731
|
+
if self.quit_when_destruct:
|
|
732
|
+
_exit_or_force_terminate(timeout=timeout, Femtet=self.Femtet, force=True)
|
|
730
733
|
|
|
731
734
|
def _setup_before_parallel(self, client):
|
|
732
735
|
client.upload_file(
|
|
@@ -738,8 +741,15 @@ class FemtetInterface(FEMInterface):
|
|
|
738
741
|
return _version(Femtet=self.Femtet)
|
|
739
742
|
|
|
740
743
|
def _create_postprocess_args(self):
|
|
741
|
-
|
|
742
|
-
|
|
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
|
|
743
753
|
|
|
744
754
|
out = dict(
|
|
745
755
|
original_femprj_path=self.original_femprj_path,
|
|
@@ -787,7 +797,7 @@ class FemtetInterface(FEMInterface):
|
|
|
787
797
|
fun=self.Femtet.SavePDT,
|
|
788
798
|
args=(pdt_path, True),
|
|
789
799
|
return_value_if_failed=False,
|
|
790
|
-
if_error=
|
|
800
|
+
if_error=FailedToPostProcess,
|
|
791
801
|
error_message=Msg.ERR_FAILED_TO_SAVE_PDT,
|
|
792
802
|
is_Gaudi_method=False,
|
|
793
803
|
)
|
|
@@ -815,10 +825,10 @@ class FemtetInterface(FEMInterface):
|
|
|
815
825
|
self.Femtet.RedrawMode = True # 逐一の描画をオン
|
|
816
826
|
|
|
817
827
|
if not succeed:
|
|
818
|
-
raise
|
|
828
|
+
raise FailedToPostProcess(Msg.ERR_FAILED_TO_SAVE_JPG)
|
|
819
829
|
|
|
820
830
|
if not os.path.exists(jpg_path):
|
|
821
|
-
raise
|
|
831
|
+
raise FailedToPostProcess(Msg.ERR_JPG_NOT_FOUND)
|
|
822
832
|
|
|
823
833
|
with open(jpg_path, 'rb') as f:
|
|
824
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(
|
|
@@ -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)
|
|
@@ -169,6 +169,21 @@ def symlog(x):
|
|
|
169
169
|
)
|
|
170
170
|
|
|
171
171
|
|
|
172
|
+
def get_minimum_YVar_and_standardizer(Y: torch.Tensor):
|
|
173
|
+
standardizer = Standardize(m=Y.shape[-1])
|
|
174
|
+
if _get_use_fixed_noise():
|
|
175
|
+
import gpytorch
|
|
176
|
+
min_noise = gpytorch.settings.min_fixed_noise.value(Y.dtype)
|
|
177
|
+
|
|
178
|
+
standardizer.forward(Y) # require to un-transform
|
|
179
|
+
_, YVar = standardizer.untransform(Y, min_noise * torch.ones_like(Y))
|
|
180
|
+
|
|
181
|
+
else:
|
|
182
|
+
YVar = None
|
|
183
|
+
|
|
184
|
+
return YVar, standardizer
|
|
185
|
+
|
|
186
|
+
|
|
172
187
|
# ベースとなる獲得関数クラスに pof 係数を追加したクラスを作成する関数
|
|
173
188
|
def acqf_patch_factory(acqf_class, pof_config=None):
|
|
174
189
|
"""ベース acqf クラスに pof 係数の計算を追加したクラスを作成します。
|
|
@@ -394,11 +409,13 @@ def logei_candidates_func(
|
|
|
394
409
|
|
|
395
410
|
train_x = normalize(train_x, bounds=bounds)
|
|
396
411
|
|
|
412
|
+
train_yvar, standardizer = get_minimum_YVar_and_standardizer(train_y)
|
|
413
|
+
|
|
397
414
|
model = SingleTaskGP(
|
|
398
415
|
train_x,
|
|
399
416
|
train_y,
|
|
400
|
-
train_Yvar=
|
|
401
|
-
outcome_transform=
|
|
417
|
+
train_Yvar=train_yvar,
|
|
418
|
+
outcome_transform=standardizer,
|
|
402
419
|
)
|
|
403
420
|
mll = ExactMarginalLogLikelihood(model.likelihood, model)
|
|
404
421
|
fit_gpytorch_mll(mll)
|
|
@@ -540,11 +557,13 @@ def qei_candidates_func(
|
|
|
540
557
|
if pending_x is not None:
|
|
541
558
|
pending_x = normalize(pending_x, bounds=bounds)
|
|
542
559
|
|
|
560
|
+
train_yvar, standardizer = get_minimum_YVar_and_standardizer(train_y)
|
|
561
|
+
|
|
543
562
|
model = SingleTaskGP(
|
|
544
563
|
train_x,
|
|
545
564
|
train_y,
|
|
546
|
-
train_Yvar=
|
|
547
|
-
outcome_transform=
|
|
565
|
+
train_Yvar=train_yvar,
|
|
566
|
+
outcome_transform=standardizer,
|
|
548
567
|
)
|
|
549
568
|
mll = ExactMarginalLogLikelihood(model.likelihood, model)
|
|
550
569
|
fit_gpytorch_mll(mll)
|
|
@@ -641,11 +660,13 @@ def qnei_candidates_func(
|
|
|
641
660
|
if pending_x is not None:
|
|
642
661
|
pending_x = normalize(pending_x, bounds=bounds)
|
|
643
662
|
|
|
663
|
+
train_yvar, standardizer = get_minimum_YVar_and_standardizer(train_y)
|
|
664
|
+
|
|
644
665
|
model = SingleTaskGP(
|
|
645
666
|
train_x,
|
|
646
667
|
train_y,
|
|
647
|
-
train_Yvar=
|
|
648
|
-
outcome_transform=
|
|
668
|
+
train_Yvar=train_yvar,
|
|
669
|
+
outcome_transform=standardizer,
|
|
649
670
|
)
|
|
650
671
|
mll = ExactMarginalLogLikelihood(model.likelihood, model)
|
|
651
672
|
fit_gpytorch_mll(mll)
|
|
@@ -747,11 +768,13 @@ def qehvi_candidates_func(
|
|
|
747
768
|
if pending_x is not None:
|
|
748
769
|
pending_x = normalize(pending_x, bounds=bounds)
|
|
749
770
|
|
|
771
|
+
train_yvar, standardizer = get_minimum_YVar_and_standardizer(train_y)
|
|
772
|
+
|
|
750
773
|
model = SingleTaskGP(
|
|
751
774
|
train_x,
|
|
752
775
|
train_y,
|
|
753
|
-
train_Yvar=
|
|
754
|
-
outcome_transform=
|
|
776
|
+
train_Yvar=train_yvar,
|
|
777
|
+
outcome_transform=standardizer,
|
|
755
778
|
)
|
|
756
779
|
mll = ExactMarginalLogLikelihood(model.likelihood, model)
|
|
757
780
|
fit_gpytorch_mll(mll)
|
|
@@ -850,11 +873,13 @@ def ehvi_candidates_func(
|
|
|
850
873
|
train_y = train_obj
|
|
851
874
|
train_x = normalize(train_x, bounds=bounds)
|
|
852
875
|
|
|
876
|
+
train_yvar, standardizer = get_minimum_YVar_and_standardizer(train_y)
|
|
877
|
+
|
|
853
878
|
model = SingleTaskGP(
|
|
854
879
|
train_x,
|
|
855
880
|
train_y,
|
|
856
|
-
train_Yvar=
|
|
857
|
-
outcome_transform=
|
|
881
|
+
train_Yvar=train_yvar,
|
|
882
|
+
outcome_transform=standardizer,
|
|
858
883
|
)
|
|
859
884
|
mll = ExactMarginalLogLikelihood(model.likelihood, model)
|
|
860
885
|
fit_gpytorch_mll(mll)
|
|
@@ -962,11 +987,13 @@ def qnehvi_candidates_func(
|
|
|
962
987
|
if pending_x is not None:
|
|
963
988
|
pending_x = normalize(pending_x, bounds=bounds)
|
|
964
989
|
|
|
990
|
+
train_yvar, standardizer = get_minimum_YVar_and_standardizer(train_y)
|
|
991
|
+
|
|
965
992
|
model = SingleTaskGP(
|
|
966
993
|
train_x,
|
|
967
994
|
train_y,
|
|
968
|
-
train_Yvar=
|
|
969
|
-
outcome_transform=
|
|
995
|
+
train_Yvar=train_yvar,
|
|
996
|
+
outcome_transform=standardizer,
|
|
970
997
|
)
|
|
971
998
|
mll = ExactMarginalLogLikelihood(model.likelihood, model)
|
|
972
999
|
fit_gpytorch_mll(mll)
|
|
@@ -1083,11 +1110,13 @@ def qparego_candidates_func(
|
|
|
1083
1110
|
if pending_x is not None:
|
|
1084
1111
|
pending_x = normalize(pending_x, bounds=bounds)
|
|
1085
1112
|
|
|
1113
|
+
train_yvar, standardizer = get_minimum_YVar_and_standardizer(train_y)
|
|
1114
|
+
|
|
1086
1115
|
model = SingleTaskGP(
|
|
1087
1116
|
train_x,
|
|
1088
1117
|
train_y,
|
|
1089
|
-
train_Yvar=
|
|
1090
|
-
outcome_transform=
|
|
1118
|
+
train_Yvar=train_yvar,
|
|
1119
|
+
outcome_transform=standardizer,
|
|
1091
1120
|
)
|
|
1092
1121
|
mll = ExactMarginalLogLikelihood(model.likelihood, model)
|
|
1093
1122
|
fit_gpytorch_mll(mll)
|
|
@@ -1184,11 +1213,13 @@ def qkg_candidates_func(
|
|
|
1184
1213
|
if pending_x is not None:
|
|
1185
1214
|
pending_x = normalize(pending_x, bounds=bounds)
|
|
1186
1215
|
|
|
1216
|
+
train_yvar, standardizer = get_minimum_YVar_and_standardizer(train_y)
|
|
1217
|
+
|
|
1187
1218
|
model = SingleTaskGP(
|
|
1188
1219
|
train_x,
|
|
1189
1220
|
train_y,
|
|
1190
|
-
train_Yvar=
|
|
1191
|
-
outcome_transform=
|
|
1221
|
+
train_Yvar=train_yvar,
|
|
1222
|
+
outcome_transform=standardizer,
|
|
1192
1223
|
)
|
|
1193
1224
|
mll = ExactMarginalLogLikelihood(model.likelihood, model)
|
|
1194
1225
|
fit_gpytorch_mll(mll)
|
|
@@ -1286,7 +1317,7 @@ def qhvkg_candidates_func(
|
|
|
1286
1317
|
SingleTaskGP(
|
|
1287
1318
|
train_x,
|
|
1288
1319
|
train_y[..., [i]],
|
|
1289
|
-
train_Yvar=
|
|
1320
|
+
train_Yvar=get_minimum_YVar_and_standardizer(train_y[..., [i]])[0],
|
|
1290
1321
|
outcome_transform=Standardize(m=1)
|
|
1291
1322
|
)
|
|
1292
1323
|
for i in range(train_y.shape[-1])
|
|
@@ -1763,7 +1794,18 @@ class PoFBoTorchSampler(BaseSampler):
|
|
|
1763
1794
|
for trial_idx, trial in enumerate(trials):
|
|
1764
1795
|
params[trial_idx] = trans.transform(trial.params)
|
|
1765
1796
|
if trial.state == TrialState.COMPLETE:
|
|
1766
|
-
|
|
1797
|
+
# complete, but infeasible (in case of weak constraint)
|
|
1798
|
+
if 'constraints' in trial.user_attrs.keys():
|
|
1799
|
+
cns = trial.user_attrs['constraints']
|
|
1800
|
+
if cns is None:
|
|
1801
|
+
values[trial_idx, 0] = 1. # feasible (or should RuntimeError)
|
|
1802
|
+
else:
|
|
1803
|
+
if numpy.array(cns).max() > 0:
|
|
1804
|
+
values[trial_idx, 0] = 1. # feasible
|
|
1805
|
+
else:
|
|
1806
|
+
values[trial_idx, 0] = 1. # feasible
|
|
1807
|
+
else:
|
|
1808
|
+
values[trial_idx, 0] = 1. # feasible
|
|
1767
1809
|
elif trial.state == TrialState.PRUNED:
|
|
1768
1810
|
values[trial_idx, 0] = 0. # infeasible
|
|
1769
1811
|
else:
|
pyfemtet/opt/prediction/_base.py
CHANGED
|
@@ -32,6 +32,14 @@ class PyFemtetPredictionModel:
|
|
|
32
32
|
def __init__(self, history: History, df: pd.DataFrame, MetaModel: type):
|
|
33
33
|
assert issubclass(MetaModel, PredictionModelBase)
|
|
34
34
|
self.meta_model: PredictionModelBase = MetaModel()
|
|
35
|
+
|
|
36
|
+
from pyfemtet.opt.prediction.single_task_gp import SingleTaskGPModel
|
|
37
|
+
if isinstance(self.meta_model, SingleTaskGPModel):
|
|
38
|
+
self.meta_model.set_bounds_from_history(
|
|
39
|
+
history,
|
|
40
|
+
df,
|
|
41
|
+
)
|
|
42
|
+
|
|
35
43
|
self.obj_names = history.obj_names
|
|
36
44
|
self.prm_names = history.prm_names
|
|
37
45
|
self.df = df
|