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.
- pyfemtet/__init__.py +1 -1
- pyfemtet/message/locales/ja/LC_MESSAGES/messages.mo +0 -0
- pyfemtet/message/locales/ja/LC_MESSAGES/messages.po +114 -62
- pyfemtet/message/locales/messages.pot +114 -62
- pyfemtet/message/messages.py +6 -2
- pyfemtet/opt/__init__.py +4 -2
- pyfemtet/opt/_femopt.py +27 -46
- pyfemtet/opt/_femopt_core.py +50 -33
- pyfemtet/opt/_test_utils/__init__.py +0 -0
- pyfemtet/opt/_test_utils/control_femtet.py +45 -0
- pyfemtet/opt/_test_utils/hyper_sphere.py +24 -0
- pyfemtet/opt/_test_utils/record_history.py +72 -0
- pyfemtet/opt/interface/_femtet.py +39 -1
- pyfemtet/opt/optimizer/__init__.py +12 -0
- pyfemtet/opt/{opt → optimizer}/_base.py +30 -9
- pyfemtet/opt/{opt → optimizer}/_optuna.py +15 -34
- pyfemtet/opt/optimizer/_optuna_botorchsampler_parameter_constraint_helper.py +331 -0
- pyfemtet/opt/{opt → optimizer}/_scipy.py +10 -6
- pyfemtet/opt/{opt → optimizer}/_scipy_scalar.py +24 -12
- pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric_test_result.reccsv +1 -1
- pyfemtet/opt/{femprj_sample → samples/femprj_sample}/her_ex40_parametric.py +10 -8
- pyfemtet/opt/samples/femprj_sample/her_ex40_parametric_test_result.reccsv +18 -0
- pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/her_ex40_parametric_jp.py +10 -8
- pyfemtet/opt/visualization/complex_components/main_figure_creator.py +69 -0
- pyfemtet/opt/visualization/complex_components/main_graph.py +299 -14
- pyfemtet/opt/visualization/process_monitor/pages.py +1 -1
- pyfemtet/opt/visualization/result_viewer/application.py +1 -1
- pyfemtet/opt/visualization/result_viewer/pages.py +9 -9
- {pyfemtet-0.4.24.dist-info → pyfemtet-0.5.0.dist-info}/METADATA +1 -1
- pyfemtet-0.5.0.dist-info/RECORD +112 -0
- pyfemtet/_test_util.py +0 -135
- pyfemtet/opt/femprj_sample/her_ex40_parametric_test_result.reccsv +0 -18
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
- pyfemtet/opt/opt/__init__.py +0 -12
- pyfemtet/opt/opt/_optuna_botorch_helper.py +0 -209
- pyfemtet-0.4.24.dist-info/RECORD +0 -140
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/.gitignore +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF - True.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.prt +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.SLDPRT +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/her_ex40_parametric.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric_parallel.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric_parallel.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/ParametricIF_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/ParametricIF_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_NX_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_NX_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_SW_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_SW_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gal_ex58_parametric_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gal_ex58_parametric_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gau_ex08_parametric_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gau_ex08_parametric_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/her_ex40_parametric_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_parallel_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_parallel_jp.py +0 -0
- {pyfemtet-0.4.24.dist-info → pyfemtet-0.5.0.dist-info}/LICENSE +0 -0
- {pyfemtet-0.4.24.dist-info → pyfemtet-0.5.0.dist-info}/WHEEL +0 -0
- {pyfemtet-0.4.24.dist-info → pyfemtet-0.5.0.dist-info}/entry_points.txt +0 -0
pyfemtet/opt/_femopt_core.py
CHANGED
|
@@ -132,34 +132,68 @@ def _check_bound(lb, ub, name=None):
|
|
|
132
132
|
raise ValueError(message)
|
|
133
133
|
|
|
134
134
|
|
|
135
|
-
def
|
|
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
|
-
|
|
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 =
|
|
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
|
-
#
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
210
|
+
return value <= ub
|
|
177
211
|
elif lb is not None and ub is None:
|
|
178
|
-
return lb
|
|
212
|
+
return lb <= value
|
|
179
213
|
elif lb is not None and ub is not None:
|
|
180
|
-
return lb
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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.
|
|
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.
|
|
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 = [] #
|
|
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('
|
|
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 '
|
|
138
|
-
return trial.user_attrs['
|
|
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.
|
|
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
|
|
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.
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
)
|