PyFemtet 0.3.3__tar.gz → 0.3.4__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.
Files changed (25) hide show
  1. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/PKG-INFO +1 -1
  2. pyfemtet-0.3.4/pyfemtet/__init__.py +1 -0
  3. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/dispatch_extensions.py +12 -7
  4. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/logger.py +4 -2
  5. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/opt/base.py +195 -139
  6. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/opt/interface.py +1 -1
  7. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyproject.toml +1 -1
  8. pyfemtet-0.3.3/pyfemtet/__init__.py +0 -1
  9. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/LICENSE +0 -0
  10. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/README.md +0 -0
  11. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.femprj +0 -0
  12. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.prt +0 -0
  13. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.py +0 -0
  14. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/FemtetPJTSample/gau_ex08_parametric.femprj +0 -0
  15. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +0 -0
  16. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
  17. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/FemtetPJTSample/her_ex40_parametric.py +0 -0
  18. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +0 -0
  19. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
  20. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +0 -0
  21. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/_old/_SW_Femtet.py +0 -0
  22. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/core.py +0 -0
  23. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/opt/_FemtetWithNX/update_model.py +0 -0
  24. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/opt/__init__.py +0 -0
  25. {pyfemtet-0.3.3 → pyfemtet-0.3.4}/pyfemtet/opt/monitor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyfemtet
3
- Version: 0.3.3
3
+ Version: 0.3.4
4
4
  Summary: Design parameter optimization using Femtet.
5
5
  Home-page: https://github.com/pyfemtet/pyfemtet
6
6
  License: BSD-3-Clause
@@ -0,0 +1 @@
1
+ __version__ = "0.3.4"
@@ -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,6 +140,10 @@ def _get_pids(process_name):
138
140
  return pids
139
141
 
140
142
 
143
+ def _get_subprocess_log_prefix():
144
+ return f'({current_process().name}) '
145
+
146
+
141
147
  def launch_and_dispatch_femtet(timeout=DISPATCH_TIMEOUT) -> Tuple[IFemtet, int]:
142
148
  """Launch Femtet by new process and connect to it.
143
149
 
@@ -154,7 +160,8 @@ def launch_and_dispatch_femtet(timeout=DISPATCH_TIMEOUT) -> Tuple[IFemtet, int]:
154
160
  util.execute_femtet()
155
161
  pid = util.get_last_executed_femtet_process_id()
156
162
  logger.debug(f'Target pid is {pid}.')
157
- sleep(5)
163
+ for i in tqdm(range(5), 'wait for launch femtet...'):
164
+ sleep(1)
158
165
  Femtet, pid = dispatch_specific_femtet(pid, timeout)
159
166
  return Femtet, pid
160
167
 
@@ -207,9 +214,6 @@ def dispatch_femtet(timeout=DISPATCH_TIMEOUT, subprocess_log_prefix='') -> Tuple
207
214
  return Femtet, pid
208
215
 
209
216
 
210
- def _get_subprocess_log_prefix():
211
- return f'({current_process().name}) '
212
-
213
217
 
214
218
  def _block_other_femtets(
215
219
  target_pid,
@@ -386,10 +390,11 @@ def dispatch_specific_femtet_core(pid, timeout=DISPATCH_TIMEOUT) -> Tuple[IFemte
386
390
  if not (pid in pids):
387
391
  raise FemtetNotFoundException(f"Femtet (pid = {pid}) doesn't exist.")
388
392
 
393
+ logger.info('Searching specific Femtet...')
394
+
389
395
  # 子プロセスの準備
390
396
  # with Manager() as manager:
391
- from multiprocessing.managers import SyncManager
392
- m = SyncManager(ctx=_NestableSpawnContext())
397
+ m = SyncManager(ctx=_NestableSpawnContext()) # このへんが時間のかかる処理
393
398
  m.start()
394
399
  with m as manager:
395
400
  # フラグの準備
@@ -402,7 +407,7 @@ def dispatch_specific_femtet_core(pid, timeout=DISPATCH_TIMEOUT) -> Tuple[IFemte
402
407
  # 目的以外の Femtet をブロックする子プロセスを開始
403
408
  logger.debug('Start subprocess to block Femtet other than target pid.')
404
409
  processes = []
405
- for subprocess_id in range(len(pids)):
410
+ for subprocess_id in tqdm(range(len(pids)), 'Specifying connection...'):
406
411
  p = _NestableSpawnProcess(
407
412
  # p = Process(
408
413
  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
- # address = worker.address
16
- name = f'(Worker:{worker.name}) '
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 Callable, List, Tuple, Iterable, Any
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 i in range(2, p):
69
- if p % i == 0:
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(minimum):
74
- for p in range(minimum, LIMIT):
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 user-defined objective function based on the specified direction.
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
- _actor_data (HistoryDfCore): The distributed DataFrame object for storing actor data.
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.actor_data = pd.read_csv(self.path, encoding='shift-jis')
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.actor_data = pd.DataFrame([row], columns=self.actor_data.columns)
451
+ self.tmp_data = pd.DataFrame([row], columns=self.actor_data.columns)
449
452
  else:
450
- tmp = self.actor_data
451
- tmp.loc[len(tmp)] = row
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
- tmp = self.actor_data
456
- tmp['trial'] = np.arange(len(tmp)) + 1 # 1 始まり
457
- self.actor_data = tmp
458
- self._calc_non_domi(objectives)
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.actor_data[self.obj_names].copy()
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
- tmp = self.actor_data
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.actor_data.copy()
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'] == False:
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
- LAUNCHING = 20
572
- WAIT_1ST = 25
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(self, status_const):
585
- if status_const == self.UNDEFINED: return 'Undefined'
586
- if status_const == self.INITIALIZING: return 'Initializing'
587
- if status_const == self.SETTING_UP: return 'Setting up'
588
- if status_const == self.LAUNCHING: return 'Launching FEM processes'
589
- if status_const == self.WAIT_1ST: return 'Running and waiting for 1st FEM result.'
590
- if status_const == self.RUNNING: return 'Running'
591
- if status_const == self.INTERRUPTING: return 'Interrupting'
592
- if status_const == self.TERMINATED: return 'Terminated'
593
- if status_const == self.TERMINATE_ALL: return 'Terminate_all'
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
- logger.info(f'---{self.const_to_str(status_const)}---')
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.status = None # shared
618
- self.history = None # shared
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
- self.fem = self.fem_class(**self.fem_kwargs)
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 _main(self, subprocess_idx):
698
- self.set_fem()
699
- self.main(subprocess_idx)
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,
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.status.get() == OptimizationStatus.INTERRUPTING:
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.status.get() == OptimizationStatus.INTERRUPTING:
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.status.get() == OptimizationStatus.INTERRUPTING:
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
- raise FileNotFoundError(f'{storage_path} が見つかりません。.db ファイルは .csv ファイルと同じフォルダに生成されます。クラスター解析の場合は、スケジューラを起動したフォルダに生成されます。')
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 finite element method interface.
937
- history_path (str): The path to the history (.csv) file .
938
- ipv (InterprocessVariables): The interprocess variables.
939
- parameters (pd.DataFrame): The DataFrame object for storing the parameters.
940
- objectives (dict): The dictionary of objective functions.
941
- constraints (dict): The dictionary of constraint functions.
942
- history (History): The history of optimization results.
943
- monitor (Monitor): The monitor object for visualization and monitoring.
944
- monitor_thread: Thread object for monitor server.
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, automattically set to FemtetInterface.
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 inittial value from FEMInterface.
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
- method (str): The optimization method to use. Defaults to 'TPE'.
1209
- **setup_kwargs: Additional keyword arguments for setting up the optimization process.
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
- # dask メンバーの設定
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 len(subprocess_indices) <= 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
- worker_addresses = list(self.client.has_what().keys())
1246
- Popen(cmd, shell=True) #, stdout=PIPE) --> cause stream error
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
- while not (len(self.client.has_what()) > len(worker_addresses)):
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]
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.status = self.status
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=launched_worker_address, # if invalid arg,
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.LAUNCHING)
1342
+ self.status.set(OptimizationStatus.LAUNCHING_FEM)
1291
1343
  start = time()
1292
- subprocess_indices = list(range(n_parallel))
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
- if self.opt.is_cluster:
1295
- # FIXME: monitor-server-process には割り当てないようにする
1296
- worker_addresses = list(self.client.nthreads().keys())
1297
- worker_addresses.remove(launched_worker_address)
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
- else:
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
- t_main = Thread(target=self.opt.main, args=(subprocess_indices[0],))
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.join()
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
-
@@ -634,7 +634,7 @@ class FemtetWithNXInterface(FemtetInterface):
634
634
  femprj_path=femprj_path,
635
635
  model_name=model_name,
636
636
  connect_method=connect_method,
637
- prt_path = os.path.basename(prt_path) # upload_file でアップロードされたファイルへのパスになる
637
+ prt_path=os.path.basename(prt_path) # upload_file でアップロードされたファイルへのパスになる
638
638
  )
639
639
 
640
640
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pyfemtet"
3
- version = "0.3.3" # ignored by versioning plugin
3
+ version = "0.3.4" # 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