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.

Files changed (44) hide show
  1. pyfemtet/__init__.py +1 -1
  2. pyfemtet/_message/locales/ja/LC_MESSAGES/messages.mo +0 -0
  3. pyfemtet/_message/locales/ja/LC_MESSAGES/messages.po +112 -90
  4. pyfemtet/_message/locales/messages.pot +105 -89
  5. pyfemtet/_message/messages.py +6 -2
  6. pyfemtet/_util/excel_parse_util.py +138 -0
  7. pyfemtet/_util/sample.xlsx +0 -0
  8. pyfemtet/brep/__init__.py +0 -3
  9. pyfemtet/brep/_impl.py +7 -3
  10. pyfemtet/opt/_femopt.py +42 -14
  11. pyfemtet/opt/_femopt_core.py +93 -34
  12. pyfemtet/opt/advanced_samples/excel_ui/(ref) original_project.femprj +0 -0
  13. pyfemtet/opt/advanced_samples/excel_ui/femtet-macro.xlsm +0 -0
  14. pyfemtet/opt/advanced_samples/excel_ui/pyfemtet-core.py +291 -0
  15. pyfemtet/opt/advanced_samples/excel_ui/test-pyfemtet-core.cmd +22 -0
  16. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_create_training_data.py +60 -0
  17. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_create_training_data_jp.py +57 -0
  18. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate.py +100 -0
  19. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate_jp.py +90 -0
  20. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_parametric.femprj +0 -0
  21. pyfemtet/opt/interface/__init__.py +2 -0
  22. pyfemtet/opt/interface/_base.py +3 -0
  23. pyfemtet/opt/interface/_excel_interface.py +296 -124
  24. pyfemtet/opt/interface/_femtet.py +19 -9
  25. pyfemtet/opt/interface/_surrogate/__init__.py +5 -0
  26. pyfemtet/opt/interface/_surrogate/_base.py +85 -0
  27. pyfemtet/opt/interface/_surrogate/_chaospy.py +71 -0
  28. pyfemtet/opt/interface/_surrogate/_singletaskgp.py +70 -0
  29. pyfemtet/opt/optimizer/_base.py +28 -18
  30. pyfemtet/opt/optimizer/_optuna/_optuna.py +20 -8
  31. pyfemtet/opt/optimizer/_optuna/_pof_botorch.py +60 -18
  32. pyfemtet/opt/prediction/_base.py +8 -0
  33. pyfemtet/opt/prediction/single_task_gp.py +85 -62
  34. pyfemtet/opt/visualization/_complex_components/main_figure_creator.py +5 -5
  35. pyfemtet/opt/visualization/_complex_components/main_graph.py +7 -1
  36. pyfemtet/opt/visualization/_complex_components/pm_graph.py +1 -1
  37. pyfemtet/opt/visualization/_process_monitor/application.py +2 -2
  38. pyfemtet/opt/visualization/_process_monitor/pages.py +1 -1
  39. pyfemtet/opt/visualization/result_viewer/pages.py +1 -1
  40. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.1.dist-info}/METADATA +8 -8
  41. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.1.dist-info}/RECORD +44 -28
  42. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.1.dist-info}/WHEEL +1 -1
  43. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.1.dist-info}/LICENSE +0 -0
  44. {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
- _set_autosave_enabled(self._original_autosave_enabled)
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
- _exit_or_force_terminate(timeout=timeout, Femtet=self.Femtet, force=True)
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
- file_content = self._create_result_file_content()
742
- jpg_content = self._create_jpg_content()
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=SolveError,
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 Exception(Msg.ERR_FAILED_TO_SAVE_JPG)
828
+ raise FailedToPostProcess(Msg.ERR_FAILED_TO_SAVE_JPG)
819
829
 
820
830
  if not os.path.exists(jpg_path):
821
- raise Exception(Msg.ERR_JPG_NOT_FOUND)
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,5 @@
1
+ from pyfemtet.opt.interface._surrogate._singletaskgp import PoFBoTorchInterface
2
+
3
+ __all__ = [
4
+ PoFBoTorchInterface,
5
+ ]
@@ -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)}
@@ -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
- # FEM の更新
182
- try:
183
- logger.info(f'Solving FEM...')
184
- df_to_fem = self.variables.get_variables(
185
- format='df',
186
- filter_pass_to_fem=True
187
- )
188
- self.fem.update(df_to_fem)
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
- except Exception as e:
191
- logger.info(f'{type(e).__name__} : {e}')
192
- logger.info(Msg.INFO_EXCEPTION_DURING_FEM_ANALYSIS)
193
- logger.info(x)
194
- raise e # may be just a ModelError, etc. Handling them in Concrete classes.
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
- # y, _y, c の更新
197
- y = [obj.calc(self.fem) for obj in self.objectives.values()]
203
+ # y, _y, c の更新
204
+ y = [obj.calc(self.fem) for obj in self.objectives.values()]
198
205
 
199
- _y = [obj.convert(value) for obj, value in zip(self.objectives.values(), y)]
206
+ _y = [obj.convert(value) for obj, value in zip(self.objectives.values(), y)]
200
207
 
201
- c = [cns.calc(self.fem) for cns in self.constraints.values()]
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
- if self._retry_counter == 0:
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
- if os.path.exists(self._temporary_storage_path):
288
- os.remove(self._temporary_storage_path)
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=1e-4*torch.ones_like(train_y) if _get_use_fixed_noise() else None,
401
- outcome_transform=Standardize(m=train_y.size(-1))
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=1e-4*torch.ones_like(train_y) if _get_use_fixed_noise() else None,
547
- outcome_transform=Standardize(m=train_y.size(-1))
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=1e-4*torch.ones_like(train_y) if _get_use_fixed_noise() else None,
648
- outcome_transform=Standardize(m=train_y.size(-1))
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=1e-4*torch.ones_like(train_y) if _get_use_fixed_noise() else None,
754
- outcome_transform=Standardize(m=train_y.size(-1))
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=1e-4*torch.ones_like(train_y) if _get_use_fixed_noise() else None,
857
- outcome_transform=Standardize(m=train_y.size(-1))
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=1e-4*torch.ones_like(train_y) if _get_use_fixed_noise() else None,
969
- outcome_transform=Standardize(m=train_y.size(-1))
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=1e-4*torch.ones_like(train_y) if _get_use_fixed_noise() else None,
1090
- outcome_transform=Standardize(m=train_y.size(-1))
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=1e-4*torch.ones_like(train_y) if _get_use_fixed_noise() else None,
1191
- outcome_transform=Standardize(m=train_y.size(-1))
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=1e-4*torch.ones_like(train_y[..., [i]]) if _get_use_fixed_noise() else None,
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
- values[trial_idx, 0] = 1. # feasible
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:
@@ -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