pyfemtet 0.7.1__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.

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.0.dist-info}/METADATA +2 -2
  41. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.0.dist-info}/RECORD +44 -28
  42. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.0.dist-info}/WHEEL +1 -1
  43. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.0.dist-info}/LICENSE +0 -0
  44. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.0.dist-info}/entry_points.txt +0 -0
pyfemtet/opt/_femopt.py CHANGED
@@ -165,6 +165,7 @@ class FEMOpt:
165
165
  step: float = None,
166
166
  properties: dict[str, str or float] = None,
167
167
  pass_to_fem: bool = True,
168
+ fix: bool = False,
168
169
  ):
169
170
  # noinspection PyUnresolvedReferences
170
171
  """Adds a parameter to the optimization problem.
@@ -177,6 +178,15 @@ class FEMOpt:
177
178
  step (float, optional): The step of parameter. If specified, parameter is used as discrete. Defaults to None.
178
179
  properties (dict[str, str or float], optional): Additional information about the parameter. Defaults to None.
179
180
  pass_to_fem (bool, optional): If this variable is used directly in FEM model update or not. If False, this parameter can be just used as inpt of expressions. Defaults to True.
181
+ fix (bool, optiona):
182
+ パラメータを initial_value で固定します。
183
+ 開発時にパラメータを振るか振らないかを
184
+ 簡単に変更するための便利引数です。
185
+ True のとき、lower_bound, upper_bound, step, properties の
186
+ 値は、有効かどうかのチェックには使われますが、最適化では
187
+ 使われなくなります。
188
+ デフォルトは False です。
189
+
180
190
 
181
191
  Raises:
182
192
  ValueError: If initial_value is not specified and the value for the given name is also not specified in FEM.
@@ -209,16 +219,27 @@ class FEMOpt:
209
219
  if initial_value is None:
210
220
  raise ValueError('initial_value を指定してください.')
211
221
 
212
- prm = Parameter(
213
- name=name,
214
- value=float(initial_value),
215
- lower_bound=float(lower_bound) if lower_bound is not None else None,
216
- upper_bound=float(upper_bound) if upper_bound is not None else None,
217
- step=float(step) if step is not None else None,
218
- pass_to_fem=pass_to_fem,
219
- properties=properties,
220
- )
221
- self.opt.variables.add_parameter(prm)
222
+ if not fix:
223
+ prm = Parameter(
224
+ name=name,
225
+ value=float(initial_value),
226
+ lower_bound=float(lower_bound) if lower_bound is not None else None,
227
+ upper_bound=float(upper_bound) if upper_bound is not None else None,
228
+ step=float(step) if step is not None else None,
229
+ pass_to_fem=pass_to_fem,
230
+ properties=properties,
231
+ )
232
+ self.opt.variables.add_parameter(prm)
233
+
234
+ else:
235
+ warnings.filterwarnings('ignore', category=UserWarning, message="The function 'add_expression' is experimental")
236
+ self.add_expression(
237
+ name=name,
238
+ fun=lambda: initial_value,
239
+ pass_to_fem=pass_to_fem,
240
+ properties=properties,
241
+ )
242
+
222
243
 
223
244
  @experimental_feature
224
245
  def add_expression(
@@ -299,7 +320,7 @@ class FEMOpt:
299
320
 
300
321
  def add_objective(
301
322
  self,
302
- fun,
323
+ fun: callable or None = None,
303
324
  name: str or None = None,
304
325
  direction: str or float = 'minimize',
305
326
  args: tuple or None = None,
@@ -309,7 +330,7 @@ class FEMOpt:
309
330
  """Adds an objective to the optimization problem.
310
331
 
311
332
  Args:
312
- fun (callable): The objective function.
333
+ fun (callable or None, optional): The objective function. This argument is optional but
313
334
  name (str or None, optional): The name of the objective. Defaults to None.
314
335
  direction (str or float, optional): The optimization direction. Varid values are 'maximize', 'minimize' or a float value. Defaults to 'minimize'.
315
336
  args (tuple or None, optional): Additional arguments for the objective function. Defaults to None.
@@ -344,6 +365,10 @@ class FEMOpt:
344
365
  """
345
366
 
346
367
  # 引数の処理
368
+ if fun is None:
369
+ from pyfemtet.opt.interface._surrogate._base import SurrogateModelInterfaceBase
370
+ if not isinstance(self.fem, SurrogateModelInterfaceBase):
371
+ raise ValueError('`fun` argument is not specified.')
347
372
  if args is None:
348
373
  args = tuple()
349
374
  elif not isinstance(args, tuple):
@@ -403,7 +428,6 @@ class FEMOpt:
403
428
  kwargs=kwargs,
404
429
  )
405
430
 
406
-
407
431
  def add_constraint(
408
432
  self,
409
433
  fun,
@@ -612,6 +636,7 @@ class FEMOpt:
612
636
  if self.fem._load_problem_from_me:
613
637
  self.fem.load_parameter(self.opt)
614
638
  self.fem.load_objective(self.opt)
639
+ self.fem.load_constraint(self.opt)
615
640
 
616
641
  # resolve expression dependencies
617
642
  self.opt.variables.resolve()
@@ -892,7 +917,10 @@ class FEMOpt:
892
917
  print('='*len(Msg.CONFIRM_BEFORE_EXIT))
893
918
  input()
894
919
 
895
- return self.history.get_df() # with 文を抜けると actor は消えるが .copy() はこの段階では不要
920
+ df = self.history.get_df() # with 文を抜けると actor は消えるが .copy() はこの段階では不要
921
+
922
+ return df
923
+
896
924
 
897
925
  @staticmethod
898
926
  def terminate_all():
@@ -262,6 +262,8 @@ def is_feasible(value, lb, ub):
262
262
  Returns:
263
263
  bool: True if the value satisfies the bounds; False otherwise.
264
264
  """
265
+ if np.isnan(value):
266
+ return False
265
267
  if lb is None and ub is not None:
266
268
  return value <= ub
267
269
  elif lb is not None and ub is None:
@@ -290,10 +292,11 @@ class Function:
290
292
  # COM 定数を一度 _Scapegoat 型のオブジェクトにする
291
293
  # ParametricIF で使う dll 関数は _FuncPtr 型であって __globals__ を持たないが、
292
294
  # これは絶対に constants を持たないので単に無視すればよい。
293
- if not isinstance(fun, ctypes._CFuncPtr):
294
- for varname in fun.__globals__:
295
- if isinstance(fun.__globals__[varname], Constants):
296
- fun.__globals__[varname] = _Scapegoat()
295
+ if fun is not None:
296
+ if not isinstance(fun, ctypes._CFuncPtr):
297
+ for varname in fun.__globals__:
298
+ if isinstance(fun.__globals__[varname], Constants):
299
+ fun.__globals__[varname] = _Scapegoat()
297
300
 
298
301
  self.fun = fun
299
302
  self.name = name
@@ -309,6 +312,9 @@ class Function:
309
312
  Returns:
310
313
  float
311
314
  """
315
+ if self.fun is None:
316
+ RuntimeError(f'`fun` of {self.name} is not specified.')
317
+
312
318
  args = self.args
313
319
  # Femtet 特有の処理
314
320
  if isinstance(fem, FemtetInterface):
@@ -318,11 +324,12 @@ class Function:
318
324
  def _restore_constants(self):
319
325
  """Helper function for parallelize Femtet."""
320
326
  fun = self.fun
321
- if not isinstance(fun, ctypes._CFuncPtr):
322
- for varname in fun.__globals__:
323
- if isinstance(fun.__globals__[varname], _Scapegoat):
324
- if not fun.__globals__[varname]._ignore_when_restore_constants:
325
- fun.__globals__[varname] = constants
327
+ if fun is not None:
328
+ if not isinstance(fun, ctypes._CFuncPtr):
329
+ for varname in fun.__globals__:
330
+ if isinstance(fun.__globals__[varname], _Scapegoat):
331
+ if not fun.__globals__[varname]._ignore_when_restore_constants:
332
+ fun.__globals__[varname] = constants
326
333
 
327
334
 
328
335
  class Objective(Function):
@@ -621,16 +628,30 @@ class History:
621
628
 
622
629
  self.set_df(df)
623
630
 
624
- def get_df(self) -> pd.DataFrame:
631
+ def filter_valid(self, df_, keep_trial_num=False):
632
+ buff = df_[self.obj_names].notna()
633
+ idx = buff.prod(axis=1).astype(bool)
634
+ filtered_df = df_[idx]
635
+ if not keep_trial_num:
636
+ filtered_df.loc[:, 'trial'] = np.arange(len(filtered_df)) + 1
637
+ return filtered_df
638
+
639
+ def get_df(self, valid_only=False) -> pd.DataFrame:
625
640
  if self.__scheduler_address is None:
626
- return self._df
641
+ if valid_only:
642
+ return self.filter_valid(self._df)
643
+ else:
644
+ return self._df
627
645
  else:
628
646
  # scheduler がまだ存命か確認する
629
647
  try:
630
648
  with Lock('access-df'):
631
649
  client_: 'Client' = get_client(self.__scheduler_address)
632
650
  if 'df' in client_.list_datasets():
633
- return client_.get_dataset('df')
651
+ if valid_only:
652
+ return self.filter_valid(client_.get_dataset('df'))
653
+ else:
654
+ return client_.get_dataset('df')
634
655
  else:
635
656
  logger.debug('Access df of History before it is initialized.')
636
657
  return pd.DataFrame()
@@ -806,17 +827,19 @@ class History:
806
827
  df['non_domi'] = False
807
828
 
808
829
  # feasible のものに non_domi の評価結果を代入する
809
- df.loc[idx, 'non_domi'] = non_domi
830
+ if len(non_domi) > 0:
831
+ df.loc[idx, 'non_domi'] = non_domi
810
832
 
811
833
  def _calc_hypervolume(self, objectives, df):
812
834
 
835
+ # 単目的最適化ならば 0 埋めして終了
813
836
  if len(objectives) < 2:
814
837
  df.loc[len(df) - 1, 'hypervolume'] = 0.
815
838
  return
816
839
 
817
840
  # 最小化問題に変換された objective values を取得
818
841
  raw_objective_values = df[self.obj_names].values
819
- objective_values = np.empty_like(raw_objective_values)
842
+ objective_values = np.full_like(raw_objective_values, np.nan)
820
843
  for n_trial in range(len(raw_objective_values)):
821
844
  for obj_idx, (_, objective) in enumerate(objectives.items()):
822
845
  objective_values[n_trial, obj_idx] = objective.convert(raw_objective_values[n_trial, obj_idx])
@@ -830,13 +853,20 @@ class History:
830
853
  pareto_set_ = np.empty((0, len(self.obj_names)))
831
854
  for i in range(len(objective_values_)):
832
855
  target = objective_values_[i]
833
- dominated = False
834
- # TODO: Array の計算に直して高速化する
835
- for j in range(len(objective_values_)):
836
- compare = objective_values_[j]
837
- if all(target > compare):
838
- dominated = True
839
- break
856
+
857
+ if any(np.isnan(target)):
858
+ # infeasible な場合 pareto_set の計算に含めない
859
+ dominated = True
860
+
861
+ else:
862
+ dominated = False
863
+ # TODO: Array の計算に直して高速化する
864
+ for j in range(len(objective_values_)):
865
+ compare = objective_values_[j]
866
+ if all(target > compare):
867
+ dominated = True
868
+ break
869
+
840
870
  if not dominated:
841
871
  pareto_set_ = np.concatenate([pareto_set_, [target]], axis=0)
842
872
 
@@ -848,34 +878,63 @@ class History:
848
878
  else:
849
879
  return pareto_set_
850
880
 
881
+ def get_valid_worst_converted_objective_values(objective_values_: np.ndarray) -> np.ndarray:
882
+ # objective_values.max(axis=0)
883
+ ret = []
884
+ for row in objective_values_:
885
+ if not any(np.isnan(row)):
886
+ ret.append(row)
887
+ return np.array(ret).max(axis=0)
888
+
851
889
  if self._hv_reference == 'dynamic-pareto':
852
890
  pareto_set, pareto_set_list = get_pareto(objective_values, with_partial=True)
853
891
  for i, partial_pareto_set in enumerate(pareto_set_list):
854
- ref_point = pareto_set.max(axis=0) + 1e-8
855
- hv = compute_hypervolume(partial_pareto_set, ref_point)
856
- df.loc[i, 'hypervolume'] = hv
892
+ # 並列計算時など Valid な解がまだ一つもない場合は pareto_set が長さ 0 になる
893
+ # その場合 max() を取るとエラーになる
894
+ if len(pareto_set) == 0:
895
+ df.loc[i, 'hypervolume'] = 0
896
+ else:
897
+ ref_point = pareto_set.max(axis=0) + 1e-8
898
+ hv = compute_hypervolume(partial_pareto_set, ref_point)
899
+ df.loc[i, 'hypervolume'] = hv
857
900
  return
858
901
 
859
902
  elif self._hv_reference == 'dynamic-nadir':
860
903
  _, pareto_set_list = get_pareto(objective_values, with_partial=True)
861
904
  for i, partial_pareto_set in enumerate(pareto_set_list):
862
- ref_point = objective_values.max(axis=0) + 1e-8
863
- hv = compute_hypervolume(partial_pareto_set, ref_point)
864
- df.loc[i, 'hypervolume'] = hv
905
+ # filter valid objective values only
906
+ values = get_valid_worst_converted_objective_values(objective_values)
907
+
908
+ # 並列計算時など Valid な解がまだ一つもない場合は長さ 0 になる
909
+ # その場合 max() を取るとエラーになる
910
+ if len(values) == 0:
911
+ df.loc[i, 'hypervolume'] = 0
912
+
913
+ else:
914
+ ref_point = values.max(axis=0) + 1e-8
915
+ hv = compute_hypervolume(partial_pareto_set, ref_point)
916
+ df.loc[i, 'hypervolume'] = hv
865
917
  return
866
918
 
867
919
  elif self._hv_reference == 'nadir':
868
920
  pareto_set = get_pareto(objective_values)
869
- ref_point = objective_values.max(axis=0) + 1e-8
870
- hv = compute_hypervolume(pareto_set, ref_point)
871
- df.loc[len(df) - 1, 'hypervolume'] = hv
921
+ values = get_valid_worst_converted_objective_values(objective_values)
922
+ if len(values) == 0:
923
+ df.loc[len(df) - 1, 'hypervolume'] = 0
924
+ else:
925
+ ref_point = values.max(axis=0) + 1e-8
926
+ hv = compute_hypervolume(pareto_set, ref_point)
927
+ df.loc[len(df) - 1, 'hypervolume'] = hv
872
928
  return
873
929
 
874
930
  elif self._hv_reference == 'pareto':
875
931
  pareto_set = get_pareto(objective_values)
876
- ref_point = pareto_set.max(axis=0) + 1e-8
877
- hv = compute_hypervolume(pareto_set, ref_point)
878
- df.loc[len(df) - 1, 'hypervolume'] = hv
932
+ if len(pareto_set) == 0:
933
+ df.loc[len(df) - 1, 'hypervolume'] = 0
934
+ else:
935
+ ref_point = pareto_set.max(axis=0) + 1e-8
936
+ hv = compute_hypervolume(pareto_set, ref_point)
937
+ df.loc[len(df) - 1, 'hypervolume'] = hv
879
938
  return
880
939
 
881
940
  elif (
@@ -935,7 +994,7 @@ class History:
935
994
  study = optuna.create_study(**kwargs)
936
995
 
937
996
  # add trial to study
938
- df: pd.DataFrame = self.get_df()
997
+ df: pd.DataFrame = self.get_df(valid_only=True)
939
998
  for i, row in df.iterrows():
940
999
  FD = optuna.distributions.FloatDistribution
941
1000
  kwargs = dict(
@@ -0,0 +1,291 @@
1
+ """同梱する femtet-macro.xlsm から pyfemtet を呼び出す際の pyfemtet スクリプト。"""
2
+
3
+
4
+ import os
5
+
6
+ from fire import Fire
7
+
8
+
9
+ def get_sampler_class(sampling_method):
10
+ if sampling_method is None:
11
+ # default
12
+ from pyfemtet.opt.optimizer import PoFBoTorchSampler
13
+ return PoFBoTorchSampler
14
+ elif sampling_method == 'QMC':
15
+ from optuna.samplers import QMCSampler
16
+ return QMCSampler
17
+ elif sampling_method == 'PoFBoTorch':
18
+ from pyfemtet.opt.optimizer import PoFBoTorchSampler
19
+ return PoFBoTorchSampler
20
+ elif sampling_method == 'Random':
21
+ from optuna.samplers import RandomSampler
22
+ return RandomSampler
23
+ elif sampling_method == 'NSGA2':
24
+ from optuna.samplers import NSGAIISampler
25
+ return NSGAIISampler
26
+ elif sampling_method == 'TPE':
27
+ from optuna.samplers import TPESampler
28
+ return TPESampler
29
+ elif sampling_method == 'BoTorch':
30
+ from optuna_integration import BoTorchSampler
31
+ return BoTorchSampler
32
+ else:
33
+ raise NotImplementedError(f'The method {sampling_method} is not implemented.')
34
+
35
+
36
+ def core(
37
+ xlsm_path: str,
38
+ csv_path: str or None,
39
+ femprj_path: str or None, # xlsm と同じフォルダに配置する前提。
40
+ model_name: str or None,
41
+ input_sheet_name: str,
42
+
43
+ output_sheet_name: str or None,
44
+ constraint_sheet_name: str or None,
45
+ procedure_name: str or None, # 引数に拡張子を除く femprj ファイル名を取るように実装すること
46
+ setup_procedure_name: str or None,
47
+ teardown_procedure_name: str or None,
48
+
49
+ sampler_class: type('BaseSampler') or None,
50
+ sampler_kwargs: dict or None,
51
+
52
+ n_parallel: int,
53
+ n_trials: int or None,
54
+ timeout: float or None,
55
+ seed: int or None,
56
+ ):
57
+ from pathlib import Path
58
+ from pyfemtet.opt import FEMOpt, OptunaOptimizer
59
+ from pyfemtet.opt.interface._excel_interface import ExcelInterface
60
+
61
+ procedure_args = []
62
+ related_file_paths = []
63
+ if femprj_path is not None:
64
+ prj_name = os.path.basename(femprj_path).removesuffix('.femprj')
65
+ procedure_args.append(prj_name)
66
+ related_file_paths = [Path(femprj_path)]
67
+ if model_name is not None:
68
+ procedure_args.append(model_name)
69
+ if femprj_path is None and model_name is not None:
70
+ raise NotImplementedError
71
+
72
+ fem = ExcelInterface(
73
+ input_xlsm_path=xlsm_path,
74
+ input_sheet_name=input_sheet_name,
75
+ output_xlsm_path=None,
76
+ output_sheet_name=output_sheet_name,
77
+ constraint_xlsm_path=None,
78
+ constraint_sheet_name=constraint_sheet_name,
79
+ procedure_name=procedure_name,
80
+ procedure_args=procedure_args,
81
+ connect_method='new',
82
+ setup_procedure_name=setup_procedure_name,
83
+ teardown_procedure_name=teardown_procedure_name,
84
+ related_file_paths=related_file_paths,
85
+ visible=False,
86
+ interactive=True,
87
+ )
88
+
89
+ opt = OptunaOptimizer(
90
+ sampler_class=sampler_class,
91
+ sampler_kwargs=sampler_kwargs,
92
+ )
93
+
94
+ femopt = FEMOpt(
95
+ fem=fem,
96
+ opt=opt,
97
+ history_path=csv_path,
98
+ )
99
+
100
+ if seed is not None:
101
+ femopt.set_random_seed(42)
102
+
103
+ femopt.optimize(
104
+ n_trials=n_trials,
105
+ n_parallel=n_parallel,
106
+ timeout=timeout,
107
+ confirm_before_exit=True,
108
+ )
109
+
110
+
111
+ def main(
112
+ # これらは Fire キーワード引数指定できるように None を与えているが必須
113
+ xlsm_path: str = None,
114
+ input_sheet_name: str = None,
115
+ n_parallel: int = 1,
116
+ output_sheet_name: str or None = None,
117
+
118
+ femprj_path: str or None = None, # 指定する場合は xlsm と同じフォルダに配置する前提にすること
119
+ model_name: str = None,
120
+ csv_path: str or None = None,
121
+ constraint_sheet_name: str or None = None,
122
+ procedure_name: str or None = None, # 引数に拡張子を除く femprj ファイル名を取るように実装すること
123
+ setup_procedure_name: str or None = None,
124
+ teardown_procedure_name: str or None = None,
125
+
126
+ algorithm: str or None = None,
127
+
128
+ n_trials: int or None = None,
129
+ timeout: float or None = None,
130
+ seed: int or None = None,
131
+
132
+ **algorithm_settings: dict,
133
+
134
+ ):
135
+ import sys
136
+ import inspect
137
+ from pyfemtet.logger import get_module_logger
138
+
139
+ # ----- Fire memo -----
140
+ # print(csv_path) # 与えなければ None
141
+ # print(algorithm_settings) # 与えなければ {}, 与えれば {'n_startup_trials': 10} など
142
+ # print(n_parallel, type(n_parallel)) # int か float に自動変換される
143
+ # print(timeout, type(timeout)) # int か float に自動変換される
144
+
145
+
146
+ # ----- check 必須 args -----
147
+ logger = get_module_logger('opt.script', __name__)
148
+
149
+ os.chdir(os.path.dirname(__file__))
150
+
151
+ if xlsm_path is None:
152
+ logger.error(f'xlsm_path を指定してください。')
153
+ input('終了するには Enter を押してください。')
154
+ sys.exit(1)
155
+
156
+ if input_sheet_name is None:
157
+ logger.error(f'input_sheet_name を指定してください。')
158
+ input('終了するには Enter を押してください。')
159
+ sys.exit(1)
160
+
161
+ if output_sheet_name is None:
162
+ logger.error(f'output_sheet_name を指定してください。')
163
+ input('終了するには Enter を押してください。')
164
+ sys.exit(1)
165
+
166
+
167
+ # ----- check args -----
168
+ logger.info(f'{os.path.basename(__file__)} は {os.path.basename(xlsm_path)} に呼び出されました.')
169
+
170
+ # xlsm_path
171
+ xlsm_path = os.path.abspath(xlsm_path)
172
+ if not os.path.exists(xlsm_path):
173
+ logger.error(f'{xlsm_path} が見つかりませんでした。')
174
+ input('終了するには Enter を押してください。')
175
+ sys.exit(1)
176
+
177
+ # femprj_path
178
+ if femprj_path is not None:
179
+ femprj_path = os.path.abspath(femprj_path)
180
+ if not os.path.exists(femprj_path):
181
+ logger.error(f'{femprj_path} が見つかりませんでした。')
182
+ input('終了するには Enter を押してください。')
183
+ sys.exit(1)
184
+
185
+ # model_name
186
+ if model_name is not None and femprj_path is None:
187
+ logger.error(f'model_name ({model_name}) を指定する場合は femprj_path も指定してください。')
188
+ input('終了するには Enter を押してください。')
189
+ sys.exit(1)
190
+
191
+ # n_parallel
192
+ try:
193
+ n_parallel = int(n_parallel)
194
+ except ValueError:
195
+ logger.error(f'n_parallel ({n_parallel}) は自然数にできません。')
196
+ input('終了するには Enter を押してください。')
197
+ sys.exit(1)
198
+
199
+ # csv_path
200
+ csv_path = os.path.abspath(csv_path) if csv_path is not None else csv_path
201
+
202
+ # n_trials
203
+ if n_trials is not None:
204
+ try:
205
+ n_trials = int(n_trials)
206
+ except ValueError:
207
+ logger.error(f'n_trials ({n_trials}) は自然数にできません。')
208
+ input('終了するには Enter を押してください。')
209
+ sys.exit(1)
210
+
211
+ # timeout
212
+ if timeout is not None:
213
+ try:
214
+ timeout = float(timeout)
215
+ except ValueError:
216
+ logger.error(f'timeout ({timeout}) は数値にできません。')
217
+ input('終了するには Enter を押してください。')
218
+ sys.exit(1)
219
+
220
+ # seed
221
+ if seed is not None:
222
+ try:
223
+ seed = int(seed)
224
+ except ValueError:
225
+ logger.error(f'seed ({seed}) は自然数にできません。')
226
+ input('終了するには Enter を押してください。')
227
+ sys.exit(1)
228
+
229
+ # sampler
230
+ try:
231
+ sampler_class = get_sampler_class(algorithm)
232
+ except NotImplementedError:
233
+ logger.error(f'algorithm ({algorithm}) は非対応です。')
234
+ input('終了するには Enter を押してください。')
235
+ sys.exit(1)
236
+
237
+ # sampler_kwargs
238
+ sampler_kwargs = algorithm_settings
239
+ # noinspection PyUnboundLocalVariable
240
+ available_sampler_kwarg_keys = inspect.signature(sampler_class).parameters.keys()
241
+ for given_key in sampler_kwargs.keys():
242
+ if given_key not in available_sampler_kwarg_keys:
243
+ print()
244
+ print(sampler_class.__doc__)
245
+ print()
246
+ logger.error(f'algorithm_setting の項目 ({given_key}) は {sampler_class.__name__} に設定できません。詳しくは上記のドキュメントをご覧ください。')
247
+ input('終了するには Enter を押してください。')
248
+ sys.exit(1)
249
+
250
+ logger.info('引数の整合性チェックが終了しました。最適化を実行します。しばらくお待ちください...')
251
+
252
+ core(
253
+ xlsm_path,
254
+ csv_path,
255
+ femprj_path, # xlsm と同じフォルダに配置する前提。
256
+ model_name,
257
+ input_sheet_name,
258
+
259
+ output_sheet_name,
260
+ constraint_sheet_name,
261
+ procedure_name, # 引数に拡張子を除く femprj ファイル名を取るように実装すること
262
+ setup_procedure_name,
263
+ teardown_procedure_name,
264
+
265
+ sampler_class,
266
+ sampler_kwargs,
267
+
268
+ n_parallel,
269
+ n_trials,
270
+ timeout,
271
+ seed,
272
+ )
273
+
274
+
275
+ if __name__ == '__main__':
276
+ Fire(main)
277
+
278
+ # ===== Debug Code =====
279
+ # import os
280
+ # os.chdir(os.path.dirname(__file__))
281
+ # main(
282
+ # xlsm_path='インターフェース.xlsm',
283
+ # input_sheet_name='設計変数',
284
+ # n_parallel=1,
285
+ # output_sheet_name='目的関数',
286
+ # constraint_sheet_name='拘束関数',
287
+ # procedure_name='FemtetMacro.FemtetMain',
288
+ # setup_procedure_name='setup',
289
+ # teardown_procedure_name='teardown',
290
+ # n_trials=3,
291
+ # )
@@ -0,0 +1,22 @@
1
+ cd %~dp0
2
+ rem poetry run python pyfemtet-core.py --help
3
+ rem poetry run python pyfemtet-core.py "xlsm" "femprj" "input_sheet" 3
4
+ rem poetry run python pyfemtet-core.py "xlsm" "femprj" "input_sheet" 3 --n_startup_trials=10 --timeout=3.14
5
+ rem poetry run python pyfemtet-core.py "xlsm" "femprj" "input_sheet" 3 --n_startup_trials=10 --timeout=5
6
+ rem poetry run python pyfemtet-core.py "xlsm" "femprj" "input_sheet" 3.14 --n_startup_trials=10 --timeout=5
7
+ poetry run python pyfemtet-core.py ^
8
+ �C���^�[�t�F�[�X.xlsm ^
9
+ --input_sheet_name="�݌v�ϐ�" ^
10
+ --output_sheet_name="�ړI�֐�" ^
11
+ --constraint_sheet_name="�S���֐�" ^
12
+
13
+ --n_parallel=1 ^
14
+ --csv_path="test.csv" ^
15
+ --procedure_name=FemtetMacro.FemtetMain ^
16
+ --setup_procedure_name=PrePostProcessing.setup ^
17
+ --teardown_procedure_name=PrePostProcessing.teardown ^
18
+
19
+ --algorithm=Random ^
20
+ --n_startup_trials=10 ^
21
+
22
+ pause