pyfemtet 0.3.12__py3-none-any.whl → 0.4.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 (35) hide show
  1. pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.py +1 -1
  2. pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.py +1 -1
  3. pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +1 -1
  4. pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
  5. pyfemtet/FemtetPJTSample/her_ex40_parametric.py +1 -1
  6. pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +1 -1
  7. pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
  8. pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +1 -1
  9. pyfemtet/__init__.py +1 -1
  10. pyfemtet/core.py +14 -0
  11. pyfemtet/dispatch_extensions.py +5 -0
  12. pyfemtet/opt/__init__.py +22 -2
  13. pyfemtet/opt/_femopt.py +544 -0
  14. pyfemtet/opt/_femopt_core.py +730 -0
  15. pyfemtet/opt/interface/__init__.py +15 -0
  16. pyfemtet/opt/interface/_base.py +71 -0
  17. pyfemtet/opt/{interface.py → interface/_femtet.py} +120 -407
  18. pyfemtet/opt/interface/_femtet_with_nx/__init__.py +3 -0
  19. pyfemtet/opt/interface/_femtet_with_nx/_interface.py +128 -0
  20. pyfemtet/opt/interface/_femtet_with_sldworks.py +174 -0
  21. pyfemtet/opt/opt/__init__.py +8 -0
  22. pyfemtet/opt/opt/_base.py +202 -0
  23. pyfemtet/opt/opt/_optuna.py +240 -0
  24. pyfemtet/opt/visualization/__init__.py +7 -0
  25. pyfemtet/opt/visualization/_graphs.py +222 -0
  26. pyfemtet/opt/visualization/_monitor.py +1149 -0
  27. {pyfemtet-0.3.12.dist-info → pyfemtet-0.4.1.dist-info}/METADATA +4 -4
  28. pyfemtet-0.4.1.dist-info/RECORD +38 -0
  29. {pyfemtet-0.3.12.dist-info → pyfemtet-0.4.1.dist-info}/WHEEL +1 -1
  30. pyfemtet-0.4.1.dist-info/entry_points.txt +3 -0
  31. pyfemtet/opt/base.py +0 -1490
  32. pyfemtet/opt/monitor.py +0 -474
  33. pyfemtet-0.3.12.dist-info/RECORD +0 -26
  34. /pyfemtet/opt/{_FemtetWithNX → interface/_femtet_with_nx}/update_model.py +0 -0
  35. {pyfemtet-0.3.12.dist-info → pyfemtet-0.4.1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,3 @@
1
+ from ._interface import FemtetWithNXInterface
2
+
3
+ __all__ = ['FemtetWithNXInterface']
@@ -0,0 +1,128 @@
1
+ import os
2
+ import json
3
+ import subprocess
4
+
5
+ import pandas as pd
6
+ from dask.distributed import get_worker
7
+
8
+ from pyfemtet.core import ModelError
9
+ from pyfemtet.opt.interface import FemtetInterface, logger
10
+
11
+
12
+ here, me = os.path.split(__file__)
13
+
14
+
15
+ class FemtetWithNXInterface(FemtetInterface):
16
+ """Femtet with NX interface class.
17
+
18
+ Args:
19
+ prt_path: The path to the prt file.
20
+
21
+ For details of The other arguments, see ``FemtetInterface``.
22
+
23
+ """
24
+
25
+ _JOURNAL_PATH = os.path.abspath(os.path.join(here, 'update_model.py'))
26
+
27
+ def __init__(
28
+ self,
29
+ prt_path,
30
+ femprj_path=None,
31
+ model_name=None,
32
+ connect_method='auto',
33
+ strictly_pid_specify=True,
34
+ ):
35
+
36
+ # check NX installation
37
+ self.run_journal_path = os.path.join(os.environ.get('UGII_BASE_DIR'), 'NXBIN', 'run_journal.exe')
38
+ if not os.path.isfile(self.run_journal_path):
39
+ raise FileNotFoundError(
40
+ r'"%UGII_BASE_DIR%\NXBIN\run_journal.exe" が見つかりませんでした。環境変数 UGII_BASE_DIR 又は NX のインストール状態を確認してください。')
41
+
42
+ # 引数の処理
43
+ # dask サブプロセスのときは prt_path を worker space から取るようにする
44
+ try:
45
+ worker = get_worker()
46
+ space = worker.local_directory
47
+ self.prt_path = os.path.join(space, os.path.basename(prt_path))
48
+ except ValueError: # get_worker に失敗した場合
49
+ self.prt_path = os.path.abspath(prt_path)
50
+
51
+ # FemtetInterface の設定 (femprj_path, model_name の更新など)
52
+ # + restore 情報の上書き
53
+ super().__init__(
54
+ femprj_path=femprj_path,
55
+ model_name=model_name,
56
+ connect_method=connect_method,
57
+ strictly_pid_specify=strictly_pid_specify,
58
+ prt_path=self.prt_path,
59
+ )
60
+
61
+ def check_param_value(self, name):
62
+ """Override FemtetInterface.check_param_value().
63
+
64
+ Do nothing because the parameter can be registered
65
+ to not only .femprj but also .prt.
66
+
67
+ """
68
+ pass
69
+
70
+ def _setup_before_parallel(self, client):
71
+ client.upload_file(
72
+ self.kwargs['prt_path'],
73
+ False
74
+ )
75
+ super()._setup_before_parallel(client)
76
+
77
+ def update_model(self, parameters: 'pd.DataFrame') -> None:
78
+ """Update .x_t"""
79
+
80
+ self.parameters = parameters.copy()
81
+
82
+ # Femtet が参照している x_t パスを取得する
83
+ x_t_path = self.Femtet.Gaudi.LastXTPath
84
+
85
+ # 前のが存在するならば消しておく
86
+ if os.path.isfile(x_t_path):
87
+ os.remove(x_t_path)
88
+
89
+ # 変数の json 文字列を作る
90
+ tmp_dict = {}
91
+ for i, row in parameters.iterrows():
92
+ tmp_dict[row['name']] = row['value']
93
+ str_json = json.dumps(tmp_dict)
94
+
95
+ # NX journal を使ってモデルを編集する
96
+ env = os.environ.copy()
97
+ subprocess.run(
98
+ [self.run_journal_path, self._JOURNAL_PATH, '-args', self.prt_path, str_json, x_t_path],
99
+ env=env,
100
+ shell=True,
101
+ cwd=os.path.dirname(self.prt_path)
102
+ )
103
+
104
+ # この時点で x_t ファイルがなければ NX がモデル更新に失敗しているはず
105
+ if not os.path.isfile(x_t_path):
106
+ raise ModelError
107
+
108
+ # モデルの再インポート
109
+ self._call_femtet_api(
110
+ self.Femtet.Gaudi.ReExecute,
111
+ False,
112
+ ModelError, # 生きてるのに失敗した場合
113
+ error_message=f'モデル再構築に失敗しました.',
114
+ is_Gaudi_method=True,
115
+ )
116
+
117
+ # 処理を確定
118
+ self._call_femtet_api(
119
+ self.Femtet.Redraw,
120
+ False, # 戻り値は常に None なのでこの変数に意味はなく None 以外なら何でもいい
121
+ ModelError, # 生きてるのに失敗した場合
122
+ error_message=f'モデル再構築に失敗しました.',
123
+ is_Gaudi_method=True,
124
+ )
125
+
126
+ # femprj モデルの変数も更新
127
+ super().update_model(parameters)
128
+
@@ -0,0 +1,174 @@
1
+ import os
2
+ import re
3
+ from time import sleep, time
4
+
5
+ import pandas as pd
6
+ from dask.distributed import get_worker
7
+
8
+ from win32com.client import DispatchEx
9
+ from pythoncom import CoInitialize, CoUninitialize
10
+
11
+ from pyfemtet.core import ModelError
12
+ from pyfemtet.opt.interface import FemtetInterface, logger
13
+
14
+
15
+ class FemtetWithSolidworksInterface(FemtetInterface):
16
+ # 定数の宣言
17
+ swThisConfiguration = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swInConfigurationOpts_e.html
18
+ swAllConfiguration = 2
19
+ swSpecifyConfiguration = 3 # use with ConfigName argument
20
+ swSaveAsCurrentVersion = 0
21
+ swSaveAsOptions_Copy = 2 #
22
+ swSaveAsOptions_Silent = 1 # https://help.solidworks.com/2021/english/api/swconst/solidworks.interop.swconst~solidworks.interop.swconst.swsaveasoptions_e.html
23
+ swSaveWithReferencesOptions_None = 0 # https://help-solidworks-com.translate.goog/2023/english/api/swconst/SolidWorks.Interop.swconst~SolidWorks.Interop.swconst.swSaveWithReferencesOptions_e.html?_x_tr_sl=auto&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=wapp
24
+ swDocPART = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swDocumentTypes_e.html
25
+
26
+ def __init__(
27
+ self,
28
+ sldprt_path,
29
+ femprj_path=None,
30
+ model_name=None,
31
+ connect_method='auto',
32
+ strictly_pid_specify=True,
33
+ ):
34
+ # 引数の処理
35
+ # dask サブプロセスのときは space 直下の sldprt_path を参照する
36
+ try:
37
+ worker = get_worker()
38
+ space = worker.local_directory
39
+ self.sldprt_path = os.path.join(space, os.path.basename(sldprt_path))
40
+ except ValueError: # get_worker に失敗した場合
41
+ self.sldprt_path = os.path.abspath(sldprt_path)
42
+
43
+ # FemtetInterface の設定 (femprj_path, model_name の更新など)
44
+ # + restore 情報の上書き
45
+ super().__init__(
46
+ femprj_path=femprj_path,
47
+ model_name=model_name,
48
+ connect_method=connect_method,
49
+ strictly_pid_specify=strictly_pid_specify,
50
+ sldprt_path=self.sldprt_path,
51
+ )
52
+
53
+ def initialize_sldworks_connection(self):
54
+ # SolidWorks を捕まえ、ファイルを開く
55
+ self.swApp = DispatchEx('SLDWORKS.Application')
56
+ self.swApp.Visible = True
57
+
58
+ # open model
59
+ self.swApp.OpenDoc(self.sldprt_path, self.swDocPART)
60
+ self.swModel = self.swApp.ActiveDoc
61
+ self.swEqnMgr = self.swModel.GetEquationMgr
62
+ self.nEquation = self.swEqnMgr.GetCount
63
+
64
+ def check_param_value(self, param_name):
65
+ """Override FemtetInterface.check_param_value().
66
+
67
+ Do nothing because the parameter can be registered
68
+ to not only .femprj but also .SLDPRT.
69
+
70
+ """
71
+ pass
72
+
73
+ def _setup_before_parallel(self, client):
74
+ client.upload_file(
75
+ self.kwargs['sldprt_path'],
76
+ False
77
+ )
78
+ super()._setup_before_parallel(client)
79
+
80
+ def _setup_after_parallel(self):
81
+ CoInitialize()
82
+ self.initialize_sldworks_connection()
83
+
84
+ def update_model(self, parameters: pd.DataFrame):
85
+ """Update .x_t"""
86
+
87
+ self.parameters = parameters.copy()
88
+
89
+ # Femtet が参照している x_t パスを取得する
90
+ x_t_path = self.Femtet.Gaudi.LastXTPath
91
+
92
+ # 前のが存在するならば消しておく
93
+ if os.path.isfile(x_t_path):
94
+ os.remove(x_t_path)
95
+
96
+ # solidworks のモデルの更新
97
+ self.update_sw_model(parameters)
98
+
99
+ # export as x_t
100
+ self.swModel.SaveAs(x_t_path)
101
+
102
+ # 30 秒待っても x_t ができてなければエラー(COM なので)
103
+ timeout = 30
104
+ start = time()
105
+ while True:
106
+ if os.path.isfile(x_t_path):
107
+ break
108
+ if time() - start > timeout:
109
+ raise ModelError('モデル再構築に失敗しました')
110
+ sleep(1)
111
+
112
+ # モデルの再インポート
113
+ self._call_femtet_api(
114
+ self.Femtet.Gaudi.ReExecute,
115
+ False,
116
+ ModelError, # 生きてるのに失敗した場合
117
+ error_message=f'モデル再構築に失敗しました.',
118
+ is_Gaudi_method=True,
119
+ )
120
+
121
+ # 処理を確定
122
+ self._call_femtet_api(
123
+ self.Femtet.Redraw,
124
+ False, # 戻り値は常に None なのでこの変数に意味はなく None 以外なら何でもいい
125
+ ModelError, # 生きてるのに失敗した場合
126
+ error_message=f'モデル再構築に失敗しました.',
127
+ is_Gaudi_method=True,
128
+ )
129
+
130
+ # femprj モデルの変数も更新
131
+ super().update_model(parameters)
132
+
133
+ def update_sw_model(self, parameters: pd.DataFrame):
134
+ """Update .sldprt"""
135
+ # df を dict に変換
136
+ user_param_dict = {}
137
+ for i, row in parameters.iterrows():
138
+ user_param_dict[row['name']] = row['value']
139
+
140
+ # プロパティを退避
141
+ buffer_aso = self.swEqnMgr.AutomaticSolveOrder
142
+ buffer_ar = self.swEqnMgr.AutomaticRebuild
143
+ self.swEqnMgr.AutomaticSolveOrder = False
144
+ self.swEqnMgr.AutomaticRebuild = False
145
+
146
+ for i in range(self.nEquation):
147
+ # name, equation の取得
148
+ current_equation = self.swEqnMgr.Equation(i)
149
+ current_name = self._get_name_from_equation(current_equation)
150
+ # 対象なら処理
151
+ if current_name in list(user_param_dict.keys()):
152
+ new_equation = f'"{current_name}" = {user_param_dict[current_name]}'
153
+ self.swEqnMgr.Equation(i, new_equation)
154
+
155
+ # 式の計算
156
+ # noinspection PyStatementEffect
157
+ self.swEqnMgr.EvaluateAll # always returns -1
158
+
159
+ # プロパティをもとに戻す
160
+ self.swEqnMgr.AutomaticSolveOrder = buffer_aso
161
+ self.swEqnMgr.AutomaticRebuild = buffer_ar
162
+
163
+ # 更新する(ここで失敗はしうる)
164
+ result = self.swModel.EditRebuild3 # モデル再構築
165
+ if not result:
166
+ raise ModelError('モデル再構築に失敗しました')
167
+
168
+ def _get_name_from_equation(self, equation: str):
169
+ pattern = r'^\s*"(.+?)"\s*$'
170
+ matched = re.match(pattern, equation.split('=')[0])
171
+ if matched:
172
+ return matched.group(1)
173
+ else:
174
+ return None
@@ -0,0 +1,8 @@
1
+ from pyfemtet.opt.opt._base import AbstractOptimizer, logger
2
+ from pyfemtet.opt.opt._optuna import OptunaOptimizer
3
+
4
+ __all__ = [
5
+ 'OptunaOptimizer',
6
+ 'AbstractOptimizer',
7
+ 'logger',
8
+ ]
@@ -0,0 +1,202 @@
1
+ # typing
2
+ from abc import ABC, abstractmethod
3
+
4
+ # built-in
5
+ import json
6
+ from time import sleep
7
+
8
+ # 3rd-party
9
+ import numpy as np
10
+ import pandas as pd
11
+
12
+ # pyfemtet relative
13
+ from pyfemtet.opt.interface import FemtetInterface
14
+ from pyfemtet.opt._femopt_core import OptimizationStatus
15
+
16
+ # logger
17
+ import logging
18
+ from pyfemtet.logger import get_logger
19
+ logger = get_logger('opt')
20
+ logger.setLevel(logging.INFO)
21
+
22
+
23
+ class AbstractOptimizer(ABC):
24
+ """Abstract base class for an interface of optimization library.
25
+
26
+ Attributes:
27
+ fem (FEMInterface): The finite element method object.
28
+ fem_class (type): The class of the finite element method object.
29
+ fem_kwargs (dict): The keyword arguments used to instantiate the finite element method object.
30
+ parameters (pd.DataFrame): The parameters used in the optimization.
31
+ objectives (dict): A dictionary containing the objective functions used in the optimization.
32
+ constraints (dict): A dictionary containing the constraint functions used in the optimization.
33
+ entire_status (OptimizationStatus): The status of the entire optimization process.
34
+ history (History): An actor object that records the history of each iteration in the optimization process.
35
+ worker_status (OptimizationStatus): The status of each worker in a distributed computing environment.
36
+ message (str): A message associated with the current state of the optimization process.
37
+ seed (int or None): The random seed used for random number generation during the optimization process.
38
+ timeout (float or int or None): The maximum time allowed for each iteration of the optimization process. If exceeded, it will be interrupted and terminated early.
39
+ n_trials (int or None): The maximum number of trials allowed for each iteration of the optimization process. If exceeded, it will be interrupted and terminated early.
40
+ is_cluster (bool): Flag indicating if running on a distributed computing cluster.
41
+
42
+ """
43
+
44
+ def __init__(self):
45
+ self.fem = None
46
+ self.fem_class = None
47
+ self.fem_kwargs = dict()
48
+ self.parameters = pd.DataFrame()
49
+ self.objectives = dict()
50
+ self.constraints = dict()
51
+ self.entire_status = None # actor
52
+ self.history = None # actor
53
+ self.worker_status = None # actor
54
+ self.message = ''
55
+ self.seed = None
56
+ self.timeout = None
57
+ self.n_trials = None
58
+ self.is_cluster = False
59
+ self.subprocess_idx = None
60
+
61
+ def f(self, x):
62
+ """Get x, update fem analysis, return objectives (and constraints)."""
63
+ # interruption の実装は具象クラスに任せる
64
+
65
+ # x の更新
66
+ self.parameters['value'] = x
67
+
68
+ # FEM の更新
69
+ logger.debug('fem.update() start')
70
+ self.fem.update(self.parameters)
71
+
72
+ # y, _y, c の更新
73
+ logger.debug('calculate y start')
74
+ y = [obj.calc(self.fem) for obj in self.objectives.values()]
75
+
76
+ logger.debug('calculate _y start')
77
+ _y = [obj.convert(value) for obj, value in zip(self.objectives.values(), y)]
78
+
79
+ logger.debug('calculate c start')
80
+ c = [cns.calc(self.fem) for cns in self.constraints.values()]
81
+
82
+ logger.debug('history.record start')
83
+ self.history.record(
84
+ self.parameters,
85
+ self.objectives,
86
+ self.constraints,
87
+ y,
88
+ c,
89
+ self.message,
90
+ )
91
+
92
+ logger.debug('history.record end')
93
+ return np.array(y), np.array(_y), np.array(c)
94
+
95
+ def _reconstruct_fem(self, skip_reconstruct=False):
96
+ """Reconstruct FEMInterface in a subprocess."""
97
+ # reconstruct fem
98
+ if not skip_reconstruct:
99
+ self.fem = self.fem_class(**self.fem_kwargs)
100
+
101
+ # COM 定数の restore
102
+ for obj in self.objectives.values():
103
+ obj._restore_constants()
104
+ for cns in self.constraints.values():
105
+ cns._restore_constants()
106
+
107
+ def get_parameter(self, format='dict'):
108
+ """Returns the parameters in the specified format.
109
+
110
+ Args:
111
+ format (str, optional): The desired format of the parameters. Can be 'df' (DataFrame), 'values', or 'dict'. Defaults to 'dict'.
112
+
113
+ Returns:
114
+ object: The parameters in the specified format.
115
+
116
+ Raises:
117
+ ValueError: If an invalid format is provided.
118
+
119
+ """
120
+ if format == 'df':
121
+ return self.parameters
122
+ elif format == 'values' or format == 'value':
123
+ return self.parameters.value.values
124
+ elif format == 'dict':
125
+ ret = {}
126
+ for i, row in self.parameters.iterrows():
127
+ ret[row['name']] = row.value
128
+ return ret
129
+ else:
130
+ raise ValueError('get_parameter() got invalid format: {format}')
131
+
132
+ def _check_interruption(self):
133
+ """"""
134
+ if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
135
+ self.worker_status.set(OptimizationStatus.INTERRUPTING)
136
+ self._finalize()
137
+ return True
138
+ else:
139
+ return False
140
+
141
+ def _finalize(self):
142
+ """Destruct fem and set worker status."""
143
+ del self.fem
144
+ self.worker_status.set(OptimizationStatus.TERMINATED)
145
+
146
+ def _run(
147
+ self,
148
+ subprocess_idx,
149
+ worker_status_list,
150
+ wait_setup,
151
+ skip_set_fem=False,
152
+ ) -> None:
153
+
154
+ # 自分の worker_status の取得
155
+ self.subprocess_idx = subprocess_idx
156
+ self.worker_status = worker_status_list[subprocess_idx]
157
+ self.worker_status.set(OptimizationStatus.LAUNCHING_FEM)
158
+
159
+ if self._check_interruption():
160
+ return None
161
+
162
+ # set_fem をはじめ、終了したらそれを示す
163
+ if not skip_set_fem: # なくても動く??
164
+ self._reconstruct_fem()
165
+ self.fem._setup_after_parallel()
166
+ self.worker_status.set(OptimizationStatus.WAIT_OTHER_WORKERS)
167
+
168
+ # wait_setup or not
169
+ if wait_setup:
170
+ while True:
171
+ if self._check_interruption():
172
+ return None
173
+ # 他のすべての worker_status が wait 以上になったら break
174
+ if all([ws.get() >= OptimizationStatus.WAIT_OTHER_WORKERS for ws in worker_status_list]):
175
+ break
176
+ sleep(1)
177
+ else:
178
+ if self._check_interruption():
179
+ return None
180
+
181
+ # set status running
182
+ if self.entire_status.get() < OptimizationStatus.RUNNING:
183
+ self.entire_status.set(OptimizationStatus.RUNNING)
184
+ self.worker_status.set(OptimizationStatus.RUNNING)
185
+
186
+ # run and finalize
187
+ try:
188
+ self.run()
189
+ finally:
190
+ self._finalize()
191
+
192
+ return None
193
+
194
+ @abstractmethod
195
+ def run(self) -> None:
196
+ """Start calcuration using optimization library."""
197
+ pass
198
+
199
+ @abstractmethod
200
+ def _setup_before_parallel(self, *args, **kwargs):
201
+ """Setup before parallel processes are launched."""
202
+ pass