PyFemtet 0.3.3__tar.gz → 0.3.5__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.3 → pyfemtet-0.3.5}/PKG-INFO +1 -1
- pyfemtet-0.3.5/pyfemtet/__init__.py +1 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/dispatch_extensions.py +29 -9
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/logger.py +4 -2
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/opt/base.py +195 -139
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/opt/interface.py +9 -4
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyproject.toml +1 -1
- pyfemtet-0.3.3/pyfemtet/__init__.py +0 -1
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/LICENSE +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/README.md +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.femprj +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.prt +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.py +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/FemtetPJTSample/gau_ex08_parametric.femprj +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/FemtetPJTSample/her_ex40_parametric.py +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/_old/_SW_Femtet.py +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/core.py +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/opt/_FemtetWithNX/update_model.py +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/opt/__init__.py +0 -0
- {pyfemtet-0.3.3 → pyfemtet-0.3.5}/pyfemtet/opt/monitor.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.5"
|
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
from time import time, sleep
|
|
5
5
|
from multiprocessing import current_process
|
|
6
6
|
|
|
7
|
+
from tqdm import tqdm
|
|
7
8
|
import psutil
|
|
8
9
|
from dask.distributed import Lock
|
|
9
10
|
|
|
@@ -15,6 +16,7 @@ from femtetutils import util
|
|
|
15
16
|
|
|
16
17
|
from multiprocessing.context import BaseContext, SpawnProcess, _concrete_contexts
|
|
17
18
|
from multiprocessing.process import _children, _cleanup
|
|
19
|
+
from multiprocessing.managers import SyncManager
|
|
18
20
|
|
|
19
21
|
import logging
|
|
20
22
|
from .logger import get_logger
|
|
@@ -138,11 +140,16 @@ def _get_pids(process_name):
|
|
|
138
140
|
return pids
|
|
139
141
|
|
|
140
142
|
|
|
141
|
-
def
|
|
143
|
+
def _get_subprocess_log_prefix():
|
|
144
|
+
return f'({current_process().name}) '
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def launch_and_dispatch_femtet(timeout=DISPATCH_TIMEOUT, strict_pid_specify=True) -> Tuple[IFemtet, int]:
|
|
142
148
|
"""Launch Femtet by new process and connect to it.
|
|
143
149
|
|
|
144
150
|
Args:
|
|
145
151
|
timeout (int or float, optional): Seconds to wait for connection. Defaults to DISPATCH_TIMEOUT.
|
|
152
|
+
strict_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.
|
|
146
153
|
|
|
147
154
|
Raises:
|
|
148
155
|
FemtetNotFoundException: Launched Femtet is not found for some reason (i.e. failed to launch Femtet).
|
|
@@ -151,11 +158,26 @@ def launch_and_dispatch_femtet(timeout=DISPATCH_TIMEOUT) -> Tuple[IFemtet, int]:
|
|
|
151
158
|
Returns:
|
|
152
159
|
Tuple[IFemtet, int]:
|
|
153
160
|
"""
|
|
161
|
+
# launch femtet
|
|
154
162
|
util.execute_femtet()
|
|
155
163
|
pid = util.get_last_executed_femtet_process_id()
|
|
156
164
|
logger.debug(f'Target pid is {pid}.')
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
for _ in tqdm(range(5), 'wait for launch femtet...'):
|
|
166
|
+
sleep(1)
|
|
167
|
+
|
|
168
|
+
# dispatch femtet
|
|
169
|
+
if strict_pid_specify:
|
|
170
|
+
Femtet, pid = dispatch_specific_femtet(pid, timeout)
|
|
171
|
+
else:
|
|
172
|
+
# worker process なら排他処理する
|
|
173
|
+
try:
|
|
174
|
+
with Lock('simply-dispatch-femtet'):
|
|
175
|
+
Femtet, pid = dispatch_femtet()
|
|
176
|
+
except RuntimeError as e:
|
|
177
|
+
if "distributed.lock.Lock" in str(e):
|
|
178
|
+
Femtet, pid = dispatch_femtet()
|
|
179
|
+
else:
|
|
180
|
+
raise e
|
|
159
181
|
return Femtet, pid
|
|
160
182
|
|
|
161
183
|
|
|
@@ -207,9 +229,6 @@ def dispatch_femtet(timeout=DISPATCH_TIMEOUT, subprocess_log_prefix='') -> Tuple
|
|
|
207
229
|
return Femtet, pid
|
|
208
230
|
|
|
209
231
|
|
|
210
|
-
def _get_subprocess_log_prefix():
|
|
211
|
-
return f'({current_process().name}) '
|
|
212
|
-
|
|
213
232
|
|
|
214
233
|
def _block_other_femtets(
|
|
215
234
|
target_pid,
|
|
@@ -386,10 +405,11 @@ def dispatch_specific_femtet_core(pid, timeout=DISPATCH_TIMEOUT) -> Tuple[IFemte
|
|
|
386
405
|
if not (pid in pids):
|
|
387
406
|
raise FemtetNotFoundException(f"Femtet (pid = {pid}) doesn't exist.")
|
|
388
407
|
|
|
408
|
+
logger.info('Searching specific Femtet...')
|
|
409
|
+
|
|
389
410
|
# 子プロセスの準備
|
|
390
411
|
# with Manager() as manager:
|
|
391
|
-
|
|
392
|
-
m = SyncManager(ctx=_NestableSpawnContext())
|
|
412
|
+
m = SyncManager(ctx=_NestableSpawnContext()) # このへんが時間のかかる処理
|
|
393
413
|
m.start()
|
|
394
414
|
with m as manager:
|
|
395
415
|
# フラグの準備
|
|
@@ -402,7 +422,7 @@ def dispatch_specific_femtet_core(pid, timeout=DISPATCH_TIMEOUT) -> Tuple[IFemte
|
|
|
402
422
|
# 目的以外の Femtet をブロックする子プロセスを開始
|
|
403
423
|
logger.debug('Start subprocess to block Femtet other than target pid.')
|
|
404
424
|
processes = []
|
|
405
|
-
for subprocess_id in range(len(pids)):
|
|
425
|
+
for subprocess_id in tqdm(range(len(pids)), 'Specifying connection...'):
|
|
406
426
|
p = _NestableSpawnProcess(
|
|
407
427
|
# p = Process(
|
|
408
428
|
target=_block_other_femtets,
|
|
@@ -12,8 +12,10 @@ def _get_worker_name_as_prefix():
|
|
|
12
12
|
name = '(Main) '
|
|
13
13
|
try:
|
|
14
14
|
worker = get_worker()
|
|
15
|
-
#
|
|
16
|
-
|
|
15
|
+
if isinstance(worker.name, str): # local なら index, cluster なら tcp address
|
|
16
|
+
name = f'({worker.name}) '
|
|
17
|
+
else:
|
|
18
|
+
name = f'(Sub{worker.name}) '
|
|
17
19
|
except ValueError:
|
|
18
20
|
pass
|
|
19
21
|
return name
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import List, Iterable
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
5
|
import datetime
|
|
@@ -65,13 +65,13 @@ def generate_lhs(bounds: List[List[float]], seed: int or None = None) -> np.ndar
|
|
|
65
65
|
LIMIT = 100
|
|
66
66
|
|
|
67
67
|
def is_prime(p):
|
|
68
|
-
for
|
|
69
|
-
if p %
|
|
68
|
+
for j in range(2, p):
|
|
69
|
+
if p % j == 0:
|
|
70
70
|
return False
|
|
71
71
|
return True
|
|
72
72
|
|
|
73
|
-
def get_prime(
|
|
74
|
-
for p in range(
|
|
73
|
+
def get_prime(_minimum):
|
|
74
|
+
for p in range(_minimum, LIMIT):
|
|
75
75
|
if is_prime(p):
|
|
76
76
|
return p
|
|
77
77
|
|
|
@@ -252,7 +252,7 @@ class Objective(Function):
|
|
|
252
252
|
super().__init__(fun, name, args, kwargs)
|
|
253
253
|
|
|
254
254
|
def convert(self, value: float):
|
|
255
|
-
"""Converts an evaluation value to the value of
|
|
255
|
+
"""Converts an evaluation value to the value of objective function based on the specified direction.
|
|
256
256
|
|
|
257
257
|
When direction is `'minimize'`, ``value`` is calculated.
|
|
258
258
|
When direction is `'maximize'`, ``-value`` is calculated.
|
|
@@ -329,9 +329,8 @@ class History:
|
|
|
329
329
|
"""Class for managing the history of optimization results.
|
|
330
330
|
|
|
331
331
|
Attributes:
|
|
332
|
-
path (str): The path to the history file.
|
|
333
|
-
|
|
334
|
-
data (pd.DataFrame): The DataFrame object for storing the entire history.
|
|
332
|
+
path (str): The path to the history csv file.
|
|
333
|
+
is_restart (bool): The main session is restarted or not.
|
|
335
334
|
param_names (list): The names of the parameters in the study.
|
|
336
335
|
obj_names (list): The names of the objectives in the study.
|
|
337
336
|
cns_names (list): The names of the constraints in the study.
|
|
@@ -350,13 +349,15 @@ class History:
|
|
|
350
349
|
self.is_restart = False
|
|
351
350
|
self._future = client.submit(HistoryDfCore, actor=True)
|
|
352
351
|
self._actor_data = self._future.result()
|
|
352
|
+
self.tmp_data = pd.DataFrame()
|
|
353
353
|
self.param_names = []
|
|
354
354
|
self.obj_names = []
|
|
355
355
|
self.cns_names = []
|
|
356
356
|
|
|
357
357
|
# path が存在すれば dataframe を読み込む
|
|
358
358
|
if os.path.isfile(self.path):
|
|
359
|
-
self.
|
|
359
|
+
self.tmp_data = pd.read_csv(self.path, encoding='shift-jis')
|
|
360
|
+
self.actor_data = self.tmp_data
|
|
360
361
|
self.is_restart = True
|
|
361
362
|
|
|
362
363
|
@property
|
|
@@ -406,6 +407,8 @@ class History:
|
|
|
406
407
|
raise Exception(f'読み込んだ history と問題の設定が異なります. \n\n読み込まれた設定:\n{list(self.actor_data.columns)}\n\n現在の設定:\n{columns}')
|
|
407
408
|
|
|
408
409
|
else:
|
|
410
|
+
for column in columns:
|
|
411
|
+
self.tmp_data[column] = None
|
|
409
412
|
# actor_data は actor 経由の getter property なので self.data[column] = ... とやっても
|
|
410
413
|
# actor には変更が反映されない. 以下同様
|
|
411
414
|
tmp = self.actor_data
|
|
@@ -445,27 +448,25 @@ class History:
|
|
|
445
448
|
with Lock('calc-history'):
|
|
446
449
|
# append
|
|
447
450
|
if len(self.actor_data) == 0:
|
|
448
|
-
self.
|
|
451
|
+
self.tmp_data = pd.DataFrame([row], columns=self.actor_data.columns)
|
|
449
452
|
else:
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
self.actor_data = tmp
|
|
453
|
+
self.tmp_data = self.actor_data
|
|
454
|
+
self.tmp_data.loc[len(self.tmp_data)] = row
|
|
453
455
|
|
|
454
456
|
# calc
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
self.
|
|
458
|
-
self.
|
|
459
|
-
self._calc_hypervolume(objectives)
|
|
457
|
+
self.tmp_data['trial'] = np.arange(len(self.tmp_data)) + 1 # 1 始まり
|
|
458
|
+
self._calc_non_domi(objectives) # update self.tmp_data
|
|
459
|
+
self._calc_hypervolume(objectives) # update self.tmp_data
|
|
460
|
+
self.actor_data = self.tmp_data
|
|
460
461
|
|
|
461
462
|
def _calc_non_domi(self, objectives):
|
|
462
463
|
|
|
463
464
|
# 目的関数の履歴を取り出してくる
|
|
464
|
-
solution_set = self.
|
|
465
|
+
solution_set = self.tmp_data[self.obj_names]
|
|
465
466
|
|
|
466
467
|
# 最小化問題の座標空間に変換する
|
|
467
468
|
for name, objective in objectives.items():
|
|
468
|
-
solution_set[name] = solution_set[name].map(objective.convert)
|
|
469
|
+
solution_set.loc[:, name] = solution_set[name].map(objective.convert)
|
|
469
470
|
|
|
470
471
|
# 非劣解の計算
|
|
471
472
|
non_domi = []
|
|
@@ -473,15 +474,11 @@ class History:
|
|
|
473
474
|
non_domi.append((row > solution_set).product(axis=1).sum(axis=0) == 0)
|
|
474
475
|
|
|
475
476
|
# 非劣解の登録
|
|
476
|
-
|
|
477
|
-
tmp['non_domi'] = non_domi
|
|
478
|
-
self.actor_data = tmp
|
|
479
|
-
|
|
480
|
-
del solution_set
|
|
477
|
+
self.tmp_data['non_domi'] = non_domi
|
|
481
478
|
|
|
482
479
|
def _calc_hypervolume(self, objectives):
|
|
483
|
-
#
|
|
484
|
-
df = self.
|
|
480
|
+
# タイピングが面倒
|
|
481
|
+
df = self.tmp_data
|
|
485
482
|
|
|
486
483
|
# パレート集合の抽出
|
|
487
484
|
idx = df['non_domi'].values
|
|
@@ -540,15 +537,12 @@ class History:
|
|
|
540
537
|
# dominated の行に対して、上に見ていって
|
|
541
538
|
# 最初に見つけた non-domi 行の hypervolume の値を割り当てます
|
|
542
539
|
for i in range(len(df)):
|
|
543
|
-
if df.loc[i, 'non_domi']
|
|
540
|
+
if not df.loc[i, 'non_domi']:
|
|
544
541
|
try:
|
|
545
542
|
df.loc[i, 'hypervolume'] = df.loc[:i][df.loc[:i]['non_domi']].iloc[-1]['hypervolume']
|
|
546
543
|
except IndexError:
|
|
547
544
|
df.loc[i, 'hypervolume'] = 0
|
|
548
545
|
|
|
549
|
-
# ローカルの df を worker に戻す
|
|
550
|
-
self.actor_data = df
|
|
551
|
-
|
|
552
546
|
|
|
553
547
|
class OptimizationStatusActor:
|
|
554
548
|
status_int = -1
|
|
@@ -568,35 +562,41 @@ class OptimizationStatus:
|
|
|
568
562
|
UNDEFINED = -1
|
|
569
563
|
INITIALIZING = 0
|
|
570
564
|
SETTING_UP = 10
|
|
571
|
-
|
|
572
|
-
|
|
565
|
+
LAUNCHING_FEM = 20
|
|
566
|
+
WAIT_OTHER_WORKERS = 22
|
|
567
|
+
# WAIT_1ST = 25
|
|
573
568
|
RUNNING = 30
|
|
574
569
|
INTERRUPTING = 40
|
|
575
570
|
TERMINATED = 50
|
|
576
571
|
TERMINATE_ALL = 60
|
|
577
572
|
|
|
578
|
-
def __init__(self, client):
|
|
573
|
+
def __init__(self, client, name='entire'):
|
|
579
574
|
self._future = client.submit(OptimizationStatusActor, actor=True)
|
|
580
575
|
self._actor = self._future.result()
|
|
576
|
+
self.name = name
|
|
581
577
|
self.set(self.INITIALIZING)
|
|
582
578
|
|
|
583
579
|
@classmethod
|
|
584
|
-
def const_to_str(
|
|
585
|
-
if status_const ==
|
|
586
|
-
if status_const ==
|
|
587
|
-
if status_const ==
|
|
588
|
-
if status_const ==
|
|
589
|
-
if status_const ==
|
|
590
|
-
if status_const ==
|
|
591
|
-
if status_const ==
|
|
592
|
-
if status_const ==
|
|
593
|
-
if status_const ==
|
|
580
|
+
def const_to_str(cls, status_const):
|
|
581
|
+
if status_const == cls.UNDEFINED: return 'Undefined'
|
|
582
|
+
if status_const == cls.INITIALIZING: return 'Initializing'
|
|
583
|
+
if status_const == cls.SETTING_UP: return 'Setting up'
|
|
584
|
+
if status_const == cls.LAUNCHING_FEM: return 'Launching FEM processes'
|
|
585
|
+
if status_const == cls.WAIT_OTHER_WORKERS: return 'Waiting for launching other processes'
|
|
586
|
+
# if status_const == cls.WAIT_1ST: return 'Running and waiting for 1st FEM result.'
|
|
587
|
+
if status_const == cls.RUNNING: return 'Running'
|
|
588
|
+
if status_const == cls.INTERRUPTING: return 'Interrupting'
|
|
589
|
+
if status_const == cls.TERMINATED: return 'Terminated'
|
|
590
|
+
if status_const == cls.TERMINATE_ALL: return 'Terminate_all'
|
|
594
591
|
|
|
595
592
|
def set(self, status_const):
|
|
596
|
-
# self._actor.status_int = status_const
|
|
597
|
-
# self._actor.status = self.const_to_str(status_const)
|
|
598
593
|
self._actor.set(status_const, self.const_to_str(status_const)).result()
|
|
599
|
-
|
|
594
|
+
msg = f'---{self.const_to_str(status_const)}---'
|
|
595
|
+
if (status_const == self.INITIALIZING) and (self.name != 'entire'):
|
|
596
|
+
msg += f' (for Worker {self.name})'
|
|
597
|
+
if self.name == 'entire':
|
|
598
|
+
msg = '(entire) ' + msg
|
|
599
|
+
logger.info(msg)
|
|
600
600
|
|
|
601
601
|
def get(self):
|
|
602
602
|
return self._actor.status_int
|
|
@@ -614,8 +614,9 @@ class AbstractOptimizer(ABC):
|
|
|
614
614
|
self.parameters = pd.DataFrame()
|
|
615
615
|
self.objectives = dict()
|
|
616
616
|
self.constraints = dict()
|
|
617
|
-
self.
|
|
618
|
-
self.history = None #
|
|
617
|
+
self.entire_status = None # actor
|
|
618
|
+
self.history = None # actor
|
|
619
|
+
self.worker_status = None # actor
|
|
619
620
|
self.message = ''
|
|
620
621
|
self.seed = None
|
|
621
622
|
self.timeout = None
|
|
@@ -623,9 +624,7 @@ class AbstractOptimizer(ABC):
|
|
|
623
624
|
self.is_cluster = False
|
|
624
625
|
|
|
625
626
|
def f(self, x):
|
|
626
|
-
|
|
627
|
-
if self.status.get() == OptimizationStatus.LAUNCHING:
|
|
628
|
-
self.status.set(OptimizationStatus.WAIT_1ST)
|
|
627
|
+
# interruption の実装は具象クラスに任せる
|
|
629
628
|
|
|
630
629
|
# x の更新
|
|
631
630
|
self.parameters['value'] = x
|
|
@@ -654,14 +653,13 @@ class AbstractOptimizer(ABC):
|
|
|
654
653
|
self.message
|
|
655
654
|
)
|
|
656
655
|
|
|
657
|
-
if self.status.get() == OptimizationStatus.WAIT_1ST:
|
|
658
|
-
self.status.set(OptimizationStatus.RUNNING)
|
|
659
|
-
|
|
660
656
|
logger.debug('history.record end')
|
|
661
657
|
return np.array(y), np.array(_y), np.array(c)
|
|
662
658
|
|
|
663
|
-
def set_fem(self):
|
|
664
|
-
|
|
659
|
+
def set_fem(self, skip_reconstruct=False):
|
|
660
|
+
# restore fem
|
|
661
|
+
if not skip_reconstruct:
|
|
662
|
+
self.fem = self.fem_class(**self.fem_kwargs)
|
|
665
663
|
|
|
666
664
|
# COM 定数の restore
|
|
667
665
|
for obj in self.objectives.values():
|
|
@@ -694,12 +692,66 @@ class AbstractOptimizer(ABC):
|
|
|
694
692
|
else:
|
|
695
693
|
raise ValueError('get_parameter() got invalid format: {format}')
|
|
696
694
|
|
|
697
|
-
def
|
|
698
|
-
self.
|
|
699
|
-
|
|
695
|
+
def check_interruption(self):
|
|
696
|
+
if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
|
|
697
|
+
self.worker_status.set(OptimizationStatus.INTERRUPTING)
|
|
698
|
+
self.finalize()
|
|
699
|
+
return True
|
|
700
|
+
else:
|
|
701
|
+
return False
|
|
702
|
+
|
|
703
|
+
def finalize(self):
|
|
704
|
+
del self.fem
|
|
705
|
+
self.worker_status.set(OptimizationStatus.TERMINATED)
|
|
706
|
+
|
|
707
|
+
def _main(
|
|
708
|
+
self,
|
|
709
|
+
subprocess_idx,
|
|
710
|
+
worker_status_list,
|
|
711
|
+
wait_setup,
|
|
712
|
+
skip_set_fem=False,
|
|
713
|
+
) -> None:
|
|
714
|
+
|
|
715
|
+
# 自分の worker_status の取得
|
|
716
|
+
self.worker_status = worker_status_list[subprocess_idx]
|
|
717
|
+
self.worker_status.set(OptimizationStatus.LAUNCHING_FEM)
|
|
718
|
+
|
|
719
|
+
if self.check_interruption():
|
|
720
|
+
return None
|
|
721
|
+
|
|
722
|
+
# set_fem をはじめ、終了したらそれを示す
|
|
723
|
+
if not skip_set_fem: # なくても動く??
|
|
724
|
+
self.set_fem()
|
|
725
|
+
self.worker_status.set(OptimizationStatus.WAIT_OTHER_WORKERS)
|
|
726
|
+
|
|
727
|
+
# wait_setup or not
|
|
728
|
+
if wait_setup:
|
|
729
|
+
while True:
|
|
730
|
+
if self.check_interruption():
|
|
731
|
+
return None
|
|
732
|
+
# 他のすべての worker_status が wait 以上になったら break
|
|
733
|
+
if all([ws.get() >= OptimizationStatus.WAIT_OTHER_WORKERS for ws in worker_status_list]):
|
|
734
|
+
break
|
|
735
|
+
sleep(1)
|
|
736
|
+
else:
|
|
737
|
+
if self.check_interruption():
|
|
738
|
+
return None
|
|
739
|
+
|
|
740
|
+
# set status running
|
|
741
|
+
if self.entire_status.get() < OptimizationStatus.RUNNING:
|
|
742
|
+
self.entire_status.set(OptimizationStatus.RUNNING)
|
|
743
|
+
self.worker_status.set(OptimizationStatus.RUNNING)
|
|
744
|
+
|
|
745
|
+
# run and finalize
|
|
746
|
+
try:
|
|
747
|
+
self.main(subprocess_idx)
|
|
748
|
+
finally:
|
|
749
|
+
self.finalize()
|
|
750
|
+
|
|
751
|
+
return None
|
|
700
752
|
|
|
701
753
|
@abstractmethod
|
|
702
|
-
def main(self, subprocess_idx: int):
|
|
754
|
+
def main(self, subprocess_idx: int = 0) -> None:
|
|
703
755
|
"""Called from dask worker."""
|
|
704
756
|
pass
|
|
705
757
|
|
|
@@ -731,7 +783,7 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
731
783
|
def _objective(self, trial):
|
|
732
784
|
|
|
733
785
|
# 中断の確認 (FAIL loop に陥る対策)
|
|
734
|
-
if self.
|
|
786
|
+
if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
|
|
735
787
|
trial.study.stop() # 現在実行中の trial を最後にする
|
|
736
788
|
return None # set TrialState FAIL
|
|
737
789
|
|
|
@@ -772,7 +824,7 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
772
824
|
print(self.get_parameter('dict'))
|
|
773
825
|
|
|
774
826
|
# 中断の確認 (解析中に interrupt されている場合対策)
|
|
775
|
-
if self.
|
|
827
|
+
if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
|
|
776
828
|
trial.study.stop() # 現在実行中の trial を最後にする
|
|
777
829
|
return None # set TrialState FAIL
|
|
778
830
|
|
|
@@ -789,7 +841,7 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
789
841
|
trial.set_user_attr('constraint', _c)
|
|
790
842
|
|
|
791
843
|
# 中断の確認 (解析中に interrupt されている場合対策)
|
|
792
|
-
if self.
|
|
844
|
+
if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
|
|
793
845
|
trial.study.stop() # 現在実行中の trial を最後にする
|
|
794
846
|
return None # set TrialState FAIL
|
|
795
847
|
|
|
@@ -867,14 +919,14 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
867
919
|
# if is_restart, load study
|
|
868
920
|
else:
|
|
869
921
|
if not os.path.exists(storage_path):
|
|
870
|
-
|
|
922
|
+
msg = f'{storage_path} が見つかりません。'
|
|
923
|
+
msg += '.db ファイルは .csv ファイルと同じフォルダに生成されます。'
|
|
924
|
+
msg += 'クラスター解析の場合は、スケジューラを起動したフォルダに生成されます。'
|
|
925
|
+
raise FileNotFoundError(msg)
|
|
871
926
|
self.storage = optuna.integration.dask.DaskStorage(
|
|
872
927
|
f'sqlite:///{storage_path}',
|
|
873
928
|
)
|
|
874
929
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
930
|
def add_init_parameter(
|
|
879
931
|
self,
|
|
880
932
|
parameter: dict or Iterable,
|
|
@@ -895,7 +947,7 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
895
947
|
name = f'additional initial ({name})'
|
|
896
948
|
self.additional_initial_parameter.append([parameter, name])
|
|
897
949
|
|
|
898
|
-
def main(self, subprocess_idx):
|
|
950
|
+
def main(self, subprocess_idx=0):
|
|
899
951
|
"""Dask worker から呼ばれる関数"""
|
|
900
952
|
|
|
901
953
|
# (re)set random seed
|
|
@@ -925,27 +977,20 @@ class OptunaOptimizer(AbstractOptimizer):
|
|
|
925
977
|
callbacks=self.optimize_callbacks,
|
|
926
978
|
)
|
|
927
979
|
|
|
928
|
-
# finalize
|
|
929
|
-
del self.fem
|
|
930
|
-
|
|
931
980
|
|
|
932
981
|
class FEMOpt:
|
|
933
982
|
"""Base class to control FEM interface and optimizer.
|
|
934
983
|
|
|
935
984
|
Attributes:
|
|
936
|
-
fem (FEMInterface): The
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
seed (int or None): The random seed for reproducibility.
|
|
946
|
-
message(str) : Additional information or messages related to the optimization process
|
|
947
|
-
obj_values ([float]): A list to store objective values during optimization
|
|
948
|
-
cns_values ([float]): A list to store constraint values during optimization
|
|
985
|
+
fem (FEMInterface): The interface of FEM system.
|
|
986
|
+
client (Client): Dask client. For detail, see dask documentation.
|
|
987
|
+
scheduler_address (str or None): Dask scheduler address. If None, LocalCluster will be used.
|
|
988
|
+
status (OptimizationStatus): Entire process status. This contains dask actor.
|
|
989
|
+
history(History): History of optimization process. This contains dask actor.
|
|
990
|
+
history_path (str): The path to the history (.csv) file.
|
|
991
|
+
worker_status_list([OptimizationStatus]): Process status of each dask worker.
|
|
992
|
+
monitor_process_future(Future): Future of monitor server process. This is dask future.
|
|
993
|
+
monitor_server_kwargs(dict): Monitor server parameter. Currently, the valid arguments are hostname and port.
|
|
949
994
|
|
|
950
995
|
"""
|
|
951
996
|
|
|
@@ -959,7 +1004,7 @@ class FEMOpt:
|
|
|
959
1004
|
"""Initializes an FEMOpt instance.
|
|
960
1005
|
|
|
961
1006
|
Args:
|
|
962
|
-
fem (FEMInterface, optional): The finite element method interface. Defaults to None. If None,
|
|
1007
|
+
fem (FEMInterface, optional): The finite element method interface. Defaults to None. If None, automatically set to FemtetInterface.
|
|
963
1008
|
opt (AbstractOptimizer):
|
|
964
1009
|
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.
|
|
965
1010
|
scheduler_address (str or None): If cluster processing, set this parameter like ``"tcp://xxx.xxx.xxx.xxx:xxxx"``.
|
|
@@ -986,10 +1031,12 @@ class FEMOpt:
|
|
|
986
1031
|
|
|
987
1032
|
# メンバーの宣言
|
|
988
1033
|
self.client = None
|
|
989
|
-
self.status = None
|
|
990
|
-
self.history = None
|
|
1034
|
+
self.status = None # actor
|
|
1035
|
+
self.history = None # actor
|
|
1036
|
+
self.worker_status_list = None # [actor]
|
|
991
1037
|
self.monitor_process_future = None
|
|
992
1038
|
self.monitor_server_kwargs = dict()
|
|
1039
|
+
self.monitor_process_worker_name = None
|
|
993
1040
|
|
|
994
1041
|
# multiprocess 時に pickle できないオブジェクト参照の削除
|
|
995
1042
|
def __getstate__(self):
|
|
@@ -1021,7 +1068,7 @@ class FEMOpt:
|
|
|
1021
1068
|
|
|
1022
1069
|
Args:
|
|
1023
1070
|
name (str): The name of the parameter.
|
|
1024
|
-
initial_value (float or None, optional): The initial value of the parameter. Defaults to None. If None, try to get
|
|
1071
|
+
initial_value (float or None, optional): The initial value of the parameter. Defaults to None. If None, try to get initial value from FEMInterface.
|
|
1025
1072
|
lower_bound (float or None, optional): The lower bound of the parameter. Defaults to None. However, this argument is required for some algorithms.
|
|
1026
1073
|
upper_bound (float or None, optional): The upper bound of the parameter. Defaults to None. However, this argument is required for some algorithms.
|
|
1027
1074
|
memo (str, optional): Additional information about the parameter. Defaults to ''.
|
|
@@ -1198,16 +1245,16 @@ class FEMOpt:
|
|
|
1198
1245
|
n_trials=None,
|
|
1199
1246
|
n_parallel=1,
|
|
1200
1247
|
timeout=None,
|
|
1248
|
+
wait_setup=True,
|
|
1201
1249
|
):
|
|
1202
1250
|
"""Runs the main optimization process.
|
|
1203
1251
|
|
|
1204
1252
|
Args:
|
|
1205
|
-
n_trials (int or None): The number of trials. Defaults to None.
|
|
1206
|
-
n_parallel (int): The number of parallel processes. Defaults to 1.
|
|
1207
|
-
timeout (float or None): The maximum amount of time in seconds that each trial can run. Defaults to None.
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1253
|
+
n_trials (int or None, optional): The number of trials. Defaults to None.
|
|
1254
|
+
n_parallel (int, optional): The number of parallel processes. Defaults to 1.
|
|
1255
|
+
timeout (float or None, optional): The maximum amount of time in seconds that each trial can run. Defaults to None.
|
|
1256
|
+
wait_setup (bool, optional): Wait for all workers launching FEM system. Defaults to True.
|
|
1257
|
+
|
|
1211
1258
|
Tip:
|
|
1212
1259
|
If setup_monitor_server() is not executed, a local server for monitoring will be started at localhost:8080.
|
|
1213
1260
|
|
|
@@ -1224,37 +1271,46 @@ class FEMOpt:
|
|
|
1224
1271
|
|
|
1225
1272
|
"""
|
|
1226
1273
|
|
|
1274
|
+
# 共通引数
|
|
1275
|
+
self.opt.n_trials = n_trials
|
|
1276
|
+
self.opt.timeout = timeout
|
|
1227
1277
|
|
|
1228
|
-
#
|
|
1278
|
+
# クラスターの設定
|
|
1229
1279
|
self.opt.is_cluster = self.scheduler_address is not None
|
|
1230
1280
|
if self.opt.is_cluster:
|
|
1231
1281
|
# 既存のクラスターに接続
|
|
1232
1282
|
logger.info('Connecting to existing cluster.')
|
|
1233
1283
|
self.client = Client(self.scheduler_address)
|
|
1234
|
-
|
|
1235
1284
|
else:
|
|
1285
|
+
# ローカルクラスターを構築
|
|
1236
1286
|
logger.info('Launching single machine cluster. This may take tens of seconds.')
|
|
1237
1287
|
cluster = LocalCluster(processes=True)
|
|
1238
1288
|
self.client = Client(cluster, direct_to_workers=False)
|
|
1239
1289
|
self.scheduler_address = self.client.scheduler.address
|
|
1240
1290
|
|
|
1291
|
+
# タスクを振り分ける worker を指定
|
|
1292
|
+
subprocess_indices = list(range(n_parallel))
|
|
1293
|
+
if not self.opt.is_cluster:
|
|
1294
|
+
subprocess_indices = subprocess_indices[1:]
|
|
1295
|
+
worker_addresses = list(self.client.nthreads().keys())
|
|
1296
|
+
assert max(subprocess_indices) <= len(worker_addresses)-1, f'コア数{len(worker_addresses)}は不足しています。'
|
|
1297
|
+
worker_addresses = worker_addresses[:len(range(n_parallel))] # TODO: ノードごとに適度に振り分ける
|
|
1298
|
+
if not self.opt.is_cluster:
|
|
1299
|
+
worker_addresses[0] = 'Main'
|
|
1300
|
+
|
|
1241
1301
|
# monitor 用 worker を起動
|
|
1242
1302
|
logger.info('Launching monitor server. This may take a few seconds.')
|
|
1243
1303
|
self.monitor_process_worker_name = datetime.datetime.now().strftime("Monitor-%Y%m%d-%H%M%S")
|
|
1244
1304
|
cmd = f'dask worker {self.client.scheduler.address} --name {self.monitor_process_worker_name} --no-nanny'
|
|
1245
|
-
|
|
1246
|
-
Popen(cmd, shell=True)
|
|
1305
|
+
current_n_workers = len(self.client.nthreads().keys())
|
|
1306
|
+
Popen(cmd, shell=True) # , stdout=PIPE) --> cause stream error
|
|
1247
1307
|
|
|
1248
1308
|
# monitor 用 worker が増えるまで待つ
|
|
1249
|
-
|
|
1250
|
-
sleep(1)
|
|
1251
|
-
new_worker_addresses = list(self.client.has_what().keys())
|
|
1252
|
-
launched_worker_addresses = [w_adrs for w_adrs in new_worker_addresses if not w_adrs in worker_addresses]
|
|
1253
|
-
assert len(launched_worker_addresses) == 1
|
|
1254
|
-
launched_worker_address = launched_worker_addresses[0]
|
|
1309
|
+
self.client.wait_for_workers(n_workers=current_n_workers+1)
|
|
1255
1310
|
|
|
1256
1311
|
# actor の設定
|
|
1257
1312
|
self.status = OptimizationStatus(self.client)
|
|
1313
|
+
self.worker_status_list = [OptimizationStatus(self.client, name) for name in worker_addresses] # tqdm 検討
|
|
1258
1314
|
self.status.set(OptimizationStatus.SETTING_UP)
|
|
1259
1315
|
self.history = History(self.history_path, self.client)
|
|
1260
1316
|
self.history.init(
|
|
@@ -1263,17 +1319,13 @@ class FEMOpt:
|
|
|
1263
1319
|
list(self.opt.constraints.keys()),
|
|
1264
1320
|
)
|
|
1265
1321
|
|
|
1266
|
-
# 共通引数
|
|
1267
|
-
self.opt.n_trials = n_trials
|
|
1268
|
-
self.opt.timeout = timeout
|
|
1269
|
-
|
|
1270
1322
|
# fem
|
|
1271
1323
|
self.fem.setup_before_parallel(self.client)
|
|
1272
1324
|
|
|
1273
1325
|
# opt
|
|
1274
1326
|
self.opt.fem_class = type(self.fem)
|
|
1275
1327
|
self.opt.fem_kwargs = self.fem.kwargs
|
|
1276
|
-
self.opt.
|
|
1328
|
+
self.opt.entire_status = self.status
|
|
1277
1329
|
self.opt.history = self.history
|
|
1278
1330
|
self.opt.setup_before_parallel()
|
|
1279
1331
|
|
|
@@ -1282,39 +1334,42 @@ class FEMOpt:
|
|
|
1282
1334
|
start_monitor_server_forever,
|
|
1283
1335
|
self.history, self.status, # args
|
|
1284
1336
|
**self.monitor_server_kwargs, # kwargs
|
|
1285
|
-
workers=
|
|
1337
|
+
workers=self.monitor_process_worker_name, # if invalid arg,
|
|
1286
1338
|
allow_other_workers=False
|
|
1287
1339
|
)
|
|
1288
1340
|
|
|
1289
1341
|
# クラスターでの計算開始
|
|
1290
|
-
self.status.set(OptimizationStatus.
|
|
1342
|
+
self.status.set(OptimizationStatus.LAUNCHING_FEM)
|
|
1291
1343
|
start = time()
|
|
1292
|
-
|
|
1344
|
+
calc_futures = self.client.map(
|
|
1345
|
+
self.opt._main,
|
|
1346
|
+
subprocess_indices,
|
|
1347
|
+
[self.worker_status_list]*len(subprocess_indices),
|
|
1348
|
+
[wait_setup]*len(subprocess_indices),
|
|
1349
|
+
workers=worker_addresses,
|
|
1350
|
+
allow_other_workers=False,
|
|
1351
|
+
)
|
|
1293
1352
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
calc_futures = self.client.map(
|
|
1299
|
-
self.opt._main,
|
|
1300
|
-
subprocess_indices,
|
|
1301
|
-
workers=worker_addresses,
|
|
1302
|
-
allow_other_workers=False,
|
|
1303
|
-
)
|
|
1353
|
+
t_main = None
|
|
1354
|
+
if not self.opt.is_cluster:
|
|
1355
|
+
# ローカルプロセスでの計算(opt._main 相当の処理)
|
|
1356
|
+
subprocess_idx = 0
|
|
1304
1357
|
|
|
1305
|
-
|
|
1306
|
-
# ローカルなら既存の fem をひとつ使う
|
|
1307
|
-
calc_futures = self.client.map(self.opt._main, subprocess_indices[1:])
|
|
1308
|
-
|
|
1309
|
-
# ローカルプロセスでの計算開始
|
|
1310
|
-
# constants の restore (map の後なら大丈夫)
|
|
1311
|
-
# TODO: 最適化中に cluster を scale する場合はここに気を付ける
|
|
1312
|
-
for obj in self.opt.objectives.values():
|
|
1313
|
-
obj._restore_constants()
|
|
1314
|
-
for cns in self.opt.constraints.values():
|
|
1315
|
-
cns._restore_constants()
|
|
1358
|
+
# set_fem
|
|
1316
1359
|
self.opt.fem = self.fem
|
|
1317
|
-
|
|
1360
|
+
self.opt.set_fem(skip_reconstruct=True)
|
|
1361
|
+
|
|
1362
|
+
t_main = Thread(
|
|
1363
|
+
target=self.opt._main,
|
|
1364
|
+
args=(
|
|
1365
|
+
subprocess_idx,
|
|
1366
|
+
self.worker_status_list,
|
|
1367
|
+
wait_setup,
|
|
1368
|
+
),
|
|
1369
|
+
kwargs=dict(
|
|
1370
|
+
skip_set_fem=True,
|
|
1371
|
+
)
|
|
1372
|
+
)
|
|
1318
1373
|
t_main.start()
|
|
1319
1374
|
|
|
1320
1375
|
# save history
|
|
@@ -1330,11 +1385,11 @@ class FEMOpt:
|
|
|
1330
1385
|
t_save_history = Thread(target=save_history)
|
|
1331
1386
|
t_save_history.start()
|
|
1332
1387
|
|
|
1333
|
-
|
|
1334
1388
|
# 終了を待つ
|
|
1335
1389
|
self.client.gather(calc_futures)
|
|
1336
1390
|
if not self.opt.is_cluster: # 既存の fem を使っているならそれも待つ
|
|
1337
|
-
t_main
|
|
1391
|
+
if t_main is not None:
|
|
1392
|
+
t_main.join()
|
|
1338
1393
|
self.status.set(OptimizationStatus.TERMINATED)
|
|
1339
1394
|
end = time()
|
|
1340
1395
|
|
|
@@ -1357,6 +1412,8 @@ class FEMOpt:
|
|
|
1357
1412
|
# terminate actors
|
|
1358
1413
|
self.client.cancel(self.history._future, force=True)
|
|
1359
1414
|
self.client.cancel(self.status._future, force=True)
|
|
1415
|
+
for worker_status in self.worker_status_list:
|
|
1416
|
+
self.client.cancel(worker_status._future, force=True)
|
|
1360
1417
|
logger.info('Terminate actors.')
|
|
1361
1418
|
sleep(1)
|
|
1362
1419
|
|
|
@@ -1393,4 +1450,3 @@ def start_monitor_server_forever(history, status, host='localhost', port=8080):
|
|
|
1393
1450
|
monitor = Monitor(history, status)
|
|
1394
1451
|
monitor.start_server(host, port)
|
|
1395
1452
|
return 'Exit monitor server process gracefully'
|
|
1396
|
-
|
|
@@ -9,7 +9,7 @@ import pandas as pd
|
|
|
9
9
|
import psutil
|
|
10
10
|
from pywintypes import com_error
|
|
11
11
|
from pythoncom import CoInitialize, CoUninitialize
|
|
12
|
-
from win32com.client import constants
|
|
12
|
+
from win32com.client import constants, Dispatch
|
|
13
13
|
from dask.distributed import get_worker
|
|
14
14
|
from femtetutils import util
|
|
15
15
|
|
|
@@ -119,6 +119,7 @@ class FemtetInterface(FEMInterface):
|
|
|
119
119
|
femprj_path=None,
|
|
120
120
|
model_name=None,
|
|
121
121
|
connect_method='auto',
|
|
122
|
+
strict_pid_specify=False,
|
|
122
123
|
**kwargs
|
|
123
124
|
):
|
|
124
125
|
"""Initializes the FemtetInterface.
|
|
@@ -144,12 +145,13 @@ class FemtetInterface(FEMInterface):
|
|
|
144
145
|
self.model_name = model_name
|
|
145
146
|
self.connect_method = connect_method
|
|
146
147
|
|
|
147
|
-
#
|
|
148
|
+
# その他のメンバーの宣言や初期化
|
|
148
149
|
self.Femtet = None
|
|
149
150
|
self.quit_when_destruct = False
|
|
150
151
|
self.connected_method = 'unconnected'
|
|
151
152
|
self.parameters = None
|
|
152
153
|
self.max_api_retry = 3
|
|
154
|
+
self.strict_pid_specify = strict_pid_specify
|
|
153
155
|
|
|
154
156
|
# dask サブプロセスのときは femprj を更新し connect_method を new にする
|
|
155
157
|
try:
|
|
@@ -198,9 +200,12 @@ class FemtetInterface(FEMInterface):
|
|
|
198
200
|
|
|
199
201
|
def _connect_new_femtet(self):
|
|
200
202
|
logger.info('└ Try to launch and connect new Femtet process.')
|
|
201
|
-
|
|
203
|
+
|
|
204
|
+
self.Femtet, _ = launch_and_dispatch_femtet(strict_pid_specify=self.strict_pid_specify)
|
|
205
|
+
|
|
202
206
|
self.connected_method = 'new'
|
|
203
207
|
|
|
208
|
+
|
|
204
209
|
def _connect_existing_femtet(self, pid: int or None = None):
|
|
205
210
|
logger.info('└ Try to connect existing Femtet process.')
|
|
206
211
|
# 既存の Femtet を探して Dispatch する。
|
|
@@ -634,7 +639,7 @@ class FemtetWithNXInterface(FemtetInterface):
|
|
|
634
639
|
femprj_path=femprj_path,
|
|
635
640
|
model_name=model_name,
|
|
636
641
|
connect_method=connect_method,
|
|
637
|
-
prt_path
|
|
642
|
+
prt_path=os.path.basename(prt_path) # upload_file でアップロードされたファイルへのパスになる
|
|
638
643
|
)
|
|
639
644
|
|
|
640
645
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "pyfemtet"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.5" # 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.3"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|