pyfemtet 0.6.2__py3-none-any.whl → 0.6.3__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/_femtet_config_util/__init__.py +0 -0
- pyfemtet/_femtet_config_util/autosave.py +38 -0
- pyfemtet/_femtet_config_util/exit.py +58 -0
- pyfemtet/_message/1. make_pot.bat +2 -3
- pyfemtet/_message/2. make_mo.bat +1 -1
- pyfemtet/_message/babel.cfg +1 -1
- pyfemtet/_message/locales/ja/LC_MESSAGES/messages.po +136 -124
- pyfemtet/_message/locales/messages.pot +136 -124
- pyfemtet/_message/messages.py +3 -0
- pyfemtet/_warning.py +59 -3
- pyfemtet/opt/_femopt.py +58 -38
- pyfemtet/opt/_test_utils/record_history.py +50 -0
- pyfemtet/opt/interface/_base.py +10 -1
- pyfemtet/opt/interface/_excel_interface.py +437 -0
- pyfemtet/opt/interface/_femtet.py +4 -75
- pyfemtet/opt/interface/_femtet_with_sldworks.py +1 -1
- pyfemtet/opt/optimizer/_base.py +10 -8
- pyfemtet/opt/visualization/_complex_components/main_figure_creator.py +82 -21
- {pyfemtet-0.6.2.dist-info → pyfemtet-0.6.3.dist-info}/METADATA +1 -1
- {pyfemtet-0.6.2.dist-info → pyfemtet-0.6.3.dist-info}/RECORD +24 -20
- {pyfemtet-0.6.2.dist-info → pyfemtet-0.6.3.dist-info}/LICENSE +0 -0
- {pyfemtet-0.6.2.dist-info → pyfemtet-0.6.3.dist-info}/WHEEL +0 -0
- {pyfemtet-0.6.2.dist-info → pyfemtet-0.6.3.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from time import sleep
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import numpy as np
|
|
7
|
+
from dask.distributed import get_worker
|
|
8
|
+
|
|
9
|
+
from win32com.client import DispatchEx, Dispatch
|
|
10
|
+
from win32com.client.dynamic import CDispatch
|
|
11
|
+
from femtetutils import util
|
|
12
|
+
|
|
13
|
+
# noinspection PyUnresolvedReferences
|
|
14
|
+
from pythoncom import CoInitialize, CoUninitialize
|
|
15
|
+
# noinspection PyUnresolvedReferences
|
|
16
|
+
from pywintypes import com_error
|
|
17
|
+
|
|
18
|
+
from pyfemtet.opt import FEMInterface
|
|
19
|
+
from pyfemtet.core import SolveError
|
|
20
|
+
from pyfemtet.opt.optimizer.parameter import Parameter
|
|
21
|
+
|
|
22
|
+
from pyfemtet.dispatch_extensions import _get_pid, dispatch_specific_femtet
|
|
23
|
+
from pyfemtet.dispatch_extensions._impl import _NestableSpawnProcess
|
|
24
|
+
|
|
25
|
+
from pyfemtet._femtet_config_util.exit import _exit_or_force_terminate
|
|
26
|
+
from pyfemtet._femtet_config_util.autosave import _set_autosave_enabled, _get_autosave_enabled
|
|
27
|
+
|
|
28
|
+
from pyfemtet._warning import experimental_class
|
|
29
|
+
|
|
30
|
+
import logging
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
logger.setLevel(logging.INFO)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@experimental_class
|
|
37
|
+
class ExcelInterface(FEMInterface):
|
|
38
|
+
|
|
39
|
+
input_xlsm_path: Path # 操作対象の xlsm パス
|
|
40
|
+
input_sheet_name: str # 変数セルを定義しているシート名
|
|
41
|
+
output_xlsm_path: Path # 操作対象の xlsm パス (指定しない場合、input と同一)
|
|
42
|
+
output_sheet_name: str # 計算結果セルを定義しているシート名 (指定しない場合、input と同一)
|
|
43
|
+
|
|
44
|
+
# TODO: related_file_paths: dict[Path] を必要なら実装 # 並列時に個別に並列プロセスの space にアップロードする必要のあるパス
|
|
45
|
+
|
|
46
|
+
procedure_name: str # マクロ関数名(or モジュール名.関数名)
|
|
47
|
+
procedure_args: list # マクロ関数の引数
|
|
48
|
+
|
|
49
|
+
excel: CDispatch # Excel Application
|
|
50
|
+
wb_input: CDispatch # システムを構成する Workbook
|
|
51
|
+
sh_input: CDispatch # 変数の定義された WorkSheet
|
|
52
|
+
wb_output: CDispatch # システムを構成する Workbook
|
|
53
|
+
sh_output: CDispatch # 計算結果の定義された WorkSheet (sh_input と同じでもよい)
|
|
54
|
+
|
|
55
|
+
visible: bool = True # excel を可視化するかどうか
|
|
56
|
+
display_alerts: bool = True # ダイアログを表示するかどうか
|
|
57
|
+
|
|
58
|
+
_load_problem_from_me: bool = True # TODO: add_parameter() 等を省略するかどうか。定義するだけでフラグとして機能する。
|
|
59
|
+
_excel_pid: int
|
|
60
|
+
_excel_hwnd: int
|
|
61
|
+
_femtet_autosave_buffer: bool # Femtet の自動保存機能の一時退避場所。最適化中はオフにする。
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
input_xlsm_path: str or Path,
|
|
66
|
+
input_sheet_name: str,
|
|
67
|
+
output_xlsm_path: str or Path = None,
|
|
68
|
+
output_sheet_name: str = None,
|
|
69
|
+
procedure_name: str = None,
|
|
70
|
+
procedure_args: list or tuple = None,
|
|
71
|
+
connect_method: str = 'auto', # or 'new'
|
|
72
|
+
):
|
|
73
|
+
|
|
74
|
+
# 初期化
|
|
75
|
+
self.input_xlsm_path = None # あとで取得する
|
|
76
|
+
self.input_sheet_name = input_sheet_name
|
|
77
|
+
self.output_xlsm_path = None # あとで取得する
|
|
78
|
+
self.output_sheet_name = output_sheet_name or self.input_sheet_name
|
|
79
|
+
self.procedure_name = procedure_name or 'FemtetMacro.FemtetMain'
|
|
80
|
+
self.procedure_args = procedure_args or []
|
|
81
|
+
assert connect_method in ['new', 'auto']
|
|
82
|
+
self.connect_method = connect_method
|
|
83
|
+
self._femtet_autosave_buffer = _get_autosave_enabled()
|
|
84
|
+
|
|
85
|
+
# dask サブプロセスのときは space 直下の input_xlsm_path を参照する
|
|
86
|
+
try:
|
|
87
|
+
worker = get_worker()
|
|
88
|
+
space = worker.local_directory
|
|
89
|
+
self.input_xlsm_path = Path(os.path.join(space, os.path.basename(input_xlsm_path))).resolve()
|
|
90
|
+
self.output_xlsm_path = Path(os.path.join(space, os.path.basename(output_xlsm_path))).resolve()
|
|
91
|
+
|
|
92
|
+
# main プロセスの場合は絶対パスを参照する
|
|
93
|
+
except ValueError:
|
|
94
|
+
self.input_xlsm_path = Path(os.path.abspath(input_xlsm_path)).resolve()
|
|
95
|
+
if output_xlsm_path is None:
|
|
96
|
+
self.output_xlsm_path = self.input_xlsm_path
|
|
97
|
+
else:
|
|
98
|
+
self.output_xlsm_path = Path(os.path.abspath(output_xlsm_path)).resolve()
|
|
99
|
+
|
|
100
|
+
# FIXME:
|
|
101
|
+
# そもそも ExcelInterface なので Femtet 関連のことをやらなくていいと思う。
|
|
102
|
+
# FemtetRef が文句なく動けばいいのだが、手元環境ではなぜか動いたり動かなかったりするため
|
|
103
|
+
# 仕方なく Femtet を Python 側から動かしている仮実装。
|
|
104
|
+
# 先に femtet を起動
|
|
105
|
+
util.execute_femtet()
|
|
106
|
+
|
|
107
|
+
# 直後の Excel 起動に間に合わない場合があったため
|
|
108
|
+
# Femtet が Dispatch 可能になるまで捨てプロセスで待つ
|
|
109
|
+
p = _NestableSpawnProcess(target=wait_femtet)
|
|
110
|
+
p.start()
|
|
111
|
+
p.join()
|
|
112
|
+
|
|
113
|
+
# サブプロセスでの restore のための情報保管
|
|
114
|
+
kwargs = dict(
|
|
115
|
+
input_xlsm_path=self.input_xlsm_path,
|
|
116
|
+
input_sheet_name=self.input_sheet_name,
|
|
117
|
+
output_xlsm_path=self.output_xlsm_path,
|
|
118
|
+
output_sheet_name=self.output_sheet_name,
|
|
119
|
+
procedure_name=self.procedure_name,
|
|
120
|
+
procedure_args=self.procedure_args,
|
|
121
|
+
connect_method='new', # subprocess で connect する際は new を強制する
|
|
122
|
+
)
|
|
123
|
+
super().__init__(**kwargs)
|
|
124
|
+
|
|
125
|
+
def __del__(self):
|
|
126
|
+
try:
|
|
127
|
+
_set_autosave_enabled(self._femtet_autosave_buffer)
|
|
128
|
+
finally:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
def _setup_before_parallel(self, client) -> None:
|
|
132
|
+
# メインプロセスで、並列プロセスを開始する前に行う前処理
|
|
133
|
+
|
|
134
|
+
input_xlsm_path: Path = self.kwargs['input_xlsm_path']
|
|
135
|
+
output_xlsm_path: Path = self.kwargs['output_xlsm_path']
|
|
136
|
+
|
|
137
|
+
client.upload_file(str(input_xlsm_path), False)
|
|
138
|
+
if input_xlsm_path.resolve() != output_xlsm_path.resolve():
|
|
139
|
+
client.upload_file(str(output_xlsm_path), False)
|
|
140
|
+
|
|
141
|
+
def connect_excel(self, connect_method):
|
|
142
|
+
|
|
143
|
+
# ===== 新しい excel instance を起動 =====
|
|
144
|
+
# 起動
|
|
145
|
+
if connect_method == 'auto':
|
|
146
|
+
self.excel = Dispatch('Excel.Application')
|
|
147
|
+
else:
|
|
148
|
+
self.excel = DispatchEx('Excel.Application')
|
|
149
|
+
|
|
150
|
+
# 起動した excel の pid を記憶する
|
|
151
|
+
self._excel_hwnd = self.excel.hWnd
|
|
152
|
+
self._excel_pid = 0
|
|
153
|
+
while self._excel_pid == 0:
|
|
154
|
+
sleep(0.5)
|
|
155
|
+
self._excel_pid = _get_pid(self.excel.hWnd)
|
|
156
|
+
|
|
157
|
+
# 可視性の設定
|
|
158
|
+
self.excel.Visible = self.visible
|
|
159
|
+
self.excel.DisplayAlerts = self.display_alerts
|
|
160
|
+
|
|
161
|
+
# 開く
|
|
162
|
+
self.excel.Workbooks.Open(str(self.input_xlsm_path))
|
|
163
|
+
for wb in self.excel.Workbooks:
|
|
164
|
+
if wb.Name == os.path.basename(self.input_xlsm_path):
|
|
165
|
+
self.wb_input = wb
|
|
166
|
+
break
|
|
167
|
+
else:
|
|
168
|
+
raise RuntimeError(f'Cannot open {self.input_xlsm_path}')
|
|
169
|
+
|
|
170
|
+
# シートを特定する
|
|
171
|
+
for sh in self.wb_input.WorkSheets:
|
|
172
|
+
if sh.Name == self.input_sheet_name:
|
|
173
|
+
self.sh_input = sh
|
|
174
|
+
break
|
|
175
|
+
else:
|
|
176
|
+
raise RuntimeError(f'Sheet {self.input_sheet_name} does not exist in the book {self.wb_input.Name}.')
|
|
177
|
+
|
|
178
|
+
if self.input_xlsm_path.resolve() == self.output_xlsm_path.resolve():
|
|
179
|
+
self.wb_output = self.wb_input
|
|
180
|
+
|
|
181
|
+
else:
|
|
182
|
+
# 開く (output)
|
|
183
|
+
self.excel.Workbooks.Open(str(self.output_xlsm_path))
|
|
184
|
+
for wb in self.excel.Workbooks:
|
|
185
|
+
if wb.Name == os.path.basename(self.output_xlsm_path):
|
|
186
|
+
self.wb_output = wb
|
|
187
|
+
break
|
|
188
|
+
else:
|
|
189
|
+
raise RuntimeError(f'Cannot open {self.output_xlsm_path}')
|
|
190
|
+
|
|
191
|
+
# シートを特定する (output)
|
|
192
|
+
for sh in self.wb_output.WorkSheets:
|
|
193
|
+
if sh.Name == self.output_sheet_name:
|
|
194
|
+
self.sh_output = sh
|
|
195
|
+
break
|
|
196
|
+
else:
|
|
197
|
+
raise RuntimeError(f'Sheet {self.output_sheet_name} does not exist in the book {self.wb_output.Name}.')
|
|
198
|
+
|
|
199
|
+
# book に参照設定を追加する
|
|
200
|
+
self.add_femtet_ref_xla(self.wb_input)
|
|
201
|
+
self.add_femtet_ref_xla(self.wb_output)
|
|
202
|
+
|
|
203
|
+
def add_femtet_ref_xla(self, wb):
|
|
204
|
+
|
|
205
|
+
# search
|
|
206
|
+
ref_file_2 = os.path.abspath(util._get_femtetmacro_dllpath())
|
|
207
|
+
contain = False
|
|
208
|
+
for ref in wb.VBProject.References:
|
|
209
|
+
if ref.Description is not None:
|
|
210
|
+
if ref.Description == 'FemtetMacro': # FemtetMacro
|
|
211
|
+
contain = True
|
|
212
|
+
# add
|
|
213
|
+
if not contain:
|
|
214
|
+
wb.VBProject.References.AddFromFile(ref_file_2)
|
|
215
|
+
|
|
216
|
+
def _setup_after_parallel(self, *args, **kwargs):
|
|
217
|
+
# サブプロセス又はメインプロセスのサブスレッドで、最適化を開始する前の前処理
|
|
218
|
+
|
|
219
|
+
# スレッドが変わっているかもしれないので win32com の初期化
|
|
220
|
+
CoInitialize()
|
|
221
|
+
|
|
222
|
+
# 最適化中は femtet の autosave を無効にする
|
|
223
|
+
_set_autosave_enabled(False)
|
|
224
|
+
|
|
225
|
+
# excel に繋ぎなおす
|
|
226
|
+
self.connect_excel(self.connect_method)
|
|
227
|
+
|
|
228
|
+
# load_objective は 1 回目に呼ばれたのが main thread なので
|
|
229
|
+
# subprocess に入った後でもう一度 load objective を行う
|
|
230
|
+
from pyfemtet.opt.optimizer import AbstractOptimizer
|
|
231
|
+
from pyfemtet.opt._femopt_core import Objective
|
|
232
|
+
opt: AbstractOptimizer = kwargs['opt']
|
|
233
|
+
obj: Objective
|
|
234
|
+
for obj_name, obj in opt.objectives.items():
|
|
235
|
+
if isinstance(obj.fun, ScapeGoatObjective):
|
|
236
|
+
opt.objectives[obj_name].fun = self.objective_from_excel
|
|
237
|
+
|
|
238
|
+
# TODO:
|
|
239
|
+
# __init__ が作った Femtet 以外を排他処理して
|
|
240
|
+
# Excel がそれを使うことを保証するようにする(遅すぎるか?)
|
|
241
|
+
# そもそも、excel から Femtet を起動できればそれで済む(?)。
|
|
242
|
+
|
|
243
|
+
def update(self, parameters: pd.DataFrame) -> None:
|
|
244
|
+
|
|
245
|
+
# params を作成
|
|
246
|
+
params = dict()
|
|
247
|
+
for _, row in parameters.iterrows():
|
|
248
|
+
params[row['name']] = row['value']
|
|
249
|
+
|
|
250
|
+
# excel シートの変数更新
|
|
251
|
+
for key, value in params.items():
|
|
252
|
+
self.sh_input.Range(key).value = value
|
|
253
|
+
|
|
254
|
+
# 再計算
|
|
255
|
+
self.excel.CalculateFull()
|
|
256
|
+
|
|
257
|
+
# マクロ実行
|
|
258
|
+
try:
|
|
259
|
+
self.excel.Run(
|
|
260
|
+
f'{self.wb_input.Name}!{self.procedure_name}',
|
|
261
|
+
*self.procedure_args
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# FIXME: エラーハンドリング
|
|
265
|
+
# com_error をキャッチする(solveerror)か、
|
|
266
|
+
# sh_out に解析結果を書く(拘束違反)ようにして、
|
|
267
|
+
# それが FALSE なら SolveError を raise する。
|
|
268
|
+
except ...:
|
|
269
|
+
raise SolveError('Excelアップデートに失敗しました')
|
|
270
|
+
|
|
271
|
+
# 再計算
|
|
272
|
+
self.excel.CalculateFull()
|
|
273
|
+
|
|
274
|
+
def quit(self):
|
|
275
|
+
logger.info('Excel-Femtet の終了処理を開始します。') # FIXME: message にする
|
|
276
|
+
|
|
277
|
+
del self.sh_input
|
|
278
|
+
del self.sh_output
|
|
279
|
+
|
|
280
|
+
self.wb_input.Close(SaveChanges := False)
|
|
281
|
+
if self.input_xlsm_path.name != self.output_xlsm_path.name:
|
|
282
|
+
self.wb_output.Close(SaveChanges := False)
|
|
283
|
+
del self.wb_input
|
|
284
|
+
del self.wb_output
|
|
285
|
+
|
|
286
|
+
self.excel.Quit()
|
|
287
|
+
del self.excel
|
|
288
|
+
|
|
289
|
+
import gc
|
|
290
|
+
gc.collect()
|
|
291
|
+
|
|
292
|
+
# quit した後ならば femtet を終了できる
|
|
293
|
+
# excel の process の完全消滅を待つ
|
|
294
|
+
logger.info('Excel の終了を待っています。')
|
|
295
|
+
while self._excel_pid == _get_pid(self._excel_hwnd):
|
|
296
|
+
sleep(1)
|
|
297
|
+
|
|
298
|
+
# 正確だが時間がかかるかも
|
|
299
|
+
logger.info('終了する Femtet を特定しています。')
|
|
300
|
+
femtet_pid = util.get_last_executed_femtet_process_id()
|
|
301
|
+
Femtet, caught_pid = dispatch_specific_femtet(femtet_pid)
|
|
302
|
+
_exit_or_force_terminate(timeout=3, Femtet=Femtet, force=True)
|
|
303
|
+
|
|
304
|
+
logger.info('自動保存機能の設定を元に戻しています。')
|
|
305
|
+
_set_autosave_enabled(self._femtet_autosave_buffer)
|
|
306
|
+
|
|
307
|
+
logger.info('Excel-Femtet を終了しました。')
|
|
308
|
+
|
|
309
|
+
# 直接アクセスしてもよいが、ユーザーに易しい名前にするためだけのプロパティ
|
|
310
|
+
@property
|
|
311
|
+
def output_sheet(self) -> CDispatch:
|
|
312
|
+
return self.sh_output
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def input_sheet(self) -> CDispatch:
|
|
316
|
+
return self.sh_input
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def output_workbook(self) -> CDispatch:
|
|
320
|
+
return self.wb_output
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def input_workbook(self) -> CDispatch:
|
|
324
|
+
return self.wb_input
|
|
325
|
+
|
|
326
|
+
def load_parameter(self, opt) -> None:
|
|
327
|
+
from pyfemtet.opt.optimizer import AbstractOptimizer, logger
|
|
328
|
+
opt: AbstractOptimizer
|
|
329
|
+
|
|
330
|
+
df = pd.read_excel(
|
|
331
|
+
self.input_xlsm_path,
|
|
332
|
+
self.input_sheet_name,
|
|
333
|
+
header=0,
|
|
334
|
+
index_col=None,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
# TODO: 使い勝手を考える
|
|
338
|
+
for i, row in df.iterrows():
|
|
339
|
+
try:
|
|
340
|
+
name = row['name']
|
|
341
|
+
value = row['current']
|
|
342
|
+
lb = row['lower']
|
|
343
|
+
ub = row['upper']
|
|
344
|
+
step = row['step']
|
|
345
|
+
except KeyError:
|
|
346
|
+
logger.warn('列名が「name」「current」「lower」「upper」「step」になっていません。この順に並んでいると仮定して処理を続けます。')
|
|
347
|
+
name, value, lb, ub, step, *_residuals = row.iloc[0]
|
|
348
|
+
|
|
349
|
+
name = str(name)
|
|
350
|
+
value = float(value)
|
|
351
|
+
lb = float(lb) if not np.isnan(lb) else None
|
|
352
|
+
ub = float(ub) if not np.isnan(ub) else None
|
|
353
|
+
step = float(step) if not np.isnan(step) else None
|
|
354
|
+
|
|
355
|
+
prm = Parameter(
|
|
356
|
+
name=name,
|
|
357
|
+
value=value,
|
|
358
|
+
lower_bound=lb,
|
|
359
|
+
upper_bound=ub,
|
|
360
|
+
step=step,
|
|
361
|
+
pass_to_fem=True,
|
|
362
|
+
properties=None,
|
|
363
|
+
)
|
|
364
|
+
opt.variables.add_parameter(prm)
|
|
365
|
+
|
|
366
|
+
def load_objective(self, opt):
|
|
367
|
+
from pyfemtet.opt.optimizer import AbstractOptimizer, logger
|
|
368
|
+
from pyfemtet.opt._femopt_core import Objective
|
|
369
|
+
opt: AbstractOptimizer
|
|
370
|
+
|
|
371
|
+
df = pd.read_excel(
|
|
372
|
+
self.output_xlsm_path,
|
|
373
|
+
self.output_sheet_name,
|
|
374
|
+
header=0,
|
|
375
|
+
index_col=None,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# TODO: 使い勝手を考える
|
|
379
|
+
for i, row in df.iterrows():
|
|
380
|
+
try:
|
|
381
|
+
name = row['name']
|
|
382
|
+
_ = row['current']
|
|
383
|
+
direction = row['direction']
|
|
384
|
+
value_column_index = list(df.columns).index('current')
|
|
385
|
+
except KeyError:
|
|
386
|
+
logger.warn('列名が「name」「current」「direction」になっていません。この順に並んでいると仮定して処理を続けます。')
|
|
387
|
+
name, _, direction, *_residuals = row.iloc[0]
|
|
388
|
+
value_column_index = 1
|
|
389
|
+
|
|
390
|
+
name = str(name)
|
|
391
|
+
|
|
392
|
+
# direction は minimize or maximize or float
|
|
393
|
+
try:
|
|
394
|
+
# float or not
|
|
395
|
+
direction = float(direction)
|
|
396
|
+
|
|
397
|
+
except ValueError:
|
|
398
|
+
# 'minimize' or 'maximize
|
|
399
|
+
direction = str(direction).lower()
|
|
400
|
+
assert (direction == 'minimize') or (direction == 'maximize')
|
|
401
|
+
|
|
402
|
+
# objective を作る
|
|
403
|
+
opt.objectives[name] = Objective(
|
|
404
|
+
fun=ScapeGoatObjective(),
|
|
405
|
+
name=name,
|
|
406
|
+
direction=direction,
|
|
407
|
+
args=(i, value_column_index, ),
|
|
408
|
+
kwargs=dict(),
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
def objective_from_excel(self, i: int, value_column_index: int):
|
|
412
|
+
r = i + 2 # header が 1
|
|
413
|
+
c = value_column_index + 1
|
|
414
|
+
v = self.sh_output.Cells(r, c).value
|
|
415
|
+
return float(v)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def wait_femtet():
|
|
419
|
+
Femtet = Dispatch('FemtetMacro.Femtet')
|
|
420
|
+
while Femtet.hWnd <= 0:
|
|
421
|
+
sleep(1)
|
|
422
|
+
Femtet = Dispatch('FemtetMacro.Femtet')
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
# main thread で作成した excel への参照を含む関数を
|
|
426
|
+
# 直接 thread や process に渡すと機能しない
|
|
427
|
+
class ScapeGoatObjective:
|
|
428
|
+
def __call__(self, *args, fem: ExcelInterface or None = None, **kwargs):
|
|
429
|
+
fem.objective_from_excel(*args, **kwargs)
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
def __globals__(self):
|
|
433
|
+
return tuple()
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
if __name__ == '__main__':
|
|
437
|
+
ExcelInterface(..., ...)
|
|
@@ -35,6 +35,8 @@ from pyfemtet.dispatch_extensions import (
|
|
|
35
35
|
)
|
|
36
36
|
from pyfemtet.opt.interface import FEMInterface, logger
|
|
37
37
|
from pyfemtet._message import Msg
|
|
38
|
+
from pyfemtet._femtet_config_util.autosave import _get_autosave_enabled, _set_autosave_enabled
|
|
39
|
+
from pyfemtet._femtet_config_util.exit import _exit_or_force_terminate
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
def _post_activate_message(hwnd):
|
|
@@ -317,9 +319,6 @@ class FemtetInterface(FEMInterface):
|
|
|
317
319
|
|
|
318
320
|
"""
|
|
319
321
|
|
|
320
|
-
# FIXME: Gaudi へのアクセスなど、self.Femtet.Gaudi.SomeFunc() のような場合、この関数を呼び出す前に Gaudi へのアクセスの時点で com_error が起こる
|
|
321
|
-
# FIXME: => 文字列で渡して eval() すればよい。
|
|
322
|
-
|
|
323
322
|
if args is None:
|
|
324
323
|
args = tuple()
|
|
325
324
|
if kwargs is None:
|
|
@@ -731,43 +730,10 @@ class FemtetInterface(FEMInterface):
|
|
|
731
730
|
|
|
732
731
|
def quit(self, timeout=1, force=True):
|
|
733
732
|
"""Force to terminate connected Femtet."""
|
|
734
|
-
major, minor, bugfix = 2024, 0, 1
|
|
735
|
-
|
|
736
|
-
# すでに終了しているならば何もしない
|
|
737
|
-
if not self.femtet_is_alive():
|
|
738
|
-
return
|
|
739
733
|
|
|
740
|
-
|
|
741
|
-
# gracefully termination method without save project available from 2024.0.1
|
|
742
|
-
try:
|
|
743
|
-
self.Femtet.Exit(True)
|
|
744
|
-
except AttributeError as e:
|
|
745
|
-
print('================')
|
|
746
|
-
logger.error(Msg.ERR_CANNOT_ACCESS_API + 'Femtet.Exit()')
|
|
747
|
-
logger.error(Msg.CERTIFY_MACRO_VERSION)
|
|
748
|
-
print('================')
|
|
749
|
-
if self.confirm_before_exit:
|
|
750
|
-
input(Msg.ENTER_TO_QUIT)
|
|
751
|
-
raise e
|
|
752
|
-
|
|
753
|
-
else:
|
|
754
|
-
hwnd = self.Femtet.hWnd
|
|
755
|
-
|
|
756
|
-
# terminate
|
|
757
|
-
util.close_femtet(hwnd, timeout, force)
|
|
734
|
+
_set_autosave_enabled(self._original_autosave_enabled)
|
|
758
735
|
|
|
759
|
-
|
|
760
|
-
pid = _get_pid(hwnd)
|
|
761
|
-
start = time()
|
|
762
|
-
while psutil.pid_exists(pid):
|
|
763
|
-
if time() - start > 30: # 30 秒経っても存在するのは何かおかしい
|
|
764
|
-
logger.error(Msg.ERR_CLOSE_FEMTET_FAILED)
|
|
765
|
-
break
|
|
766
|
-
sleep(1)
|
|
767
|
-
sleep(1)
|
|
768
|
-
|
|
769
|
-
except (AttributeError, OSError): # already dead
|
|
770
|
-
pass
|
|
736
|
+
_exit_or_force_terminate(timeout=timeout, Femtet=self.Femtet, force=True)
|
|
771
737
|
|
|
772
738
|
def _setup_before_parallel(self, client):
|
|
773
739
|
client.upload_file(
|
|
@@ -912,40 +878,3 @@ class _UnPicklableNoFEM(FemtetInterface):
|
|
|
912
878
|
here = os.path.dirname(__file__)
|
|
913
879
|
pdt_path = os.path.join(here, f'trial{trial}.pdt')
|
|
914
880
|
return pdt_path
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
# レジストリのパスと値の名前
|
|
918
|
-
_REGISTRY_PATH: Final[str] = r"SOFTWARE\Murata Software\Femtet2014\Femtet"
|
|
919
|
-
_VALUE_NAME: Final[str] = "AutoSave"
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
def _get_autosave_enabled() -> bool:
|
|
923
|
-
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, _REGISTRY_PATH) as key:
|
|
924
|
-
value, regtype = winreg.QueryValueEx(key, _VALUE_NAME)
|
|
925
|
-
if regtype == winreg.REG_DWORD:
|
|
926
|
-
return bool(value)
|
|
927
|
-
else:
|
|
928
|
-
raise ValueError("Unexpected registry value type.")
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
def _set_autosave_enabled(enable: bool) -> None:
|
|
932
|
-
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, _REGISTRY_PATH, 0, winreg.KEY_SET_VALUE) as key:
|
|
933
|
-
winreg.SetValueEx(key, _VALUE_NAME, 0, winreg.REG_DWORD, int(enable))
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
def _test_autosave_setting():
|
|
937
|
-
|
|
938
|
-
# 使用例
|
|
939
|
-
current_setting = _get_autosave_enabled()
|
|
940
|
-
print(f"Current AutoSave setting is {'enabled' if current_setting else 'disabled'}.")
|
|
941
|
-
|
|
942
|
-
# 設定を変更する例
|
|
943
|
-
new_setting = not current_setting
|
|
944
|
-
_set_autosave_enabled(new_setting)
|
|
945
|
-
print(f"AutoSave setting has been {'enabled' if new_setting else 'disabled'}.")
|
|
946
|
-
|
|
947
|
-
# 再度設定を確認する
|
|
948
|
-
after_setting = _get_autosave_enabled()
|
|
949
|
-
print(f"Current AutoSave setting is {'enabled' if after_setting else 'disabled'}.")
|
|
950
|
-
|
|
951
|
-
assert new_setting == after_setting, "レジストリ編集に失敗しました。"
|
pyfemtet/opt/optimizer/_base.py
CHANGED
|
@@ -292,17 +292,17 @@ class AbstractOptimizer(ABC):
|
|
|
292
292
|
|
|
293
293
|
def _finalize(self):
|
|
294
294
|
"""Destruct fem and set worker status."""
|
|
295
|
-
|
|
295
|
+
self.fem.quit()
|
|
296
296
|
if not self.worker_status.get() == OptimizationStatus.CRASHED:
|
|
297
297
|
self.worker_status.set(OptimizationStatus.TERMINATED)
|
|
298
298
|
|
|
299
299
|
# run via FEMOpt (considering parallel processing)
|
|
300
300
|
def _run(
|
|
301
301
|
self,
|
|
302
|
-
subprocess_idx,
|
|
303
|
-
worker_status_list,
|
|
304
|
-
wait_setup,
|
|
305
|
-
|
|
302
|
+
subprocess_idx, # 自身が何番目の並列プロセスであるかを示す連番
|
|
303
|
+
worker_status_list, # 他の worker の status オブジェクト
|
|
304
|
+
wait_setup, # 他の worker の status が ready になるまで待つか
|
|
305
|
+
skip_reconstruct=False, # reconstruct fem を行うかどうか
|
|
306
306
|
) -> Optional[Exception]:
|
|
307
307
|
|
|
308
308
|
# 自分の worker_status の取得
|
|
@@ -314,9 +314,8 @@ class AbstractOptimizer(ABC):
|
|
|
314
314
|
return None
|
|
315
315
|
|
|
316
316
|
# set_fem をはじめ、終了したらそれを示す
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
self.fem._setup_after_parallel()
|
|
317
|
+
self._reconstruct_fem(skip_reconstruct)
|
|
318
|
+
self.fem._setup_after_parallel(opt=self)
|
|
320
319
|
self.worker_status.set(OptimizationStatus.WAIT_OTHER_WORKERS)
|
|
321
320
|
|
|
322
321
|
# wait_setup or not
|
|
@@ -326,6 +325,9 @@ class AbstractOptimizer(ABC):
|
|
|
326
325
|
return None
|
|
327
326
|
# 他のすべての worker_status が wait 以上になったら break
|
|
328
327
|
if all([ws.get() >= OptimizationStatus.WAIT_OTHER_WORKERS for ws in worker_status_list]):
|
|
328
|
+
# リソースの競合等を避けるため
|
|
329
|
+
# break する前に index 秒待つ
|
|
330
|
+
sleep(int(subprocess_idx))
|
|
329
331
|
break
|
|
330
332
|
sleep(1)
|
|
331
333
|
else:
|