pyfemtet 0.7.1__py3-none-any.whl → 0.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyfemtet might be problematic. Click here for more details.

Files changed (44) hide show
  1. pyfemtet/__init__.py +1 -1
  2. pyfemtet/_message/locales/ja/LC_MESSAGES/messages.mo +0 -0
  3. pyfemtet/_message/locales/ja/LC_MESSAGES/messages.po +112 -90
  4. pyfemtet/_message/locales/messages.pot +105 -89
  5. pyfemtet/_message/messages.py +6 -2
  6. pyfemtet/_util/excel_parse_util.py +138 -0
  7. pyfemtet/_util/sample.xlsx +0 -0
  8. pyfemtet/brep/__init__.py +0 -3
  9. pyfemtet/brep/_impl.py +7 -3
  10. pyfemtet/opt/_femopt.py +42 -14
  11. pyfemtet/opt/_femopt_core.py +93 -34
  12. pyfemtet/opt/advanced_samples/excel_ui/(ref) original_project.femprj +0 -0
  13. pyfemtet/opt/advanced_samples/excel_ui/femtet-macro.xlsm +0 -0
  14. pyfemtet/opt/advanced_samples/excel_ui/pyfemtet-core.py +291 -0
  15. pyfemtet/opt/advanced_samples/excel_ui/test-pyfemtet-core.cmd +22 -0
  16. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_create_training_data.py +60 -0
  17. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_create_training_data_jp.py +57 -0
  18. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate.py +100 -0
  19. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_optimize_with_surrogate_jp.py +90 -0
  20. pyfemtet/opt/advanced_samples/surrogate_model/gal_ex13_parametric.femprj +0 -0
  21. pyfemtet/opt/interface/__init__.py +2 -0
  22. pyfemtet/opt/interface/_base.py +3 -0
  23. pyfemtet/opt/interface/_excel_interface.py +296 -124
  24. pyfemtet/opt/interface/_femtet.py +19 -9
  25. pyfemtet/opt/interface/_surrogate/__init__.py +5 -0
  26. pyfemtet/opt/interface/_surrogate/_base.py +85 -0
  27. pyfemtet/opt/interface/_surrogate/_chaospy.py +71 -0
  28. pyfemtet/opt/interface/_surrogate/_singletaskgp.py +70 -0
  29. pyfemtet/opt/optimizer/_base.py +28 -18
  30. pyfemtet/opt/optimizer/_optuna/_optuna.py +20 -8
  31. pyfemtet/opt/optimizer/_optuna/_pof_botorch.py +60 -18
  32. pyfemtet/opt/prediction/_base.py +8 -0
  33. pyfemtet/opt/prediction/single_task_gp.py +85 -62
  34. pyfemtet/opt/visualization/_complex_components/main_figure_creator.py +5 -5
  35. pyfemtet/opt/visualization/_complex_components/main_graph.py +7 -1
  36. pyfemtet/opt/visualization/_complex_components/pm_graph.py +1 -1
  37. pyfemtet/opt/visualization/_process_monitor/application.py +2 -2
  38. pyfemtet/opt/visualization/_process_monitor/pages.py +1 -1
  39. pyfemtet/opt/visualization/result_viewer/pages.py +1 -1
  40. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.0.dist-info}/METADATA +2 -2
  41. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.0.dist-info}/RECORD +44 -28
  42. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.0.dist-info}/WHEEL +1 -1
  43. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.0.dist-info}/LICENSE +0 -0
  44. {pyfemtet-0.7.1.dist-info → pyfemtet-0.8.0.dist-info}/entry_points.txt +0 -0
@@ -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,7 +19,7 @@ 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
25
 
@@ -26,8 +27,10 @@ from pyfemtet._femtet_config_util.exit import _exit_or_force_terminate
26
27
 
27
28
  from pyfemtet._util.excel_macro_util import watch_excel_macro_error
28
29
  from pyfemtet._util.dask_util import lock_or_no_lock
30
+ from pyfemtet._util.excel_parse_util import *
29
31
 
30
32
  from pyfemtet._warning import show_experimental_warning
33
+ from pyfemtet._message.messages import Message as Msg
31
34
 
32
35
  from pyfemtet.opt.interface._base import logger
33
36
 
@@ -178,6 +181,8 @@ class ExcelInterface(FEMInterface):
178
181
  input_sheet_name: str # 変数セルを定義しているシート名
179
182
  output_xlsm_path: str # 操作対象の xlsm パス (指定しない場合、input と同一)
180
183
  output_sheet_name: str # 計算結果セルを定義しているシート名 (指定しない場合、input と同一)
184
+ constraint_xlsm_path: str # 操作対象の xlsm パス (指定しない場合、input と同一)
185
+ constraint_sheet_name: str # 拘束関数セルを定義しているシート名 (指定しない場合、input と同一)
181
186
 
182
187
  related_file_paths: list[str] # 並列時に個別に並列プロセスの space にアップロードする必要のあるパス
183
188
 
@@ -189,6 +194,8 @@ class ExcelInterface(FEMInterface):
189
194
  sh_input: CDispatch # 変数の定義された WorkSheet
190
195
  wb_output: CDispatch # システムを構成する Workbook
191
196
  sh_output: CDispatch # 計算結果の定義された WorkSheet (sh_input と同じでもよい)
197
+ wb_constraint: CDispatch # システムを構成する Workbook
198
+ sh_constraint: CDispatch # 計算結果の定義された WorkSheet (sh_input と同じでもよい)
192
199
  wb_setup: CDispatch # システムを構成する Workbook
193
200
  wb_teardown: CDispatch # システムを構成する Workbook
194
201
 
@@ -210,12 +217,16 @@ class ExcelInterface(FEMInterface):
210
217
  teardown_procedure_name: str
211
218
  teardown_procedure_args: list or tuple
212
219
 
220
+ use_named_range: bool # input を定義したシートにおいて input の値を名前付き範囲で指定するかどうか。
221
+
213
222
  def __init__(
214
223
  self,
215
224
  input_xlsm_path: str or Path,
216
225
  input_sheet_name: str,
217
226
  output_xlsm_path: str or Path = None,
218
227
  output_sheet_name: str = None,
228
+ constraint_xlsm_path: str or Path = None,
229
+ constraint_sheet_name: str = None,
219
230
  procedure_name: str = None,
220
231
  procedure_args: list or tuple = None,
221
232
  connect_method: str = 'auto', # or 'new'
@@ -231,6 +242,7 @@ class ExcelInterface(FEMInterface):
231
242
  display_alerts: bool = False,
232
243
  terminate_excel_when_quit: bool = None,
233
244
  interactive: bool = True,
245
+ use_named_range: bool = True,
234
246
  ):
235
247
 
236
248
  show_experimental_warning("ExcelInterface")
@@ -239,7 +251,9 @@ class ExcelInterface(FEMInterface):
239
251
  self.input_xlsm_path = str(input_xlsm_path) # あとで再取得する
240
252
  self.input_sheet_name = input_sheet_name
241
253
  self.output_xlsm_path = str(input_xlsm_path) if output_xlsm_path is None else str(output_xlsm_path)
242
- self.output_sheet_name = output_sheet_name or self.input_sheet_name
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
243
257
  self.procedure_name = procedure_name or 'FemtetMacro.FemtetMain'
244
258
  self.procedure_args = procedure_args or []
245
259
  assert connect_method in ['new', 'auto']
@@ -264,12 +278,15 @@ class ExcelInterface(FEMInterface):
264
278
  self.interactive = interactive
265
279
  self.display_alerts = display_alerts
266
280
 
281
+ self.use_named_range = use_named_range
282
+
267
283
  # dask サブプロセスのときは space 直下の input_xlsm_path を参照する
268
284
  try:
269
285
  worker = get_worker()
270
286
  space = os.path.abspath(worker.local_directory)
271
287
  self.input_xlsm_path = os.path.join(space, os.path.basename(self.input_xlsm_path))
272
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))
273
290
  self.setup_xlsm_path = os.path.join(space, os.path.basename(self.setup_xlsm_path))
274
291
  self.teardown_xlsm_path = os.path.join(space, os.path.basename(self.teardown_xlsm_path))
275
292
  self.related_file_paths = [os.path.join(space, os.path.basename(p)) for p in self.related_file_paths]
@@ -278,6 +295,7 @@ class ExcelInterface(FEMInterface):
278
295
  except ValueError:
279
296
  self.input_xlsm_path = os.path.abspath(self.input_xlsm_path)
280
297
  self.output_xlsm_path = os.path.abspath(self.output_xlsm_path)
298
+ self.constraint_xlsm_path = os.path.abspath(self.constraint_xlsm_path)
281
299
  self.setup_xlsm_path = os.path.abspath(self.setup_xlsm_path)
282
300
  self.teardown_xlsm_path = os.path.abspath(self.teardown_xlsm_path)
283
301
  self.related_file_paths = [os.path.abspath(p) for p in self.related_file_paths]
@@ -288,6 +306,8 @@ class ExcelInterface(FEMInterface):
288
306
  input_sheet_name=self.input_sheet_name,
289
307
  output_xlsm_path=self.output_xlsm_path,
290
308
  output_sheet_name=self.output_sheet_name,
309
+ constraint_xlsm_path=self.constraint_xlsm_path,
310
+ constraint_sheet_name=self.constraint_sheet_name,
291
311
  procedure_name=self.procedure_name,
292
312
  procedure_args=self.procedure_args,
293
313
  connect_method='new', # subprocess で connect する際は new を強制する
@@ -303,6 +323,7 @@ class ExcelInterface(FEMInterface):
303
323
  visible=self.visible,
304
324
  interactive=self.interactive,
305
325
  display_alerts=self.display_alerts,
326
+ use_named_range=self.use_named_range,
306
327
  )
307
328
  FEMInterface.__init__(self, **kwargs)
308
329
 
@@ -317,6 +338,9 @@ class ExcelInterface(FEMInterface):
317
338
  if not is_same_path(self.input_xlsm_path, self.output_xlsm_path):
318
339
  client.upload_file(self.output_xlsm_path, False)
319
340
 
341
+ if not is_same_path(self.input_xlsm_path, self.constraint_xlsm_path):
342
+ client.upload_file(self.constraint_xlsm_path, False)
343
+
320
344
  if not is_same_path(self.input_xlsm_path, self.setup_xlsm_path):
321
345
  client.upload_file(self.setup_xlsm_path, False)
322
346
 
@@ -336,6 +360,7 @@ class ExcelInterface(FEMInterface):
336
360
  if space is not None:
337
361
  self.input_xlsm_path = os.path.join(space, os.path.basename(self.input_xlsm_path))
338
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))
339
364
  self.setup_xlsm_path = os.path.join(space, os.path.basename(self.setup_xlsm_path))
340
365
  self.teardown_xlsm_path = os.path.join(space, os.path.basename(self.teardown_xlsm_path))
341
366
  self.related_file_paths = [os.path.join(space, os.path.basename(p)) for p in self.related_file_paths]
@@ -354,6 +379,7 @@ class ExcelInterface(FEMInterface):
354
379
 
355
380
  self.input_xlsm_path = proc_path(self.input_xlsm_path, False)
356
381
  self.output_xlsm_path = proc_path(self.output_xlsm_path, True)
382
+ self.constraint_xlsm_path = proc_path(self.constraint_xlsm_path, True)
357
383
  self.setup_xlsm_path = proc_path(self.setup_xlsm_path, True)
358
384
  self.teardown_xlsm_path = proc_path(self.teardown_xlsm_path, True)
359
385
 
@@ -367,18 +393,25 @@ class ExcelInterface(FEMInterface):
367
393
  _set_autosave_enabled(False)
368
394
 
369
395
  # excel に繋ぐ
370
- self.connect_excel(self.connect_method)
396
+ with lock_or_no_lock('connect-excel'):
397
+ self.connect_excel(self.connect_method)
398
+ sleep(1)
371
399
 
372
400
  # load_objective は 1 回目に呼ばれたのが main thread なので
373
401
  # subprocess に入った後でもう一度 load objective を行う
374
402
  from pyfemtet.opt.optimizer import AbstractOptimizer
375
- from pyfemtet.opt._femopt_core import Objective
403
+ from pyfemtet.opt._femopt_core import Objective, Constraint
376
404
  opt: AbstractOptimizer = kwargs['opt']
377
405
  obj: Objective
378
406
  for obj_name, obj in opt.objectives.items():
379
407
  if isinstance(obj.fun, ScapeGoatObjective):
380
408
  opt.objectives[obj_name].fun = self.objective_from_excel
381
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
+
382
415
  # excel の setup 関数を必要なら実行する
383
416
  if self.setup_procedure_name is not None:
384
417
  with lock_or_no_lock('excel_setup_procedure'):
@@ -391,9 +424,10 @@ class ExcelInterface(FEMInterface):
391
424
 
392
425
  # 再計算
393
426
  self.excel.CalculateFull()
427
+ sleep(1)
394
428
 
395
429
  except com_error as e:
396
- raise RuntimeError(f'Failed to run macro {self.setup_procedure_args}. The original message is: {e}')
430
+ raise RuntimeError(f'Failed to run macro {self.setup_procedure_name}. The original message is: {e}')
397
431
 
398
432
  def connect_excel(self, connect_method):
399
433
 
@@ -404,6 +438,10 @@ class ExcelInterface(FEMInterface):
404
438
  else:
405
439
  self.excel = DispatchEx('Excel.Application')
406
440
 
441
+ # FemtetRef を追加する
442
+ self.open_femtet_ref_xla() # ここでエラーが発生しているかも?
443
+ sleep(0.5)
444
+
407
445
  # 起動した excel の pid を記憶する
408
446
  self._excel_hwnd = self.excel.hWnd
409
447
  self._excel_pid = 0
@@ -415,7 +453,9 @@ class ExcelInterface(FEMInterface):
415
453
  self.excel.Visible = self.visible
416
454
  self.excel.DisplayAlerts = self.display_alerts
417
455
  self.excel.Interactive = self.interactive
456
+ sleep(0.5)
418
457
 
458
+ # ===== input =====
419
459
  # 開く
420
460
  self.excel.Workbooks.Open(str(self.input_xlsm_path))
421
461
  for wb in self.excel.Workbooks:
@@ -433,6 +473,7 @@ class ExcelInterface(FEMInterface):
433
473
  else:
434
474
  raise RuntimeError(f'Sheet {self.input_sheet_name} does not exist in the book {self.wb_input.Name}.')
435
475
 
476
+ # ===== output =====
436
477
  # 開く (output)
437
478
  if is_same_path(self.input_xlsm_path, self.output_xlsm_path):
438
479
  self.wb_output = self.wb_input
@@ -453,6 +494,28 @@ class ExcelInterface(FEMInterface):
453
494
  else:
454
495
  raise RuntimeError(f'Sheet {self.output_sheet_name} does not exist in the book {self.wb_output.Name}.')
455
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 =====
456
519
  # 開く (setup)
457
520
  if is_same_path(self.input_xlsm_path, self.setup_xlsm_path):
458
521
  self.wb_setup = self.wb_input
@@ -465,6 +528,7 @@ class ExcelInterface(FEMInterface):
465
528
  else:
466
529
  raise RuntimeError(f'Cannot open {self.setup_xlsm_path}')
467
530
 
531
+ # ===== teardown =====
468
532
  # 開く (teardown)
469
533
  if is_same_path(self.input_xlsm_path, self.teardown_xlsm_path):
470
534
  self.wb_teardown = self.wb_input
@@ -478,28 +542,29 @@ class ExcelInterface(FEMInterface):
478
542
  raise RuntimeError(f'Cannot open {self.teardown_xlsm_path}')
479
543
 
480
544
  # book に参照設定を追加する
481
- self.add_femtet_ref_xla(self.wb_input)
482
- self.add_femtet_ref_xla(self.wb_output)
483
- self.add_femtet_ref_xla(self.wb_setup)
484
- self.add_femtet_ref_xla(self.wb_teardown)
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)
485
550
 
486
- def add_femtet_ref_xla(self, wb):
551
+ def open_femtet_ref_xla(self):
487
552
 
488
- # search
489
- ref_file_1 = r'C:\Program Files\Microsoft Office\root\Office16\XLSTART\FemtetRef.xla'
490
- if not os.path.exists(ref_file_1):
491
- # 32bit
492
- ref_file_1 = r'C:\Program Files (x86)\Microsoft Office\root\Office16\XLSTART\FemtetRef.xla'
493
- if not os.path.exists(ref_file_1):
494
- raise FileNotFoundError(f'{ref_file_1} not found. Please check the "Enable Macros" command was fired.')
495
- contain_1 = False
496
- for ref in wb.VBProject.References:
497
- if ref.FullPath is not None:
498
- if ref.FullPath.lower() == ref_file_1.lower():
499
- contain_1 = True
500
- # add
501
- if not contain_1:
502
- 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):
503
568
 
504
569
  # search
505
570
  ref_file_2 = os.path.abspath(util._get_femtetmacro_dllpath())
@@ -508,44 +573,47 @@ class ExcelInterface(FEMInterface):
508
573
  if ref.Description is not None:
509
574
  if ref.Description == 'FemtetMacro': # FemtetMacro
510
575
  contain_2 = True
576
+ break
511
577
  # add
512
578
  if not contain_2:
513
579
  wb.VBProject.References.AddFromFile(ref_file_2)
514
580
 
515
581
  def remove_femtet_ref_xla(self, wb):
516
-
517
- # search
518
- ref_file_1 = r'C:\Program Files\Microsoft Office\root\Office16\XLSTART\FemtetRef.xla'
519
- if not os.path.exists(ref_file_1):
520
- # 32bit
521
- ref_file_1 = r'C:\Program Files (x86)\Microsoft Office\root\Office16\XLSTART\FemtetRef.xla'
522
- if not os.path.exists(ref_file_1):
523
- raise FileNotFoundError(f'{ref_file_1} not found. Please check the "Enable Macros" command was fired.')
524
- for ref in wb.VBProject.References:
525
- if ref.FullPath is not None:
526
- if ref.FullPath == ref_file_1: # or ``FemtetMacroを使用するための参照設定を自動で行ないます。``
527
- wb.VBProject.References.Remove(ref)
528
-
529
582
  # search
530
583
  for ref in wb.VBProject.References:
531
584
  if ref.Description is not None:
532
585
  if ref.Description == 'FemtetMacro': # FemtetMacro
533
586
  wb.VBProject.References.Remove(ref)
534
587
 
535
- def update(self, parameters: pd.DataFrame) -> None:
536
-
588
+ def update_parameter(self, parameters: pd.DataFrame, with_warning=False) -> Optional[List[str]]:
537
589
  # params を作成
538
590
  params = dict()
539
591
  for _, row in parameters.iterrows():
540
592
  params[row['name']] = row['value']
541
593
 
542
594
  # excel シートの変数更新
543
- for key, value in params.items():
544
- 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
545
610
 
546
611
  # 再計算
547
612
  self.excel.CalculateFull()
548
613
 
614
+ def update(self, parameters: pd.DataFrame) -> None:
615
+ self.update_parameter(parameters)
616
+
549
617
  # マクロ実行
550
618
  try:
551
619
  with watch_excel_macro_error(self.excel, timeout=self.procedure_timeout):
@@ -567,12 +635,12 @@ class ExcelInterface(FEMInterface):
567
635
  if already_terminated:
568
636
  return
569
637
 
570
- logger.info('Excel の終了処理を開始します。')
638
+ logger.info(Msg.INFO_TERMINATING_EXCEL)
571
639
 
572
640
  # 参照設定解除の前に終了処理を必要なら実施する
573
641
  # excel の setup 関数を必要なら実行する
574
642
  if self.teardown_procedure_name is not None:
575
- with lock_or_no_lock('excel_setup_procedure'):
643
+ with lock_or_no_lock('excel_teardown_procedure'):
576
644
  try:
577
645
  with watch_excel_macro_error(self.excel, timeout=self.procedure_timeout, restore_book=False):
578
646
  self.excel.Run(
@@ -586,15 +654,17 @@ class ExcelInterface(FEMInterface):
586
654
  except com_error as e:
587
655
  raise RuntimeError(f'Failed to run macro {self.teardown_procedure_args}. The original message is: {e}')
588
656
 
589
- # 参照設定を解除する(不要な処理かも)
590
- self.remove_femtet_ref_xla(self.wb_input)
591
- self.remove_femtet_ref_xla(self.wb_output)
592
- self.remove_femtet_ref_xla(self.wb_setup)
593
- self.remove_femtet_ref_xla(self.wb_teardown)
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)
594
663
 
595
664
  # シートの COM オブジェクト変数を削除する
596
665
  del self.sh_input
597
666
  del self.sh_output
667
+ del self.sh_constraint
598
668
 
599
669
  # workbook を閉じる
600
670
  with watch_excel_macro_error(self.excel, timeout=10, restore_book=False):
@@ -604,6 +674,10 @@ class ExcelInterface(FEMInterface):
604
674
  with watch_excel_macro_error(self.excel, timeout=10, restore_book=False):
605
675
  self.wb_output.Close(SaveChanges := False)
606
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
+
607
681
  if not is_same_path(self.input_xlsm_path, self.setup_xlsm_path):
608
682
  with watch_excel_macro_error(self.excel, timeout=10, restore_book=False):
609
683
  self.wb_setup.Close(SaveChanges := False)
@@ -614,10 +688,10 @@ class ExcelInterface(FEMInterface):
614
688
 
615
689
  del self.wb_input
616
690
  del self.wb_output
691
+ del self.wb_constraint
617
692
  del self.wb_setup
618
693
  del self.wb_teardown
619
694
 
620
-
621
695
  # excel の終了
622
696
  with watch_excel_macro_error(self.excel, timeout=10, restore_book=False):
623
697
  self.excel.Quit()
@@ -625,12 +699,12 @@ class ExcelInterface(FEMInterface):
625
699
 
626
700
  # ここで Excel のプロセスが残らず落ちる
627
701
  gc.collect()
702
+ logger.info(Msg.INFO_TERMINATED_EXCEL)
628
703
 
629
704
  if self._with_femtet_autosave_setting:
630
705
  from pyfemtet._femtet_config_util.autosave import _set_autosave_enabled
631
- logger.info('自動保存機能の設定を元に戻しています。')
706
+ logger.info(Msg.INFO_RESTORING_FEMTET_AUTOSAVE)
632
707
  _set_autosave_enabled(self._femtet_autosave_buffer)
633
- logger.info('自動保存機能の設定を元に戻しました。')
634
708
 
635
709
  # 直接アクセスしてもよいが、ユーザーに易しい名前にするためだけのプロパティ
636
710
  @property
@@ -653,93 +727,180 @@ class ExcelInterface(FEMInterface):
653
727
  from pyfemtet.opt.optimizer import AbstractOptimizer, logger
654
728
  opt: AbstractOptimizer
655
729
 
656
- df = pd.read_excel(
657
- self.input_xlsm_path,
658
- self.input_sheet_name,
659
- header=0,
660
- index_col=None,
661
- )
730
+ df = ParseAsParameter.parse(self.input_xlsm_path, self.input_sheet_name)
662
731
 
663
- # TODO: 使い勝手を考える
664
732
  for i, row in df.iterrows():
665
- try:
666
- name = row['name']
667
- value = row['current']
668
- lb = row['lower']
669
- ub = row['upper']
670
- step = row['step']
671
- except KeyError:
672
- logger.warn('列名が「name」「current」「lower」「upper」「step」になっていません。この順に並んでいると仮定して処理を続けます。')
673
- name, value, lb, ub, step, *_residuals = row.iloc[0]
674
-
675
- name = str(name)
676
- value = float(value)
677
- lb = float(lb) if not np.isnan(lb) else None
678
- ub = float(ub) if not np.isnan(ub) else None
679
- step = float(step) if not np.isnan(step) else None
680
-
681
- prm = Parameter(
682
- name=name,
683
- value=value,
684
- lower_bound=lb,
685
- upper_bound=ub,
686
- step=step,
687
- pass_to_fem=True,
688
- properties=None,
689
- )
690
- 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)
691
789
 
692
790
  def load_objective(self, opt):
693
791
  from pyfemtet.opt.optimizer import AbstractOptimizer, logger
694
792
  from pyfemtet.opt._femopt_core import Objective
695
793
  opt: AbstractOptimizer
696
794
 
697
- df = pd.read_excel(
698
- self.output_xlsm_path,
699
- self.output_sheet_name,
700
- header=0,
701
- index_col=None,
702
- )
795
+ df = ParseAsObjective.parse(self.output_xlsm_path, self.output_sheet_name)
703
796
 
704
- # TODO: 使い勝手を考える
705
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.'
706
811
  try:
707
- name = row['name']
708
- _ = row['current']
709
- direction = row['direction']
710
- value_column_index = list(df.columns).index('current')
711
- except KeyError:
712
- logger.warn('列名が「name」「current」「direction」になっていません。この順に並んでいると仮定して処理を続けます。')
713
- name, _, direction, *_residuals = row.iloc[0]
714
- value_column_index = 1
715
-
716
- name = str(name)
717
-
718
- # direction は minimize or maximize or float
719
- try:
720
- # float or not
721
812
  direction = float(direction)
722
-
723
813
  except ValueError:
724
- # 'minimize' or 'maximize
725
814
  direction = str(direction).lower()
726
- assert (direction == 'minimize') or (direction == 'maximize')
727
-
728
- # objective を作る
729
- opt.objectives[name] = Objective(
730
- fun=ScapeGoatObjective(),
731
- name=name,
732
- direction=direction,
733
- args=(i, value_column_index, ),
734
- kwargs=dict(),
735
- )
736
-
737
- def objective_from_excel(self, i: int, value_column_index: int):
738
- r = i + 2 # header が 1
739
- 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)
740
895
  v = self.sh_output.Cells(r, c).value
741
896
  return float(v)
742
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
+
743
904
 
744
905
  def wait_femtet():
745
906
  Femtet = Dispatch('FemtetMacro.Femtet')
@@ -757,8 +918,8 @@ def _terminate_femtet(femtet_pid_):
757
918
  # main thread で作成した excel への参照を含む関数を
758
919
  # 直接 thread や process に渡すと機能しない
759
920
  class ScapeGoatObjective:
760
- def __call__(self, *args, fem: ExcelInterface or None = None, **kwargs):
761
- fem.objective_from_excel(*args, **kwargs)
921
+ # def __call__(self, *args, fem: ExcelInterface or None = None, **kwargs):
922
+ # fem.objective_from_excel(*args, **kwargs)
762
923
 
763
924
  @property
764
925
  def __globals__(self):
@@ -771,6 +932,17 @@ def is_same_path(p1, p2):
771
932
  return _p1 == _p2
772
933
 
773
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
+
774
946
 
775
947
  if __name__ == '__main__':
776
948
  ExcelInterface(..., ...)