pyfemtet 0.7.0__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 (46) 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/dask_util.py +10 -0
  7. pyfemtet/_util/excel_macro_util.py +16 -4
  8. pyfemtet/_util/excel_parse_util.py +138 -0
  9. pyfemtet/_util/sample.xlsx +0 -0
  10. pyfemtet/brep/__init__.py +0 -3
  11. pyfemtet/brep/_impl.py +7 -3
  12. pyfemtet/opt/_femopt.py +69 -31
  13. pyfemtet/opt/_femopt_core.py +100 -36
  14. pyfemtet/opt/advanced_samples/excel_ui/(ref) original_project.femprj +0 -0
  15. pyfemtet/opt/advanced_samples/excel_ui/femtet-macro.xlsm +0 -0
  16. pyfemtet/opt/advanced_samples/excel_ui/pyfemtet-core.py +291 -0
  17. pyfemtet/opt/advanced_samples/excel_ui/test-pyfemtet-core.cmd +22 -0
  18. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_create_training_data.py +60 -0
  19. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_create_training_data_jp.py +57 -0
  20. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate.py +100 -0
  21. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate_jp.py +90 -0
  22. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_parametric.femprj +0 -0
  23. pyfemtet/opt/interface/__init__.py +2 -0
  24. pyfemtet/opt/interface/_base.py +3 -0
  25. pyfemtet/opt/interface/_excel_interface.py +565 -204
  26. pyfemtet/opt/interface/_femtet.py +26 -29
  27. pyfemtet/opt/interface/_surrogate/__init__.py +5 -0
  28. pyfemtet/opt/interface/_surrogate/_base.py +85 -0
  29. pyfemtet/opt/interface/_surrogate/_chaospy.py +71 -0
  30. pyfemtet/opt/interface/_surrogate/_singletaskgp.py +70 -0
  31. pyfemtet/opt/optimizer/_base.py +30 -19
  32. pyfemtet/opt/optimizer/_optuna/_optuna.py +20 -8
  33. pyfemtet/opt/optimizer/_optuna/_pof_botorch.py +60 -18
  34. pyfemtet/opt/prediction/_base.py +8 -0
  35. pyfemtet/opt/prediction/single_task_gp.py +85 -62
  36. pyfemtet/opt/visualization/_complex_components/main_figure_creator.py +5 -5
  37. pyfemtet/opt/visualization/_complex_components/main_graph.py +7 -1
  38. pyfemtet/opt/visualization/_complex_components/pm_graph.py +1 -1
  39. pyfemtet/opt/visualization/_process_monitor/application.py +2 -2
  40. pyfemtet/opt/visualization/_process_monitor/pages.py +1 -1
  41. pyfemtet/opt/visualization/result_viewer/pages.py +1 -1
  42. {pyfemtet-0.7.0.dist-info → pyfemtet-0.8.0.dist-info}/METADATA +3 -2
  43. {pyfemtet-0.7.0.dist-info → pyfemtet-0.8.0.dist-info}/RECORD +46 -29
  44. {pyfemtet-0.7.0.dist-info → pyfemtet-0.8.0.dist-info}/WHEEL +1 -1
  45. {pyfemtet-0.7.0.dist-info → pyfemtet-0.8.0.dist-info}/LICENSE +0 -0
  46. {pyfemtet-0.7.0.dist-info → pyfemtet-0.8.0.dist-info}/entry_points.txt +0 -0
@@ -2,6 +2,7 @@ import os
2
2
  from pathlib import Path
3
3
  from time import sleep
4
4
  import gc
5
+ from typing import Optional, List
5
6
 
6
7
  import pandas as pd
7
8
  import numpy as np
@@ -18,17 +19,18 @@ from pywintypes import com_error
18
19
 
19
20
  from pyfemtet.opt import FEMInterface
20
21
  from pyfemtet.core import SolveError
21
- from pyfemtet.opt.optimizer.parameter import Parameter
22
+ from pyfemtet.opt.optimizer.parameter import Parameter, Expression
22
23
 
23
24
  from pyfemtet.dispatch_extensions import _get_pid, dispatch_specific_femtet
24
- from pyfemtet.dispatch_extensions._impl import _NestableSpawnProcess
25
25
 
26
26
  from pyfemtet._femtet_config_util.exit import _exit_or_force_terminate
27
- from pyfemtet._femtet_config_util.autosave import _set_autosave_enabled, _get_autosave_enabled
28
27
 
29
28
  from pyfemtet._util.excel_macro_util import watch_excel_macro_error
29
+ from pyfemtet._util.dask_util import lock_or_no_lock
30
+ from pyfemtet._util.excel_parse_util import *
30
31
 
31
32
  from pyfemtet._warning import show_experimental_warning
33
+ from pyfemtet._message.messages import Message as Msg
32
34
 
33
35
  from pyfemtet.opt.interface._base import logger
34
36
 
@@ -80,11 +82,53 @@ class ExcelInterface(FEMInterface):
80
82
  Excel マクロ関数のタイムアウト時間を秒単位で指定
81
83
  します。 None の場合はタイムアウトなしとなります。
82
84
 
83
- with_call_femtet (bool, optional):
84
- Femtet を呼び出すかどうかを指定します。
85
+ setup_xlsm_path (str or Path, optional):
86
+ セットアップ時に呼ぶ関数を含む xlsm のパスです。
87
+ 指定しない場合は ``input_xlsm_path`` と
88
+ 同じと見做します。
89
+
90
+ setup_procedure_name (str, optional):
91
+ セットアップ時に呼ぶマクロ関数名です。
92
+ 指定しない場合、セットアップ時に何もしません。
93
+
94
+ setup_procedure_args (list or tuple, optional):
95
+ セットアップ時に呼ぶマクロ関数の引数です。
96
+
97
+ teardown_xlsm_path (str or Path, optional):
98
+ 終了時に呼ぶ関数を含む xlsm のパスです。
99
+ 指定しない場合は ``input_xlsm_path`` と
100
+ 同じと見做します。
101
+
102
+ teardown_procedure_name (str, optional):
103
+ 終了時に呼ぶマクロ関数名です。
104
+ 指定しない場合、終了時に何もしません。
105
+
106
+ teardown_procedure_args (list or tuple, optional):
107
+ 終了時に呼ぶマクロ関数の引数です。
108
+
109
+ visible (bool):
110
+ excel を可視化するかどうかです。
111
+ ただし、 True を指定した場合でもマクロの実行中は
112
+ 不可視になります。
113
+ デフォルトは False です。
114
+
115
+ display_alerts (bool):
116
+ excel ダイアログを表示するかどうかです。
117
+ デバッグ目的の場合以外は True にしないでください。
85
118
  デフォルトは False です。
86
119
 
87
-
120
+ terminate_excel_when_quit (bool):
121
+ 終了時に Excel を終了するかどうかです。
122
+ 指定しない場合、 connect_method が 'new' の場合
123
+ True とふるまい 'auto' の場合 False と振舞います。
124
+
125
+ interactive (bool):
126
+ excel を対話モードにするかどうかです。
127
+ False にすると、 visible == True であっても
128
+ 自動化プロセス中にユーザーが誤って
129
+ Excel 本体を操作できないようにします。
130
+ デフォルトは True です。
131
+
88
132
  Attributes:
89
133
  input_xlsm_path (Path):
90
134
  設計変数の定義を含む Excel ファイルのパス。
@@ -133,12 +177,14 @@ class ExcelInterface(FEMInterface):
133
177
 
134
178
  """
135
179
 
136
- input_xlsm_path: Path # 操作対象の xlsm パス
180
+ input_xlsm_path: str # 操作対象の xlsm パス
137
181
  input_sheet_name: str # 変数セルを定義しているシート名
138
- output_xlsm_path: Path # 操作対象の xlsm パス (指定しない場合、input と同一)
182
+ output_xlsm_path: str # 操作対象の xlsm パス (指定しない場合、input と同一)
139
183
  output_sheet_name: str # 計算結果セルを定義しているシート名 (指定しない場合、input と同一)
184
+ constraint_xlsm_path: str # 操作対象の xlsm パス (指定しない場合、input と同一)
185
+ constraint_sheet_name: str # 拘束関数セルを定義しているシート名 (指定しない場合、input と同一)
140
186
 
141
- # TODO: related_file_paths: dict[Path] を必要なら実装 # 並列時に個別に並列プロセスの space にアップロードする必要のあるパス
187
+ related_file_paths: list[str] # 並列時に個別に並列プロセスの space にアップロードする必要のあるパス
142
188
 
143
189
  procedure_name: str # マクロ関数名(or モジュール名.関数名)
144
190
  procedure_args: list # マクロ関数の引数
@@ -148,17 +194,30 @@ class ExcelInterface(FEMInterface):
148
194
  sh_input: CDispatch # 変数の定義された WorkSheet
149
195
  wb_output: CDispatch # システムを構成する Workbook
150
196
  sh_output: CDispatch # 計算結果の定義された WorkSheet (sh_input と同じでもよい)
197
+ wb_constraint: CDispatch # システムを構成する Workbook
198
+ sh_constraint: CDispatch # 計算結果の定義された WorkSheet (sh_input と同じでもよい)
199
+ wb_setup: CDispatch # システムを構成する Workbook
200
+ wb_teardown: CDispatch # システムを構成する Workbook
151
201
 
152
- visible: bool = False # excel を可視化するかどうか
153
- display_alerts: bool = False # ダイアログを表示するかどうか
202
+ visible: bool # excel を可視化するかどうか
203
+ display_alerts: bool # ダイアログを表示するかどうか
154
204
  terminate_excel_when_quit: bool # 終了時に Excel を終了するかどうか
205
+ interactive: bool # excel を対話モードにするかどうか
155
206
 
156
207
  _load_problem_from_me: bool = True
157
208
  _excel_pid: int
158
209
  _excel_hwnd: int
210
+ _with_femtet_autosave_setting: bool = True # Femtet の自動保存機能の自動設定を行うかどうか。Femtet がインストールされていない場合はオフにする。クラス変数なので、インスタンス化前に設定する。
159
211
  _femtet_autosave_buffer: bool # Femtet の自動保存機能の一時退避場所。最適化中はオフにする。
160
- _with_call_femtet: bool # Femtet を Python から起動するかどうか。Excel から起動できる場合は False でよい。
161
212
 
213
+ setup_xlsm_path: str
214
+ setup_procedure_name: str
215
+ setup_procedure_args: list or tuple
216
+ teardown_xlsm_path: str
217
+ teardown_procedure_name: str
218
+ teardown_procedure_args: list or tuple
219
+
220
+ use_named_range: bool # input を定義したシートにおいて input の値を名前付き範囲で指定するかどうか。
162
221
 
163
222
  def __init__(
164
223
  self,
@@ -166,43 +225,80 @@ class ExcelInterface(FEMInterface):
166
225
  input_sheet_name: str,
167
226
  output_xlsm_path: str or Path = None,
168
227
  output_sheet_name: str = None,
228
+ constraint_xlsm_path: str or Path = None,
229
+ constraint_sheet_name: str = None,
169
230
  procedure_name: str = None,
170
231
  procedure_args: list or tuple = None,
171
232
  connect_method: str = 'auto', # or 'new'
172
233
  procedure_timeout: float or None = None,
173
- with_call_femtet: bool = False
234
+ setup_xlsm_path: str or Path = None,
235
+ setup_procedure_name: str = None,
236
+ setup_procedure_args: list or tuple = None,
237
+ teardown_xlsm_path: str or Path = None,
238
+ teardown_procedure_name: str = None,
239
+ teardown_procedure_args: list or tuple = None,
240
+ related_file_paths: list[str or Path] = None,
241
+ visible: bool = False,
242
+ display_alerts: bool = False,
243
+ terminate_excel_when_quit: bool = None,
244
+ interactive: bool = True,
245
+ use_named_range: bool = True,
174
246
  ):
175
247
 
176
248
  show_experimental_warning("ExcelInterface")
177
249
 
178
250
  # 初期化
179
- self.input_xlsm_path = None # あとで取得する
251
+ self.input_xlsm_path = str(input_xlsm_path) # あとで再取得する
180
252
  self.input_sheet_name = input_sheet_name
181
- self.output_xlsm_path = None # あとで取得する
182
- self.output_sheet_name = output_sheet_name or self.input_sheet_name
253
+ self.output_xlsm_path = str(input_xlsm_path) if output_xlsm_path is None else str(output_xlsm_path)
254
+ self.output_sheet_name = output_sheet_name if output_sheet_name is not None else input_sheet_name
255
+ self.constraint_xlsm_path = str(input_xlsm_path) if constraint_xlsm_path is None else str(constraint_xlsm_path)
256
+ self.constraint_sheet_name = constraint_sheet_name or self.input_sheet_name
183
257
  self.procedure_name = procedure_name or 'FemtetMacro.FemtetMain'
184
258
  self.procedure_args = procedure_args or []
185
259
  assert connect_method in ['new', 'auto']
186
260
  self.connect_method = connect_method
187
- self._femtet_autosave_buffer = _get_autosave_enabled()
188
261
  self.procedure_timeout = procedure_timeout
189
- self._with_call_femtet = with_call_femtet
190
- self.terminate_excel_when_quit = self.connect_method == 'new'
262
+ if terminate_excel_when_quit is None:
263
+ self.terminate_excel_when_quit = self.connect_method == 'new'
264
+ else:
265
+ self.terminate_excel_when_quit = terminate_excel_when_quit
266
+
267
+ self.setup_xlsm_path = str(input_xlsm_path) if setup_xlsm_path is None else str(setup_xlsm_path) # あとで取得する
268
+ self.setup_procedure_name = setup_procedure_name
269
+ self.setup_procedure_args = setup_procedure_args or []
270
+
271
+ self.teardown_xlsm_path = str(input_xlsm_path) if teardown_xlsm_path is None else str(teardown_xlsm_path) # あとで取得する
272
+ self.teardown_procedure_name = teardown_procedure_name
273
+ self.teardown_procedure_args = teardown_procedure_args or []
274
+
275
+ self.related_file_paths = [str(p) for p in related_file_paths] if related_file_paths is not None else []
276
+
277
+ self.visible = visible
278
+ self.interactive = interactive
279
+ self.display_alerts = display_alerts
280
+
281
+ self.use_named_range = use_named_range
191
282
 
192
283
  # dask サブプロセスのときは space 直下の input_xlsm_path を参照する
193
284
  try:
194
285
  worker = get_worker()
195
- space = worker.local_directory
196
- self.input_xlsm_path = Path(os.path.join(space, os.path.basename(input_xlsm_path))).resolve()
197
- self.output_xlsm_path = Path(os.path.join(space, os.path.basename(output_xlsm_path))).resolve()
286
+ space = os.path.abspath(worker.local_directory)
287
+ self.input_xlsm_path = os.path.join(space, os.path.basename(self.input_xlsm_path))
288
+ self.output_xlsm_path = os.path.join(space, os.path.basename(self.output_xlsm_path))
289
+ self.constraint_xlsm_path = os.path.join(space, os.path.basename(self.constraint_xlsm_path))
290
+ self.setup_xlsm_path = os.path.join(space, os.path.basename(self.setup_xlsm_path))
291
+ self.teardown_xlsm_path = os.path.join(space, os.path.basename(self.teardown_xlsm_path))
292
+ self.related_file_paths = [os.path.join(space, os.path.basename(p)) for p in self.related_file_paths]
198
293
 
199
294
  # main プロセスの場合は絶対パスを参照する
200
295
  except ValueError:
201
- self.input_xlsm_path = Path(os.path.abspath(input_xlsm_path)).resolve()
202
- if output_xlsm_path is None:
203
- self.output_xlsm_path = self.input_xlsm_path
204
- else:
205
- self.output_xlsm_path = Path(os.path.abspath(output_xlsm_path)).resolve()
296
+ self.input_xlsm_path = os.path.abspath(self.input_xlsm_path)
297
+ self.output_xlsm_path = os.path.abspath(self.output_xlsm_path)
298
+ self.constraint_xlsm_path = os.path.abspath(self.constraint_xlsm_path)
299
+ self.setup_xlsm_path = os.path.abspath(self.setup_xlsm_path)
300
+ self.teardown_xlsm_path = os.path.abspath(self.teardown_xlsm_path)
301
+ self.related_file_paths = [os.path.abspath(p) for p in self.related_file_paths]
206
302
 
207
303
  # サブプロセスでの restore のための情報保管
208
304
  kwargs = dict(
@@ -210,29 +306,128 @@ class ExcelInterface(FEMInterface):
210
306
  input_sheet_name=self.input_sheet_name,
211
307
  output_xlsm_path=self.output_xlsm_path,
212
308
  output_sheet_name=self.output_sheet_name,
309
+ constraint_xlsm_path=self.constraint_xlsm_path,
310
+ constraint_sheet_name=self.constraint_sheet_name,
213
311
  procedure_name=self.procedure_name,
214
312
  procedure_args=self.procedure_args,
215
313
  connect_method='new', # subprocess で connect する際は new を強制する
314
+ terminate_excel_when_quit=True, # なので終了時は破棄する
216
315
  procedure_timeout=self.procedure_timeout,
217
- with_call_femtet=self._with_call_femtet,
316
+ setup_xlsm_path=self.setup_xlsm_path,
317
+ setup_procedure_name=self.setup_procedure_name,
318
+ setup_procedure_args=self.setup_procedure_args,
319
+ teardown_xlsm_path=self.teardown_xlsm_path,
320
+ teardown_procedure_name=self.teardown_procedure_name,
321
+ teardown_procedure_args=self.teardown_procedure_args,
322
+ related_file_paths=self.related_file_paths,
323
+ visible=self.visible,
324
+ interactive=self.interactive,
325
+ display_alerts=self.display_alerts,
326
+ use_named_range=self.use_named_range,
218
327
  )
219
328
  FEMInterface.__init__(self, **kwargs)
220
329
 
221
330
  def __del__(self):
222
- try:
223
- _set_autosave_enabled(self._femtet_autosave_buffer)
224
- finally:
225
- pass
331
+ pass
226
332
 
227
333
  def _setup_before_parallel(self, client) -> None:
228
334
  # メインプロセスで、並列プロセスを開始する前に行う前処理
229
335
 
230
- input_xlsm_path: Path = self.kwargs['input_xlsm_path']
231
- output_xlsm_path: Path = self.kwargs['output_xlsm_path']
336
+ client.upload_file(self.input_xlsm_path, False)
337
+
338
+ if not is_same_path(self.input_xlsm_path, self.output_xlsm_path):
339
+ client.upload_file(self.output_xlsm_path, False)
340
+
341
+ if not is_same_path(self.input_xlsm_path, self.constraint_xlsm_path):
342
+ client.upload_file(self.constraint_xlsm_path, False)
232
343
 
233
- client.upload_file(str(input_xlsm_path), False)
234
- if input_xlsm_path.resolve() != output_xlsm_path.resolve():
235
- client.upload_file(str(output_xlsm_path), False)
344
+ if not is_same_path(self.input_xlsm_path, self.setup_xlsm_path):
345
+ client.upload_file(self.setup_xlsm_path, False)
346
+
347
+ if not is_same_path(self.input_xlsm_path, self.teardown_xlsm_path):
348
+ client.upload_file(self.setup_xlsm_path, False)
349
+
350
+ for path in self.related_file_paths:
351
+ client.upload_file(path, False)
352
+
353
+ def _setup_after_parallel(self, *args, **kwargs):
354
+ """サブプロセス又はメインプロセスのサブスレッドで、最適化を開始する前の前処理"""
355
+
356
+ # kwargs で space_dir が与えられている場合、そちらを使用する
357
+ # メインプロセスで呼ばれることを想定
358
+ if 'space_dir' in kwargs.keys():
359
+ space = kwargs['space_dir']
360
+ if space is not None:
361
+ self.input_xlsm_path = os.path.join(space, os.path.basename(self.input_xlsm_path))
362
+ self.output_xlsm_path = os.path.join(space, os.path.basename(self.output_xlsm_path))
363
+ self.constraint_xlsm_path = os.path.join(space, os.path.basename(self.constraint_xlsm_path))
364
+ self.setup_xlsm_path = os.path.join(space, os.path.basename(self.setup_xlsm_path))
365
+ self.teardown_xlsm_path = os.path.join(space, os.path.basename(self.teardown_xlsm_path))
366
+ self.related_file_paths = [os.path.join(space, os.path.basename(p)) for p in self.related_file_paths]
367
+
368
+ # connect_method が auto でかつ使用中のファイルを開こうとする場合に備えて excel のファイル名を変更
369
+ subprocess_idx = kwargs['opt'].subprocess_idx
370
+
371
+ def proc_path(path, ignore_no_exists):
372
+ exclude_ext, ext = os.path.splitext(path)
373
+ new_path = exclude_ext + f'{subprocess_idx}' + ext
374
+ if os.path.exists(path): # input と output が同じの場合など。input がないのはおかしい
375
+ os.rename(path, new_path)
376
+ elif not ignore_no_exists:
377
+ raise FileNotFoundError(f'{path} が見つかりません。')
378
+ return new_path
379
+
380
+ self.input_xlsm_path = proc_path(self.input_xlsm_path, False)
381
+ self.output_xlsm_path = proc_path(self.output_xlsm_path, True)
382
+ self.constraint_xlsm_path = proc_path(self.constraint_xlsm_path, True)
383
+ self.setup_xlsm_path = proc_path(self.setup_xlsm_path, True)
384
+ self.teardown_xlsm_path = proc_path(self.teardown_xlsm_path, True)
385
+
386
+ # スレッドが変わっているかもしれないので win32com の初期化
387
+ CoInitialize()
388
+
389
+ # 最適化中は femtet の autosave を無効にする
390
+ if self._with_femtet_autosave_setting:
391
+ from pyfemtet._femtet_config_util.autosave import _set_autosave_enabled, _get_autosave_enabled
392
+ self._femtet_autosave_buffer = _get_autosave_enabled()
393
+ _set_autosave_enabled(False)
394
+
395
+ # excel に繋ぐ
396
+ with lock_or_no_lock('connect-excel'):
397
+ self.connect_excel(self.connect_method)
398
+ sleep(1)
399
+
400
+ # load_objective は 1 回目に呼ばれたのが main thread なので
401
+ # subprocess に入った後でもう一度 load objective を行う
402
+ from pyfemtet.opt.optimizer import AbstractOptimizer
403
+ from pyfemtet.opt._femopt_core import Objective, Constraint
404
+ opt: AbstractOptimizer = kwargs['opt']
405
+ obj: Objective
406
+ for obj_name, obj in opt.objectives.items():
407
+ if isinstance(obj.fun, ScapeGoatObjective):
408
+ opt.objectives[obj_name].fun = self.objective_from_excel
409
+
410
+ cns: Constraint
411
+ for cns_name, cns in opt.constraints.items():
412
+ if isinstance(cns.fun, ScapeGoatObjective):
413
+ opt.constraints[cns_name].fun = self.constraint_from_excel
414
+
415
+ # excel の setup 関数を必要なら実行する
416
+ if self.setup_procedure_name is not None:
417
+ with lock_or_no_lock('excel_setup_procedure'):
418
+ try:
419
+ with watch_excel_macro_error(self.excel, timeout=self.procedure_timeout, restore_book=False):
420
+ self.excel.Run(
421
+ f'{self.setup_procedure_name}',
422
+ *self.setup_procedure_args
423
+ )
424
+
425
+ # 再計算
426
+ self.excel.CalculateFull()
427
+ sleep(1)
428
+
429
+ except com_error as e:
430
+ raise RuntimeError(f'Failed to run macro {self.setup_procedure_name}. The original message is: {e}')
236
431
 
237
432
  def connect_excel(self, connect_method):
238
433
 
@@ -243,6 +438,10 @@ class ExcelInterface(FEMInterface):
243
438
  else:
244
439
  self.excel = DispatchEx('Excel.Application')
245
440
 
441
+ # FemtetRef を追加する
442
+ self.open_femtet_ref_xla() # ここでエラーが発生しているかも?
443
+ sleep(0.5)
444
+
246
445
  # 起動した excel の pid を記憶する
247
446
  self._excel_hwnd = self.excel.hWnd
248
447
  self._excel_pid = 0
@@ -253,7 +452,10 @@ class ExcelInterface(FEMInterface):
253
452
  # 可視性の設定
254
453
  self.excel.Visible = self.visible
255
454
  self.excel.DisplayAlerts = self.display_alerts
455
+ self.excel.Interactive = self.interactive
456
+ sleep(0.5)
256
457
 
458
+ # ===== input =====
257
459
  # 開く
258
460
  self.excel.Workbooks.Open(str(self.input_xlsm_path))
259
461
  for wb in self.excel.Workbooks:
@@ -271,11 +473,11 @@ class ExcelInterface(FEMInterface):
271
473
  else:
272
474
  raise RuntimeError(f'Sheet {self.input_sheet_name} does not exist in the book {self.wb_input.Name}.')
273
475
 
274
- if self.input_xlsm_path.resolve() == self.output_xlsm_path.resolve():
476
+ # ===== output =====
477
+ # 開く (output)
478
+ if is_same_path(self.input_xlsm_path, self.output_xlsm_path):
275
479
  self.wb_output = self.wb_input
276
-
277
480
  else:
278
- # 開く (output)
279
481
  self.excel.Workbooks.Open(str(self.output_xlsm_path))
280
482
  for wb in self.excel.Workbooks:
281
483
  if wb.Name == os.path.basename(self.output_xlsm_path):
@@ -292,27 +494,77 @@ class ExcelInterface(FEMInterface):
292
494
  else:
293
495
  raise RuntimeError(f'Sheet {self.output_sheet_name} does not exist in the book {self.wb_output.Name}.')
294
496
 
497
+ # ===== constraint =====
498
+ # 開く (constraint)
499
+ if is_same_path(self.input_xlsm_path, self.constraint_xlsm_path):
500
+ self.wb_constraint = self.wb_input
501
+ else:
502
+ self.excel.Workbooks.Open(str(self.constraint_xlsm_path))
503
+ for wb in self.excel.Workbooks:
504
+ if wb.Name == os.path.basename(self.constraint_xlsm_path):
505
+ self.wb_constraint = wb
506
+ break
507
+ else:
508
+ raise RuntimeError(f'Cannot open {self.constraint_xlsm_path}')
509
+
510
+ # シートを特定する (constraint)
511
+ for sh in self.wb_constraint.WorkSheets:
512
+ if sh.Name == self.constraint_sheet_name:
513
+ self.sh_constraint = sh
514
+ break
515
+ else:
516
+ raise RuntimeError(f'Sheet {self.constraint_sheet_name} does not exist in the book {self.wb_constraint.Name}.')
517
+
518
+ # ===== setup =====
519
+ # 開く (setup)
520
+ if is_same_path(self.input_xlsm_path, self.setup_xlsm_path):
521
+ self.wb_setup = self.wb_input
522
+ else:
523
+ self.excel.Workbooks.Open(self.setup_xlsm_path)
524
+ for wb in self.excel.Workbooks:
525
+ if wb.Name == os.path.basename(self.setup_xlsm_path):
526
+ self.wb_setup = wb
527
+ break
528
+ else:
529
+ raise RuntimeError(f'Cannot open {self.setup_xlsm_path}')
530
+
531
+ # ===== teardown =====
532
+ # 開く (teardown)
533
+ if is_same_path(self.input_xlsm_path, self.teardown_xlsm_path):
534
+ self.wb_teardown = self.wb_input
535
+ else:
536
+ self.excel.Workbooks.Open(self.teardown_xlsm_path)
537
+ for wb in self.excel.Workbooks:
538
+ if wb.Name == os.path.basename(self.teardown_xlsm_path):
539
+ self.wb_teardown = wb
540
+ break
541
+ else:
542
+ raise RuntimeError(f'Cannot open {self.teardown_xlsm_path}')
543
+
295
544
  # book に参照設定を追加する
296
- self.add_femtet_ref_xla(self.wb_input)
297
- self.add_femtet_ref_xla(self.wb_output)
545
+ self.add_femtet_macro_reference(self.wb_input)
546
+ self.add_femtet_macro_reference(self.wb_output)
547
+ self.add_femtet_macro_reference(self.wb_setup)
548
+ self.add_femtet_macro_reference(self.wb_teardown)
549
+ self.add_femtet_macro_reference(self.wb_constraint)
298
550
 
299
- def add_femtet_ref_xla(self, wb):
551
+ def open_femtet_ref_xla(self):
300
552
 
301
- # search
302
- ref_file_1 = r'C:\Program Files\Microsoft Office\root\Office16\XLSTART\FemtetRef.xla'
303
- if not os.path.exists(ref_file_1):
304
- # 32bit
305
- ref_file_1 = r'C:\Program Files (x86)\Microsoft Office\root\Office16\XLSTART\FemtetRef.xla'
306
- if not os.path.exists(ref_file_1):
307
- raise FileNotFoundError(f'{ref_file_1} not found. Please check the "Enable Macros" command was fired.')
308
- contain_1 = False
309
- for ref in wb.VBProject.References:
310
- if ref.FullPath is not None:
311
- if ref.FullPath.lower() == ref_file_1.lower():
312
- contain_1 = True
313
- # add
314
- if not contain_1:
315
- wb.VBProject.References.AddFromFile(ref_file_1)
553
+ # get 64 bit
554
+ xla_file_path = r'C:\Program Files\Microsoft Office\root\Office16\XLSTART\FemtetRef.xla'
555
+
556
+ # if not exist, get 32bit
557
+ if not os.path.exists(xla_file_path):
558
+ xla_file_path = r'C:\Program Files (x86)\Microsoft Office\root\Office16\XLSTART\FemtetRef.xla'
559
+
560
+ # certify
561
+ if not os.path.exists(xla_file_path):
562
+ raise FileNotFoundError(f'{xla_file_path} not found. Please check the "Enable Macros" command was fired.')
563
+
564
+ # self.excel.Workbooks.Add(xla_file_path)
565
+ self.excel.Workbooks.Open(xla_file_path, ReadOnly=True)
566
+
567
+ def add_femtet_macro_reference(self, wb):
316
568
 
317
569
  # search
318
570
  ref_file_2 = os.path.abspath(util._get_femtetmacro_dllpath())
@@ -321,76 +573,47 @@ class ExcelInterface(FEMInterface):
321
573
  if ref.Description is not None:
322
574
  if ref.Description == 'FemtetMacro': # FemtetMacro
323
575
  contain_2 = True
576
+ break
324
577
  # add
325
578
  if not contain_2:
326
579
  wb.VBProject.References.AddFromFile(ref_file_2)
327
580
 
328
581
  def remove_femtet_ref_xla(self, wb):
329
-
330
- # search
331
- ref_file_1 = r'C:\Program Files\Microsoft Office\root\Office16\XLSTART\FemtetRef.xla'
332
- if not os.path.exists(ref_file_1):
333
- # 32bit
334
- ref_file_1 = r'C:\Program Files (x86)\Microsoft Office\root\Office16\XLSTART\FemtetRef.xla'
335
- if not os.path.exists(ref_file_1):
336
- raise FileNotFoundError(f'{ref_file_1} not found. Please check the "Enable Macros" command was fired.')
337
- for ref in wb.VBProject.References:
338
- if ref.FullPath is not None:
339
- if ref.FullPath == ref_file_1: # or ``FemtetMacroを使用するための参照設定を自動で行ないます。``
340
- wb.VBProject.References.Remove(ref)
341
-
342
582
  # search
343
583
  for ref in wb.VBProject.References:
344
584
  if ref.Description is not None:
345
585
  if ref.Description == 'FemtetMacro': # FemtetMacro
346
586
  wb.VBProject.References.Remove(ref)
347
587
 
348
- def _setup_after_parallel(self, *args, **kwargs):
349
- # サブプロセス又はメインプロセスのサブスレッドで、最適化を開始する前の前処理
350
-
351
- # スレッドが変わっているかもしれないので win32com の初期化
352
- CoInitialize()
353
-
354
- # 最適化中は femtet の autosave を無効にする
355
- _set_autosave_enabled(False)
356
-
357
- # 必要なら Femtet を起動する
358
- if self._with_call_femtet:
359
- util.execute_femtet()
360
-
361
- # 直後の Excel 起動に間に合わない場合があるため
362
- # Femtet が Dispatch 可能になるまで捨てプロセスで待つ
363
- p = _NestableSpawnProcess(target=wait_femtet)
364
- p.start()
365
- p.join()
366
-
367
- # excel に繋ぐ
368
- self.connect_excel(self.connect_method)
369
-
370
- # load_objective は 1 回目に呼ばれたのが main thread なので
371
- # subprocess に入った後でもう一度 load objective を行う
372
- from pyfemtet.opt.optimizer import AbstractOptimizer
373
- from pyfemtet.opt._femopt_core import Objective
374
- opt: AbstractOptimizer = kwargs['opt']
375
- obj: Objective
376
- for obj_name, obj in opt.objectives.items():
377
- if isinstance(obj.fun, ScapeGoatObjective):
378
- opt.objectives[obj_name].fun = self.objective_from_excel
379
-
380
- def update(self, parameters: pd.DataFrame) -> None:
381
-
588
+ def update_parameter(self, parameters: pd.DataFrame, with_warning=False) -> Optional[List[str]]:
382
589
  # params を作成
383
590
  params = dict()
384
591
  for _, row in parameters.iterrows():
385
592
  params[row['name']] = row['value']
386
593
 
387
594
  # excel シートの変数更新
388
- for key, value in params.items():
389
- self.sh_input.Range(key).value = value
595
+ if self.use_named_range:
596
+ for key, value in params.items():
597
+ try:
598
+ self.sh_input.Range(key).value = value
599
+ except com_error:
600
+ logger.warn('The cell address specification by named range is failed. '
601
+ 'The process changes the specification way to table based method.')
602
+ self.use_named_range = False
603
+ break
604
+
605
+ if not self.use_named_range: # else にしないこと
606
+ for name, value in params.items():
607
+ r = 1 + search_r(self.input_xlsm_path, self.input_sheet_name, name)
608
+ c = 1 + search_c(self.input_xlsm_path, self.input_sheet_name, ParseAsParameter.value)
609
+ self.sh_input.Cells(r, c).value = value
390
610
 
391
611
  # 再計算
392
612
  self.excel.CalculateFull()
393
613
 
614
+ def update(self, parameters: pd.DataFrame) -> None:
615
+ self.update_parameter(parameters)
616
+
394
617
  # マクロ実行
395
618
  try:
396
619
  with watch_excel_macro_error(self.excel, timeout=self.procedure_timeout):
@@ -407,48 +630,81 @@ class ExcelInterface(FEMInterface):
407
630
 
408
631
  def quit(self):
409
632
  if self.terminate_excel_when_quit:
410
- logger.info('Excel の終了処理を開始します。')
411
-
412
- self.remove_femtet_ref_xla(self.wb_input)
413
- self.remove_femtet_ref_xla(self.wb_output)
414
633
 
634
+ already_terminated = not hasattr(self, 'excel')
635
+ if already_terminated:
636
+ return
637
+
638
+ logger.info(Msg.INFO_TERMINATING_EXCEL)
639
+
640
+ # 参照設定解除の前に終了処理を必要なら実施する
641
+ # excel の setup 関数を必要なら実行する
642
+ if self.teardown_procedure_name is not None:
643
+ with lock_or_no_lock('excel_teardown_procedure'):
644
+ try:
645
+ with watch_excel_macro_error(self.excel, timeout=self.procedure_timeout, restore_book=False):
646
+ self.excel.Run(
647
+ f'{self.teardown_procedure_name}',
648
+ *self.teardown_procedure_args
649
+ )
650
+
651
+ # 再計算
652
+ self.excel.CalculateFull()
653
+
654
+ except com_error as e:
655
+ raise RuntimeError(f'Failed to run macro {self.teardown_procedure_args}. The original message is: {e}')
656
+
657
+ # 不具合の原因になる場合があるので参照設定は解除しないこと
658
+ # self.remove_femtet_ref_xla(self.wb_input)
659
+ # self.remove_femtet_ref_xla(self.wb_output)
660
+ # self.remove_femtet_ref_xla(self.wb_constraint)
661
+ # self.remove_femtet_ref_xla(self.wb_setup)
662
+ # self.remove_femtet_ref_xla(self.wb_teardown)
663
+
664
+ # シートの COM オブジェクト変数を削除する
415
665
  del self.sh_input
416
666
  del self.sh_output
667
+ del self.sh_constraint
417
668
 
669
+ # workbook を閉じる
418
670
  with watch_excel_macro_error(self.excel, timeout=10, restore_book=False):
419
671
  self.wb_input.Close(SaveChanges := False)
420
- if self.input_xlsm_path.name != self.output_xlsm_path.name:
672
+
673
+ if not is_same_path(self.input_xlsm_path, self.output_xlsm_path):
421
674
  with watch_excel_macro_error(self.excel, timeout=10, restore_book=False):
422
675
  self.wb_output.Close(SaveChanges := False)
423
676
 
677
+ if not is_same_path(self.input_xlsm_path, self.constraint_xlsm_path):
678
+ with watch_excel_macro_error(self.excel, timeout=10, restore_book=False):
679
+ self.wb_constraint.Close(SaveChanges := False)
680
+
681
+ if not is_same_path(self.input_xlsm_path, self.setup_xlsm_path):
682
+ with watch_excel_macro_error(self.excel, timeout=10, restore_book=False):
683
+ self.wb_setup.Close(SaveChanges := False)
684
+
685
+ if not is_same_path(self.input_xlsm_path, self.teardown_xlsm_path):
686
+ with watch_excel_macro_error(self.excel, timeout=10, restore_book=False):
687
+ self.wb_teardown.Close(SaveChanges := False)
688
+
424
689
  del self.wb_input
425
690
  del self.wb_output
691
+ del self.wb_constraint
692
+ del self.wb_setup
693
+ del self.wb_teardown
426
694
 
695
+ # excel の終了
427
696
  with watch_excel_macro_error(self.excel, timeout=10, restore_book=False):
428
697
  self.excel.Quit()
429
698
  del self.excel
430
699
 
431
- gc.collect() # ここで Excel のプロセスが残らず落ちる
432
-
433
- if self._with_call_femtet:
434
-
435
- # quit した後ならば femtet を終了できる
436
- # excel の process の消滅を待つ
437
- logger.info('Excel の終了を待っています。')
438
- while self._excel_pid == _get_pid(self._excel_hwnd):
439
- sleep(1)
440
-
441
- # TODO: 正確だが時間がかかる。選択できるようにしたほうがいいかもしれない。
442
- logger.info('終了する Femtet を特定しています。')
443
- femtet_pid = util.get_last_executed_femtet_process_id()
444
- from multiprocessing import Process
445
- p = Process(target=_terminate_femtet, args=(femtet_pid,))
446
- p.start()
447
- p.join()
448
- logger.info('Excel-Femtet を終了しました。')
700
+ # ここで Excel のプロセスが残らず落ちる
701
+ gc.collect()
702
+ logger.info(Msg.INFO_TERMINATED_EXCEL)
449
703
 
450
- logger.info('自動保存機能の設定を元に戻しています。')
451
- _set_autosave_enabled(self._femtet_autosave_buffer)
704
+ if self._with_femtet_autosave_setting:
705
+ from pyfemtet._femtet_config_util.autosave import _set_autosave_enabled
706
+ logger.info(Msg.INFO_RESTORING_FEMTET_AUTOSAVE)
707
+ _set_autosave_enabled(self._femtet_autosave_buffer)
452
708
 
453
709
  # 直接アクセスしてもよいが、ユーザーに易しい名前にするためだけのプロパティ
454
710
  @property
@@ -471,93 +727,180 @@ class ExcelInterface(FEMInterface):
471
727
  from pyfemtet.opt.optimizer import AbstractOptimizer, logger
472
728
  opt: AbstractOptimizer
473
729
 
474
- df = pd.read_excel(
475
- self.input_xlsm_path,
476
- self.input_sheet_name,
477
- header=0,
478
- index_col=None,
479
- )
730
+ df = ParseAsParameter.parse(self.input_xlsm_path, self.input_sheet_name)
480
731
 
481
- # TODO: 使い勝手を考える
482
732
  for i, row in df.iterrows():
483
- try:
484
- name = row['name']
485
- value = row['current']
486
- lb = row['lower']
487
- ub = row['upper']
488
- step = row['step']
489
- except KeyError:
490
- logger.warn('列名が「name」「current」「lower」「upper」「step」になっていません。この順に並んでいると仮定して処理を続けます。')
491
- name, value, lb, ub, step, *_residuals = row.iloc[0]
492
-
493
- name = str(name)
494
- value = float(value)
495
- lb = float(lb) if not np.isnan(lb) else None
496
- ub = float(ub) if not np.isnan(ub) else None
497
- step = float(step) if not np.isnan(step) else None
498
-
499
- prm = Parameter(
500
- name=name,
501
- value=value,
502
- lower_bound=lb,
503
- upper_bound=ub,
504
- step=step,
505
- pass_to_fem=True,
506
- properties=None,
507
- )
508
- opt.variables.add_parameter(prm)
733
+
734
+ # use(optional)
735
+ use = True
736
+ if ParseAsParameter.use in df.columns:
737
+ _use = row[ParseAsParameter.use]
738
+ use = False if is_cell_value_empty(_use) else bool(_use) # bool or NaN
739
+
740
+ # name
741
+ name = str(row[ParseAsParameter.name])
742
+
743
+ # value
744
+ value = float(row[ParseAsParameter.value])
745
+
746
+ # lb (optional)
747
+ lb = None
748
+ if ParseAsParameter.lb in df.columns:
749
+ lb = row[ParseAsParameter.lb]
750
+ lb = None if is_cell_value_empty(lb) else float(lb)
751
+
752
+ # ub (optional)
753
+ ub = None
754
+ if ParseAsParameter.ub in df.columns:
755
+ ub = row[ParseAsParameter.ub]
756
+ ub = None if is_cell_value_empty(ub) else float(ub)
757
+
758
+ # step (optional)
759
+ step = None
760
+ if ParseAsParameter.step in df.columns:
761
+ step = row[ParseAsParameter.step]
762
+ step = None if is_cell_value_empty(step) else float(step)
763
+
764
+ if use:
765
+ prm = Parameter(
766
+ name=name,
767
+ value=value,
768
+ lower_bound=lb,
769
+ upper_bound=ub,
770
+ step=step,
771
+ pass_to_fem=True,
772
+ properties=None,
773
+ )
774
+ opt.variables.add_parameter(prm)
775
+
776
+ else:
777
+ fixed_prm = Expression(
778
+ name=name,
779
+ fun=lambda: value,
780
+ value=None,
781
+ pass_to_fem=True,
782
+ properties=dict(
783
+ lower_bound=lb,
784
+ upper_bound=ub,
785
+ ),
786
+ kwargs=dict(),
787
+ )
788
+ opt.variables.add_expression(fixed_prm)
509
789
 
510
790
  def load_objective(self, opt):
511
791
  from pyfemtet.opt.optimizer import AbstractOptimizer, logger
512
792
  from pyfemtet.opt._femopt_core import Objective
513
793
  opt: AbstractOptimizer
514
794
 
515
- df = pd.read_excel(
516
- self.output_xlsm_path,
517
- self.output_sheet_name,
518
- header=0,
519
- index_col=None,
520
- )
795
+ df = ParseAsObjective.parse(self.output_xlsm_path, self.output_sheet_name)
521
796
 
522
- # TODO: 使い勝手を考える
523
797
  for i, row in df.iterrows():
798
+
799
+ # use(optional)
800
+ use = True
801
+ if ParseAsObjective.use in df.columns:
802
+ _use = row[ParseAsObjective.use]
803
+ use = False if is_cell_value_empty(_use) else bool(_use) # bool or NaN
804
+
805
+ # name
806
+ name = str(row[ParseAsObjective.name])
807
+
808
+ # direction
809
+ direction = row[ParseAsObjective.direction]
810
+ assert not is_cell_value_empty(direction), 'direction is empty.'
524
811
  try:
525
- name = row['name']
526
- _ = row['current']
527
- direction = row['direction']
528
- value_column_index = list(df.columns).index('current')
529
- except KeyError:
530
- logger.warn('列名が「name」「current」「direction」になっていません。この順に並んでいると仮定して処理を続けます。')
531
- name, _, direction, *_residuals = row.iloc[0]
532
- value_column_index = 1
533
-
534
- name = str(name)
535
-
536
- # direction は minimize or maximize or float
537
- try:
538
- # float or not
539
812
  direction = float(direction)
540
-
541
813
  except ValueError:
542
- # 'minimize' or 'maximize
543
814
  direction = str(direction).lower()
544
- assert (direction == 'minimize') or (direction == 'maximize')
545
-
546
- # objective を作る
547
- opt.objectives[name] = Objective(
548
- fun=ScapeGoatObjective(),
549
- name=name,
550
- direction=direction,
551
- args=(i, value_column_index, ),
552
- kwargs=dict(),
553
- )
554
-
555
- def objective_from_excel(self, i: int, value_column_index: int):
556
- r = i + 2 # header が 1
557
- c = value_column_index + 1
815
+ assert direction in ['minimize', 'maximize']
816
+
817
+ if use:
818
+ # objective を作る
819
+ opt.objectives[name] = Objective(
820
+ fun=ScapeGoatObjective(),
821
+ name=name,
822
+ direction=direction,
823
+ args=(name,),
824
+ kwargs=dict(),
825
+ )
826
+
827
+ def load_constraint(self, opt):
828
+ from pyfemtet.opt.optimizer import AbstractOptimizer, logger
829
+ from pyfemtet.opt._femopt_core import Constraint
830
+ opt: AbstractOptimizer
831
+
832
+ # TODO:
833
+ # constraint は optional である。
834
+ # 現在は実装していないが、シートから問題を取得するよりよいロジックができたら
835
+ # (つまり、同じシートからパラメータと拘束を取得出来るようになったら)__init__ 内で
836
+ # constraint に None が与えられたのか故意に input_sheet_name と同じシート名を
837
+ # 与えられたのか分別できる実装に変えてそのチェック処理をここに反映する。
838
+ # constraint_sheet_name が指定されていない場合何もしない
839
+ if (self.constraint_sheet_name == self.input_sheet_name) and is_same_path(self.input_xlsm_path, self.constraint_xlsm_path):
840
+ return
841
+
842
+ df = ParseAsConstraint.parse(self.constraint_xlsm_path, self.constraint_sheet_name)
843
+
844
+ for i, row in df.iterrows():
845
+
846
+ # use(optional)
847
+ use = True
848
+ if ParseAsConstraint.use in df.columns:
849
+ _use = row[ParseAsConstraint.use]
850
+ use = False if is_cell_value_empty(_use) else bool(_use) # bool or NaN
851
+
852
+ # name
853
+ name = str(row[ParseAsConstraint.name])
854
+
855
+ # lb (optional)
856
+ lb = None
857
+ if ParseAsConstraint.lb in df.columns:
858
+ lb = row[ParseAsConstraint.lb]
859
+ lb = None if is_cell_value_empty(lb) else float(lb)
860
+
861
+ # ub (optional)
862
+ ub = None
863
+ if ParseAsConstraint.ub in df.columns:
864
+ ub = row[ParseAsConstraint.ub]
865
+ ub = None if is_cell_value_empty(ub) else float(ub)
866
+
867
+ # strict (optional)
868
+ strict = True
869
+ if ParseAsConstraint.strict in df.columns:
870
+ _strict = row[ParseAsConstraint.strict]
871
+ strict = True if is_cell_value_empty(_strict) else bool(_strict) # bool or NaN
872
+
873
+ # using_fem (optional)
874
+ calc_before_solve = True
875
+ if ParseAsConstraint.calc_before_solve in df.columns:
876
+ _calc_before_solve = row[ParseAsConstraint.calc_before_solve]
877
+ calc_before_solve = True if is_cell_value_empty(_calc_before_solve) else bool(_calc_before_solve) # bool or NaN
878
+
879
+ if use:
880
+ # constraint を作る
881
+ opt.constraints[name] = Constraint(
882
+ fun=ScapeGoatObjective(),
883
+ name=name,
884
+ lb=lb,
885
+ ub=ub,
886
+ strict=strict,
887
+ args=(name,),
888
+ kwargs=dict(),
889
+ using_fem=not calc_before_solve,
890
+ )
891
+
892
+ def objective_from_excel(self, name: str):
893
+ r = 1 + search_r(self.output_xlsm_path, self.output_sheet_name, name)
894
+ c = 1 + search_c(self.output_xlsm_path, self.output_sheet_name, ParseAsObjective.value)
558
895
  v = self.sh_output.Cells(r, c).value
559
896
  return float(v)
560
897
 
898
+ def constraint_from_excel(self, name: str):
899
+ r = 1 + search_r(self.constraint_xlsm_path, self.constraint_sheet_name, name)
900
+ c = 1 + search_c(self.constraint_xlsm_path, self.constraint_sheet_name, ParseAsConstraint.value)
901
+ v = self.sh_constraint.Cells(r, c).value
902
+ return float(v)
903
+
561
904
 
562
905
  def wait_femtet():
563
906
  Femtet = Dispatch('FemtetMacro.Femtet')
@@ -575,13 +918,31 @@ def _terminate_femtet(femtet_pid_):
575
918
  # main thread で作成した excel への参照を含む関数を
576
919
  # 直接 thread や process に渡すと機能しない
577
920
  class ScapeGoatObjective:
578
- def __call__(self, *args, fem: ExcelInterface or None = None, **kwargs):
579
- fem.objective_from_excel(*args, **kwargs)
921
+ # def __call__(self, *args, fem: ExcelInterface or None = None, **kwargs):
922
+ # fem.objective_from_excel(*args, **kwargs)
580
923
 
581
924
  @property
582
925
  def __globals__(self):
583
926
  return tuple()
584
927
 
585
928
 
929
+ def is_same_path(p1, p2):
930
+ _p1 = os.path.abspath(p1).lower()
931
+ _p2 = os.path.abspath(p2).lower()
932
+ return _p1 == _p2
933
+
934
+
935
+ def is_cell_value_empty(cell_value):
936
+ if isinstance(cell_value, str):
937
+ return cell_value == ''
938
+ elif isinstance(cell_value, int) \
939
+ or isinstance(cell_value, float):
940
+ return np.isnan(cell_value)
941
+ elif cell_value is None:
942
+ return True
943
+ else:
944
+ return False
945
+
946
+
586
947
  if __name__ == '__main__':
587
948
  ExcelInterface(..., ...)