pyfemtet 0.4.24__py3-none-any.whl → 0.5.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 (113) 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 +114 -62
  4. pyfemtet/message/locales/messages.pot +114 -62
  5. pyfemtet/message/messages.py +6 -2
  6. pyfemtet/opt/__init__.py +4 -2
  7. pyfemtet/opt/_femopt.py +27 -46
  8. pyfemtet/opt/_femopt_core.py +50 -33
  9. pyfemtet/opt/_test_utils/__init__.py +0 -0
  10. pyfemtet/opt/_test_utils/control_femtet.py +45 -0
  11. pyfemtet/opt/_test_utils/hyper_sphere.py +24 -0
  12. pyfemtet/opt/_test_utils/record_history.py +72 -0
  13. pyfemtet/opt/interface/_femtet.py +39 -1
  14. pyfemtet/opt/optimizer/__init__.py +12 -0
  15. pyfemtet/opt/{opt → optimizer}/_base.py +30 -9
  16. pyfemtet/opt/{opt → optimizer}/_optuna.py +15 -34
  17. pyfemtet/opt/optimizer/_optuna_botorchsampler_parameter_constraint_helper.py +331 -0
  18. pyfemtet/opt/{opt → optimizer}/_scipy.py +10 -6
  19. pyfemtet/opt/{opt → optimizer}/_scipy_scalar.py +24 -12
  20. pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric_test_result.reccsv +1 -1
  21. pyfemtet/opt/{femprj_sample → samples/femprj_sample}/her_ex40_parametric.py +10 -8
  22. pyfemtet/opt/samples/femprj_sample/her_ex40_parametric_test_result.reccsv +18 -0
  23. pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/her_ex40_parametric_jp.py +10 -8
  24. pyfemtet/opt/visualization/complex_components/main_figure_creator.py +69 -0
  25. pyfemtet/opt/visualization/complex_components/main_graph.py +299 -14
  26. pyfemtet/opt/visualization/process_monitor/pages.py +1 -1
  27. pyfemtet/opt/visualization/result_viewer/application.py +1 -1
  28. pyfemtet/opt/visualization/result_viewer/pages.py +9 -9
  29. {pyfemtet-0.4.24.dist-info → pyfemtet-0.5.0.dist-info}/METADATA +1 -1
  30. pyfemtet-0.5.0.dist-info/RECORD +112 -0
  31. pyfemtet/_test_util.py +0 -135
  32. pyfemtet/opt/femprj_sample/her_ex40_parametric_test_result.reccsv +0 -18
  33. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14.pdt +0 -0
  34. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
  35. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
  36. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
  37. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
  38. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
  39. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
  40. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
  41. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
  42. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
  43. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
  44. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
  45. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
  46. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
  47. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
  48. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
  49. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
  50. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
  51. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
  52. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
  53. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
  54. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
  55. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
  56. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
  57. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
  58. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
  59. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
  60. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
  61. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
  62. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
  63. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
  64. pyfemtet/opt/opt/__init__.py +0 -12
  65. pyfemtet/opt/opt/_optuna_botorch_helper.py +0 -209
  66. pyfemtet-0.4.24.dist-info/RECORD +0 -140
  67. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/.gitignore +0 -0
  68. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF - True.femprj +0 -0
  69. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF.femprj +0 -0
  70. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF.py +0 -0
  71. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF_test_result.reccsv +0 -0
  72. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.femprj +0 -0
  73. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.prt +0 -0
  74. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.py +0 -0
  75. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX_test_result.reccsv +0 -0
  76. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.SLDPRT +0 -0
  77. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.femprj +0 -0
  78. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.py +0 -0
  79. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW_test_result.reccsv +0 -0
  80. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric.femprj +0 -0
  81. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric.py +0 -0
  82. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric.femprj +0 -0
  83. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric.py +0 -0
  84. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric_test_result.reccsv +0 -0
  85. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/her_ex40_parametric.femprj +0 -0
  86. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric.femprj +0 -0
  87. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric.py +0 -0
  88. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric_parallel.py +0 -0
  89. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric_test_result.reccsv +0 -0
  90. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric.femprj +0 -0
  91. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric.py +0 -0
  92. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric_parallel.py +0 -0
  93. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric_test_result.reccsv +0 -0
  94. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/ParametricIF_jp.femprj +0 -0
  95. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/ParametricIF_jp.py +0 -0
  96. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_NX_jp.femprj +0 -0
  97. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_NX_jp.py +0 -0
  98. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_SW_jp.femprj +0 -0
  99. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_SW_jp.py +0 -0
  100. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gal_ex58_parametric_jp.femprj +0 -0
  101. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gal_ex58_parametric_jp.py +0 -0
  102. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gau_ex08_parametric_jp.femprj +0 -0
  103. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gau_ex08_parametric_jp.py +0 -0
  104. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/her_ex40_parametric_jp.femprj +0 -0
  105. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_jp.femprj +0 -0
  106. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_jp.py +0 -0
  107. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_parallel_jp.py +0 -0
  108. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_jp.femprj +0 -0
  109. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_jp.py +0 -0
  110. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_parallel_jp.py +0 -0
  111. {pyfemtet-0.4.24.dist-info → pyfemtet-0.5.0.dist-info}/LICENSE +0 -0
  112. {pyfemtet-0.4.24.dist-info → pyfemtet-0.5.0.dist-info}/WHEEL +0 -0
  113. {pyfemtet-0.4.24.dist-info → pyfemtet-0.5.0.dist-info}/entry_points.txt +0 -0
@@ -132,34 +132,68 @@ def _check_bound(lb, ub, name=None):
132
132
  raise ValueError(message)
133
133
 
134
134
 
135
- def _is_access_gogh(fun):
135
+ def _get_scope_indent(source: str) -> int:
136
+ SPACES = [' ', '\t']
137
+ indent = 0
138
+ while True:
139
+ if source[indent] not in SPACES:
140
+ break
141
+ else:
142
+ indent += 1
143
+ return indent
144
+
145
+
146
+ def _remove_indent(source: str, indent: int) -> str: # returns source
147
+ lines = source.splitlines()
148
+ edited_lines = [l[indent:] for l in lines]
149
+ edited_source = '\n'.join(edited_lines)
150
+ return edited_source
151
+
152
+
153
+ def _check_accsess_femtet_objects(fun, target: str = None):
154
+ if target is None:
155
+ target = ''
136
156
 
137
157
  # 関数fのソースコードを取得
138
158
  source = inspect.getsource(fun)
139
159
 
140
160
  # ソースコードを抽象構文木(AST)に変換
141
- tree = ast.parse(source)
161
+ try:
162
+ # instanceメソッドなどの場合を想定してインデントを削除
163
+ source = _remove_indent(source, _get_scope_indent(source))
164
+ tree = ast.parse(source)
165
+ except:
166
+ return False # パースに失敗するからと言ってエラーにするまででもない
142
167
 
143
168
  # 関数定義を見つける
144
169
  for node in ast.walk(tree):
145
170
  if isinstance(node, ast.FunctionDef):
146
- # 関数の第一引数の名前を取得
147
- first_arg_name = node.args.args[0].arg
171
+ # Femtet という名前の引数を取得
172
+ first_arg_name = 'Femtet'
148
173
 
149
174
  # 関数内の全ての属性アクセスをチェック
150
175
  for sub_node in ast.walk(node):
151
176
  if isinstance(sub_node, ast.Attribute):
152
- # 第一引数に対して 'Gogh' へのアクセスがあるかチェック
153
- if (
154
- isinstance(sub_node.value, ast.Name)
155
- and sub_node.value.id == first_arg_name
156
- and sub_node.attr == 'Gogh'
157
- ):
177
+ # Femtet に対してアクセスがあるかチェック
178
+ conditions = [isinstance(sub_node.value, ast.Name)]
179
+ conditions.append(sub_node.value.id == first_arg_name) # Femtet にアクセスしている
180
+ if target == 'Gogh':
181
+ conditions.append(sub_node.attr == 'Gogh') # Femtet.Gogh にアクセスしている
182
+ if all(conditions):
158
183
  return True
184
+
159
185
  # ここまできてもなければアクセスしてない
160
186
  return False
161
187
 
162
188
 
189
+ def _is_access_gogh(fun):
190
+ return _check_accsess_femtet_objects(fun, target='Gogh')
191
+
192
+
193
+ def _is_access_femtet(fun):
194
+ return _check_accsess_femtet_objects(fun)
195
+
196
+
163
197
  def is_feasible(value, lb, ub):
164
198
  """
165
199
  Check if a value is within the specified lower bound and upper bound.
@@ -173,11 +207,11 @@ def is_feasible(value, lb, ub):
173
207
  bool: True if the value satisfies the bounds; False otherwise.
174
208
  """
175
209
  if lb is None and ub is not None:
176
- return value < ub
210
+ return value <= ub
177
211
  elif lb is not None and ub is None:
178
- return lb < value
212
+ return lb <= value
179
213
  elif lb is not None and ub is not None:
180
- return lb < value < ub
214
+ return lb <= value <= ub
181
215
  else:
182
216
  return True
183
217
 
@@ -313,7 +347,7 @@ class Constraint(Function):
313
347
 
314
348
  default_name = 'cns'
315
349
 
316
- def __init__(self, fun, name, lb, ub, strict, args, kwargs):
350
+ def __init__(self, fun, name, lb, ub, strict, args, kwargs, using_fem):
317
351
  """Initializes a Constraint instance.
318
352
 
319
353
  Args:
@@ -324,6 +358,7 @@ class Constraint(Function):
324
358
  strict (bool): Whether to enforce strict inequality for the bounds.
325
359
  args: Additional arguments for the constraint function.
326
360
  kwargs: Additional keyword arguments for the constraint function.
361
+ using_fem: Update fem or not before run calc().
327
362
 
328
363
  Raises:
329
364
  ValueError: If the lower bound is greater than or equal to the upper bound.
@@ -334,28 +369,10 @@ class Constraint(Function):
334
369
  self.lb = lb
335
370
  self.ub = ub
336
371
  self.strict = strict
372
+ self.using_fem = using_fem
337
373
  super().__init__(fun, name, args, kwargs)
338
374
 
339
375
 
340
- # from pyfemtet.opt.opt import AbstractOptimizer
341
- class ParameterConstraint(Constraint):
342
- """Variable のみを引数とする constraint 関数"""
343
-
344
- def __init__(self, fun, name, lb, ub, opt: 'AbstractOptimizer'):
345
- super().__init__(fun, name, lb, ub, strict=True, args=None, kwargs=None)
346
- self.opt = opt
347
- self.prm_args = [arg.name for arg in inspect.signature(self.fun).parameters.values()]
348
-
349
- def calc(self, _):
350
- # variables 全体から fun に対する引数を作成する
351
- kwargs: dict = self.opt.get_parameter('dict')
352
- keys = list(kwargs.keys())
353
- for k in keys:
354
- if k not in self.prm_args:
355
- kwargs.pop(k)
356
- return float(self.fun(**kwargs))
357
-
358
-
359
376
  class _HistoryDfCore:
360
377
  """Class for managing a DataFrame object in a distributed manner."""
361
378
 
File without changes
@@ -0,0 +1,45 @@
1
+ from time import sleep
2
+ from subprocess import run
3
+ from multiprocessing import Process
4
+ from tqdm import tqdm
5
+ from win32com.client import Dispatch
6
+ from femtetutils import util
7
+
8
+ import logging
9
+
10
+
11
+ logger = logging.getLogger("test")
12
+ logger.setLevel(logging.DEBUG)
13
+
14
+
15
+ def _open_femprj(femprj_path):
16
+ Femtet = Dispatch("FemtetMacro.Femtet")
17
+ for _ in tqdm(range(5), "Wait to complete Dispatch before opening femprj..."):
18
+ sleep(1)
19
+ Femtet.LoadProject(femprj_path, True)
20
+
21
+
22
+ def launch_femtet(femprj_path):
23
+ # launch Femtet externally
24
+ util.execute_femtet()
25
+ pid = util.get_last_executed_femtet_process_id()
26
+ for _ in tqdm(range(8), "Wait to complete Femtet launching."):
27
+ sleep(1)
28
+
29
+ # open femprj in a different process
30
+ # to release Femtet for sample program
31
+ if femprj_path:
32
+ p = Process(
33
+ target=_open_femprj,
34
+ args=(femprj_path,),
35
+ )
36
+ p.start()
37
+ p.join()
38
+
39
+
40
+ def taskkill_femtet():
41
+ for _ in tqdm(range(3), "Wait before taskkill Femtet"):
42
+ sleep(1)
43
+ run(["taskkill", "/f", "/im", "Femtet.exe"])
44
+ for _ in tqdm(range(3), "Wait after taskkill Femtet"):
45
+ sleep(1)
@@ -0,0 +1,24 @@
1
+ import numpy as np
2
+ from numpy import sin, cos
3
+ from pyfemtet.opt import AbstractOptimizer
4
+
5
+
6
+ class HyperSphere(object):
7
+
8
+ def __init__(self, N):
9
+ self.N = N
10
+ self._x = np.zeros(self.N, dtype=float)
11
+
12
+ def calc(self, r, *angles):
13
+ _x = []
14
+ for i in range(self.N - 1):
15
+ __x = r * np.prod([sin(angles[j]) for j in range(i)])
16
+ __x = __x * cos(angles[i])
17
+ _x.append(__x)
18
+ _x.append(r * np.prod([sin(angles[j]) for j in range(self.N - 1)]))
19
+ self._x = np.array(_x)
20
+
21
+ def x(self, opt: AbstractOptimizer, index: int):
22
+ r, *angles = opt.get_parameter("values")
23
+ self.calc(r, *angles)
24
+ return self._x[index]
@@ -0,0 +1,72 @@
1
+ import os
2
+ import csv
3
+ from shutil import copy
4
+ from glob import glob
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ from pyfemtet.opt import FEMOpt
10
+ from pyfemtet.message import encoding as ENCODING
11
+
12
+
13
+ def find_latest_csv(dir_path=None):
14
+ if dir_path is None:
15
+ dir_path = ""
16
+ target = os.path.join(dir_path, "*.csv")
17
+ files = [(f, os.path.getmtime(f)) for f in glob(target)]
18
+ out = sorted(files, key=lambda files: files[1])[-1]
19
+ return os.path.abspath(out[0])
20
+
21
+
22
+ def py_to_reccsv(py_path, suffix=""):
23
+ dst_csv_path = py_path + suffix
24
+ dst_csv_path = dst_csv_path.replace(f".py{suffix}", f"{suffix}.reccsv")
25
+ return dst_csv_path
26
+
27
+
28
+ def record_result(src: FEMOpt or str, py_path, suffix=""):
29
+ """Record the result csv for `is_equal_result`."""
30
+
31
+ if isinstance(src, FEMOpt): # get df directory
32
+ src_csv_path = src.history_path
33
+ else:
34
+ src_csv_path = os.path.abspath(src)
35
+
36
+ dst_csv_path = py_to_reccsv(py_path, suffix)
37
+ copy(src_csv_path, dst_csv_path)
38
+
39
+
40
+ def _get_obj_from_csv(csv_path, encoding=ENCODING):
41
+ df = pd.read_csv(csv_path, encoding=encoding, header=2)
42
+ columns = df.columns
43
+ with open(csv_path, mode="r", encoding=encoding, newline="\n") as f:
44
+ reader = csv.reader(f, delimiter=",")
45
+ meta = reader.__next__()
46
+ obj_indices = np.where(np.array(meta) == "obj")[0]
47
+ out = df.iloc[:, obj_indices]
48
+ return out, columns
49
+
50
+
51
+ def is_equal_result(ref_path, dif_path, log_path):
52
+ """Check the equality of two result csv files."""
53
+ ref_df, ref_columns = _get_obj_from_csv(ref_path)
54
+ dif_df, dif_columns = _get_obj_from_csv(dif_path)
55
+
56
+ with open(log_path, "a", newline="\n", encoding=ENCODING) as f:
57
+ f.write("\n\n===== 結果の分析 =====\n\n")
58
+ f.write(f" \tref\tdif\n")
59
+ f.write(f"---------------------\n")
60
+ f.write(f"len(col)\t{len(ref_columns)}\t{len(dif_columns)}\n")
61
+ f.write(f"len(df) \t{len(ref_df)}\t{len(dif_df)}\n")
62
+ try:
63
+ difference = (
64
+ np.abs(ref_df.values - dif_df.values) / np.abs(dif_df.values)
65
+ ).mean()
66
+ f.write(f"diff \t{int(difference*100)}%\n")
67
+ except Exception:
68
+ f.write(f"diff \tcannot calc\n")
69
+
70
+ assert len(ref_columns) == len(dif_columns), "結果 csv の column 数が異なります。"
71
+ assert len(ref_df) == len(dif_df), "結果 csv の row 数が異なります。"
72
+ assert difference <= 0.05, "前回の結果との平均差異が 5% を超えています。"
@@ -1,8 +1,9 @@
1
- from typing import Optional, List
1
+ from typing import Optional, List, Final
2
2
 
3
3
  import os
4
4
  import sys
5
5
  from time import sleep, time
6
+ import winreg
6
7
 
7
8
  import pandas as pd
8
9
  import psutil
@@ -101,6 +102,8 @@ class FemtetInterface(FEMInterface):
101
102
  self.max_api_retry = 3
102
103
  self.strictly_pid_specify = strictly_pid_specify
103
104
  self.parametric_output_indexes_use_as_objective = parametric_output_indexes_use_as_objective
105
+ self._original_autosave_enabled = get_autosave_enabled()
106
+ set_autosave_enabled(False)
104
107
 
105
108
  # dask サブプロセスのときは femprj を更新し connect_method を new にする
106
109
  try:
@@ -142,6 +145,7 @@ class FemtetInterface(FEMInterface):
142
145
  )
143
146
 
144
147
  def __del__(self):
148
+ set_autosave_enabled(self._original_autosave_enabled)
145
149
  if self.quit_when_destruct:
146
150
  self.quit()
147
151
  # CoUninitialize() # Win32 exception occurred releasing IUnknown at 0x0000022427692748
@@ -854,3 +858,37 @@ class _UnPicklableNoFEM(FemtetInterface):
854
858
  pdt_path = os.path.join(here, f'trial{trial}.pdt')
855
859
  return pdt_path
856
860
 
861
+
862
+ # レジストリのパスと値の名前
863
+ REGISTRY_PATH: Final[str] = r"SOFTWARE\Murata Software\Femtet2014\Femtet"
864
+ VALUE_NAME: Final[str] = "AutoSave"
865
+
866
+ def get_autosave_enabled() -> bool:
867
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, REGISTRY_PATH) as key:
868
+ value, regtype = winreg.QueryValueEx(key, VALUE_NAME)
869
+ if regtype == winreg.REG_DWORD:
870
+ return bool(value)
871
+ else:
872
+ raise ValueError("Unexpected registry value type.")
873
+
874
+ def set_autosave_enabled(enable: bool) -> None:
875
+ with winreg.OpenKey(winreg.HKEY_CURRENT_USER, REGISTRY_PATH, 0, winreg.KEY_SET_VALUE) as key:
876
+ winreg.SetValueEx(key, VALUE_NAME, 0, winreg.REG_DWORD, int(enable))
877
+
878
+
879
+ def _test_autosave_setting():
880
+
881
+ # 使用例
882
+ current_setting = get_autosave_enabled()
883
+ print(f"Current AutoSave setting is {'enabled' if current_setting else 'disabled'}.")
884
+
885
+ # 設定を変更する例
886
+ new_setting = not current_setting
887
+ set_autosave_enabled(new_setting)
888
+ print(f"AutoSave setting has been {'enabled' if new_setting else 'disabled'}.")
889
+
890
+ # 再度設定を確認する
891
+ after_setting = get_autosave_enabled()
892
+ print(f"Current AutoSave setting is {'enabled' if after_setting else 'disabled'}.")
893
+
894
+ assert new_setting == after_setting, "レジストリ編集に失敗しました。"
@@ -0,0 +1,12 @@
1
+ from pyfemtet.opt.optimizer._base import AbstractOptimizer, logger, OptimizationMethodChecker
2
+ from pyfemtet.opt.optimizer._optuna import OptunaOptimizer
3
+ from pyfemtet.opt.optimizer._scipy import ScipyOptimizer
4
+ from pyfemtet.opt.optimizer._scipy_scalar import ScipyScalarOptimizer
5
+
6
+ __all__ = [
7
+ 'ScipyScalarOptimizer',
8
+ 'ScipyOptimizer',
9
+ 'OptunaOptimizer',
10
+ 'AbstractOptimizer',
11
+ 'logger',
12
+ ]
@@ -12,7 +12,7 @@ import pandas as pd
12
12
 
13
13
  # pyfemtet relative
14
14
  from pyfemtet.opt.interface import FemtetInterface
15
- from pyfemtet.opt._femopt_core import OptimizationStatus
15
+ from pyfemtet.opt._femopt_core import OptimizationStatus, Objective, Constraint
16
16
  from pyfemtet.message import Msg
17
17
  from pyfemtet.opt.parameter import ExpressionEvaluator
18
18
 
@@ -135,10 +135,9 @@ class AbstractOptimizer(ABC):
135
135
  self.fem = None
136
136
  self.fem_class = None
137
137
  self.fem_kwargs = dict()
138
- self.parameters: pd.DataFrame = pd.DataFrame()
139
138
  self.variables: ExpressionEvaluator = ExpressionEvaluator()
140
- self.objectives: dict = dict()
141
- self.constraints: dict = dict()
139
+ self.objectives: dict[str, Constraint] = dict()
140
+ self.constraints: dict[str, Constraint] = dict()
142
141
  self.entire_status = None # actor
143
142
  self.history = None # actor
144
143
  self.worker_status = None # actor
@@ -155,10 +154,11 @@ class AbstractOptimizer(ABC):
155
154
  """Get x, update fem analysis, return objectives (and constraints)."""
156
155
  # interruption の実装は具象クラスに任せる
157
156
 
158
- # x の更新
159
- prm_names = self.variables.get_parameter_names()
160
- for name, value in zip(prm_names, x):
161
- self.variables.variables[name].value = value
157
+ if isinstance(x, np.float64):
158
+ x = np.array([x])
159
+
160
+ # Optimizer の x の更新
161
+ self.set_parameter_values(x)
162
162
 
163
163
  logger.info('---------------------')
164
164
  logger.info(f'input: {x}')
@@ -237,7 +237,28 @@ class AbstractOptimizer(ABC):
237
237
  ValueError: If an invalid format is provided.
238
238
 
239
239
  """
240
- return self.variables.get_variables(format=format)
240
+ return self.variables.get_variables(format=format, filter_parameter=True)
241
+
242
+ def set_parameter(self, params: dict) -> None:
243
+ """Update parameter.
244
+
245
+ Args:
246
+ params (dict): Key is the name of parameter and the value is the value of it. The partial set is available.
247
+
248
+ """
249
+ for name, value in params.items():
250
+ self.variables.variables[name].value = value
251
+ self.variables.evaluate()
252
+
253
+ def set_parameter_values(self, values: np.ndarray) -> None:
254
+ """Update parameter with values.
255
+
256
+ Args:
257
+ values (np.ndarray): Values of all parameters.
258
+ """
259
+ prm_names = self.variables.get_parameter_names()
260
+ assert len(values) == len(prm_names)
261
+ self.set_parameter({k: v for k, v in zip(prm_names, values)})
241
262
 
242
263
  def _check_interruption(self):
243
264
  """"""
@@ -11,8 +11,8 @@ from optuna.trial import TrialState
11
11
  from optuna.study import MaxTrialsCallback
12
12
 
13
13
  # pyfemtet relative
14
- from pyfemtet.opt._femopt_core import OptimizationStatus, generate_lhs
15
- from pyfemtet.opt.opt import AbstractOptimizer, logger, OptimizationMethodChecker
14
+ from pyfemtet.opt._femopt_core import OptimizationStatus, generate_lhs, Constraint
15
+ from pyfemtet.opt.optimizer import AbstractOptimizer, logger, OptimizationMethodChecker
16
16
  from pyfemtet.core import MeshError, ModelError, SolveError
17
17
  from pyfemtet.message import Msg
18
18
 
@@ -54,7 +54,7 @@ class OptunaOptimizer(AbstractOptimizer):
54
54
  self.additional_initial_parameter = []
55
55
  self.additional_initial_methods = add_init_method if hasattr(add_init_method, '__iter__') else [add_init_method]
56
56
  self.method_checker = OptunaMethodChecker(self)
57
- self.parameter_constraints = []
57
+ self._do_monkey_patch = False
58
58
 
59
59
  def _objective(self, trial):
60
60
 
@@ -114,14 +114,14 @@ class OptunaOptimizer(AbstractOptimizer):
114
114
 
115
115
  # 拘束 attr の更新
116
116
  if len(self.constraints) > 0:
117
- _c = [] # 非正なら OK
117
+ _c = [] # <= 0 is feasible
118
118
  for (name, cns), c_value in zip(self.constraints.items(), c):
119
119
  lb, ub = cns.lb, cns.ub
120
120
  if lb is not None: # fun >= lb <=> lb - fun <= 0
121
121
  _c.append(lb - c_value)
122
122
  if ub is not None: # ub >= fun <=> fun - ub <= 0
123
123
  _c.append(c_value - ub)
124
- trial.set_user_attr('constraint', _c)
124
+ trial.set_user_attr('constraints', _c)
125
125
 
126
126
  # 中断の確認 (解析中に interrupt されている場合対策)
127
127
  if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
@@ -134,8 +134,8 @@ class OptunaOptimizer(AbstractOptimizer):
134
134
 
135
135
  def _constraint(self, trial):
136
136
  # if break trial without weak constraint calculation, return 1 (as infeasible).
137
- if 'constraint' in trial.user_attrs.keys():
138
- return trial.user_attrs['constraint']
137
+ if 'constraints' in trial.user_attrs.keys():
138
+ return trial.user_attrs['constraints']
139
139
  else:
140
140
  _c = []
141
141
  for name, cns in self.constraints.items():
@@ -203,7 +203,7 @@ class OptunaOptimizer(AbstractOptimizer):
203
203
  if 'LHS' in self.additional_initial_methods:
204
204
  names = []
205
205
  bounds = []
206
- for i, row in self.parameters.iterrows():
206
+ for i, row in self.get_parameter('df').iterrows():
207
207
  names.append(row['name'])
208
208
  lb = row['lower_bound']
209
209
  ub = row['upper_bound']
@@ -275,20 +275,16 @@ class OptunaOptimizer(AbstractOptimizer):
275
275
  )
276
276
 
277
277
  # monkey patch
278
- if len(self.parameter_constraints) > 0:
278
+ if self._do_monkey_patch:
279
279
  assert isinstance(sampler, optuna.integration.BoTorchSampler), Msg.ERR_PARAMETER_CONSTRAINT_ONLY_BOTORCH
280
280
 
281
- from pyfemtet.opt.opt._optuna_botorch_helper import OptunaBotorchWithParameterConstraintMonkeyPatch
282
- mp = OptunaBotorchWithParameterConstraintMonkeyPatch(
283
- study,
284
- self,
281
+ from pyfemtet.opt.optimizer._optuna_botorchsampler_parameter_constraint_helper import do_patch
282
+
283
+ do_patch(
284
+ self.study,
285
+ self.constraints,
286
+ self
285
287
  )
286
- for p_cns in self.parameter_constraints:
287
- fun = p_cns['fun']
288
- prm_args = p_cns['prm_args']
289
- kwargs = p_cns['kwargs']
290
- mp.add_nonlinear_constraint(fun, prm_args, kwargs)
291
- mp.do_monkey_patch()
292
288
 
293
289
  # run
294
290
  study.optimize(
@@ -296,18 +292,3 @@ class OptunaOptimizer(AbstractOptimizer):
296
292
  timeout=self.timeout,
297
293
  callbacks=self.optimize_callbacks,
298
294
  )
299
-
300
- def add_parameter_constraints(
301
- self,
302
- fun,
303
- prm_args=None,
304
- kwargs=None
305
- ):
306
- kwargs = kwargs if kwargs is not None else {}
307
- self.parameter_constraints.append(
308
- dict(
309
- fun=fun,
310
- prm_args=prm_args,
311
- kwargs=kwargs,
312
- )
313
- )