PyFemtet 0.3.7__tar.gz → 0.3.9__tar.gz
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.
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/PKG-INFO +1 -1
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.py +1 -1
- pyfemtet-0.3.9/pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.SLDPRT +0 -0
- pyfemtet-0.3.9/pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.femprj +0 -0
- pyfemtet-0.3.9/pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.py +99 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +0 -1
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/FemtetPJTSample/her_ex40_parametric.py +0 -1
- pyfemtet-0.3.9/pyfemtet/__init__.py +1 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/dispatch_extensions.py +4 -25
- pyfemtet-0.3.9/pyfemtet/opt/__init__.py +2 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/opt/base.py +75 -45
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/opt/interface.py +253 -97
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyproject.toml +1 -1
- pyfemtet-0.3.7/pyfemtet/__init__.py +0 -1
- pyfemtet-0.3.7/pyfemtet/_old/_SW_Femtet.py +0 -163
- pyfemtet-0.3.7/pyfemtet/opt/__init__.py +0 -2
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/LICENSE +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/README.md +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.femprj +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.prt +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/FemtetPJTSample/gau_ex08_parametric.femprj +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/core.py +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/logger.py +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/opt/_FemtetWithNX/update_model.py +0 -0
- {pyfemtet-0.3.7 → pyfemtet-0.3.9}/pyfemtet/opt/monitor.py +0 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""外部 CAD ソフト Solidworks との連携
|
|
2
|
+
|
|
3
|
+
外部 CAD ソフト Solidworks で作成したパラメトリックモデルを
|
|
4
|
+
Femtet にインポートして最適化を行います。
|
|
5
|
+
|
|
6
|
+
事前準備として、以下の作業を行ってください。
|
|
7
|
+
- Sldworks_ex01.SLDPRT と Sldworks_ex01.femprj を同じフォルダに配置する
|
|
8
|
+
- C:\temp フォルダを作成する
|
|
9
|
+
※ 最適化の実行中に Solidworks から .x_t ファイルがエクスポートされます。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from pyfemtet.opt import FEMOpt, OptunaOptimizer
|
|
14
|
+
from pyfemtet.opt.interface import FemtetWithSolidworksInterface
|
|
15
|
+
from pyfemtet.core import ModelError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
here, me = os.path.split(__file__)
|
|
19
|
+
os.chdir(here)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def disp(Femtet):
|
|
23
|
+
"""解析結果から Z 方向最大変位を取得します。
|
|
24
|
+
目的関数の第一引数は Femtet インスタンスである必要があります。
|
|
25
|
+
"""
|
|
26
|
+
_, _, ret = Femtet.Gogh.Galileo.GetMaxDisplacement_py()
|
|
27
|
+
|
|
28
|
+
# CAD 連携の場合、境界条件をトポロジーIDに基づいて割り当てているので、
|
|
29
|
+
# 意図しない箇所に境界条件が割り当てられることがあります。
|
|
30
|
+
# この問題では意図通りの割り当てができていれば最大変位は必ず負なので
|
|
31
|
+
# 答えが正の場合は境界条件割り当てに失敗しているとみなして
|
|
32
|
+
# ModelError を送出するようにします。
|
|
33
|
+
# 最適化ルーチン中に ModelError, MeshError, SolveError が送出された場合
|
|
34
|
+
# Optimizer はその試行を失敗とみなし、スキップして次の試行を行います。
|
|
35
|
+
if ret >= 0:
|
|
36
|
+
raise ModelError('境界条件の割り当てが間違えています。')
|
|
37
|
+
return ret
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def volume(Femtet):
|
|
41
|
+
"""解析結果からモデル体積を取得します。
|
|
42
|
+
目的関数の第一引数は Femtet インスタンスである必要があります。
|
|
43
|
+
"""
|
|
44
|
+
_, ret = Femtet.Gogh.CalcVolume_py([0])
|
|
45
|
+
return ret
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def C_minus_B(_, opt):
|
|
49
|
+
"""C 寸法と B 寸法の差を計算します。
|
|
50
|
+
|
|
51
|
+
拘束関数の第一引数は Femtet インスタンスである必要がありますが、
|
|
52
|
+
この例では使用していません。
|
|
53
|
+
|
|
54
|
+
ほかの例では設計変数にアクセスする際以下のスニペットを用いますが、
|
|
55
|
+
CAD 連携を行う場合、Femtet に設計変数が設定されていないためです。
|
|
56
|
+
A = Femtet.GetVariableValue('A')
|
|
57
|
+
|
|
58
|
+
pyfemtet.opt では目的関数・拘束関数の第二引数以降に任意の変数を設定できます。
|
|
59
|
+
この例では、第二引数に FEMOpt のメンバー変数 opt を取り、
|
|
60
|
+
そのメソッド get_parameter() を用いて設計変数を取得しています。
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
A, B, C = opt.get_parameter('values')
|
|
64
|
+
return C - B
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == '__main__':
|
|
68
|
+
|
|
69
|
+
# Solidworks-Femtet 連携オブジェクトを用意
|
|
70
|
+
# ここで起動している Femtet と紐づけがされます。
|
|
71
|
+
fem = FemtetWithSolidworksInterface(
|
|
72
|
+
sldprt_path='Sldworks_ex01.SLDPRT',
|
|
73
|
+
femprj_path='Sldworks_ex01.femprj',
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
opt = OptunaOptimizer(
|
|
77
|
+
sampler_kwargs=dict(
|
|
78
|
+
n_startup_trials=5,
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# 最適化処理を行うオブジェクトに連携オブジェクトを紐づけ
|
|
83
|
+
femopt = FEMOpt(fem=fem, opt=opt)
|
|
84
|
+
|
|
85
|
+
# 設計変数の設定
|
|
86
|
+
femopt.add_parameter('A', 10, lower_bound=1, upper_bound=59)
|
|
87
|
+
femopt.add_parameter('B', 10, lower_bound=1, upper_bound=40)
|
|
88
|
+
femopt.add_parameter('C', 20, lower_bound=5, upper_bound=59)
|
|
89
|
+
|
|
90
|
+
# 拘束関数の設定
|
|
91
|
+
femopt.add_constraint(C_minus_B, 'C>B', lower_bound=1, args=femopt.opt)
|
|
92
|
+
|
|
93
|
+
# 目的関数の設定
|
|
94
|
+
femopt.add_objective(disp, name='変位', direction=0)
|
|
95
|
+
femopt.add_objective(volume, name='体積', direction='minimize')
|
|
96
|
+
|
|
97
|
+
# 最適化の実行
|
|
98
|
+
femopt.set_random_seed(42)
|
|
99
|
+
femopt.main(n_trials=20)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.9"
|
|
@@ -144,12 +144,12 @@ def _get_subprocess_log_prefix():
|
|
|
144
144
|
return f'({current_process().name}) '
|
|
145
145
|
|
|
146
146
|
|
|
147
|
-
def launch_and_dispatch_femtet(timeout=DISPATCH_TIMEOUT,
|
|
147
|
+
def launch_and_dispatch_femtet(timeout=DISPATCH_TIMEOUT, strictly_pid_specify=True) -> Tuple[IFemtet, int]:
|
|
148
148
|
"""Launch Femtet by new process and connect to it.
|
|
149
149
|
|
|
150
150
|
Args:
|
|
151
151
|
timeout (int or float, optional): Seconds to wait for connection. Defaults to DISPATCH_TIMEOUT.
|
|
152
|
-
|
|
152
|
+
strictly_pid_specify (bool): If True, try to connect the launched femtet process strictly. If False, launch new process but try to connect any connectable femtet.
|
|
153
153
|
|
|
154
154
|
Raises:
|
|
155
155
|
FemtetNotFoundException: Launched Femtet is not found for some reason (i.e. failed to launch Femtet).
|
|
@@ -166,7 +166,7 @@ def launch_and_dispatch_femtet(timeout=DISPATCH_TIMEOUT, strict_pid_specify=True
|
|
|
166
166
|
sleep(1)
|
|
167
167
|
|
|
168
168
|
# dispatch femtet
|
|
169
|
-
if
|
|
169
|
+
if strictly_pid_specify:
|
|
170
170
|
Femtet, pid = dispatch_specific_femtet(pid, timeout)
|
|
171
171
|
else:
|
|
172
172
|
# worker process なら排他処理する
|
|
@@ -350,6 +350,7 @@ def dispatch_specific_femtet_core(pid, timeout=DISPATCH_TIMEOUT) -> Tuple[IFemte
|
|
|
350
350
|
If you have 2 free Femtet processes (their pid is 1000 and 1001),
|
|
351
351
|
you can only connect to first connected Femtet
|
|
352
352
|
and cannot change the connection.
|
|
353
|
+
|
|
353
354
|
>>> from pyfemtet.dispatch_extensions import dispatch_specific_femtet
|
|
354
355
|
>>> Femtet1, pid1 = dispatch_specific_femtet(pid=1000)
|
|
355
356
|
>>> print(pid1) # 1000
|
|
@@ -361,28 +362,6 @@ def dispatch_specific_femtet_core(pid, timeout=DISPATCH_TIMEOUT) -> Tuple[IFemte
|
|
|
361
362
|
If you want to connect 2 more Femtet processes from single script or interprete,
|
|
362
363
|
please consider parallel processing.
|
|
363
364
|
|
|
364
|
-
Code:
|
|
365
|
-
|
|
366
|
-
from multiprocessing import Process
|
|
367
|
-
from pyfemtet.dispatch_extensions import dispatch_specific_femtet
|
|
368
|
-
|
|
369
|
-
def connect_femtet(pid):
|
|
370
|
-
Femtet, pid = dispatch_specific_femtet(pid=pid)
|
|
371
|
-
... # some processing
|
|
372
|
-
|
|
373
|
-
if __name__ == '__main__':
|
|
374
|
-
# setup parallel processing
|
|
375
|
-
p1 = Process(target=connect_femtet, args=(1000,))
|
|
376
|
-
p2 = Process(target=connect_femtet, args=(1001,))
|
|
377
|
-
|
|
378
|
-
# start parallel processing
|
|
379
|
-
p1.start()
|
|
380
|
-
p2.start()
|
|
381
|
-
|
|
382
|
-
# wait for finish processes
|
|
383
|
-
p1.join()
|
|
384
|
-
p2.join()
|
|
385
|
-
|
|
386
365
|
Args:
|
|
387
366
|
pid (int): Process id of Femtet that you want to connect.
|
|
388
367
|
timeout (int or float, optional): Seconds to wait for connection. Defaults to 10.
|
|
@@ -204,9 +204,6 @@ class Function:
|
|
|
204
204
|
def calc(self, fem: FEMInterface):
|
|
205
205
|
"""Execute user-defined fun.
|
|
206
206
|
|
|
207
|
-
If fem is a FemtetInterface,
|
|
208
|
-
the 1st argument of fun is set to fem automatically.
|
|
209
|
-
|
|
210
207
|
Args:
|
|
211
208
|
fem (FEMInterface)
|
|
212
209
|
|
|
@@ -246,6 +243,11 @@ class Objective(Function):
|
|
|
246
243
|
Raises:
|
|
247
244
|
ValueError: If the direction is not valid.
|
|
248
245
|
|
|
246
|
+
Note:
|
|
247
|
+
If FEMOpt.fem is a instance of FemtetInterface or its subclass,
|
|
248
|
+
the 1st argument of fun is set to fem automatically.
|
|
249
|
+
|
|
250
|
+
|
|
249
251
|
"""
|
|
250
252
|
_check_direction(direction)
|
|
251
253
|
self.direction = direction
|
|
@@ -257,7 +259,7 @@ class Objective(Function):
|
|
|
257
259
|
When direction is `'minimize'`, ``value`` is calculated.
|
|
258
260
|
When direction is `'maximize'`, ``-value`` is calculated.
|
|
259
261
|
When direction is float, ``abs(value - direction)`` is calculated.
|
|
260
|
-
Finally, the calculated value is passed to the symlog function.
|
|
262
|
+
Finally, the calculated value is passed to the symlog function and returns it.
|
|
261
263
|
|
|
262
264
|
``value`` is the return value of the user-defined function.
|
|
263
265
|
|
|
@@ -312,7 +314,7 @@ class Constraint(Function):
|
|
|
312
314
|
super().__init__(fun, name, args, kwargs)
|
|
313
315
|
|
|
314
316
|
|
|
315
|
-
class
|
|
317
|
+
class _HistoryDfCore:
|
|
316
318
|
"""Class for managing a DataFrame object in a distributed manner."""
|
|
317
319
|
|
|
318
320
|
def __init__(self):
|
|
@@ -334,6 +336,7 @@ class History:
|
|
|
334
336
|
param_names (list): The names of the parameters in the study.
|
|
335
337
|
obj_names (list): The names of the objectives in the study.
|
|
336
338
|
cns_names (list): The names of the constraints in the study.
|
|
339
|
+
actor_data (pd.DataFrame): The history data of optimization.
|
|
337
340
|
|
|
338
341
|
"""
|
|
339
342
|
def __init__(self, history_path, client):
|
|
@@ -347,7 +350,7 @@ class History:
|
|
|
347
350
|
# 引数の処理
|
|
348
351
|
self.path = history_path # .csv
|
|
349
352
|
self.is_restart = False
|
|
350
|
-
self._future = client.submit(
|
|
353
|
+
self._future = client.submit(_HistoryDfCore, actor=True)
|
|
351
354
|
self._actor_data = self._future.result()
|
|
352
355
|
self.tmp_data = pd.DataFrame()
|
|
353
356
|
self.param_names = []
|
|
@@ -544,7 +547,7 @@ class History:
|
|
|
544
547
|
df.loc[i, 'hypervolume'] = 0
|
|
545
548
|
|
|
546
549
|
|
|
547
|
-
class
|
|
550
|
+
class _OptimizationStatusActor:
|
|
548
551
|
status_int = -1
|
|
549
552
|
status = 'undefined'
|
|
550
553
|
|
|
@@ -554,11 +557,7 @@ class OptimizationStatusActor:
|
|
|
554
557
|
|
|
555
558
|
|
|
556
559
|
class OptimizationStatus:
|
|
557
|
-
"""Optimization status.
|
|
558
|
-
|
|
559
|
-
undefined -> setting up -> launching -> running (-> interrupting) -> terminated
|
|
560
|
-
|
|
561
|
-
"""
|
|
560
|
+
"""Optimization status."""
|
|
562
561
|
UNDEFINED = -1
|
|
563
562
|
INITIALIZING = 0
|
|
564
563
|
SETTING_UP = 10
|
|
@@ -571,7 +570,7 @@ class OptimizationStatus:
|
|
|
571
570
|
TERMINATE_ALL = 60
|
|
572
571
|
|
|
573
572
|
def __init__(self, client, name='entire'):
|
|
574
|
-
self._future = client.submit(
|
|
573
|
+
self._future = client.submit(_OptimizationStatusActor, actor=True)
|
|
575
574
|
self._actor = self._future.result()
|
|
576
575
|
self.name = name
|
|
577
576
|
self.set(self.INITIALIZING)
|
|
@@ -606,6 +605,25 @@ class OptimizationStatus:
|
|
|
606
605
|
|
|
607
606
|
|
|
608
607
|
class AbstractOptimizer(ABC):
|
|
608
|
+
"""Abstract base class for an interface of optimization library.
|
|
609
|
+
|
|
610
|
+
Attributes:
|
|
611
|
+
fem (FEMInterface): The finite element method object.
|
|
612
|
+
fem_class (type): The class of the finite element method object.
|
|
613
|
+
fem_kwargs (dict): The keyword arguments used to instantiate the finite element method object.
|
|
614
|
+
parameters (pd.DataFrame): The parameters used in the optimization.
|
|
615
|
+
objectives (dict): A dictionary containing the objective functions used in the optimization.
|
|
616
|
+
constraints (dict): A dictionary containing the constraint functions used in the optimization.
|
|
617
|
+
entire_status (OptimizationStatus): The status of the entire optimization process.
|
|
618
|
+
history (History): An actor object that records the history of each iteration in the optimization process.
|
|
619
|
+
worker_status (OptimizationStatus): The status of each worker in a distributed computing environment.
|
|
620
|
+
message (str): A message associated with the current state of the optimization process.
|
|
621
|
+
seed (int or None): The random seed used for random number generation during the optimization process.
|
|
622
|
+
timeout (float or int or None): The maximum time allowed for each iteration of the optimization process. If exceeded, it will be interrupted and terminated early.
|
|
623
|
+
n_trials (int or None): The maximum number of trials allowed for each iteration of the optimization process. If exceeded, it will be interrupted and terminated early.
|
|
624
|
+
is_cluster (bool): Flag indicating if running on a distributed computing cluster.
|
|
625
|
+
|
|
626
|
+
"""
|
|
609
627
|
|
|
610
628
|
def __init__(self):
|
|
611
629
|
self.fem = None
|
|
@@ -624,6 +642,7 @@ class AbstractOptimizer(ABC):
|
|
|
624
642
|
self.is_cluster = False
|
|
625
643
|
|
|
626
644
|
def f(self, x):
|
|
645
|
+
"""Get x, update fem analysis, return objectives (and constraints)."""
|
|
627
646
|
# interruption の実装は具象クラスに任せる
|
|
628
647
|
|
|
629
648
|
# x の更新
|
|
@@ -657,6 +676,7 @@ class AbstractOptimizer(ABC):
|
|
|
657
676
|
return np.array(y), np.array(_y), np.array(c)
|
|
658
677
|
|
|
659
678
|
def set_fem(self, skip_reconstruct=False):
|
|
679
|
+
"""Reconstruct FEMInterface in a subprocess."""
|
|
660
680
|
# restore fem
|
|
661
681
|
if not skip_reconstruct:
|
|
662
682
|
self.fem = self.fem_class(**self.fem_kwargs)
|
|
@@ -692,7 +712,8 @@ class AbstractOptimizer(ABC):
|
|
|
692
712
|
else:
|
|
693
713
|
raise ValueError('get_parameter() got invalid format: {format}')
|
|
694
714
|
|
|
695
|
-
def
|
|
715
|
+
def _check_interruption(self):
|
|
716
|
+
""""""
|
|
696
717
|
if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
|
|
697
718
|
self.worker_status.set(OptimizationStatus.INTERRUPTING)
|
|
698
719
|
self.finalize()
|
|
@@ -701,6 +722,7 @@ class AbstractOptimizer(ABC):
|
|
|
701
722
|
return False
|
|
702
723
|
|
|
703
724
|
def finalize(self):
|
|
725
|
+
"""Destruct fem and set worker status."""
|
|
704
726
|
del self.fem
|
|
705
727
|
self.worker_status.set(OptimizationStatus.TERMINATED)
|
|
706
728
|
|
|
@@ -716,25 +738,26 @@ class AbstractOptimizer(ABC):
|
|
|
716
738
|
self.worker_status = worker_status_list[subprocess_idx]
|
|
717
739
|
self.worker_status.set(OptimizationStatus.LAUNCHING_FEM)
|
|
718
740
|
|
|
719
|
-
if self.
|
|
741
|
+
if self._check_interruption():
|
|
720
742
|
return None
|
|
721
743
|
|
|
722
744
|
# set_fem をはじめ、終了したらそれを示す
|
|
723
745
|
if not skip_set_fem: # なくても動く??
|
|
724
746
|
self.set_fem()
|
|
747
|
+
self.fem.setup_after_parallel()
|
|
725
748
|
self.worker_status.set(OptimizationStatus.WAIT_OTHER_WORKERS)
|
|
726
749
|
|
|
727
750
|
# wait_setup or not
|
|
728
751
|
if wait_setup:
|
|
729
752
|
while True:
|
|
730
|
-
if self.
|
|
753
|
+
if self._check_interruption():
|
|
731
754
|
return None
|
|
732
755
|
# 他のすべての worker_status が wait 以上になったら break
|
|
733
756
|
if all([ws.get() >= OptimizationStatus.WAIT_OTHER_WORKERS for ws in worker_status_list]):
|
|
734
757
|
break
|
|
735
758
|
sleep(1)
|
|
736
759
|
else:
|
|
737
|
-
if self.
|
|
760
|
+
if self._check_interruption():
|
|
738
761
|
return None
|
|
739
762
|
|
|
740
763
|
# set status running
|
|
@@ -752,12 +775,12 @@ class AbstractOptimizer(ABC):
|
|
|
752
775
|
|
|
753
776
|
@abstractmethod
|
|
754
777
|
def main(self, subprocess_idx: int = 0) -> None:
|
|
755
|
-
"""
|
|
778
|
+
"""Start calcuration using optimization library."""
|
|
756
779
|
pass
|
|
757
780
|
|
|
758
781
|
@abstractmethod
|
|
759
782
|
def setup_before_parallel(self, *args, **kwargs):
|
|
760
|
-
"""
|
|
783
|
+
"""Setup before parallel processes are launched."""
|
|
761
784
|
pass
|
|
762
785
|
|
|
763
786
|
|
|
@@ -779,7 +802,6 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
779
802
|
self.additional_initial_parameter = []
|
|
780
803
|
self.additional_initial_methods = add_init_method if hasattr(add_init_method, '__iter__') else [add_init_method]
|
|
781
804
|
|
|
782
|
-
|
|
783
805
|
def _objective(self, trial):
|
|
784
806
|
|
|
785
807
|
# 中断の確認 (FAIL loop に陥る対策)
|
|
@@ -820,7 +842,7 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
820
842
|
try:
|
|
821
843
|
_, _y, c = self.f(x)
|
|
822
844
|
except (ModelError, MeshError, SolveError) as e:
|
|
823
|
-
logger.info(
|
|
845
|
+
logger.info(e)
|
|
824
846
|
logger.info('以下の変数で FEM 解析に失敗しました。')
|
|
825
847
|
print(self.get_parameter('dict'))
|
|
826
848
|
|
|
@@ -855,7 +877,7 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
855
877
|
return trial.user_attrs['constraint'] if 'constraint' in trial.user_attrs.keys() else (1,) # infeasible
|
|
856
878
|
|
|
857
879
|
def setup_before_parallel(self):
|
|
858
|
-
"""
|
|
880
|
+
"""Create storage, study and set initial parameter."""
|
|
859
881
|
|
|
860
882
|
# create storage
|
|
861
883
|
self.study_name = os.path.basename(self.history.path)
|
|
@@ -951,7 +973,7 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
951
973
|
self.additional_initial_parameter.append([parameter, name])
|
|
952
974
|
|
|
953
975
|
def main(self, subprocess_idx=0):
|
|
954
|
-
"""
|
|
976
|
+
"""Set random seed, sampler, study and run study.optimize()."""
|
|
955
977
|
|
|
956
978
|
# (re)set random seed
|
|
957
979
|
seed = self.seed
|
|
@@ -1167,7 +1189,8 @@ class FEMOpt:
|
|
|
1167
1189
|
lower_bound (float or Non, optional): The lower bound of the constraint. Defaults to None.
|
|
1168
1190
|
upper_bound (float or Non, optional): The upper bound of the constraint. Defaults to None.
|
|
1169
1191
|
strict (bool, optional): Flag indicating if it is a strict constraint. Defaults to True.
|
|
1170
|
-
args (tuple or None, optional): Additional arguments for the constraint function. Defaults
|
|
1192
|
+
args (tuple or None, optional): Additional arguments for the constraint function. Defaults to None.
|
|
1193
|
+
kwargs (dict): Additional arguments for the constraint function. Defaults to None.
|
|
1171
1194
|
|
|
1172
1195
|
Note:
|
|
1173
1196
|
If the FEMInterface is FemtetInterface, the 1st argument of fun should be Femtet (IPyDispatch) object.
|
|
@@ -1229,10 +1252,13 @@ class FEMOpt:
|
|
|
1229
1252
|
port (int or None, optional): The port number of the monitor server. If None, ``8080`` will be used. Defaults to None.
|
|
1230
1253
|
|
|
1231
1254
|
Tip:
|
|
1232
|
-
If
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1255
|
+
If you do not know the host IP address
|
|
1256
|
+
for connecting to the local network,
|
|
1257
|
+
use the ``ipconfig`` command to find out.
|
|
1258
|
+
|
|
1259
|
+
Alternatively, you can specify host as 0.0.0.0
|
|
1260
|
+
to access the monitor server through all network interfaces
|
|
1261
|
+
used by that computer.
|
|
1236
1262
|
|
|
1237
1263
|
However, please note that in this case,
|
|
1238
1264
|
it will be visible to all users on the local network.
|
|
@@ -1262,15 +1288,14 @@ class FEMOpt:
|
|
|
1262
1288
|
If setup_monitor_server() is not executed, a local server for monitoring will be started at localhost:8080.
|
|
1263
1289
|
|
|
1264
1290
|
Note:
|
|
1265
|
-
If ``n_trials`` and ``timeout`` are both None, it
|
|
1291
|
+
If ``n_trials`` and ``timeout`` are both None, it runs forever until interrupting by the user.
|
|
1266
1292
|
|
|
1267
1293
|
Note:
|
|
1268
1294
|
If ``n_parallel`` >= 2, depending on the end timing, ``n_trials`` may be exceeded by up to ``n_parallel-1`` times.
|
|
1269
1295
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1296
|
+
Warning:
|
|
1297
|
+
If ``n_parallel`` >= 2 and ``fem`` is a subclass of ``FemtetInterface``, the ``strictly_pid_specify`` of subprocess is set to ``False``.
|
|
1298
|
+
So **it is recommended to close all other Femtet processes before running main().**
|
|
1274
1299
|
|
|
1275
1300
|
"""
|
|
1276
1301
|
|
|
@@ -1323,19 +1348,9 @@ class FEMOpt:
|
|
|
1323
1348
|
list(self.opt.constraints.keys()),
|
|
1324
1349
|
)
|
|
1325
1350
|
|
|
1326
|
-
# fem
|
|
1327
|
-
self.fem.setup_before_parallel(self.client)
|
|
1328
|
-
|
|
1329
|
-
# opt
|
|
1330
|
-
self.opt.fem_class = type(self.fem)
|
|
1331
|
-
self.opt.fem_kwargs = self.fem.kwargs
|
|
1332
|
-
self.opt.entire_status = self.status
|
|
1333
|
-
self.opt.history = self.history
|
|
1334
|
-
self.opt.setup_before_parallel()
|
|
1335
|
-
|
|
1336
1351
|
# launch monitor
|
|
1337
1352
|
self.monitor_process_future = self.client.submit(
|
|
1338
|
-
|
|
1353
|
+
start_monitor_server,
|
|
1339
1354
|
self.history,
|
|
1340
1355
|
self.status,
|
|
1341
1356
|
worker_addresses,
|
|
@@ -1345,6 +1360,16 @@ class FEMOpt:
|
|
|
1345
1360
|
allow_other_workers=False
|
|
1346
1361
|
)
|
|
1347
1362
|
|
|
1363
|
+
# fem
|
|
1364
|
+
self.fem.setup_before_parallel(self.client)
|
|
1365
|
+
|
|
1366
|
+
# opt
|
|
1367
|
+
self.opt.fem_class = type(self.fem)
|
|
1368
|
+
self.opt.fem_kwargs = self.fem.kwargs
|
|
1369
|
+
self.opt.entire_status = self.status
|
|
1370
|
+
self.opt.history = self.history
|
|
1371
|
+
self.opt.setup_before_parallel()
|
|
1372
|
+
|
|
1348
1373
|
# クラスターでの計算開始
|
|
1349
1374
|
self.status.set(OptimizationStatus.LAUNCHING_FEM)
|
|
1350
1375
|
start = time()
|
|
@@ -1407,6 +1432,11 @@ class FEMOpt:
|
|
|
1407
1432
|
logger.info(f'結果は{self.history.path}を確認してください.')
|
|
1408
1433
|
|
|
1409
1434
|
def terminate_all(self):
|
|
1435
|
+
"""Try to terminate all launched processes.
|
|
1436
|
+
|
|
1437
|
+
If distributed computing, Scheduler and Workers will NOT be terminated.
|
|
1438
|
+
|
|
1439
|
+
"""
|
|
1410
1440
|
|
|
1411
1441
|
# monitor が terminated 状態で少なくとも一度更新されなければ running のまま固まる
|
|
1412
1442
|
sleep(1)
|
|
@@ -1453,7 +1483,7 @@ class FEMOpt:
|
|
|
1453
1483
|
sleep(3)
|
|
1454
1484
|
|
|
1455
1485
|
|
|
1456
|
-
def
|
|
1486
|
+
def start_monitor_server(history, status, worker_addresses, worker_status_list, host='localhost', port=8080):
|
|
1457
1487
|
monitor = Monitor(history, status, worker_addresses, worker_status_list)
|
|
1458
1488
|
monitor.start_server(worker_addresses, worker_status_list, host, port)
|
|
1459
1489
|
return 'Exit monitor server process gracefully'
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
2
3
|
import sys
|
|
3
4
|
from time import sleep, time
|
|
4
5
|
import json
|
|
5
6
|
import subprocess
|
|
6
7
|
import signal
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
7
9
|
|
|
8
10
|
import pandas as pd
|
|
9
11
|
import psutil
|
|
10
12
|
from pywintypes import com_error
|
|
11
13
|
from pythoncom import CoInitialize, CoUninitialize
|
|
12
|
-
from win32com.client import constants,
|
|
14
|
+
from win32com.client import constants, DispatchEx
|
|
13
15
|
from dask.distributed import get_worker
|
|
16
|
+
from tqdm import trange
|
|
14
17
|
from femtetutils import util
|
|
15
18
|
|
|
16
19
|
from ..core import (
|
|
@@ -34,7 +37,7 @@ logger.setLevel(logging.INFO)
|
|
|
34
37
|
here, me = os.path.split(__file__)
|
|
35
38
|
|
|
36
39
|
|
|
37
|
-
class FEMInterface:
|
|
40
|
+
class FEMInterface(ABC):
|
|
38
41
|
"""Abstract base class for the interface with FEM software."""
|
|
39
42
|
|
|
40
43
|
def __init__(
|
|
@@ -46,59 +49,28 @@ class FEMInterface:
|
|
|
46
49
|
The concrete class should call super().__init__() with the desired arguments when restoring.
|
|
47
50
|
|
|
48
51
|
Args:
|
|
49
|
-
|
|
50
|
-
subprocess_settings ('Any' or None, optional): Additional settings for the subprocess. Defaults to None.
|
|
51
|
-
**kwargs: keyword arguments for FEMInterface constructor.
|
|
52
|
+
**kwargs: keyword arguments for FEMInterface (re)constructor.
|
|
52
53
|
|
|
53
54
|
"""
|
|
54
55
|
# restore のための情報保管
|
|
55
56
|
self.kwargs = kwargs
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
"""Checks the value of a parameter in the FEM model.
|
|
59
|
-
|
|
60
|
-
Args:
|
|
61
|
-
param_name (str): The name of the parameter.
|
|
62
|
-
|
|
63
|
-
Raises:
|
|
64
|
-
Exception: If param_name does not exist in the FEM model.
|
|
65
|
-
|
|
66
|
-
Returns:
|
|
67
|
-
float: The value of the parameter.
|
|
68
|
-
|
|
69
|
-
"""
|
|
70
|
-
return 0.0
|
|
71
|
-
|
|
58
|
+
@abstractmethod
|
|
72
59
|
def update(self, parameters: pd.DataFrame) -> None:
|
|
73
|
-
"""Updates the FEM analysis based on the proposed parameters.
|
|
74
|
-
|
|
75
|
-
Args:
|
|
76
|
-
parameters (pd.DataFrame): The parameters to be updated. Must have at least 'name' and 'value'.
|
|
60
|
+
"""Updates the FEM analysis based on the proposed parameters."""
|
|
61
|
+
raise NotImplementedError('update() must be implemented.')
|
|
77
62
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
None
|
|
83
|
-
|
|
84
|
-
"""
|
|
85
|
-
pass
|
|
63
|
+
def check_param_value(self, param_name) -> float or None:
|
|
64
|
+
"""Checks the value of a parameter in the FEM model (if implemented in concrete class)."""
|
|
65
|
+
if False:
|
|
66
|
+
raise RuntimeError(f"{param_name} doesn't exist on FEM model.")
|
|
86
67
|
|
|
87
68
|
def update_parameter(self, parameters: pd.DataFrame) -> None:
|
|
88
|
-
"""Updates only FEM variables
|
|
89
|
-
|
|
90
|
-
If users define a function to be passed to add_objective etc. to take OptimizerBase as an argument,
|
|
91
|
-
and access the variable by calling OptimizerBase.get_parameter() within the function,
|
|
92
|
-
there is no need to implement this method in the concrete class of FEMInterface.
|
|
93
|
-
|
|
94
|
-
Args:
|
|
95
|
-
parameters (pd.DataFrame): The parameters to be updated.
|
|
96
|
-
|
|
97
|
-
"""
|
|
69
|
+
"""Updates only FEM variables (if implemented in concrete class)."""
|
|
98
70
|
pass
|
|
99
71
|
|
|
100
72
|
def setup_before_parallel(self, client) -> None:
|
|
101
|
-
"""Preprocessing before launching a dask worker.
|
|
73
|
+
"""Preprocessing before launching a dask worker (if implemented in concrete class).
|
|
102
74
|
|
|
103
75
|
Args:
|
|
104
76
|
client: dask client.
|
|
@@ -110,30 +82,42 @@ class FEMInterface:
|
|
|
110
82
|
"""
|
|
111
83
|
pass
|
|
112
84
|
|
|
85
|
+
def setup_after_parallel(self):
|
|
86
|
+
"""Preprocessing after launching a dask worker and before run optimization (if implemented in concrete class)."""
|
|
87
|
+
pass
|
|
113
88
|
|
|
114
|
-
class FemtetInterface(FEMInterface):
|
|
115
|
-
"""Concrete class for the interface with Femtet software."""
|
|
116
|
-
|
|
117
|
-
def __init__(
|
|
118
|
-
self,
|
|
119
|
-
femprj_path=None,
|
|
120
|
-
model_name=None,
|
|
121
|
-
connect_method='auto',
|
|
122
|
-
strict_pid_specify=False,
|
|
123
|
-
**kwargs
|
|
124
|
-
):
|
|
125
|
-
"""Initializes the FemtetInterface.
|
|
126
89
|
|
|
90
|
+
class FemtetInterface(FEMInterface):
|
|
91
|
+
"""Concrete class for the interface with Femtet software.
|
|
92
|
+
|
|
127
93
|
Args:
|
|
128
94
|
femprj_path (str or None, optional): The path to the .femprj file. Defaults to None.
|
|
129
95
|
model_name (str or None, optional): The name of the analysis model. Defaults to None.
|
|
130
96
|
connect_method (str, optional): The connection method to use. Can be 'new', 'existing', or 'auto'. Defaults to 'auto'.
|
|
131
|
-
|
|
97
|
+
strictly_pid_specify (bool, optional): If True and connect_method=='new', search launched Femtet process strictly based on its process id.
|
|
98
|
+
|
|
99
|
+
Warning:
|
|
100
|
+
Even if you specify ``strictly_pid_specify=True`` on the constructor,
|
|
101
|
+
**the connection behavior is like** ``strictly_pid_specify=False`` **in parallel processing**
|
|
102
|
+
because of its large overhead.
|
|
103
|
+
So you should close all Femtet processes before running FEMOpt.main()
|
|
104
|
+
if ``n_parallel`` >= 2.
|
|
105
|
+
|
|
106
|
+
|
|
132
107
|
|
|
133
108
|
Tip:
|
|
134
109
|
If you search for information about the method to connect python and Femtet, see :func:`connect_femtet`.
|
|
110
|
+
|
|
111
|
+
"""
|
|
135
112
|
|
|
136
|
-
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
femprj_path=None,
|
|
116
|
+
model_name=None,
|
|
117
|
+
connect_method='auto',
|
|
118
|
+
strictly_pid_specify=True,
|
|
119
|
+
**kwargs # 継承されたクラスからの引数
|
|
120
|
+
):
|
|
137
121
|
|
|
138
122
|
# win32com の初期化
|
|
139
123
|
CoInitialize()
|
|
@@ -152,7 +136,7 @@ class FemtetInterface(FEMInterface):
|
|
|
152
136
|
self.connected_method = 'unconnected'
|
|
153
137
|
self.parameters = None
|
|
154
138
|
self.max_api_retry = 3
|
|
155
|
-
self.
|
|
139
|
+
self.strictly_pid_specify = strictly_pid_specify
|
|
156
140
|
|
|
157
141
|
# dask サブプロセスのときは femprj を更新し connect_method を new にする
|
|
158
142
|
try:
|
|
@@ -161,6 +145,7 @@ class FemtetInterface(FEMInterface):
|
|
|
161
145
|
# worker なら femprj_path が None でないはず
|
|
162
146
|
self.femprj_path = os.path.join(space, os.path.basename(self.femprj_path))
|
|
163
147
|
self.connect_method = 'new'
|
|
148
|
+
self.strictly_pid_specify = False
|
|
164
149
|
except ValueError: # get_worker に失敗した場合
|
|
165
150
|
pass
|
|
166
151
|
|
|
@@ -171,13 +156,12 @@ class FemtetInterface(FEMInterface):
|
|
|
171
156
|
# 接続した Femtet の種類に応じて del 時に quit するかどうか決める
|
|
172
157
|
self.quit_when_destruct = self.connected_method == 'new'
|
|
173
158
|
|
|
174
|
-
# restore するための情報保管
|
|
159
|
+
# subprocess で restore するための情報保管
|
|
175
160
|
# パスなどは connect_and_open_femtet での処理結果を反映し
|
|
176
161
|
# メインで開いた解析モデルが確実に開かれるようにする
|
|
177
162
|
super().__init__(
|
|
178
163
|
femprj_path=self.femprj_path,
|
|
179
164
|
model_name=self.model_name,
|
|
180
|
-
strict_pid_specify=self.strict_pid_specify,
|
|
181
165
|
**kwargs
|
|
182
166
|
)
|
|
183
167
|
|
|
@@ -203,7 +187,7 @@ class FemtetInterface(FEMInterface):
|
|
|
203
187
|
def _connect_new_femtet(self):
|
|
204
188
|
logger.info('└ Try to launch and connect new Femtet process.')
|
|
205
189
|
|
|
206
|
-
self.Femtet, _ = launch_and_dispatch_femtet(
|
|
190
|
+
self.Femtet, _ = launch_and_dispatch_femtet(strictly_pid_specify=self.strictly_pid_specify)
|
|
207
191
|
|
|
208
192
|
self.connected_method = 'new'
|
|
209
193
|
|
|
@@ -219,21 +203,25 @@ class FemtetInterface(FEMInterface):
|
|
|
219
203
|
|
|
220
204
|
def connect_femtet(self, connect_method: str = 'auto', pid: int or None = None):
|
|
221
205
|
"""Connects to a Femtet process.
|
|
206
|
+
|
|
222
207
|
Args:
|
|
223
208
|
connect_method (str, optional): The connection method. Can be 'new', 'existing', or 'auto'. Defaults to 'new'.
|
|
224
209
|
pid (int or None, optional): The process ID of an existing Femtet process and wanted to connect.
|
|
225
210
|
|
|
226
|
-
|
|
227
|
-
|
|
211
|
+
Note:
|
|
212
|
+
When connect_method is 'new', starts a new Femtet process and connects to it.
|
|
213
|
+
**`pid` will be ignored.**
|
|
228
214
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
215
|
+
Note:
|
|
216
|
+
When 'existing', connect to an existing Femtet process.
|
|
217
|
+
However, if there are no Femtets to which it can connect
|
|
218
|
+
(i.e. already connected to another Python or Excel process),
|
|
219
|
+
it throws an exception.
|
|
233
220
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
221
|
+
Note:
|
|
222
|
+
When set to 'auto', first tries 'existing', and if that fails, connects with 'new'.
|
|
223
|
+
If `pid` is specified and failed to connect,
|
|
224
|
+
it will not try existing another Femtet process.
|
|
237
225
|
|
|
238
226
|
"""
|
|
239
227
|
|
|
@@ -400,6 +388,7 @@ class FemtetInterface(FEMInterface):
|
|
|
400
388
|
)
|
|
401
389
|
|
|
402
390
|
def femtet_is_alive(self) -> bool:
|
|
391
|
+
"""Returns connected femtet process is exsiting or not."""
|
|
403
392
|
return _get_pid(self.Femtet.hWnd) > 0
|
|
404
393
|
|
|
405
394
|
def open(self, femprj_path: str, model_name: str or None = None) -> None:
|
|
@@ -598,12 +587,21 @@ class FemtetInterface(FEMInterface):
|
|
|
598
587
|
|
|
599
588
|
class NoFEM(FEMInterface):
|
|
600
589
|
"""Interface with no FEM for debug."""
|
|
601
|
-
|
|
590
|
+
def update(self, parameters: pd.DataFrame) -> None:
|
|
591
|
+
pass
|
|
602
592
|
|
|
603
593
|
|
|
604
594
|
class FemtetWithNXInterface(FemtetInterface):
|
|
595
|
+
"""Femtet with NX interface class.
|
|
596
|
+
|
|
597
|
+
Args:
|
|
598
|
+
prt_path: The path to the prt file.
|
|
599
|
+
|
|
600
|
+
For details of The other arguments, see ``FemtetInterface``.
|
|
601
|
+
|
|
602
|
+
"""
|
|
605
603
|
|
|
606
|
-
|
|
604
|
+
_JOURNAL_PATH = os.path.abspath(os.path.join(here, '_FemtetWithNX/update_model.py'))
|
|
607
605
|
|
|
608
606
|
def __init__(
|
|
609
607
|
self,
|
|
@@ -611,21 +609,8 @@ class FemtetWithNXInterface(FemtetInterface):
|
|
|
611
609
|
femprj_path=None,
|
|
612
610
|
model_name=None,
|
|
613
611
|
connect_method='auto',
|
|
612
|
+
strictly_pid_specify=True,
|
|
614
613
|
):
|
|
615
|
-
"""
|
|
616
|
-
Initializes the FemtetWithNXInterface class.
|
|
617
|
-
|
|
618
|
-
Args:
|
|
619
|
-
prt_path: The path to the prt file.
|
|
620
|
-
femprj_path: The path to the femprj file (optional).
|
|
621
|
-
model_name: The name of the model (optional).
|
|
622
|
-
connect_method: The connection method (default 'auto').
|
|
623
|
-
subprocess_idx: The subprocess index (optional).
|
|
624
|
-
subprocess_settings: The subprocess settings (optional).
|
|
625
|
-
|
|
626
|
-
Returns:
|
|
627
|
-
None
|
|
628
|
-
"""
|
|
629
614
|
|
|
630
615
|
# check NX installation
|
|
631
616
|
self.run_journal_path = os.path.join(os.environ.get('UGII_BASE_DIR'), 'NXBIN', 'run_journal.exe')
|
|
@@ -635,33 +620,44 @@ class FemtetWithNXInterface(FemtetInterface):
|
|
|
635
620
|
# 引数の処理
|
|
636
621
|
self.prt_path = os.path.abspath(prt_path)
|
|
637
622
|
|
|
623
|
+
# dask サブプロセスのときは prt_path を worker space から取るようにする
|
|
624
|
+
try:
|
|
625
|
+
worker = get_worker()
|
|
626
|
+
space = worker.local_directory
|
|
627
|
+
# worker なら femprj_path が None でないはず
|
|
628
|
+
self.prt_path = os.path.join(space, os.path.basename(self.prt_path))
|
|
629
|
+
except ValueError: # get_worker に失敗した場合
|
|
630
|
+
pass
|
|
631
|
+
|
|
638
632
|
# FemtetInterface の設定 (femprj_path, model_name の更新など)
|
|
639
633
|
# + restore 情報の上書き
|
|
640
634
|
super().__init__(
|
|
641
635
|
femprj_path=femprj_path,
|
|
642
636
|
model_name=model_name,
|
|
643
637
|
connect_method=connect_method,
|
|
644
|
-
|
|
638
|
+
strictly_pid_specify=strictly_pid_specify,
|
|
639
|
+
prt_path=self.prt_path,
|
|
645
640
|
)
|
|
646
641
|
|
|
647
642
|
|
|
648
643
|
def check_param_value(self, name):
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
644
|
+
"""Override FemtetInterface.check_param_value().
|
|
645
|
+
|
|
646
|
+
Do nothing because the parameter can be registered
|
|
647
|
+
to not only .femprj but also .prt.
|
|
648
|
+
|
|
649
|
+
"""
|
|
650
|
+
pass
|
|
652
651
|
|
|
653
652
|
def setup_before_parallel(self, client):
|
|
654
|
-
client.upload_file(
|
|
655
|
-
self.kwargs['femprj_path'],
|
|
656
|
-
False
|
|
657
|
-
)
|
|
658
653
|
client.upload_file(
|
|
659
654
|
self.kwargs['prt_path'],
|
|
660
655
|
False
|
|
661
656
|
)
|
|
657
|
+
super().setup_before_parallel(client)
|
|
662
658
|
|
|
663
659
|
def update_model(self, parameters: 'pd.DataFrame') -> None:
|
|
664
|
-
"""Update .x_t
|
|
660
|
+
"""Update .x_t"""
|
|
665
661
|
|
|
666
662
|
self.parameters = parameters.copy()
|
|
667
663
|
|
|
@@ -681,7 +677,7 @@ class FemtetWithNXInterface(FemtetInterface):
|
|
|
681
677
|
# NX journal を使ってモデルを編集する
|
|
682
678
|
env = os.environ.copy()
|
|
683
679
|
subprocess.run(
|
|
684
|
-
[self.run_journal_path, self.
|
|
680
|
+
[self.run_journal_path, self._JOURNAL_PATH, '-args', self.prt_path, str_json, x_t_path],
|
|
685
681
|
env=env,
|
|
686
682
|
shell=True,
|
|
687
683
|
cwd=os.path.dirname(self.prt_path)
|
|
@@ -713,7 +709,167 @@ class FemtetWithNXInterface(FemtetInterface):
|
|
|
713
709
|
super().update_model(parameters)
|
|
714
710
|
|
|
715
711
|
|
|
712
|
+
class FemtetWithSolidworksInterface(FemtetInterface):
|
|
716
713
|
|
|
717
|
-
#
|
|
714
|
+
# 定数の宣言
|
|
715
|
+
swThisConfiguration = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swInConfigurationOpts_e.html
|
|
716
|
+
swAllConfiguration = 2
|
|
717
|
+
swSpecifyConfiguration = 3 # use with ConfigName argument
|
|
718
|
+
swSaveAsCurrentVersion = 0
|
|
719
|
+
swSaveAsOptions_Copy = 2 #
|
|
720
|
+
swSaveAsOptions_Silent = 1 # https://help.solidworks.com/2021/english/api/swconst/solidworks.interop.swconst~solidworks.interop.swconst.swsaveasoptions_e.html
|
|
721
|
+
swSaveWithReferencesOptions_None = 0 # https://help-solidworks-com.translate.goog/2023/english/api/swconst/SolidWorks.Interop.swconst~SolidWorks.Interop.swconst.swSaveWithReferencesOptions_e.html?_x_tr_sl=auto&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=wapp
|
|
722
|
+
swDocPART = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swDocumentTypes_e.html
|
|
718
723
|
|
|
724
|
+
def __init__(
|
|
725
|
+
self,
|
|
726
|
+
sldprt_path,
|
|
727
|
+
femprj_path=None,
|
|
728
|
+
model_name=None,
|
|
729
|
+
connect_method='auto',
|
|
730
|
+
strictly_pid_specify=True,
|
|
731
|
+
):
|
|
732
|
+
# 引数の処理
|
|
733
|
+
self.sldprt_path = os.path.abspath(sldprt_path)
|
|
719
734
|
|
|
735
|
+
# dask サブプロセスのときは prt_path を worker space から取るようにする
|
|
736
|
+
try:
|
|
737
|
+
worker = get_worker()
|
|
738
|
+
space = worker.local_directory
|
|
739
|
+
# worker なら femprj_path が None でないはず
|
|
740
|
+
self.prt_path = os.path.join(space, os.path.basename(self.prt_path))
|
|
741
|
+
except ValueError: # get_worker に失敗した場合
|
|
742
|
+
pass
|
|
743
|
+
|
|
744
|
+
# FemtetInterface の設定 (femprj_path, model_name の更新など)
|
|
745
|
+
# + restore 情報の上書き
|
|
746
|
+
super().__init__(
|
|
747
|
+
femprj_path=femprj_path,
|
|
748
|
+
model_name=model_name,
|
|
749
|
+
connect_method=connect_method,
|
|
750
|
+
strictly_pid_specify=strictly_pid_specify,
|
|
751
|
+
sldprt_path=self.sldprt_path,
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
def initialize_sldworks_connection(self):
|
|
755
|
+
# SolidWorks を捕まえ、ファイルを開く
|
|
756
|
+
self.swApp = DispatchEx('SLDWORKS.Application')
|
|
757
|
+
self.swApp.Visible = True
|
|
758
|
+
|
|
759
|
+
# open model
|
|
760
|
+
self.swApp.OpenDoc(self.sldprt_path, self.swDocPART)
|
|
761
|
+
self.swModel = self.swApp.ActiveDoc
|
|
762
|
+
self.swEqnMgr = self.swModel.GetEquationMgr
|
|
763
|
+
self.nEquation = self.swEqnMgr.GetCount
|
|
764
|
+
|
|
765
|
+
def check_param_value(self, param_name):
|
|
766
|
+
"""Override FemtetInterface.check_param_value().
|
|
767
|
+
|
|
768
|
+
Do nothing because the parameter can be registered
|
|
769
|
+
to not only .femprj but also .SLDPRT.
|
|
770
|
+
|
|
771
|
+
"""
|
|
772
|
+
pass
|
|
773
|
+
|
|
774
|
+
def setup_before_parallel(self, client):
|
|
775
|
+
client.upload_file(
|
|
776
|
+
self.kwargs['sldprt_path'],
|
|
777
|
+
False
|
|
778
|
+
)
|
|
779
|
+
super().setup_before_parallel(client)
|
|
780
|
+
|
|
781
|
+
def setup_after_parallel(self):
|
|
782
|
+
CoInitialize()
|
|
783
|
+
self.initialize_sldworks_connection()
|
|
784
|
+
|
|
785
|
+
def update_model(self, parameters: pd.DataFrame):
|
|
786
|
+
"""Update .x_t"""
|
|
787
|
+
|
|
788
|
+
self.parameters = parameters.copy()
|
|
789
|
+
|
|
790
|
+
# Femtet が参照している x_t パスを取得する
|
|
791
|
+
x_t_path = self.Femtet.Gaudi.LastXTPath
|
|
792
|
+
|
|
793
|
+
# 前のが存在するならば消しておく
|
|
794
|
+
if os.path.isfile(x_t_path):
|
|
795
|
+
os.remove(x_t_path)
|
|
796
|
+
|
|
797
|
+
# solidworks のモデルの更新
|
|
798
|
+
self.update_sw_model(parameters)
|
|
799
|
+
|
|
800
|
+
# export as x_t
|
|
801
|
+
self.swModel.SaveAs(x_t_path)
|
|
802
|
+
|
|
803
|
+
# 30 秒待っても x_t ができてなければエラー(COM なので)
|
|
804
|
+
timeout = 30
|
|
805
|
+
start = time()
|
|
806
|
+
while True:
|
|
807
|
+
if os.path.isfile(x_t_path):
|
|
808
|
+
break
|
|
809
|
+
if time()-start > timeout:
|
|
810
|
+
raise ModelError('モデル再構築に失敗しました')
|
|
811
|
+
sleep(1)
|
|
812
|
+
|
|
813
|
+
# モデルの再インポート
|
|
814
|
+
self._call_femtet_api(
|
|
815
|
+
self.Femtet.Gaudi.ReExecute,
|
|
816
|
+
False,
|
|
817
|
+
ModelError, # 生きてるのに失敗した場合
|
|
818
|
+
error_message=f'モデル再構築に失敗しました.',
|
|
819
|
+
is_Gaudi_method=True,
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
# 処理を確定
|
|
823
|
+
self._call_femtet_api(
|
|
824
|
+
self.Femtet.Redraw,
|
|
825
|
+
False, # 戻り値は常に None なのでこの変数に意味はなく None 以外なら何でもいい
|
|
826
|
+
ModelError, # 生きてるのに失敗した場合
|
|
827
|
+
error_message=f'モデル再構築に失敗しました.',
|
|
828
|
+
is_Gaudi_method=True,
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
# femprj モデルの変数も更新
|
|
832
|
+
super().update_model(parameters)
|
|
833
|
+
|
|
834
|
+
def update_sw_model(self, parameters: pd.DataFrame):
|
|
835
|
+
"""Update .sldprt"""
|
|
836
|
+
# df を dict に変換
|
|
837
|
+
user_param_dict = {}
|
|
838
|
+
for i, row in parameters.iterrows():
|
|
839
|
+
user_param_dict[row['name']] = row['value']
|
|
840
|
+
|
|
841
|
+
# プロパティを退避
|
|
842
|
+
buffer_aso = self.swEqnMgr.AutomaticSolveOrder
|
|
843
|
+
buffer_ar = self.swEqnMgr.AutomaticRebuild
|
|
844
|
+
self.swEqnMgr.AutomaticSolveOrder = False
|
|
845
|
+
self.swEqnMgr.AutomaticRebuild = False
|
|
846
|
+
|
|
847
|
+
for i in range(self.nEquation):
|
|
848
|
+
# name, equation の取得
|
|
849
|
+
current_equation = self.swEqnMgr.Equation(i)
|
|
850
|
+
current_name = self._get_name_from_equation(current_equation)
|
|
851
|
+
# 対象なら処理
|
|
852
|
+
if current_name in list(user_param_dict.keys()):
|
|
853
|
+
new_equation = f'"{current_name}" = {user_param_dict[current_name]}'
|
|
854
|
+
self.swEqnMgr.Equation(i, new_equation)
|
|
855
|
+
|
|
856
|
+
# 式の計算
|
|
857
|
+
# noinspection PyStatementEffect
|
|
858
|
+
self.swEqnMgr.EvaluateAll # always returns -1
|
|
859
|
+
|
|
860
|
+
# プロパティをもとに戻す
|
|
861
|
+
self.swEqnMgr.AutomaticSolveOrder = buffer_aso
|
|
862
|
+
self.swEqnMgr.AutomaticRebuild = buffer_ar
|
|
863
|
+
|
|
864
|
+
# 更新する(ここで失敗はしうる)
|
|
865
|
+
result = self.swModel.EditRebuild3 # モデル再構築
|
|
866
|
+
if not result:
|
|
867
|
+
raise ModelError('モデル再構築に失敗しました')
|
|
868
|
+
|
|
869
|
+
def _get_name_from_equation(self, equation:str):
|
|
870
|
+
pattern = r'^\s*"(.+?)"\s*$'
|
|
871
|
+
matched = re.match(pattern, equation.split('=')[0])
|
|
872
|
+
if matched:
|
|
873
|
+
return matched.group(1)
|
|
874
|
+
else:
|
|
875
|
+
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pyfemtet"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.9" # ignored by versioning plugin
|
|
4
4
|
description = "Design parameter optimization using Femtet."
|
|
5
5
|
authors = ["kazuma.naito <kazuma.naito@murata.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.7"
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import os
|
|
3
|
-
from time import sleep
|
|
4
|
-
|
|
5
|
-
import pandas as pd
|
|
6
|
-
from win32com.client import DispatchEx, Dispatch
|
|
7
|
-
|
|
8
|
-
from .core import Femtet
|
|
9
|
-
|
|
10
|
-
# 定数の宣言
|
|
11
|
-
swThisConfiguration = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swInConfigurationOpts_e.html
|
|
12
|
-
swAllConfiguration = 2
|
|
13
|
-
swSpecifyConfiguration = 3 # use with ConfigName argument
|
|
14
|
-
swSaveAsCurrentVersion = 0
|
|
15
|
-
swSaveAsOptions_Copy = 2 #
|
|
16
|
-
swSaveAsOptions_Silent = 1 # https://help.solidworks.com/2021/english/api/swconst/solidworks.interop.swconst~solidworks.interop.swconst.swsaveasoptions_e.html
|
|
17
|
-
swSaveWithReferencesOptions_None = 0 # https://help-solidworks-com.translate.goog/2023/english/api/swconst/SolidWorks.Interop.swconst~SolidWorks.Interop.swconst.swSaveWithReferencesOptions_e.html?_x_tr_sl=auto&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=wapp
|
|
18
|
-
swDocPART = 1 # https://help.solidworks.com/2023/english/api/swconst/SOLIDWORKS.Interop.swconst~SOLIDWORKS.Interop.swconst.swDocumentTypes_e.html
|
|
19
|
-
|
|
20
|
-
class SolidWorksAutomationError(Exception):
|
|
21
|
-
pass
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class SW_Femtet(Femtet):
|
|
25
|
-
|
|
26
|
-
def __init__(self, path_sldprt):
|
|
27
|
-
# 引数の処理
|
|
28
|
-
self.path_sldprt = os.path.abspath(path_sldprt)
|
|
29
|
-
|
|
30
|
-
# SolidWorks を捕まえ、ファイルを開く
|
|
31
|
-
self.swApp = DispatchEx('SLDWORKS.Application')
|
|
32
|
-
self.swApp.Visible = True
|
|
33
|
-
|
|
34
|
-
# open model
|
|
35
|
-
self.swApp.OpenDoc(self.path_sldprt, swDocPART)
|
|
36
|
-
self.swModel = self.swApp.ActiveDoc
|
|
37
|
-
self.swEqnMgr = self.swModel.GetEquationMgr
|
|
38
|
-
self.nEquation = self.swEqnMgr.GetCount
|
|
39
|
-
|
|
40
|
-
# 引数の保存
|
|
41
|
-
super().__init__(self.path_sldprt)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def update(self, df:pd.DataFrame) ->None:
|
|
45
|
-
"""Update_model_via_SW と run を実行します.
|
|
46
|
-
|
|
47
|
-
Parameters
|
|
48
|
-
----------
|
|
49
|
-
df
|
|
50
|
-
|
|
51
|
-
Returns
|
|
52
|
-
-------
|
|
53
|
-
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
# df を dict に変換
|
|
57
|
-
user_param_dict = {}
|
|
58
|
-
for i, row in df.iterrows():
|
|
59
|
-
user_param_dict[row['name']] = row['value']
|
|
60
|
-
|
|
61
|
-
# 変数の存在チェック
|
|
62
|
-
self.check_parameter(user_param_dict)
|
|
63
|
-
|
|
64
|
-
# Femtet 解析モデルを更新
|
|
65
|
-
self.update_model_via_SW(user_param_dict)
|
|
66
|
-
|
|
67
|
-
# 解析を実行する
|
|
68
|
-
super().run()
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def check_parameter(self, user_param_dict):
|
|
72
|
-
"""与えられた変数セットが .sldprt ファイルに存在するか確認します."""
|
|
73
|
-
swEqnMgr = self.swModel.GetEquationMgr
|
|
74
|
-
|
|
75
|
-
# 指定された変数が SW に存在するかどうかをチェック
|
|
76
|
-
for user_param_name in user_param_dict.keys():
|
|
77
|
-
is_in_model = False
|
|
78
|
-
# global 変数に存在するかどうか
|
|
79
|
-
nEquation = swEqnMgr.GetCount
|
|
80
|
-
global_param_names = [self.get_name(swEqnMgr.Equation(i)) for i in range(nEquation) if swEqnMgr.GlobalVariable(i)]
|
|
81
|
-
is_in_model = is_in_model or (user_param_name in global_param_names)
|
|
82
|
-
# 寸法に存在するかどうか
|
|
83
|
-
swParam = self.swModel.Parameter(user_param_name)
|
|
84
|
-
is_in_model = is_in_model or (swParam is not None)
|
|
85
|
-
# is_in_model が False ならエラー
|
|
86
|
-
if not is_in_model:
|
|
87
|
-
raise Exception(f'no such parameter in SW:{user_param_name}')
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def update_model_via_SW(self, user_param_dict):
|
|
91
|
-
"""SW を使って parasolid を作り, Femtet 解析モデルを更新します."""
|
|
92
|
-
|
|
93
|
-
# Femtet が参照している x_t パスを取得する
|
|
94
|
-
path_x_t = self.Femtet.Gaudi.LastXTPath
|
|
95
|
-
|
|
96
|
-
# 先にそれを消しておく
|
|
97
|
-
if os.path.isfile(path_x_t):
|
|
98
|
-
os.remove(path_x_t)
|
|
99
|
-
|
|
100
|
-
# sldprt モデルの変数の更新
|
|
101
|
-
self.update_SW_parameter(user_param_dict)
|
|
102
|
-
|
|
103
|
-
# export parasolid
|
|
104
|
-
self.swModel.SaveAs(path_x_t)
|
|
105
|
-
|
|
106
|
-
# 30 秒待っても parasolid ができてなければエラー(COM なので)
|
|
107
|
-
timeout = 30
|
|
108
|
-
waiting = 0
|
|
109
|
-
while True:
|
|
110
|
-
sleep(1)
|
|
111
|
-
waiting += 1
|
|
112
|
-
if os.path.isfile(path_x_t):
|
|
113
|
-
break
|
|
114
|
-
else:
|
|
115
|
-
if waiting<timeout:
|
|
116
|
-
continue
|
|
117
|
-
else:
|
|
118
|
-
# from .core import ModelError
|
|
119
|
-
# 再構築できていれば保存はできているはず
|
|
120
|
-
raise SolidWorksAutomationError('parasolid 書き出しに失敗しました')
|
|
121
|
-
|
|
122
|
-
# Femtet モデルの更新 by ReExecute Femtet History
|
|
123
|
-
self.Femtet.Gaudi.ReExecute()
|
|
124
|
-
self.Femtet.Redraw()
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def update_SW_parameter(self, user_param_dict):
|
|
128
|
-
# プロパティを退避
|
|
129
|
-
buffer_aso = self.swEqnMgr.AutomaticSolveOrder
|
|
130
|
-
buffer_ar = self.swEqnMgr.AutomaticRebuild
|
|
131
|
-
self.swEqnMgr.AutomaticSolveOrder = False
|
|
132
|
-
self.swEqnMgr.AutomaticRebuild = False
|
|
133
|
-
|
|
134
|
-
from tqdm import trange
|
|
135
|
-
for i in trange(self.nEquation):
|
|
136
|
-
# name, equation の取得
|
|
137
|
-
current_equation = self.swEqnMgr.Equation(i)
|
|
138
|
-
current_name = self.get_name(current_equation)
|
|
139
|
-
# 対象なら処理
|
|
140
|
-
if current_name in list(user_param_dict.keys()):
|
|
141
|
-
new_equation = f'"{current_name}" = {user_param_dict[current_name]}'
|
|
142
|
-
self.swEqnMgr.Equation(i, new_equation)
|
|
143
|
-
|
|
144
|
-
# 式の計算
|
|
145
|
-
self.swEqnMgr.EvaluateAll # always returns -1
|
|
146
|
-
|
|
147
|
-
# プロパティをもとに戻す
|
|
148
|
-
self.swEqnMgr.AutomaticSolveOrder = buffer_aso
|
|
149
|
-
self.swEqnMgr.AutomaticRebuild = buffer_ar
|
|
150
|
-
|
|
151
|
-
# 更新する(ここで失敗はしうる)
|
|
152
|
-
result = self.swModel.EditRebuild3 # モデル再構築
|
|
153
|
-
if result==False:
|
|
154
|
-
from .core import ModelError
|
|
155
|
-
raise ModelError('モデル再構築に失敗しました')
|
|
156
|
-
|
|
157
|
-
def get_name(self, equation:str):
|
|
158
|
-
pattern = r'^\s*"(.+?)"\s*$'
|
|
159
|
-
matched = re.match(pattern, equation.split('=')[0])
|
|
160
|
-
if matched:
|
|
161
|
-
return matched.group(1)
|
|
162
|
-
else:
|
|
163
|
-
return None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|