pyfemtet 0.4.10__py3-none-any.whl → 0.4.12__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 (77) hide show
  1. pyfemtet/__init__.py +1 -1
  2. pyfemtet/_test_util.py +26 -0
  3. pyfemtet/opt/__init__.py +1 -1
  4. pyfemtet/opt/_femopt.py +41 -5
  5. pyfemtet/opt/interface/_base.py +6 -1
  6. pyfemtet/opt/interface/_femtet.py +32 -10
  7. pyfemtet/opt/interface/_femtet_parametric.py +5 -25
  8. pyfemtet/opt/opt/__init__.py +3 -1
  9. pyfemtet/opt/opt/_base.py +88 -8
  10. pyfemtet/opt/opt/_optuna.py +17 -2
  11. pyfemtet/opt/opt/_scipy.py +144 -0
  12. pyfemtet/opt/opt/_scipy_scalar.py +104 -0
  13. pyfemtet/opt/visualization/__init__.py +0 -7
  14. pyfemtet/opt/visualization/_create_wrapped_components.py +93 -0
  15. pyfemtet/opt/visualization/base.py +254 -0
  16. pyfemtet/opt/visualization/complex_components/__init__.py +0 -0
  17. pyfemtet/opt/visualization/complex_components/alert_region.py +71 -0
  18. pyfemtet/opt/visualization/complex_components/control_femtet.py +195 -0
  19. pyfemtet/opt/visualization/{_graphs.py → complex_components/main_figure_creator.py} +13 -49
  20. pyfemtet/opt/visualization/complex_components/main_graph.py +263 -0
  21. pyfemtet/opt/visualization/process_monitor/__init__.py +0 -0
  22. pyfemtet/opt/visualization/process_monitor/application.py +201 -0
  23. pyfemtet/opt/visualization/process_monitor/pages.py +276 -0
  24. pyfemtet/opt/visualization/result_viewer/.gitignore +1 -0
  25. pyfemtet/opt/visualization/result_viewer/__init__.py +0 -0
  26. pyfemtet/opt/visualization/result_viewer/application.py +44 -0
  27. pyfemtet/opt/visualization/result_viewer/pages.py +692 -0
  28. pyfemtet/opt/visualization/result_viewer/tutorial/tutorial.csv +18 -0
  29. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.jpg +0 -0
  30. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.log +81 -0
  31. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.pdt +0 -0
  32. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow.csv +28 -0
  33. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow_el.csv +22 -0
  34. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
  35. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
  36. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
  37. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
  38. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
  39. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
  40. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
  41. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
  42. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
  43. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
  44. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
  45. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
  46. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
  47. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
  48. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
  49. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
  50. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
  51. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
  52. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
  53. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
  54. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
  55. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
  56. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
  57. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
  58. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
  59. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
  60. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
  61. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
  62. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
  63. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
  64. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.femprj +0 -0
  65. pyfemtet/opt/visualization/wrapped_components/__init__.py +0 -0
  66. pyfemtet/opt/visualization/wrapped_components/dbc.py +1518 -0
  67. pyfemtet/opt/visualization/wrapped_components/dcc.py +609 -0
  68. pyfemtet/opt/visualization/wrapped_components/html.py +3570 -0
  69. pyfemtet/opt/visualization/wrapped_components/str_enum.py +43 -0
  70. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/METADATA +1 -1
  71. pyfemtet-0.4.12.dist-info/RECORD +138 -0
  72. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/entry_points.txt +1 -1
  73. pyfemtet/opt/visualization/_monitor.py +0 -1227
  74. pyfemtet/opt/visualization/result_viewer.py +0 -13
  75. pyfemtet-0.4.10.dist-info/RECORD +0 -83
  76. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/LICENSE +0 -0
  77. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/WHEEL +0 -0
pyfemtet/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.4.10"
1
+ __version__ = "0.4.12"
pyfemtet/_test_util.py CHANGED
@@ -15,6 +15,32 @@ from femtetutils import util
15
15
  from pyfemtet.opt import FEMOpt
16
16
 
17
17
 
18
+ class SuperSphere:
19
+ def __init__(self, n):
20
+ self.n = n
21
+
22
+ def x(self, radius, *angles):
23
+ assert len(angles) == self.n - 1, 'invalid angles length'
24
+
25
+ out = []
26
+
27
+ for i in range(self.n):
28
+ if i == 0:
29
+ out.append(radius * np.cos(angles[0]))
30
+ elif i < self.n - 1:
31
+ product = radius
32
+ for j in range(i):
33
+ product *= np.sin(angles[j])
34
+ product *= np.cos(angles[i])
35
+ out.append(product)
36
+ else:
37
+ product = radius
38
+ for j in range(i):
39
+ product *= np.sin(angles[j])
40
+ out.append(product)
41
+ return out
42
+
43
+
18
44
  def _open_femprj(femprj_path):
19
45
  Femtet = Dispatch('FemtetMacro.Femtet')
20
46
  for _ in tqdm(range(5), 'wait for dispatch Femtet'):
pyfemtet/opt/__init__.py CHANGED
@@ -4,7 +4,7 @@ from pyfemtet.opt.interface import FemtetInterface
4
4
  from pyfemtet.opt.interface import FemtetWithNXInterface
5
5
  from pyfemtet.opt.interface import FemtetWithSolidworksInterface
6
6
 
7
- from pyfemtet.opt.opt import OptunaOptimizer
7
+ from pyfemtet.opt.opt import OptunaOptimizer, ScipyOptimizer
8
8
  from pyfemtet.opt.opt import AbstractOptimizer
9
9
 
10
10
  from pyfemtet.opt._femopt import FEMOpt
pyfemtet/opt/_femopt.py CHANGED
@@ -13,7 +13,7 @@ from dask.distributed import LocalCluster, Client
13
13
  # pyfemtet relative
14
14
  from pyfemtet.opt.interface import FEMInterface, FemtetInterface
15
15
  from pyfemtet.opt.opt import AbstractOptimizer, OptunaOptimizer
16
- from pyfemtet.opt.visualization._monitor import ProcessMonitorApp
16
+ from pyfemtet.opt.visualization.process_monitor.application import main as process_monitor_main
17
17
  from pyfemtet.opt._femopt_core import (
18
18
  _check_bound,
19
19
  _is_access_gogh,
@@ -107,6 +107,7 @@ class FEMOpt:
107
107
  initial_value: float or None = None,
108
108
  lower_bound: float or None = None,
109
109
  upper_bound: float or None = None,
110
+ step: float or None = None,
110
111
  memo: str = ''
111
112
  ):
112
113
  """Adds a parameter to the optimization problem.
@@ -133,8 +134,9 @@ class FEMOpt:
133
134
  d = {
134
135
  'name': name,
135
136
  'value': float(initial_value),
136
- 'lb': float(lower_bound),
137
- 'ub': float(upper_bound),
137
+ 'lb': float(lower_bound) if lower_bound is not None else None,
138
+ 'ub': float(upper_bound) if upper_bound is not None else None,
139
+ 'step': float(step) if step is not None else None,
138
140
  'memo': memo,
139
141
  }
140
142
  pdf = pd.DataFrame(d, index=[0], dtype=object)
@@ -314,6 +316,34 @@ class FEMOpt:
314
316
 
315
317
  """
316
318
 
319
+ # method checker
320
+ if n_parallel > 1:
321
+ self.opt.method_checker.check_parallel()
322
+
323
+ if timeout is not None:
324
+ self.opt.method_checker.check_timeout()
325
+
326
+ if len(self.opt.objectives) > 1:
327
+ self.opt.method_checker.check_multi_objective()
328
+
329
+ if len(self.opt.constraints) > 0:
330
+ self.opt.method_checker.check_constraint()
331
+
332
+ for key, value in self.opt.constraints.items():
333
+ if value.strict:
334
+ self.opt.method_checker.check_strict_constraint()
335
+ break
336
+
337
+ if self.opt.seed is not None:
338
+ self.opt.method_checker.check_seed()
339
+
340
+ is_incomplete_bounds = False
341
+ for _, row in self.opt.parameters.iterrows():
342
+ lb, ub = row['lb'], row['ub']
343
+ is_incomplete_bounds = is_incomplete_bounds + (lb is None) + (ub is None)
344
+ if is_incomplete_bounds:
345
+ self.opt.method_checker.check_incomplete_bounds()
346
+
317
347
  # 共通引数
318
348
  self.opt.n_trials = n_trials
319
349
  self.opt.timeout = timeout
@@ -574,6 +604,12 @@ def _start_monitor_server(
574
604
  host=None,
575
605
  port=None,
576
606
  ):
577
- monitor = ProcessMonitorApp(history, status, worker_addresses, worker_status_list)
578
- monitor.start_server(host, port)
607
+ process_monitor_main(
608
+ history,
609
+ status,
610
+ worker_addresses,
611
+ worker_status_list,
612
+ host,
613
+ port,
614
+ )
579
615
  return 'Exit monitor server process gracefully'
@@ -43,7 +43,12 @@ class FEMInterface(ABC):
43
43
  pass
44
44
 
45
45
  def update_parameter(self, parameters: pd.DataFrame, with_warning=False) -> Optional[List[str]]:
46
- """Updates only FEM variables (if implemented in concrete class)."""
46
+ """Updates only FEM variables (if implemented in concrete class).
47
+
48
+ If this method is implemented,
49
+ it is able to get parameter via FEMInterface.
50
+
51
+ """
47
52
  pass
48
53
 
49
54
  def _setup_before_parallel(self, client) -> None:
@@ -3,15 +3,19 @@ from typing import Optional, List
3
3
  import os
4
4
  import sys
5
5
  from time import sleep, time
6
- import signal
7
6
 
8
7
  import pandas as pd
9
8
  import psutil
10
9
  from dask.distributed import get_worker
11
10
 
12
- from pywintypes import com_error
11
+ # noinspection PyUnresolvedReferences
12
+ from pywintypes import com_error, error
13
+ # noinspection PyUnresolvedReferences
13
14
  from pythoncom import CoInitialize, CoUninitialize
15
+ # noinspection PyUnresolvedReferences
14
16
  from win32com.client import constants
17
+ import win32con
18
+ import win32gui
15
19
  from femtetutils import util
16
20
 
17
21
  from pyfemtet.core import (
@@ -31,6 +35,10 @@ from pyfemtet.dispatch_extensions import (
31
35
  from pyfemtet.opt.interface import FEMInterface, logger
32
36
 
33
37
 
38
+ def post_activate_message(hwnd):
39
+ win32gui.PostMessage(hwnd, win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
40
+
41
+
34
42
  class FemtetInterface(FEMInterface):
35
43
  """Concrete class for the interface with Femtet.
36
44
 
@@ -113,7 +121,7 @@ class FemtetInterface(FEMInterface):
113
121
  if self.original_femprj_path is None:
114
122
  # dask worker でなければ original のはず
115
123
  try:
116
- worker = get_worker()
124
+ _ = get_worker()
117
125
  except ValueError:
118
126
  self.original_femprj_path = self.femprj_path
119
127
 
@@ -252,7 +260,7 @@ class FemtetInterface(FEMInterface):
252
260
 
253
261
  """
254
262
 
255
- # FIXME: Gaudi へのアクセスなど、self.Femtet.Gaudi.somefunc() のような場合、この関数を呼び出す前に Gaudi へのアクセスの時点で com_error が起こる
263
+ # FIXME: Gaudi へのアクセスなど、self.Femtet.Gaudi.SomeFunc() のような場合、この関数を呼び出す前に Gaudi へのアクセスの時点で com_error が起こる
256
264
  # FIXME: => 文字列で渡して eval() すればよい。
257
265
 
258
266
  if args is None:
@@ -282,9 +290,16 @@ class FemtetInterface(FEMInterface):
282
290
 
283
291
  # API を実行
284
292
  try:
285
- returns = fun(*args, **kwargs)
286
- except com_error:
293
+ # 解析結果を開いた状態で Gaudi.Activate して ReExecute する場合、ReExecute の前後にアクティブ化イベントが必要
294
+ if fun.__name__ == 'ReExecute':
295
+ post_activate_message(self.Femtet.hWnd) # can raise pywintypes.error
296
+ returns = fun(*args, **kwargs)
297
+ post_activate_message(self.Femtet.hWnd)
298
+ else:
299
+ returns = fun(*args, **kwargs)
300
+ except (com_error, error):
287
301
  # パターン 2 エラーが生じたことは確定なのでエラーが起こるよう returns を作る
302
+ # com_error ではなく error の場合はおそらく Femtet が落ちている
288
303
  if ret_for_check_idx is None:
289
304
  returns = return_value_if_failed
290
305
  else:
@@ -665,6 +680,13 @@ class FemtetInterface(FEMInterface):
665
680
  )
666
681
  return out
667
682
 
683
+ @staticmethod
684
+ def create_pdt_path(femprj_path, model_name, trial):
685
+ result_dir = femprj_path.replace('.femprj', '.Results')
686
+ pdt_path = os.path.join(result_dir, model_name + f'_trial{trial}.pdt')
687
+ return pdt_path
688
+
689
+ # noinspection PyMethodOverriding
668
690
  @staticmethod
669
691
  def postprocess_func(
670
692
  trial: int,
@@ -676,7 +698,7 @@ class FemtetInterface(FEMInterface):
676
698
  ):
677
699
  result_dir = original_femprj_path.replace('.femprj', '.Results')
678
700
  if pdt_file_content is not None:
679
- pdt_path = os.path.join(result_dir, model_name + f'_trial{trial}.pdt')
701
+ pdt_path = FemtetInterface.create_pdt_path(original_femprj_path, model_name, trial)
680
702
  with open(pdt_path, 'wb') as f:
681
703
  f.write(pdt_file_content)
682
704
 
@@ -710,13 +732,12 @@ class FemtetInterface(FEMInterface):
710
732
  jpg_path = os.path.join(result_dir, self.model_name + '.jpg')
711
733
 
712
734
  # モデル表示画面の設定
713
- self.Femtet.SetWindowSize(200, 200)
735
+ self.Femtet.SetWindowSize(600, 600)
714
736
  self.Femtet.Fit()
715
- self.Femtet.ViewNumeric.SetCoord(1, 1, 1)
716
737
 
717
738
  # ---モデルの画面を保存---
718
739
  self.Femtet.Redraw() # 再描画
719
- succeed = self.Femtet.SavePicture(jpg_path, 200, 200, 80)
740
+ succeed = self.Femtet.SavePicture(jpg_path, 600, 600, 80)
720
741
 
721
742
  self.Femtet.RedrawMode = True # 逐一の描画をオン
722
743
 
@@ -745,6 +766,7 @@ class _UnPicklableNoFEM(FemtetInterface):
745
766
  Femtet = None
746
767
  quit_when_destruct = False
747
768
 
769
+ # noinspection PyMissingConstructor
748
770
  def __init__(self):
749
771
  CoInitialize()
750
772
  self.unpicklable_member = Dispatch('FemtetMacro.Femtet')
@@ -1,36 +1,15 @@
1
+ from femtetutils import util
1
2
  from pyfemtet.dispatch_extensions import _get_pid
2
3
 
3
- import winreg
4
4
  import ctypes
5
5
 
6
6
 
7
- def _get_dll(Femtet):
8
- femtet_major_version = _get_femtet_major_version(Femtet)
9
- dll_path = _get_parametric_dll_path(femtet_major_version)
7
+ def _get_dll():
8
+ femtet_exe_path = util.get_femtet_exe_path()
9
+ dll_path = femtet_exe_path.replace('Femtet.exe', 'ParametricIF.dll')
10
10
  return ctypes.cdll.LoadLibrary(dll_path)
11
11
 
12
12
 
13
- def _get_femtet_major_version(Femtet):
14
- from pyfemtet.core import _version
15
- version_int8 = _version(Femtet=Femtet)
16
- return str(version_int8)[0:4]
17
-
18
-
19
- def _get_parametric_dll_path(femtet_major_version) -> str:
20
- key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\FemtetInfo\InstallVersion\x64')
21
- _, nValues, _ = winreg.QueryInfoKey(key)
22
- for i in range(nValues):
23
- name, data, _ = winreg.EnumValue(key, i)
24
- if name == str(femtet_major_version):
25
- winreg.CloseKey(key)
26
- import os
27
- dll_path = os.path.join(data, 'Program', 'ParametricIF.dll')
28
- return dll_path
29
- # ここまで来ていたら失敗
30
- winreg.CloseKey(key)
31
- raise RuntimeError('パラメトリック解析機能へのアクセスに失敗しました')
32
-
33
-
34
13
  def _get_dll_with_set_femtet(Femtet):
35
14
  dll = _get_dll(Femtet)
36
15
  pid = _get_pid(Femtet.hWnd)
@@ -40,6 +19,7 @@ def _get_dll_with_set_femtet(Femtet):
40
19
 
41
20
 
42
21
  def _get_prm_result_names(Femtet):
22
+ """Used by pyfemtet-opt-gui"""
43
23
  out = []
44
24
 
45
25
  # load dll and set target femtet
@@ -1,7 +1,9 @@
1
- from pyfemtet.opt.opt._base import AbstractOptimizer, logger
1
+ from pyfemtet.opt.opt._base import AbstractOptimizer, logger, OptimizationMethodChecker
2
2
  from pyfemtet.opt.opt._optuna import OptunaOptimizer
3
+ from pyfemtet.opt.opt._scipy import ScipyOptimizer
3
4
 
4
5
  __all__ = [
6
+ 'ScipyOptimizer',
5
7
  'OptunaOptimizer',
6
8
  'AbstractOptimizer',
7
9
  'logger',
pyfemtet/opt/opt/_base.py CHANGED
@@ -20,6 +20,77 @@ logger = get_logger('opt')
20
20
  logger.setLevel(logging.INFO)
21
21
 
22
22
 
23
+ class OptimizationMethodChecker:
24
+ """Check implementation of PyFemtet functions."""
25
+
26
+ def __init__(self, opt):
27
+ self.opt = opt
28
+
29
+ def check_parallel(self, raise_error=True):
30
+ function = 'parallel computing'
31
+ message = f'{type(self.opt)} is not implement {function}'
32
+ if raise_error:
33
+ raise NotImplementedError(message)
34
+ else:
35
+ logger.warning(message)
36
+
37
+ def check_timeout(self, raise_error=True):
38
+ function = 'timeout'
39
+ message = f'{type(self.opt)} is not implement {function}'
40
+ if raise_error:
41
+ raise NotImplementedError(message)
42
+ else:
43
+ logger.warning(message)
44
+
45
+ def check_multi_objective(self, raise_error=True):
46
+ function = 'multi-objective'
47
+ message = f'{type(self.opt)} is not implement {function}'
48
+ if raise_error:
49
+ raise NotImplementedError(message)
50
+ else:
51
+ logger.warning(message)
52
+
53
+ def check_strict_constraint(self, raise_error=True):
54
+ function = 'strict_constraint'
55
+ message = f'{type(self.opt)} is not implement {function}'
56
+ if raise_error:
57
+ raise NotImplementedError(message)
58
+ else:
59
+ logger.warning(message)
60
+
61
+ def check_constraint(self, raise_error=True):
62
+ function = 'strict_constraint'
63
+ message = f'{type(self.opt)} is not implement {function}'
64
+ if raise_error:
65
+ raise NotImplementedError(message)
66
+ else:
67
+ logger.warning(message)
68
+
69
+ def check_skip(self, raise_error=True):
70
+ function = 'skip'
71
+ message = f'{type(self.opt)} is not implement {function}'
72
+ if raise_error:
73
+ raise NotImplementedError(message)
74
+ else:
75
+ logger.warning(message)
76
+
77
+ def check_seed(self, raise_error=True):
78
+ function = 'random seed setting'
79
+ message = f'{type(self.opt)} is not implement {function}'
80
+ if raise_error:
81
+ raise NotImplementedError(message)
82
+ else:
83
+ logger.warning(message)
84
+
85
+ def check_incomplete_bounds(self, raise_error=True):
86
+ function = 'optimize with no or incomplete bounds'
87
+ message = f'{type(self.opt)} is not implement "{function}"'
88
+ if raise_error:
89
+ raise NotImplementedError(message)
90
+ else:
91
+ logger.warning(message)
92
+
93
+
23
94
  class AbstractOptimizer(ABC):
24
95
  """Abstract base class for an interface of optimization library.
25
96
 
@@ -45,9 +116,9 @@ class AbstractOptimizer(ABC):
45
116
  self.fem = None
46
117
  self.fem_class = None
47
118
  self.fem_kwargs = dict()
48
- self.parameters = pd.DataFrame()
49
- self.objectives = dict()
50
- self.constraints = dict()
119
+ self.parameters: pd.DataFrame = pd.DataFrame()
120
+ self.objectives: dict = dict()
121
+ self.constraints: dict = dict()
51
122
  self.entire_status = None # actor
52
123
  self.history = None # actor
53
124
  self.worker_status = None # actor
@@ -58,6 +129,7 @@ class AbstractOptimizer(ABC):
58
129
  self.is_cluster = False
59
130
  self.subprocess_idx = None
60
131
  self._is_error_exit = False
132
+ self.method_checker: OptimizationMethodChecker = OptimizationMethodChecker(self)
61
133
 
62
134
  def f(self, x):
63
135
  """Get x, update fem analysis, return objectives (and constraints)."""
@@ -65,10 +137,15 @@ class AbstractOptimizer(ABC):
65
137
 
66
138
  # x の更新
67
139
  self.parameters['value'] = x
140
+ logger.info(f'Start calculation with input: {x}')
68
141
 
69
142
  # FEM の更新
70
143
  logger.debug('fem.update() start')
71
- self.fem.update(self.parameters)
144
+ try:
145
+ self.fem.update(self.parameters)
146
+ except Exception as e:
147
+ logger.warning('An exception has occurred during FEM update.')
148
+ raise e
72
149
 
73
150
  # y, _y, c の更新
74
151
  logger.debug('calculate y start')
@@ -93,6 +170,9 @@ class AbstractOptimizer(ABC):
93
170
  )
94
171
 
95
172
  logger.debug('history.record end')
173
+
174
+ logger.info(f'End calculation with output: {_y}')
175
+
96
176
  return np.array(y), np.array(_y), np.array(c)
97
177
 
98
178
  def _reconstruct_fem(self, skip_reconstruct=False):
@@ -191,10 +271,10 @@ class AbstractOptimizer(ABC):
191
271
  try:
192
272
  self.run()
193
273
  except Exception as e:
194
- logger.error("================================")
195
- logger.error("An unexpected error has occured!")
196
- logger.error("================================")
197
- logger.error(e)
274
+ logger.error("=================================")
275
+ logger.error("An unexpected error has occurred!")
276
+ logger.error("=================================")
277
+ logger.error(f'{type(e)}: {e}')
198
278
  self._is_error_exit = True
199
279
  self.worker_status.set(OptimizationStatus.CRASHED)
200
280
  finally:
@@ -12,15 +12,29 @@ from optuna.study import MaxTrialsCallback
12
12
 
13
13
  # pyfemtet relative
14
14
  from pyfemtet.opt._femopt_core import OptimizationStatus, generate_lhs
15
- from pyfemtet.opt.opt import AbstractOptimizer, logger
15
+ from pyfemtet.opt.opt import AbstractOptimizer, logger, OptimizationMethodChecker
16
16
  from pyfemtet.core import MeshError, ModelError, SolveError
17
17
 
18
18
  # filter warnings
19
19
  import warnings
20
20
  from optuna.exceptions import ExperimentalWarning
21
+
22
+
23
+ optuna.logging.set_verbosity(optuna.logging.ERROR)
24
+
21
25
  warnings.filterwarnings('ignore', category=ExperimentalWarning)
22
26
 
23
27
 
28
+ class OptunaMethodChecker(OptimizationMethodChecker):
29
+ def check_multi_objective(self, raise_error=True): return True
30
+ def check_timeout(self, raise_error=True): return True
31
+ def check_parallel(self, raise_error=True): return True
32
+ def check_constraint(self, raise_error=True): return True
33
+ def check_strict_constraint(self, raise_error=True): return True
34
+ def check_skip(self, raise_error=True): return True
35
+ def check_seed(self, raise_error=True): return True
36
+
37
+
24
38
  class OptunaOptimizer(AbstractOptimizer):
25
39
 
26
40
  def __init__(
@@ -38,6 +52,7 @@ class OptunaOptimizer(AbstractOptimizer):
38
52
  self.sampler_kwargs = dict() if sampler_kwargs is None else sampler_kwargs
39
53
  self.additional_initial_parameter = []
40
54
  self.additional_initial_methods = add_init_method if hasattr(add_init_method, '__iter__') else [add_init_method]
55
+ self.method_checker = OptunaMethodChecker(self)
41
56
 
42
57
  def _objective(self, trial):
43
58
 
@@ -50,7 +65,7 @@ class OptunaOptimizer(AbstractOptimizer):
50
65
  # candidate x
51
66
  x = []
52
67
  for i, row in self.parameters.iterrows():
53
- v = trial.suggest_float(row['name'], row['lb'], row['ub'])
68
+ v = trial.suggest_float(row['name'], row['lb'], row['ub'], step=row['step'])
54
69
  x.append(v)
55
70
  x = np.array(x).astype(float)
56
71
 
@@ -0,0 +1,144 @@
1
+ # typing
2
+ import logging
3
+ from typing import Iterable
4
+
5
+ # built-in
6
+ import os
7
+
8
+ # 3rd-party
9
+ import numpy as np
10
+ import pandas as pd
11
+ import scipy.optimize
12
+ from scipy.optimize import minimize, OptimizeResult
13
+
14
+ # pyfemtet relative
15
+ from pyfemtet.opt._femopt_core import OptimizationStatus, generate_lhs
16
+ from pyfemtet.opt.opt import AbstractOptimizer, logger, OptimizationMethodChecker
17
+ from pyfemtet.core import MeshError, ModelError, SolveError
18
+
19
+
20
+ class StopIteration2(Exception):
21
+ pass
22
+
23
+
24
+ class StopIterationCallback:
25
+ def __init__(self, opt):
26
+ self.opt: ScipyOptimizer = opt
27
+ self.res: OptimizeResult = None
28
+
29
+ def stop_iteration(self):
30
+ # stop iteration gimmick
31
+ # https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
32
+ if self.opt.minimize_kwargs['method'] == "trust-constr":
33
+ raise StopIteration2 # supports nothing
34
+ elif (
35
+ self.opt.minimize_kwargs['method'] == 'TNC'
36
+ or self.opt.minimize_kwargs['method'] == 'SLSQP'
37
+ or self.opt.minimize_kwargs['method'] == 'COBYLA'
38
+ ):
39
+ raise StopIteration2 # supports xk
40
+ else:
41
+ raise StopIteration # supports xk , intermediate_result and StopIteration
42
+
43
+
44
+ def __call__(self, xk=None, intermediate_result=None):
45
+ self.res = intermediate_result
46
+ if self.opt.entire_status.get() == OptimizationStatus.INTERRUPTING:
47
+ self.opt.worker_status.set(OptimizationStatus.INTERRUPTING)
48
+ self.stop_iteration()
49
+
50
+
51
+ class ScipyMethodChecker(OptimizationMethodChecker):
52
+ def check_incomplete_bounds(self, raise_error=True): return True
53
+
54
+
55
+ class ScipyOptimizer(AbstractOptimizer):
56
+
57
+ def __init__(
58
+ self,
59
+ **minimize_kwargs,
60
+ ):
61
+ """
62
+ Args:
63
+ **minimize_kwargs: Kwargs of `scipy.optimize.minimize`.
64
+ """
65
+ super().__init__()
66
+
67
+ # define members
68
+ self.minimize_kwargs: dict = dict(
69
+ method='L-BFGS-B',
70
+ )
71
+ self.minimize_kwargs.update(minimize_kwargs)
72
+ self.res: OptimizeResult = None
73
+ self.method_checker: OptimizationMethodChecker = ScipyMethodChecker(self)
74
+ self.stop_iteration_callback = StopIterationCallback(self)
75
+
76
+ def _objective(self, x: np.ndarray): # x: candidate parameter
77
+ # update parameter
78
+ self.parameters['value'] = x
79
+ self.fem.update_parameter(self.parameters)
80
+
81
+ # strict constraints
82
+ ...
83
+
84
+ # fem
85
+ try:
86
+ _, obj_values, cns_values = self.f(x)
87
+ except (ModelError, MeshError, SolveError) as e:
88
+ logger.info(e)
89
+ logger.info('以下の変数で FEM 解析に失敗しました。')
90
+ print(self.get_parameter('dict'))
91
+
92
+ # 現状、エラーが起きたらスキップできない
93
+ raise StopIteration2
94
+
95
+ # constraints
96
+ ...
97
+
98
+ # # check interruption command
99
+ # if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
100
+ # self.worker_status.set(OptimizationStatus.INTERRUPTING)
101
+ # raise StopOptimize
102
+
103
+ # objectives to objective
104
+
105
+ return obj_values[0]
106
+
107
+ def _setup_before_parallel(self):
108
+ pass
109
+
110
+ def run(self):
111
+
112
+ # create init
113
+ x0 = self.parameters['value'].values
114
+
115
+ # create bounds
116
+ if 'bounds' not in self.minimize_kwargs.keys():
117
+ bounds = []
118
+ for i, row in self.parameters.iterrows():
119
+ lb, ub = row['lb'], row['ub']
120
+ if lb is None: lb = -np.inf
121
+ if ub is None: ub = np.inf
122
+ bounds.append([lb, ub])
123
+ self.minimize_kwargs.update(
124
+ {'bounds': bounds}
125
+ )
126
+
127
+ # run optimize
128
+ try:
129
+ res = minimize(
130
+ fun=self._objective,
131
+ x0=x0,
132
+ **self.minimize_kwargs,
133
+ callback=self.stop_iteration_callback,
134
+ )
135
+ except StopIteration2:
136
+ res = None
137
+ logger.warn('Optimization has been interrupted. '
138
+ 'Note that you cannot acquire the OptimizationResult '
139
+ 'in case of `trust-constr`, `TNC`, `SLSQP` or `COBYLA`.')
140
+
141
+ if res is None:
142
+ self.res = self.stop_iteration_callback.res
143
+ else:
144
+ self.res = res