pyfemtet 0.3.12__py3-none-any.whl → 0.4.1__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/FemtetPJTSample/NX_ex01/NX_ex01.py +1 -1
- pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.py +1 -1
- pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +1 -1
- pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
- pyfemtet/FemtetPJTSample/her_ex40_parametric.py +1 -1
- pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +1 -1
- pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
- pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +1 -1
- pyfemtet/__init__.py +1 -1
- pyfemtet/core.py +14 -0
- pyfemtet/dispatch_extensions.py +5 -0
- pyfemtet/opt/__init__.py +22 -2
- pyfemtet/opt/_femopt.py +544 -0
- pyfemtet/opt/_femopt_core.py +730 -0
- pyfemtet/opt/interface/__init__.py +15 -0
- pyfemtet/opt/interface/_base.py +71 -0
- pyfemtet/opt/{interface.py → interface/_femtet.py} +120 -407
- pyfemtet/opt/interface/_femtet_with_nx/__init__.py +3 -0
- pyfemtet/opt/interface/_femtet_with_nx/_interface.py +128 -0
- pyfemtet/opt/interface/_femtet_with_sldworks.py +174 -0
- pyfemtet/opt/opt/__init__.py +8 -0
- pyfemtet/opt/opt/_base.py +202 -0
- pyfemtet/opt/opt/_optuna.py +240 -0
- pyfemtet/opt/visualization/__init__.py +7 -0
- pyfemtet/opt/visualization/_graphs.py +222 -0
- pyfemtet/opt/visualization/_monitor.py +1149 -0
- {pyfemtet-0.3.12.dist-info → pyfemtet-0.4.1.dist-info}/METADATA +4 -4
- pyfemtet-0.4.1.dist-info/RECORD +38 -0
- {pyfemtet-0.3.12.dist-info → pyfemtet-0.4.1.dist-info}/WHEEL +1 -1
- pyfemtet-0.4.1.dist-info/entry_points.txt +3 -0
- pyfemtet/opt/base.py +0 -1490
- pyfemtet/opt/monitor.py +0 -474
- pyfemtet-0.3.12.dist-info/RECORD +0 -26
- /pyfemtet/opt/{_FemtetWithNX → interface/_femtet_with_nx}/update_model.py +0 -0
- {pyfemtet-0.3.12.dist-info → pyfemtet-0.4.1.dist-info}/LICENSE +0 -0
|
Binary file
|
|
@@ -59,5 +59,5 @@ if __name__ == '__main__':
|
|
|
59
59
|
# 最適化の実行
|
|
60
60
|
femopt.set_random_seed(42)
|
|
61
61
|
# femopt.main(n_trials=20)
|
|
62
|
-
femopt.
|
|
62
|
+
femopt.optimize(n_trials=20, n_parallel=3) # ここのみ wat_ex14_parametric.py から変更しました。
|
|
63
63
|
femopt.terminate_all()
|
|
Binary file
|
pyfemtet/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.4.1"
|
pyfemtet/core.py
CHANGED
|
@@ -36,3 +36,17 @@ class SolveError(Exception):
|
|
|
36
36
|
class FemtetAutomationError(Exception):
|
|
37
37
|
"""Exception raised for errors in automating Femtet."""
|
|
38
38
|
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _version(
|
|
42
|
+
main=None,
|
|
43
|
+
major=None,
|
|
44
|
+
minor=None,
|
|
45
|
+
Femtet=None,
|
|
46
|
+
):
|
|
47
|
+
if Femtet is not None:
|
|
48
|
+
assert (main is None) and (major is None) and (minor is None), 'バージョンを指定しないでください'
|
|
49
|
+
main, major, minor = [int(v) for v in Femtet.Version.split('.')[:3]]
|
|
50
|
+
else:
|
|
51
|
+
assert (main is not None) and (major is not None) and (minor is not None), 'バージョンを指定してください'
|
|
52
|
+
return main*10000 + major*100 + minor
|
pyfemtet/dispatch_extensions.py
CHANGED
|
@@ -186,6 +186,7 @@ def dispatch_femtet(timeout=DISPATCH_TIMEOUT, subprocess_log_prefix='') -> Tuple
|
|
|
186
186
|
|
|
187
187
|
Args:
|
|
188
188
|
timeout (int or float, optional): Seconds to wait for connection. Defaults to DISPATCH_TIMEOUT.
|
|
189
|
+
subprocess_log_prefix (str, optional): The prefix of log message.
|
|
189
190
|
|
|
190
191
|
Raises:
|
|
191
192
|
FemtetConnectionTimeoutError: Couldn't connect Femtet process for some reason (i.e. Femtet.exe is not launched).
|
|
@@ -461,6 +462,10 @@ def dispatch_specific_femtet_core(pid, timeout=DISPATCH_TIMEOUT) -> Tuple[IFemte
|
|
|
461
462
|
return Femtet, my_pid
|
|
462
463
|
|
|
463
464
|
|
|
465
|
+
def _debug():
|
|
466
|
+
launch_and_dispatch_femtet(5)
|
|
467
|
+
|
|
468
|
+
|
|
464
469
|
if __name__ == '__main__':
|
|
465
470
|
_Femtet, _my_pid = launch_and_dispatch_femtet(5)
|
|
466
471
|
# _Femtet, _my_pid = dispatch_specific_femtet(pid=26124)
|
pyfemtet/opt/__init__.py
CHANGED
|
@@ -1,2 +1,22 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .interface import
|
|
1
|
+
from pyfemtet.opt.interface import FEMInterface
|
|
2
|
+
from pyfemtet.opt.interface import NoFEM
|
|
3
|
+
from pyfemtet.opt.interface import FemtetInterface
|
|
4
|
+
from pyfemtet.opt.interface import FemtetWithNXInterface
|
|
5
|
+
from pyfemtet.opt.interface import FemtetWithSolidworksInterface
|
|
6
|
+
|
|
7
|
+
from pyfemtet.opt.opt import OptunaOptimizer
|
|
8
|
+
from pyfemtet.opt.opt import AbstractOptimizer
|
|
9
|
+
|
|
10
|
+
from pyfemtet.opt._femopt import FEMOpt
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'FEMOpt',
|
|
15
|
+
'FEMInterface',
|
|
16
|
+
'NoFEM',
|
|
17
|
+
'FemtetInterface',
|
|
18
|
+
'FemtetWithNXInterface',
|
|
19
|
+
'FemtetWithSolidworksInterface',
|
|
20
|
+
'AbstractOptimizer',
|
|
21
|
+
'OptunaOptimizer',
|
|
22
|
+
]
|
pyfemtet/opt/_femopt.py
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
# built-in
|
|
2
|
+
import os
|
|
3
|
+
import datetime
|
|
4
|
+
from time import time, sleep
|
|
5
|
+
from threading import Thread
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
# 3rd-party
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from dask.distributed import LocalCluster, Client
|
|
12
|
+
|
|
13
|
+
# pyfemtet relative
|
|
14
|
+
from pyfemtet.opt.interface import FEMInterface, FemtetInterface
|
|
15
|
+
from pyfemtet.opt.opt import AbstractOptimizer, OptunaOptimizer
|
|
16
|
+
from pyfemtet.opt.visualization._monitor import ProcessMonitorApp
|
|
17
|
+
from pyfemtet.opt._femopt_core import (
|
|
18
|
+
_check_bound,
|
|
19
|
+
_is_access_gogh,
|
|
20
|
+
Objective,
|
|
21
|
+
Constraint,
|
|
22
|
+
History,
|
|
23
|
+
OptimizationStatus,
|
|
24
|
+
logger,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class FEMOpt:
|
|
29
|
+
"""Class to control FEM interface and optimizer.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
fem (FEMInterface, optional): The finite element method interface. Defaults to None. If None, automatically set to FemtetInterface.
|
|
33
|
+
opt (AbstractOptimizer):
|
|
34
|
+
history_path (str, optional): The path to the history file. Defaults to None. If None, '%Y_%m_%d_%H_%M_%S.csv' is created in current directory.
|
|
35
|
+
scheduler_address (str or None): If cluster processing, set this parameter like "tcp://xxx.xxx.xxx.xxx:xxxx".
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
fem (FEMInterface): The interface of FEM system.
|
|
39
|
+
opt (AbstractOptimizer): The optimizer.
|
|
40
|
+
scheduler_address (str or None): Dask scheduler address. If None, LocalCluster will be used.
|
|
41
|
+
client (Client): Dask client. For detail, see dask documentation.
|
|
42
|
+
status (OptimizationStatus): Entire process status. This contains dask actor.
|
|
43
|
+
history(History): History of optimization process. This contains dask actor.
|
|
44
|
+
history_path (str): The path to the history (.csv) file.
|
|
45
|
+
worker_status_list([OptimizationStatus]): Process status of each dask worker.
|
|
46
|
+
monitor_process_future(Future): Future of monitor server process. This is dask future.
|
|
47
|
+
monitor_server_kwargs(dict): Monitor server parameter. Currently, the valid arguments are hostname and port.
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
fem: FEMInterface = None,
|
|
54
|
+
opt: AbstractOptimizer = None,
|
|
55
|
+
history_path: str = None,
|
|
56
|
+
scheduler_address: str = None
|
|
57
|
+
):
|
|
58
|
+
logger.info('Initialize FEMOpt')
|
|
59
|
+
|
|
60
|
+
# 引数の処理
|
|
61
|
+
if history_path is None:
|
|
62
|
+
history_path = datetime.datetime.now().strftime('%Y%m%d_%H%M%S.csv')
|
|
63
|
+
self.history_path = os.path.abspath(history_path)
|
|
64
|
+
self.scheduler_address = scheduler_address
|
|
65
|
+
|
|
66
|
+
if fem is None:
|
|
67
|
+
self.fem = FemtetInterface()
|
|
68
|
+
else:
|
|
69
|
+
self.fem = fem
|
|
70
|
+
|
|
71
|
+
if opt is None:
|
|
72
|
+
self.opt = OptunaOptimizer()
|
|
73
|
+
else:
|
|
74
|
+
self.opt = opt
|
|
75
|
+
|
|
76
|
+
# メンバーの宣言
|
|
77
|
+
self.client = None
|
|
78
|
+
self.status = None # actor
|
|
79
|
+
self.history = None # actor
|
|
80
|
+
self.worker_status_list = None # [actor]
|
|
81
|
+
self.monitor_process_future = None
|
|
82
|
+
self.monitor_server_kwargs = dict()
|
|
83
|
+
self.monitor_process_worker_name = None
|
|
84
|
+
|
|
85
|
+
# multiprocess 時に pickle できないオブジェクト参照の削除
|
|
86
|
+
def __getstate__(self):
|
|
87
|
+
state = self.__dict__.copy()
|
|
88
|
+
del state['fem']
|
|
89
|
+
return state
|
|
90
|
+
|
|
91
|
+
def __setstate__(self, state):
|
|
92
|
+
self.__dict__.update(state)
|
|
93
|
+
|
|
94
|
+
def set_random_seed(self, seed: int):
|
|
95
|
+
"""Sets the random seed for reproducibility.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
seed (int): The random seed value to be set.
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
self.opt.seed = seed
|
|
102
|
+
|
|
103
|
+
def add_parameter(
|
|
104
|
+
self,
|
|
105
|
+
name: str,
|
|
106
|
+
initial_value: float or None = None,
|
|
107
|
+
lower_bound: float or None = None,
|
|
108
|
+
upper_bound: float or None = None,
|
|
109
|
+
memo: str = ''
|
|
110
|
+
):
|
|
111
|
+
"""Adds a parameter to the optimization problem.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
name (str): The name of the parameter.
|
|
115
|
+
initial_value (float or None, optional): The initial value of the parameter. Defaults to None. If None, try to get initial value from FEMInterface.
|
|
116
|
+
lower_bound (float or None, optional): The lower bound of the parameter. Defaults to None. However, this argument is required for some algorithms.
|
|
117
|
+
upper_bound (float or None, optional): The upper bound of the parameter. Defaults to None. However, this argument is required for some algorithms.
|
|
118
|
+
memo (str, optional): Additional information about the parameter. Defaults to ''.
|
|
119
|
+
Raises:
|
|
120
|
+
ValueError: If initial_value is not specified and the value for the given name is also not specified.
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
_check_bound(lower_bound, upper_bound, name)
|
|
125
|
+
value = self.fem.check_param_value(name)
|
|
126
|
+
if initial_value is None:
|
|
127
|
+
if value is not None:
|
|
128
|
+
initial_value = value
|
|
129
|
+
else:
|
|
130
|
+
raise ValueError('initial_value を指定してください.')
|
|
131
|
+
|
|
132
|
+
d = {
|
|
133
|
+
'name': name,
|
|
134
|
+
'value': float(initial_value),
|
|
135
|
+
'lb': float(lower_bound),
|
|
136
|
+
'ub': float(upper_bound),
|
|
137
|
+
'memo': memo,
|
|
138
|
+
}
|
|
139
|
+
pdf = pd.DataFrame(d, index=[0], dtype=object)
|
|
140
|
+
|
|
141
|
+
if len(self.opt.parameters) == 0:
|
|
142
|
+
self.opt.parameters = pdf
|
|
143
|
+
else:
|
|
144
|
+
self.opt.parameters = pd.concat([self.opt.parameters, pdf], ignore_index=True)
|
|
145
|
+
|
|
146
|
+
def add_objective(
|
|
147
|
+
self,
|
|
148
|
+
fun,
|
|
149
|
+
name: str or None = None,
|
|
150
|
+
direction: str or float = 'minimize',
|
|
151
|
+
args: tuple or None = None,
|
|
152
|
+
kwargs: dict or None = None
|
|
153
|
+
):
|
|
154
|
+
"""Adds an objective to the optimization problem.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
fun (callable): The objective function.
|
|
158
|
+
name (str or None, optional): The name of the objective. Defaults to None.
|
|
159
|
+
direction (str or float, optional): The optimization direction. Defaults to 'minimize'.
|
|
160
|
+
args (tuple or None, optional): Additional arguments for the objective function. Defaults to None.
|
|
161
|
+
kwargs (dict or None, optional): Additional keyword arguments for the objective function. Defaults to None.
|
|
162
|
+
|
|
163
|
+
Note:
|
|
164
|
+
If the FEMInterface is FemtetInterface, the 1st argument of fun should be Femtet (IPyDispatch) object.
|
|
165
|
+
|
|
166
|
+
Tip:
|
|
167
|
+
If name is None, name is a string with the prefix `"obj_"` followed by a sequential number.
|
|
168
|
+
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
# 引数の処理
|
|
172
|
+
if args is None:
|
|
173
|
+
args = tuple()
|
|
174
|
+
elif not isinstance(args, tuple):
|
|
175
|
+
args = (args,)
|
|
176
|
+
if kwargs is None:
|
|
177
|
+
kwargs = dict()
|
|
178
|
+
if name is None:
|
|
179
|
+
prefix = Objective.default_name
|
|
180
|
+
i = 0
|
|
181
|
+
while True:
|
|
182
|
+
candidate = f'{prefix}_{str(int(i))}'
|
|
183
|
+
is_existing = candidate in list(self.opt.objectives.keys())
|
|
184
|
+
if not is_existing:
|
|
185
|
+
break
|
|
186
|
+
else:
|
|
187
|
+
i += 1
|
|
188
|
+
name = candidate
|
|
189
|
+
|
|
190
|
+
self.opt.objectives[name] = Objective(fun, name, direction, args, kwargs)
|
|
191
|
+
|
|
192
|
+
def add_constraint(
|
|
193
|
+
self,
|
|
194
|
+
fun,
|
|
195
|
+
name: str or None = None,
|
|
196
|
+
lower_bound: float or None = None,
|
|
197
|
+
upper_bound: float or None = None,
|
|
198
|
+
strict: bool = True,
|
|
199
|
+
args: tuple or None = None,
|
|
200
|
+
kwargs: dict or None = None,
|
|
201
|
+
):
|
|
202
|
+
"""Adds a constraint to the optimization problem.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
fun (callable): The constraint function.
|
|
206
|
+
name (str or None, optional): The name of the constraint. Defaults to None.
|
|
207
|
+
lower_bound (float or Non, optional): The lower bound of the constraint. Defaults to None.
|
|
208
|
+
upper_bound (float or Non, optional): The upper bound of the constraint. Defaults to None.
|
|
209
|
+
strict (bool, optional): Flag indicating if it is a strict constraint. Defaults to True.
|
|
210
|
+
args (tuple or None, optional): Additional arguments for the constraint function. Defaults to None.
|
|
211
|
+
kwargs (dict): Additional arguments for the constraint function. Defaults to None.
|
|
212
|
+
|
|
213
|
+
Note:
|
|
214
|
+
If the FEMInterface is FemtetInterface, the 1st argument of fun should be Femtet (IPyDispatch) object.
|
|
215
|
+
|
|
216
|
+
Tip:
|
|
217
|
+
If name is None, name is a string with the prefix `"cns_"` followed by a sequential number.
|
|
218
|
+
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
# 引数の処理
|
|
222
|
+
if args is None:
|
|
223
|
+
args = tuple()
|
|
224
|
+
elif not isinstance(args, tuple):
|
|
225
|
+
args = (args,)
|
|
226
|
+
if kwargs is None:
|
|
227
|
+
kwargs = dict()
|
|
228
|
+
if name is None:
|
|
229
|
+
prefix = Constraint.default_name
|
|
230
|
+
i = 0
|
|
231
|
+
while True:
|
|
232
|
+
candidate = f'{prefix}_{str(int(i))}'
|
|
233
|
+
is_existing = candidate in list(self.opt.constraints.keys())
|
|
234
|
+
if not is_existing:
|
|
235
|
+
break
|
|
236
|
+
else:
|
|
237
|
+
i += 1
|
|
238
|
+
name = candidate
|
|
239
|
+
|
|
240
|
+
# strict constraint の場合、solve 前に評価したいので Gogh へのアクセスを禁ずる
|
|
241
|
+
if strict:
|
|
242
|
+
if _is_access_gogh(fun):
|
|
243
|
+
message = f'関数 {fun.__name__} に Gogh (Femtet 解析結果)へのアクセスがあります.'
|
|
244
|
+
message += 'デフォルトでは constraint は解析前に評価され, 条件を満たさない場合解析を行いません.'
|
|
245
|
+
message += '拘束に解析結果を含めたい場合は, strict=False を設定してください.'
|
|
246
|
+
raise Exception(message)
|
|
247
|
+
|
|
248
|
+
self.opt.constraints[name] = Constraint(fun, name, lower_bound, upper_bound, strict, args, kwargs)
|
|
249
|
+
|
|
250
|
+
def get_parameter(self, format='dict') -> pd.DataFrame or dict or np.ndarray:
|
|
251
|
+
"""Returns the parameter in a specified format.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
format (str, optional): The desired output format. Defaults to 'dict'. Valid formats are 'values', 'df' and 'dict'.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
pd.DataFrame or dict or np.ndarray: The parameter data converted into the specified format.
|
|
258
|
+
|
|
259
|
+
Raises:
|
|
260
|
+
ValueError: If an invalid format is provided.
|
|
261
|
+
|
|
262
|
+
"""
|
|
263
|
+
return self.opt.get_parameter(format)
|
|
264
|
+
|
|
265
|
+
def set_monitor_host(self, host=None, port=None):
|
|
266
|
+
"""Sets up the monitor server with the specified host and port.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
host (str): The hostname or IP address of the monitor server.
|
|
270
|
+
port (int or None, optional): The port number of the monitor server. If None, ``8080`` will be used. Defaults to None.
|
|
271
|
+
|
|
272
|
+
Tip:
|
|
273
|
+
Specifying host ``0.0.0.0`` allows viewing monitor from all computers on the local network.
|
|
274
|
+
|
|
275
|
+
However, please note that in this case,
|
|
276
|
+
it will be visible to all users on the local network.
|
|
277
|
+
|
|
278
|
+
If no hostname is specified, the monitor server will be hosted on ``localhost``.
|
|
279
|
+
|
|
280
|
+
"""
|
|
281
|
+
self.monitor_server_kwargs = dict(
|
|
282
|
+
host=host,
|
|
283
|
+
port=port
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def optimize(
|
|
287
|
+
self,
|
|
288
|
+
n_trials=None,
|
|
289
|
+
n_parallel=1,
|
|
290
|
+
timeout=None,
|
|
291
|
+
wait_setup=True,
|
|
292
|
+
):
|
|
293
|
+
"""Runs the main optimization process.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
n_trials (int or None, optional): The number of trials. Defaults to None.
|
|
297
|
+
n_parallel (int, optional): The number of parallel processes. Defaults to 1.
|
|
298
|
+
timeout (float or None, optional): The maximum amount of time in seconds that each trial can run. Defaults to None.
|
|
299
|
+
wait_setup (bool, optional): Wait for all workers launching FEM system. Defaults to True.
|
|
300
|
+
|
|
301
|
+
Tip:
|
|
302
|
+
If set_monitor_host() is not executed, a local server for monitoring will be started at localhost:8080.
|
|
303
|
+
|
|
304
|
+
Note:
|
|
305
|
+
If ``n_trials`` and ``timeout`` are both None, it runs forever until interrupting by the user.
|
|
306
|
+
|
|
307
|
+
Note:
|
|
308
|
+
If ``n_parallel`` >= 2, depending on the end timing, ``n_trials`` may be exceeded by up to ``n_parallel-1`` times.
|
|
309
|
+
|
|
310
|
+
Warning:
|
|
311
|
+
If ``n_parallel`` >= 2 and ``fem`` is a subclass of ``FemtetInterface``, the ``strictly_pid_specify`` of subprocess is set to ``False``.
|
|
312
|
+
So **it is recommended to close all other Femtet processes before running.**
|
|
313
|
+
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
# 共通引数
|
|
317
|
+
self.opt.n_trials = n_trials
|
|
318
|
+
self.opt.timeout = timeout
|
|
319
|
+
|
|
320
|
+
# クラスターの設定
|
|
321
|
+
self.opt.is_cluster = self.scheduler_address is not None
|
|
322
|
+
if self.opt.is_cluster:
|
|
323
|
+
# 既存のクラスターに接続
|
|
324
|
+
logger.info('Connecting to existing cluster.')
|
|
325
|
+
self.client = Client(self.scheduler_address)
|
|
326
|
+
|
|
327
|
+
# 最適化タスクを振り分ける worker を指定
|
|
328
|
+
subprocess_indices = list(range(n_parallel))
|
|
329
|
+
worker_addresses = list(self.client.nthreads().keys())
|
|
330
|
+
|
|
331
|
+
# monitor worker の設定
|
|
332
|
+
logger.info('Launching monitor server. This may take a few seconds.')
|
|
333
|
+
self.monitor_process_worker_name = datetime.datetime.now().strftime("Monitor-%Y%m%d-%H%M%S")
|
|
334
|
+
current_n_workers = len(self.client.nthreads().keys())
|
|
335
|
+
from dask.distributed import Worker
|
|
336
|
+
Worker(scheduler_ip=self.client.scheduler.address, nthreads=1, name=self.monitor_process_worker_name)
|
|
337
|
+
|
|
338
|
+
# monitor 用 worker が増えるまで待つ
|
|
339
|
+
self.client.wait_for_workers(n_workers=current_n_workers + 1)
|
|
340
|
+
|
|
341
|
+
else:
|
|
342
|
+
# ローカルクラスターを構築
|
|
343
|
+
logger.info('Launching single machine cluster. This may take tens of seconds.')
|
|
344
|
+
cluster = LocalCluster(processes=True, n_workers=n_parallel,
|
|
345
|
+
threads_per_worker=1) # n_parallel = n_parallel - 1 + 1; main 分減らし、monitor 分増やす
|
|
346
|
+
self.client = Client(cluster, direct_to_workers=False)
|
|
347
|
+
self.scheduler_address = self.client.scheduler.address
|
|
348
|
+
|
|
349
|
+
# 最適化タスクを振り分ける worker を指定
|
|
350
|
+
subprocess_indices = list(range(n_parallel))[1:]
|
|
351
|
+
worker_addresses = list(self.client.nthreads().keys())
|
|
352
|
+
|
|
353
|
+
# monitor worker の設定
|
|
354
|
+
self.monitor_process_worker_name = worker_addresses[0]
|
|
355
|
+
worker_addresses[0] = 'Main'
|
|
356
|
+
|
|
357
|
+
# Femtet 特有の処理
|
|
358
|
+
metadata = None
|
|
359
|
+
if isinstance(self.fem, FemtetInterface):
|
|
360
|
+
metadata = json.dumps(
|
|
361
|
+
dict(
|
|
362
|
+
femprj_path=self.fem.original_femprj_path,
|
|
363
|
+
model_name=self.fem.model_name
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# actor の設定
|
|
368
|
+
self.status = OptimizationStatus(self.client)
|
|
369
|
+
self.worker_status_list = [OptimizationStatus(self.client, name) for name in worker_addresses] # tqdm 検討
|
|
370
|
+
self.status.set(OptimizationStatus.SETTING_UP)
|
|
371
|
+
self.history = History(
|
|
372
|
+
self.history_path,
|
|
373
|
+
self.opt.parameters['name'].to_list(),
|
|
374
|
+
list(self.opt.objectives.keys()),
|
|
375
|
+
list(self.opt.constraints.keys()),
|
|
376
|
+
self.client,
|
|
377
|
+
metadata,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# launch monitor
|
|
381
|
+
self.monitor_process_future = self.client.submit(
|
|
382
|
+
# func
|
|
383
|
+
_start_monitor_server,
|
|
384
|
+
# args
|
|
385
|
+
self.history,
|
|
386
|
+
self.status,
|
|
387
|
+
worker_addresses,
|
|
388
|
+
self.worker_status_list,
|
|
389
|
+
# kwargs
|
|
390
|
+
**self.monitor_server_kwargs,
|
|
391
|
+
# kwargs of submit
|
|
392
|
+
workers=self.monitor_process_worker_name, # if invalid arg,
|
|
393
|
+
allow_other_workers=False
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# fem
|
|
397
|
+
self.fem._setup_before_parallel(self.client)
|
|
398
|
+
|
|
399
|
+
# opt
|
|
400
|
+
self.opt.fem_class = type(self.fem)
|
|
401
|
+
self.opt.fem_kwargs = self.fem.kwargs
|
|
402
|
+
self.opt.entire_status = self.status
|
|
403
|
+
self.opt.history = self.history
|
|
404
|
+
self.opt._setup_before_parallel()
|
|
405
|
+
|
|
406
|
+
# クラスターでの計算開始
|
|
407
|
+
self.status.set(OptimizationStatus.LAUNCHING_FEM)
|
|
408
|
+
start = time()
|
|
409
|
+
calc_futures = self.client.map(
|
|
410
|
+
self.opt._run,
|
|
411
|
+
subprocess_indices,
|
|
412
|
+
[self.worker_status_list] * len(subprocess_indices),
|
|
413
|
+
[wait_setup] * len(subprocess_indices),
|
|
414
|
+
workers=worker_addresses,
|
|
415
|
+
allow_other_workers=False,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
t_main = None
|
|
419
|
+
if not self.opt.is_cluster:
|
|
420
|
+
# ローカルプロセスでの計算(opt._main 相当の処理)
|
|
421
|
+
subprocess_idx = 0
|
|
422
|
+
|
|
423
|
+
# set_fem
|
|
424
|
+
self.opt.fem = self.fem
|
|
425
|
+
self.opt._reconstruct_fem(skip_reconstruct=True)
|
|
426
|
+
|
|
427
|
+
t_main = Thread(
|
|
428
|
+
target=self.opt._run,
|
|
429
|
+
args=(
|
|
430
|
+
subprocess_idx,
|
|
431
|
+
self.worker_status_list,
|
|
432
|
+
wait_setup,
|
|
433
|
+
),
|
|
434
|
+
kwargs=dict(
|
|
435
|
+
skip_set_fem=True,
|
|
436
|
+
)
|
|
437
|
+
)
|
|
438
|
+
t_main.start()
|
|
439
|
+
|
|
440
|
+
# save history
|
|
441
|
+
def save_history():
|
|
442
|
+
while True:
|
|
443
|
+
sleep(2)
|
|
444
|
+
try:
|
|
445
|
+
self.history.save()
|
|
446
|
+
except PermissionError:
|
|
447
|
+
logger.warning(f'{self.history.path} が使用中のため書き込みできません。プログラム終了までにこのファイルを解放してください。履歴データが失われます。')
|
|
448
|
+
if self.status.get() >= OptimizationStatus.TERMINATED:
|
|
449
|
+
break
|
|
450
|
+
|
|
451
|
+
t_save_history = Thread(target=save_history)
|
|
452
|
+
t_save_history.start()
|
|
453
|
+
|
|
454
|
+
# 終了を待つ
|
|
455
|
+
self.client.gather(calc_futures)
|
|
456
|
+
if not self.opt.is_cluster: # 既存の fem を使っているならそれも待つ
|
|
457
|
+
if t_main is not None:
|
|
458
|
+
t_main.join()
|
|
459
|
+
self.status.set(OptimizationStatus.TERMINATED)
|
|
460
|
+
end = time()
|
|
461
|
+
|
|
462
|
+
# 一応
|
|
463
|
+
t_save_history.join()
|
|
464
|
+
|
|
465
|
+
logger.info(f'計算が終了しました. 実行時間は {int(end - start)} 秒でした。ウィンドウを閉じると終了します.')
|
|
466
|
+
logger.info(f'結果は{self.history.path}を確認してください.')
|
|
467
|
+
|
|
468
|
+
def terminate_all(self):
|
|
469
|
+
"""Try to terminate all launched processes.
|
|
470
|
+
|
|
471
|
+
If distributed computing, Scheduler and Workers will NOT be terminated.
|
|
472
|
+
|
|
473
|
+
"""
|
|
474
|
+
|
|
475
|
+
# monitor が terminated 状態で少なくとも一度更新されなければ running のまま固まる
|
|
476
|
+
sleep(1)
|
|
477
|
+
|
|
478
|
+
# terminate monitor process
|
|
479
|
+
self.status.set(OptimizationStatus.TERMINATE_ALL)
|
|
480
|
+
logger.info(self.monitor_process_future.result())
|
|
481
|
+
sleep(1)
|
|
482
|
+
|
|
483
|
+
# terminate actors
|
|
484
|
+
self.client.cancel(self.history._future, force=True)
|
|
485
|
+
self.client.cancel(self.status._future, force=True)
|
|
486
|
+
for worker_status in self.worker_status_list:
|
|
487
|
+
self.client.cancel(worker_status._future, force=True)
|
|
488
|
+
logger.info('Terminate actors.')
|
|
489
|
+
sleep(1)
|
|
490
|
+
|
|
491
|
+
# これがないと dash app が落ちないとか問題あるの?
|
|
492
|
+
|
|
493
|
+
# terminate monitor worker
|
|
494
|
+
n_workers = len(self.client.nthreads())
|
|
495
|
+
|
|
496
|
+
found_worker_dict = self.client.retire_workers(
|
|
497
|
+
names=[self.monitor_process_worker_name], # name
|
|
498
|
+
close_workers=True,
|
|
499
|
+
remove=True,
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
if len(found_worker_dict) == 0:
|
|
503
|
+
found_worker_dict = self.client.retire_workers(
|
|
504
|
+
workers=[self.monitor_process_worker_name], # address
|
|
505
|
+
close_workers=True,
|
|
506
|
+
remove=True,
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
if len(found_worker_dict) > 0:
|
|
510
|
+
while n_workers == len(self.client.nthreads()):
|
|
511
|
+
sleep(1)
|
|
512
|
+
logger.info('Terminate monitor processes worker.')
|
|
513
|
+
sleep(1)
|
|
514
|
+
else:
|
|
515
|
+
logger.warn('Monitor process worker not found.')
|
|
516
|
+
|
|
517
|
+
# close scheduler, other workers(, cluster)
|
|
518
|
+
self.client.close()
|
|
519
|
+
while self.client.scheduler is not None:
|
|
520
|
+
sleep(1)
|
|
521
|
+
logger.info('Terminate client.')
|
|
522
|
+
|
|
523
|
+
# close FEM (if specified to quit when deconstruct)
|
|
524
|
+
del self.fem
|
|
525
|
+
logger.info('Terminate FEM.')
|
|
526
|
+
|
|
527
|
+
# terminate dask relative processes.
|
|
528
|
+
if not self.opt.is_cluster:
|
|
529
|
+
self.client.shutdown()
|
|
530
|
+
logger.info('Terminate all relative processes.')
|
|
531
|
+
sleep(3)
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _start_monitor_server(
|
|
535
|
+
history,
|
|
536
|
+
status,
|
|
537
|
+
worker_addresses,
|
|
538
|
+
worker_status_list,
|
|
539
|
+
host=None,
|
|
540
|
+
port=None,
|
|
541
|
+
):
|
|
542
|
+
monitor = ProcessMonitorApp(history, status, worker_addresses, worker_status_list)
|
|
543
|
+
monitor.start_server(host, port)
|
|
544
|
+
return 'Exit monitor server process gracefully'
|