pyfemtet 0.8.8__py3-none-any.whl → 0.8.10__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 +10 -2
- pyfemtet/_message/locales/messages.pot +10 -2
- pyfemtet/_message/messages.py +2 -1
- pyfemtet/_util/excel_parse_util.py +33 -15
- pyfemtet/opt/_femopt.py +3 -1
- pyfemtet/opt/_femopt_core.py +34 -26
- pyfemtet/opt/_test_utils/record_history.py +1 -0
- pyfemtet/opt/advanced_samples/excel_ui/femtet-macro.xlsm +0 -0
- pyfemtet/opt/advanced_samples/meta_script/meta_script.py +163 -0
- pyfemtet/opt/advanced_samples/meta_script/sample.yaml +14 -0
- pyfemtet/opt/advanced_samples/meta_script/yaml_generator.txt +0 -0
- pyfemtet/opt/advanced_samples/meta_script/yaml_generator.xlsm +0 -0
- pyfemtet/opt/advanced_samples/restart/gal_ex13_parametric.femprj +0 -0
- pyfemtet/opt/advanced_samples/restart/gal_ex13_parametric_restart.py +99 -0
- pyfemtet/opt/advanced_samples/restart/gal_ex13_parametric_restart_jp.py +102 -0
- pyfemtet/opt/interface/__init__.py +1 -1
- pyfemtet/opt/interface/_base.py +13 -4
- pyfemtet/opt/interface/_excel_interface.py +45 -39
- pyfemtet/opt/interface/_femtet.py +63 -2
- pyfemtet/opt/interface/_femtet_excel.py +138 -0
- pyfemtet/opt/interface/_surrogate/_base.py +43 -0
- pyfemtet/opt/interface/_surrogate_excel.py +102 -0
- pyfemtet/opt/optimizer/_base.py +2 -6
- pyfemtet/opt/optimizer/_optuna/_optuna.py +2 -2
- pyfemtet/opt/samples/femprj_sample/gau_ex12_parametric.femprj +0 -0
- pyfemtet/opt/samples/femprj_sample/gau_ex12_parametric.py +52 -0
- pyfemtet/opt/samples/femprj_sample_jp/gau_ex12_parametric_jp.py +52 -0
- pyfemtet/opt/visualization/_process_monitor/pages.py +13 -0
- {pyfemtet-0.8.8.dist-info → pyfemtet-0.8.10.dist-info}/METADATA +2 -1
- {pyfemtet-0.8.8.dist-info → pyfemtet-0.8.10.dist-info}/RECORD +35 -23
- {pyfemtet-0.8.8.dist-info → pyfemtet-0.8.10.dist-info}/WHEEL +1 -1
- {pyfemtet-0.8.8.dist-info → pyfemtet-0.8.10.dist-info}/LICENSE +0 -0
- {pyfemtet-0.8.8.dist-info → pyfemtet-0.8.10.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from time import sleep
|
|
3
|
+
|
|
4
|
+
from optuna.samplers import RandomSampler, NSGAIISampler, GPSampler, BaseSampler
|
|
5
|
+
|
|
6
|
+
from pyfemtet.opt import FEMOpt, FemtetInterface, OptunaOptimizer
|
|
7
|
+
|
|
8
|
+
os.chdir(os.path.dirname(__file__))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_res_freq(Femtet):
|
|
12
|
+
Galileo = Femtet.Gogh.Galileo
|
|
13
|
+
Galileo.Mode = 0
|
|
14
|
+
sleep(0.01)
|
|
15
|
+
return Galileo.GetFreq().Real
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main(n_trials, sampler_class: type[BaseSampler], sampler_kwargs: dict):
|
|
19
|
+
"""メイン関数
|
|
20
|
+
|
|
21
|
+
このサンプルは最適化を途中で中断して続きから行う場合で、
|
|
22
|
+
各リスタートで異なるアルゴリズムを使用して
|
|
23
|
+
最適化を再開する方法を示しています。
|
|
24
|
+
|
|
25
|
+
このメイン関数は n_trials と sampler_class を与えると
|
|
26
|
+
その回数、アルゴリズムに応じて最適化を行います。
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
|
|
30
|
+
n_trials (int):
|
|
31
|
+
最適化を終了するために必要な追加の成功した試行回数。
|
|
32
|
+
|
|
33
|
+
sampler_class (type[optuna.samplers.BaseSampler]):
|
|
34
|
+
使用するアルゴリズム。
|
|
35
|
+
|
|
36
|
+
sampler_kwargs (dict):
|
|
37
|
+
アルゴリズムの引数。
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Femtet に接続します。
|
|
43
|
+
fem = FemtetInterface(
|
|
44
|
+
femprj_path='gal_ex13_parametric.femprj',
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 最適化オブジェクトを初期化します。
|
|
48
|
+
opt = OptunaOptimizer(
|
|
49
|
+
sampler_class=sampler_class,
|
|
50
|
+
sampler_kwargs=sampler_kwargs,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# リスタートするためには以前の最適化の履歴を
|
|
54
|
+
# 新しい最適化プログラムに知らせる必要があります。
|
|
55
|
+
# FEMOpt の `history_path` 引数に csv を指定すると、
|
|
56
|
+
# それが存在しない場合、新しい csv ファイルを作り、
|
|
57
|
+
# それが存在する場合、その csv ファイルの続きから
|
|
58
|
+
# 最適化を実行します。
|
|
59
|
+
#
|
|
60
|
+
# 注意:
|
|
61
|
+
# リスタートする場合、変数の数と名前、目的関数の数と
|
|
62
|
+
# 名前、および拘束関数の数と名前が一貫している必要が
|
|
63
|
+
# あります。
|
|
64
|
+
# ただし、変数の上下限や目的関数の方向、拘束関数の内容
|
|
65
|
+
# などは変更できます。
|
|
66
|
+
#
|
|
67
|
+
# 注意:
|
|
68
|
+
# OptunaOptimizer を使用する場合、csv と同名の
|
|
69
|
+
# .db ファイル (ここでは restarting-sample.db) が
|
|
70
|
+
# csv ファイルと同じフォルダにある必要があります。
|
|
71
|
+
femopt = FEMOpt(
|
|
72
|
+
fem=fem,
|
|
73
|
+
opt=opt,
|
|
74
|
+
history_path='restarting-sample.csv'
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# 設計パラメータを指定します。
|
|
78
|
+
femopt.add_parameter('length', 0.1, 0.02, 0.2)
|
|
79
|
+
femopt.add_parameter('width', 0.01, 0.001, 0.02)
|
|
80
|
+
femopt.add_parameter('base_radius', 0.008, 0.006, 0.01)
|
|
81
|
+
|
|
82
|
+
# 目的関数を指定します。
|
|
83
|
+
femopt.add_objective(fun=get_res_freq, name='First Resonant Frequency (Hz)', direction=800)
|
|
84
|
+
|
|
85
|
+
# 最適化を実行します。
|
|
86
|
+
femopt.set_random_seed(42)
|
|
87
|
+
femopt.optimize(n_trials=n_trials, confirm_before_exit=False)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == '__main__':
|
|
91
|
+
# 最初に、RandomSampler を使用して 3 回計算を行います。
|
|
92
|
+
main(3, RandomSampler, {})
|
|
93
|
+
|
|
94
|
+
# 次に、NSGAIISampler を使用して 3 回計算を行います。
|
|
95
|
+
main(3, NSGAIISampler, {})
|
|
96
|
+
|
|
97
|
+
# 最後に、GPSampler を使用して 3 回計算を行います。
|
|
98
|
+
main(3, GPSampler, {'n_startup_trials': 0, 'deterministic_objective': True})
|
|
99
|
+
|
|
100
|
+
# このプログラム終了後、
|
|
101
|
+
# restarting-sample.csv と同名の .db ファイルを用いて
|
|
102
|
+
# さらに続きの最適化を行うことができます。
|
pyfemtet/opt/interface/_base.py
CHANGED
|
@@ -24,18 +24,27 @@ class FEMInterface(ABC):
|
|
|
24
24
|
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
|
+
kwargs = None
|
|
28
|
+
|
|
27
29
|
def __init__(
|
|
28
30
|
self,
|
|
29
31
|
**kwargs
|
|
30
32
|
):
|
|
31
33
|
# restore のための情報保管
|
|
32
|
-
self.kwargs
|
|
34
|
+
if self.kwargs is None:
|
|
35
|
+
self.kwargs = kwargs
|
|
36
|
+
else:
|
|
37
|
+
self.kwargs.update(kwargs)
|
|
33
38
|
|
|
34
39
|
@abstractmethod
|
|
35
40
|
def update(self, parameters: pd.DataFrame) -> None:
|
|
36
41
|
"""Updates the FEM analysis based on the proposed parameters."""
|
|
37
42
|
raise NotImplementedError('update() must be implemented.')
|
|
38
43
|
|
|
44
|
+
@property
|
|
45
|
+
def object_passed_to_functions(self):
|
|
46
|
+
return self
|
|
47
|
+
|
|
39
48
|
def check_param_value(self, param_name) -> float or None:
|
|
40
49
|
"""Checks the value of a parameter in the FEM model (if implemented in concrete class)."""
|
|
41
50
|
pass
|
|
@@ -50,13 +59,13 @@ class FEMInterface(ABC):
|
|
|
50
59
|
pass
|
|
51
60
|
|
|
52
61
|
def load_parameter(self, opt) -> None: # opt: AbstractOptimizer
|
|
53
|
-
|
|
62
|
+
pass
|
|
54
63
|
|
|
55
64
|
def load_objective(self, opt) -> None: # opt: AbstractOptimizer
|
|
56
|
-
|
|
65
|
+
pass
|
|
57
66
|
|
|
58
67
|
def load_constraint(self, opt) -> None: # opt: AbstractOptimizer
|
|
59
|
-
|
|
68
|
+
pass
|
|
60
69
|
|
|
61
70
|
def _setup_before_parallel(self, client) -> None:
|
|
62
71
|
"""Preprocessing before launching a dask worker (if implemented in concrete class).
|
|
@@ -55,36 +55,36 @@ class ExcelInterface(FEMInterface):
|
|
|
55
55
|
input_xlsm_path (str or Path):
|
|
56
56
|
設計変数の定義を含む Excel ファイルのパスを指定
|
|
57
57
|
します。
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
input_sheet_name (str):
|
|
60
60
|
設計変数の定義を含むシートの名前を指定します。
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
output_xlsm_path (str or Path, optional):
|
|
63
63
|
目的関数の定義を含む Excel ファイルのパスを指定
|
|
64
64
|
します。指定しない場合は ``input_xlsm_path`` と
|
|
65
65
|
同じと見做します。
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
output_sheet_name (str, optional):
|
|
68
68
|
目的関数の定義を含む含むシートの名前を指定します。
|
|
69
69
|
指定しない場合は ``input_sheet_name`` と同じと見
|
|
70
70
|
做します。
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
procedure_name (str, optional):
|
|
73
73
|
Excel マクロ関数名を指定します。指定しない場合は
|
|
74
74
|
``FemtetMacro.FemtetMain`` と見做します。
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
procedure_args (list or tuple, optional):
|
|
77
77
|
Excel マクロ関数に渡す引数をリストまたはタプルで
|
|
78
78
|
指定します。
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
connect_method (str, optional):
|
|
81
81
|
Excel との接続方法を指定します。 'auto' または
|
|
82
82
|
'new' が利用可能です。デフォルトは 'auto' です。
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
procedure_timeout (float or None, optional):
|
|
85
85
|
Excel マクロ関数のタイムアウト時間を秒単位で指定
|
|
86
86
|
します。 None の場合はタイムアウトなしとなります。
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
setup_xlsm_path (str or Path, optional):
|
|
89
89
|
セットアップ時に呼ぶ関数を含む xlsm のパスです。
|
|
90
90
|
指定しない場合は ``input_xlsm_path`` と
|
|
@@ -135,7 +135,7 @@ class ExcelInterface(FEMInterface):
|
|
|
135
135
|
Attributes:
|
|
136
136
|
input_xlsm_path (Path):
|
|
137
137
|
設計変数の定義を含む Excel ファイルのパス。
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
input_sheet_name (str):
|
|
140
140
|
設計変数の定義を含むシートの名前。
|
|
141
141
|
|
|
@@ -257,7 +257,7 @@ class ExcelInterface(FEMInterface):
|
|
|
257
257
|
self.output_sheet_name = output_sheet_name if output_sheet_name is not None else input_sheet_name
|
|
258
258
|
self.constraint_xlsm_path = str(input_xlsm_path) if constraint_xlsm_path is None else str(constraint_xlsm_path)
|
|
259
259
|
self.constraint_sheet_name = constraint_sheet_name or self.input_sheet_name
|
|
260
|
-
self.procedure_name = procedure_name
|
|
260
|
+
self.procedure_name = procedure_name
|
|
261
261
|
self.procedure_args = procedure_args or []
|
|
262
262
|
assert connect_method in ['new', 'auto']
|
|
263
263
|
self.connect_method = connect_method
|
|
@@ -271,7 +271,8 @@ class ExcelInterface(FEMInterface):
|
|
|
271
271
|
self.setup_procedure_name = setup_procedure_name
|
|
272
272
|
self.setup_procedure_args = setup_procedure_args or []
|
|
273
273
|
|
|
274
|
-
self.teardown_xlsm_path = str(input_xlsm_path) if teardown_xlsm_path is None else str(
|
|
274
|
+
self.teardown_xlsm_path = str(input_xlsm_path) if teardown_xlsm_path is None else str(
|
|
275
|
+
teardown_xlsm_path) # あとで取得する
|
|
275
276
|
self.teardown_procedure_name = teardown_procedure_name
|
|
276
277
|
self.teardown_procedure_args = teardown_procedure_args or []
|
|
277
278
|
|
|
@@ -516,7 +517,8 @@ class ExcelInterface(FEMInterface):
|
|
|
516
517
|
self.sh_constraint = sh
|
|
517
518
|
break
|
|
518
519
|
else:
|
|
519
|
-
raise RuntimeError(
|
|
520
|
+
raise RuntimeError(
|
|
521
|
+
f'Sheet {self.constraint_sheet_name} does not exist in the book {self.wb_constraint.Name}.')
|
|
520
522
|
|
|
521
523
|
# ===== setup =====
|
|
522
524
|
# 開く (setup)
|
|
@@ -618,18 +620,19 @@ class ExcelInterface(FEMInterface):
|
|
|
618
620
|
self.update_parameter(parameters)
|
|
619
621
|
|
|
620
622
|
# マクロ実行
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
self.excel.
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
623
|
+
if self.procedure_name is not None:
|
|
624
|
+
try:
|
|
625
|
+
with watch_excel_macro_error(self.excel, timeout=self.procedure_timeout):
|
|
626
|
+
self.excel.Run(
|
|
627
|
+
f'{self.procedure_name}',
|
|
628
|
+
*self.procedure_args
|
|
629
|
+
)
|
|
627
630
|
|
|
628
|
-
|
|
629
|
-
|
|
631
|
+
# 再計算
|
|
632
|
+
self.excel.CalculateFull()
|
|
630
633
|
|
|
631
|
-
|
|
632
|
-
|
|
634
|
+
except com_error as e:
|
|
635
|
+
raise SolveError(f'Failed to run macro {self.procedure_name}. The original message is: {e}')
|
|
633
636
|
|
|
634
637
|
def quit(self):
|
|
635
638
|
if self.terminate_excel_when_quit:
|
|
@@ -655,7 +658,8 @@ class ExcelInterface(FEMInterface):
|
|
|
655
658
|
self.excel.CalculateFull()
|
|
656
659
|
|
|
657
660
|
except com_error as e:
|
|
658
|
-
raise RuntimeError(
|
|
661
|
+
raise RuntimeError(
|
|
662
|
+
f'Failed to run macro {self.teardown_procedure_args}. The original message is: {e}')
|
|
659
663
|
|
|
660
664
|
# 不具合の原因になる場合があるので参照設定は解除しないこと
|
|
661
665
|
# self.remove_femtet_ref_xla(self.wb_input)
|
|
@@ -791,11 +795,14 @@ class ExcelInterface(FEMInterface):
|
|
|
791
795
|
opt.variables.add_expression(fixed_prm)
|
|
792
796
|
|
|
793
797
|
def load_objective(self, opt):
|
|
794
|
-
from pyfemtet.opt.optimizer import AbstractOptimizer
|
|
798
|
+
from pyfemtet.opt.optimizer import AbstractOptimizer
|
|
795
799
|
from pyfemtet.opt._femopt_core import Objective
|
|
796
800
|
opt: AbstractOptimizer
|
|
797
801
|
|
|
798
|
-
df = ParseAsObjective.parse(
|
|
802
|
+
df = ParseAsObjective.parse(
|
|
803
|
+
self.output_xlsm_path,
|
|
804
|
+
self.output_sheet_name,
|
|
805
|
+
)
|
|
799
806
|
|
|
800
807
|
for i, row in df.iterrows():
|
|
801
808
|
|
|
@@ -827,22 +834,19 @@ class ExcelInterface(FEMInterface):
|
|
|
827
834
|
kwargs=dict(),
|
|
828
835
|
)
|
|
829
836
|
|
|
830
|
-
def load_constraint(self, opt):
|
|
831
|
-
from pyfemtet.opt.optimizer import AbstractOptimizer
|
|
837
|
+
def load_constraint(self, opt, raise_if_no_keyword=False):
|
|
838
|
+
from pyfemtet.opt.optimizer import AbstractOptimizer
|
|
832
839
|
from pyfemtet.opt._femopt_core import Constraint
|
|
833
840
|
opt: AbstractOptimizer
|
|
834
841
|
|
|
835
|
-
#
|
|
836
|
-
#
|
|
837
|
-
#
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
return
|
|
844
|
-
|
|
845
|
-
df = ParseAsConstraint.parse(self.constraint_xlsm_path, self.constraint_sheet_name)
|
|
842
|
+
# constraint は optional であるが
|
|
843
|
+
# __init__ で input_sheet_name を入れられるので
|
|
844
|
+
# ここで constraint が実際に与えられているか判断する
|
|
845
|
+
df = ParseAsConstraint.parse(
|
|
846
|
+
self.constraint_xlsm_path,
|
|
847
|
+
self.constraint_sheet_name,
|
|
848
|
+
raise_if_no_keyword=raise_if_no_keyword,
|
|
849
|
+
)
|
|
846
850
|
|
|
847
851
|
for i, row in df.iterrows():
|
|
848
852
|
|
|
@@ -877,7 +881,8 @@ class ExcelInterface(FEMInterface):
|
|
|
877
881
|
calc_before_solve = True
|
|
878
882
|
if ParseAsConstraint.calc_before_solve in df.columns:
|
|
879
883
|
_calc_before_solve = row[ParseAsConstraint.calc_before_solve]
|
|
880
|
-
calc_before_solve = True if is_cell_value_empty(_calc_before_solve) else bool(
|
|
884
|
+
calc_before_solve = True if is_cell_value_empty(_calc_before_solve) else bool(
|
|
885
|
+
_calc_before_solve) # bool or NaN
|
|
881
886
|
|
|
882
887
|
if use:
|
|
883
888
|
# constraint を作る
|
|
@@ -892,6 +897,7 @@ class ExcelInterface(FEMInterface):
|
|
|
892
897
|
using_fem=not calc_before_solve,
|
|
893
898
|
)
|
|
894
899
|
|
|
900
|
+
# TODO: femopt_core.Function の仕様を変えたらここも変える
|
|
895
901
|
def objective_from_excel(self, name: str):
|
|
896
902
|
r = 1 + search_r(self.output_xlsm_path, self.output_sheet_name, name)
|
|
897
903
|
c = 1 + search_c(self.output_xlsm_path, self.output_sheet_name, ParseAsObjective.value)
|
|
@@ -192,7 +192,8 @@ class FemtetInterface(FEMInterface):
|
|
|
192
192
|
# subprocess で restore するための情報保管
|
|
193
193
|
# パスなどは connect_and_open_femtet での処理結果を反映し
|
|
194
194
|
# メインで開いた解析モデルが確実に開かれるようにする
|
|
195
|
-
|
|
195
|
+
FEMInterface.__init__(
|
|
196
|
+
self,
|
|
196
197
|
femprj_path=self.femprj_path,
|
|
197
198
|
model_name=self.model_name,
|
|
198
199
|
open_result_with_gui=self.open_result_with_gui,
|
|
@@ -201,6 +202,43 @@ class FemtetInterface(FEMInterface):
|
|
|
201
202
|
**kwargs
|
|
202
203
|
)
|
|
203
204
|
|
|
205
|
+
@property
|
|
206
|
+
def object_passed_to_functions(self):
|
|
207
|
+
return self.Femtet
|
|
208
|
+
|
|
209
|
+
def use_parametric_output_as_objective(self, number: int, direction: str | float = 'minimize') -> None:
|
|
210
|
+
"""Use output setting of Femtet parametric analysis as an objective function.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
number (int): The index of output settings tab in parametric analysis dialog of Femtet. Starts at 1.
|
|
214
|
+
direction (str | float): Objective direction. Valid input is one of 'minimize', 'maximize' or a specific value. Defaults to 'minimize'.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
None
|
|
218
|
+
|
|
219
|
+
"""
|
|
220
|
+
# check
|
|
221
|
+
if isinstance(direction, str):
|
|
222
|
+
if direction not in ('minimize', 'maximize'):
|
|
223
|
+
raise ValueError(f'direction must be one of "minimize", "maximize" or a specific value. Passed value is {direction}')
|
|
224
|
+
else:
|
|
225
|
+
try:
|
|
226
|
+
direction = float(direction)
|
|
227
|
+
except (TypeError, ValueError):
|
|
228
|
+
raise ValueError(f'direction must be one of "minimize", "maximize" or a specific value. Passed value is {direction}')
|
|
229
|
+
|
|
230
|
+
index = {number - 1: direction}
|
|
231
|
+
|
|
232
|
+
if self.parametric_output_indexes_use_as_objective is None:
|
|
233
|
+
self.parametric_output_indexes_use_as_objective = index
|
|
234
|
+
|
|
235
|
+
else:
|
|
236
|
+
self.parametric_output_indexes_use_as_objective.update(index)
|
|
237
|
+
|
|
238
|
+
# TODO: FEMInterface.__init__ の仕様を変えたらここも変える
|
|
239
|
+
self.kwargs['parametric_output_indexes_use_as_objective'] = self.parametric_output_indexes_use_as_objective
|
|
240
|
+
|
|
241
|
+
|
|
204
242
|
def __del__(self):
|
|
205
243
|
self.quit()
|
|
206
244
|
# CoUninitialize() # Win32 exception occurred releasing IUnknown at 0x0000022427692748
|
|
@@ -682,6 +720,14 @@ class FemtetInterface(FEMInterface):
|
|
|
682
720
|
|
|
683
721
|
if self.parametric_output_indexes_use_as_objective is not None:
|
|
684
722
|
from pyfemtet.opt.interface._femtet_parametric import solve_via_parametric_dll
|
|
723
|
+
|
|
724
|
+
pdt_path = self.Femtet.ResultFilePath + '.pdt'
|
|
725
|
+
|
|
726
|
+
# 前のものが残っているとややこしいので消しておく
|
|
727
|
+
if os.path.exists(pdt_path):
|
|
728
|
+
os.remove(pdt_path)
|
|
729
|
+
|
|
730
|
+
# parametric analysis 経由で解析
|
|
685
731
|
self._call_femtet_api(
|
|
686
732
|
fun=solve_via_parametric_dll,
|
|
687
733
|
return_value_if_failed=False,
|
|
@@ -690,8 +736,23 @@ class FemtetInterface(FEMInterface):
|
|
|
690
736
|
is_Gaudi_method=True,
|
|
691
737
|
args=(self.Femtet,),
|
|
692
738
|
)
|
|
739
|
+
|
|
740
|
+
# parametric analysis の場合
|
|
741
|
+
# ダイアログで「解析結果を保存する」に
|
|
742
|
+
# チェックがついていないと次にすべき
|
|
743
|
+
# OpenCurrentResult に失敗するので
|
|
744
|
+
# parametric の場合も pdt を保存する
|
|
745
|
+
self._call_femtet_api(
|
|
746
|
+
fun=self.Femtet.SavePDT,
|
|
747
|
+
args=(pdt_path, True),
|
|
748
|
+
return_value_if_failed=False,
|
|
749
|
+
if_error=SolveError,
|
|
750
|
+
error_message=Msg.ERR_FAILED_TO_SAVE_PDT,
|
|
751
|
+
is_Gaudi_method=False,
|
|
752
|
+
)
|
|
753
|
+
|
|
693
754
|
else:
|
|
694
|
-
#
|
|
755
|
+
# ソルブする
|
|
695
756
|
self._call_femtet_api(
|
|
696
757
|
self.Femtet.Solve,
|
|
697
758
|
False,
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from pyfemtet.opt.interface._base import FEMInterface
|
|
5
|
+
from pyfemtet.opt.interface._femtet import FemtetInterface
|
|
6
|
+
from pyfemtet.opt.interface._excel_interface import (
|
|
7
|
+
ExcelInterface, is_cell_value_empty, ParseAsObjective, ScapeGoatObjective,
|
|
8
|
+
ParseAsConstraint, search_c, search_r
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
PARAMETRIC_PREFIX = 'パラメトリック'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_number(name):
|
|
16
|
+
numbers = re.findall(r'\d+', name)
|
|
17
|
+
if len(numbers) == 0:
|
|
18
|
+
raise ValueError('パラメトリック結果出力の番号指定が検出できませんでした。')
|
|
19
|
+
else:
|
|
20
|
+
return int(numbers[0])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FemtetWithExcelSettingsInterface(FemtetInterface, ExcelInterface, FEMInterface):
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
|
|
29
|
+
# FemtetInterface arguments
|
|
30
|
+
femprj_path: str = None, model_name: str = None, connect_method: str = 'auto',
|
|
31
|
+
save_pdt: str = 'all', strictly_pid_specify: bool = True, allow_without_project: bool = False,
|
|
32
|
+
open_result_with_gui: bool = True,
|
|
33
|
+
parametric_output_indexes_use_as_objective: dict[int, str or float] = None,
|
|
34
|
+
|
|
35
|
+
# ExcelInterface arguments
|
|
36
|
+
input_xlsm_path: str or Path = None, input_sheet_name: str = None, output_xlsm_path: str or Path = None,
|
|
37
|
+
output_sheet_name: str = None, constraint_xlsm_path: str or Path = None,
|
|
38
|
+
constraint_sheet_name: str = None, procedure_name: str = None, procedure_args: list or tuple = None,
|
|
39
|
+
procedure_timeout: float or None = None,
|
|
40
|
+
setup_xlsm_path: str or Path = None, setup_procedure_name: str = None,
|
|
41
|
+
setup_procedure_args: list or tuple = None, teardown_xlsm_path: str or Path = None,
|
|
42
|
+
teardown_procedure_name: str = None, teardown_procedure_args: list or tuple = None,
|
|
43
|
+
related_file_paths: list[str or Path] = None, visible: bool = False, display_alerts: bool = False,
|
|
44
|
+
terminate_excel_when_quit: bool = None, interactive: bool = True, use_named_range: bool = True,
|
|
45
|
+
|
|
46
|
+
):
|
|
47
|
+
ExcelInterface.__init__(
|
|
48
|
+
self, input_xlsm_path, input_sheet_name, output_xlsm_path, output_sheet_name, constraint_xlsm_path,
|
|
49
|
+
constraint_sheet_name, procedure_name, procedure_args, connect_method, procedure_timeout,
|
|
50
|
+
setup_xlsm_path, setup_procedure_name, setup_procedure_args, teardown_xlsm_path,
|
|
51
|
+
teardown_procedure_name, teardown_procedure_args, related_file_paths, visible, display_alerts,
|
|
52
|
+
terminate_excel_when_quit, interactive, use_named_range)
|
|
53
|
+
|
|
54
|
+
FemtetInterface.__init__(
|
|
55
|
+
self,
|
|
56
|
+
femprj_path, model_name, connect_method, save_pdt, strictly_pid_specify, allow_without_project,
|
|
57
|
+
open_result_with_gui, parametric_output_indexes_use_as_objective
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def load_objective(self, opt):
|
|
62
|
+
from pyfemtet.opt.optimizer import AbstractOptimizer
|
|
63
|
+
from pyfemtet.opt._femopt_core import Objective
|
|
64
|
+
opt: AbstractOptimizer
|
|
65
|
+
|
|
66
|
+
df = ParseAsObjective.parse(
|
|
67
|
+
self.output_xlsm_path,
|
|
68
|
+
self.output_sheet_name,
|
|
69
|
+
False
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
for i, row in df.iterrows():
|
|
73
|
+
|
|
74
|
+
# use(optional)
|
|
75
|
+
use = True
|
|
76
|
+
if ParseAsObjective.use in df.columns:
|
|
77
|
+
_use = row[ParseAsObjective.use]
|
|
78
|
+
use = False if is_cell_value_empty(_use) else bool(_use) # bool or NaN
|
|
79
|
+
|
|
80
|
+
# name
|
|
81
|
+
name = str(row[ParseAsObjective.name])
|
|
82
|
+
|
|
83
|
+
# direction
|
|
84
|
+
direction = row[ParseAsObjective.direction]
|
|
85
|
+
assert not is_cell_value_empty(direction), 'direction is empty.'
|
|
86
|
+
try:
|
|
87
|
+
direction = float(direction)
|
|
88
|
+
except ValueError:
|
|
89
|
+
direction = str(direction).lower()
|
|
90
|
+
assert direction in ['minimize', 'maximize']
|
|
91
|
+
|
|
92
|
+
if use:
|
|
93
|
+
|
|
94
|
+
# name が「パラメトリック」から始まっていたら
|
|
95
|
+
# パラメトリック解析の結果を目的関数にする
|
|
96
|
+
if name.startswith(PARAMETRIC_PREFIX):
|
|
97
|
+
number = get_number(name)
|
|
98
|
+
self.use_parametric_output_as_objective(number, direction)
|
|
99
|
+
|
|
100
|
+
# そうでなければ通常の Excel objective を作る
|
|
101
|
+
else:
|
|
102
|
+
opt.objectives[name] = Objective(
|
|
103
|
+
fun=ScapeGoatObjective(),
|
|
104
|
+
name=name,
|
|
105
|
+
direction=direction,
|
|
106
|
+
args=(name,),
|
|
107
|
+
kwargs=dict(),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def _setup_before_parallel(self, client):
|
|
111
|
+
FemtetInterface._setup_before_parallel(self, client)
|
|
112
|
+
ExcelInterface._setup_before_parallel(self, client)
|
|
113
|
+
|
|
114
|
+
def _setup_after_parallel(self, *args, **kwargs):
|
|
115
|
+
FemtetInterface._setup_after_parallel(self, *args, **kwargs)
|
|
116
|
+
ExcelInterface._setup_after_parallel(self, *args, **kwargs)
|
|
117
|
+
|
|
118
|
+
def update(self, parameters) -> None:
|
|
119
|
+
FemtetInterface.update(self, parameters)
|
|
120
|
+
ExcelInterface.update(self, parameters)
|
|
121
|
+
|
|
122
|
+
def quit(self, timeout=1, force=True):
|
|
123
|
+
FemtetInterface.quit(self, timeout, force)
|
|
124
|
+
ExcelInterface.quit(self)
|
|
125
|
+
|
|
126
|
+
# noinspection PyMethodOverriding
|
|
127
|
+
def objective_from_excel(self, _, name: str):
|
|
128
|
+
r = 1 + search_r(self.output_xlsm_path, self.output_sheet_name, name)
|
|
129
|
+
c = 1 + search_c(self.output_xlsm_path, self.output_sheet_name, ParseAsObjective.value)
|
|
130
|
+
v = self.sh_output.Cells(r, c).value
|
|
131
|
+
return float(v)
|
|
132
|
+
|
|
133
|
+
# noinspection PyMethodOverriding
|
|
134
|
+
def constraint_from_excel(self, _, name: str):
|
|
135
|
+
r = 1 + search_r(self.constraint_xlsm_path, self.constraint_sheet_name, name)
|
|
136
|
+
c = 1 + search_c(self.constraint_xlsm_path, self.constraint_sheet_name, ParseAsConstraint.value)
|
|
137
|
+
v = self.sh_constraint.Cells(r, c).value
|
|
138
|
+
return float(v)
|
|
@@ -17,6 +17,7 @@ class SurrogateModelInterfaceBase(FEMInterface, ABC):
|
|
|
17
17
|
self,
|
|
18
18
|
history_path: str = None,
|
|
19
19
|
train_history: History = None,
|
|
20
|
+
_output_directions: dict[int, str | float] | list[str | float] = None,
|
|
20
21
|
):
|
|
21
22
|
|
|
22
23
|
self.train_history: History
|
|
@@ -25,6 +26,7 @@ class SurrogateModelInterfaceBase(FEMInterface, ABC):
|
|
|
25
26
|
self.obj: dict[str, float] = dict()
|
|
26
27
|
self.df_prm: pd.DataFrame
|
|
27
28
|
self.df_obj: pd.DataFrame
|
|
29
|
+
self._output_directions = _output_directions
|
|
28
30
|
|
|
29
31
|
# history_path が与えられた場合、train_history をコンストラクトする
|
|
30
32
|
if history_path is not None:
|
|
@@ -54,11 +56,52 @@ class SurrogateModelInterfaceBase(FEMInterface, ABC):
|
|
|
54
56
|
self.df_prm = df_prm
|
|
55
57
|
self.df_obj = df_obj
|
|
56
58
|
|
|
59
|
+
# _output_directions が与えられている場合、
|
|
60
|
+
# history から objective の設定を読み込む
|
|
61
|
+
if self._output_directions is not None:
|
|
62
|
+
self._load_problem_from_me: bool = True
|
|
63
|
+
|
|
57
64
|
FEMInterface.__init__(
|
|
58
65
|
self,
|
|
59
66
|
train_history=train_history, # コンストラクト済み train_history を渡せば並列計算時も何もしなくてよい
|
|
60
67
|
)
|
|
61
68
|
|
|
69
|
+
|
|
70
|
+
def load_objective(self, opt) -> None:
|
|
71
|
+
from pyfemtet.opt._femopt_core import Objective
|
|
72
|
+
|
|
73
|
+
assert self._output_directions is not None
|
|
74
|
+
|
|
75
|
+
if isinstance(self._output_directions, dict):
|
|
76
|
+
|
|
77
|
+
for index, direction in self._output_directions:
|
|
78
|
+
obj_name = self.train_history.obj_names[index]
|
|
79
|
+
opt.objectives[obj_name] = Objective(
|
|
80
|
+
lambda obj_name_=obj_name: self.obj[obj_name_],
|
|
81
|
+
name=obj_name,
|
|
82
|
+
direction=direction,
|
|
83
|
+
args=(),
|
|
84
|
+
kwargs={},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
elif isinstance(self._output_directions, list) \
|
|
88
|
+
or isinstance(self._output_directions, tuple):
|
|
89
|
+
|
|
90
|
+
obj_names = self.train_history.obj_names
|
|
91
|
+
assert len(self._output_directions) == len(obj_names)
|
|
92
|
+
|
|
93
|
+
for obj_name, direction in zip(obj_names, self._output_directions):
|
|
94
|
+
opt.objectives[obj_name] = Objective(
|
|
95
|
+
lambda obj_name_=obj_name: self.obj[obj_name_],
|
|
96
|
+
name=obj_name,
|
|
97
|
+
direction=direction,
|
|
98
|
+
args=(),
|
|
99
|
+
kwargs={},
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
else:
|
|
103
|
+
raise ValueError('Invalid _output_directions')
|
|
104
|
+
|
|
62
105
|
def filter_feasible(self, x: np.ndarray, y: np.ndarray, return_feasibility=False):
|
|
63
106
|
feasible_idx = np.where(~np.isnan(y.sum(axis=1)))
|
|
64
107
|
if return_feasibility:
|