pyfemtet 0.4.20__py3-none-any.whl → 0.4.23__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.

Files changed (61) hide show
  1. pyfemtet/__init__.py +1 -1
  2. pyfemtet/_test_util.py +0 -2
  3. pyfemtet/message/locales/ja/LC_MESSAGES/messages.mo +0 -0
  4. pyfemtet/message/locales/ja/LC_MESSAGES/messages.po +107 -96
  5. pyfemtet/message/locales/messages.pot +104 -96
  6. pyfemtet/message/messages.py +15 -1
  7. pyfemtet/opt/_femopt.py +289 -230
  8. pyfemtet/opt/_femopt_core.py +118 -49
  9. pyfemtet/opt/femprj_sample/ParametricIF.py +0 -2
  10. pyfemtet/opt/femprj_sample/cad_ex01_NX.py +0 -8
  11. pyfemtet/opt/femprj_sample/cad_ex01_SW.py +0 -8
  12. pyfemtet/opt/femprj_sample/gal_ex58_parametric.py +0 -8
  13. pyfemtet/opt/femprj_sample/gau_ex08_parametric.py +0 -8
  14. pyfemtet/opt/femprj_sample/her_ex40_parametric.py +0 -8
  15. pyfemtet/opt/femprj_sample/paswat_ex1_parametric.py +0 -8
  16. pyfemtet/opt/femprj_sample/paswat_ex1_parametric_parallel.py +0 -8
  17. pyfemtet/opt/femprj_sample/wat_ex14_parametric.py +0 -8
  18. pyfemtet/opt/femprj_sample/wat_ex14_parametric_parallel.py +0 -8
  19. pyfemtet/opt/femprj_sample_jp/ParametricIF_jp.py +0 -2
  20. pyfemtet/opt/femprj_sample_jp/cad_ex01_NX_jp.py +0 -8
  21. pyfemtet/opt/femprj_sample_jp/cad_ex01_SW_jp.py +0 -8
  22. pyfemtet/opt/femprj_sample_jp/gal_ex58_parametric_jp.py +0 -8
  23. pyfemtet/opt/femprj_sample_jp/gau_ex08_parametric_jp.py +0 -8
  24. pyfemtet/opt/femprj_sample_jp/her_ex40_parametric_jp.py +0 -8
  25. pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_jp.py +0 -8
  26. pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_parallel_jp.py +0 -8
  27. pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_jp.py +0 -8
  28. pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_parallel_jp.py +0 -8
  29. pyfemtet/opt/interface/_femtet.py +77 -24
  30. pyfemtet/opt/opt/_base.py +25 -18
  31. pyfemtet/opt/opt/_optuna.py +53 -14
  32. pyfemtet/opt/opt/_optuna_botorch_helper.py +209 -0
  33. pyfemtet/opt/opt/_scipy.py +1 -1
  34. pyfemtet/opt/opt/_scipy_scalar.py +1 -1
  35. pyfemtet/opt/parameter.py +113 -0
  36. pyfemtet/opt/visualization/complex_components/main_graph.py +22 -5
  37. pyfemtet/opt/visualization/complex_components/pm_graph.py +77 -25
  38. pyfemtet/opt/visualization/complex_components/pm_graph_creator.py +7 -0
  39. pyfemtet/opt/visualization/process_monitor/application.py +10 -6
  40. pyfemtet/opt/visualization/process_monitor/pages.py +102 -0
  41. pyfemtet/opt/visualization/result_viewer/application.py +6 -0
  42. pyfemtet/opt/visualization/result_viewer/pages.py +1 -1
  43. {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/METADATA +3 -4
  44. {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/RECORD +47 -59
  45. pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.femprj +0 -0
  46. pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.prt +0 -0
  47. pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.py +0 -118
  48. pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.SLDPRT +0 -0
  49. pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.femprj +0 -0
  50. pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.py +0 -121
  51. pyfemtet/FemtetPJTSample/_her_ex40_parametric.py +0 -148
  52. pyfemtet/FemtetPJTSample/gau_ex08_parametric.femprj +0 -0
  53. pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +0 -58
  54. pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
  55. pyfemtet/FemtetPJTSample/her_ex40_parametric.py +0 -148
  56. pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +0 -65
  57. pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
  58. pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +0 -64
  59. {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/LICENSE +0 -0
  60. {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/WHEEL +0 -0
  61. {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/entry_points.txt +0 -0
pyfemtet/opt/_femopt.py CHANGED
@@ -1,9 +1,13 @@
1
1
  # built-in
2
+ import inspect
3
+ import warnings
4
+ from typing import Optional, Any, Callable, List
2
5
  import os
3
6
  import datetime
4
7
  from time import time, sleep
5
8
  from threading import Thread
6
9
  import json
10
+ from traceback import print_exception
7
11
 
8
12
  # 3rd-party
9
13
  import numpy as np
@@ -24,6 +28,30 @@ from pyfemtet.opt._femopt_core import (
24
28
  logger,
25
29
  )
26
30
  from pyfemtet.message import Msg, encoding
31
+ from pyfemtet.opt.parameter import Parameter, Expression
32
+
33
+
34
+ def add_worker(client, worker_name):
35
+ import sys
36
+ from subprocess import Popen, DEVNULL
37
+
38
+ current_n_workers = len(client.nthreads().keys())
39
+
40
+ Popen(
41
+ f'{sys.executable} -m dask worker '
42
+ f'{client.scheduler.address} '
43
+ f'--nthreads 1 '
44
+ f'--nworkers 1 '
45
+ f'--name {worker_name} '
46
+ f'--no-nanny',
47
+ shell=True,
48
+ stderr=DEVNULL,
49
+ stdout=DEVNULL,
50
+ )
51
+
52
+ # worker が増えるまで待つ
53
+ client.wait_for_workers(n_workers=current_n_workers + 1)
54
+
27
55
 
28
56
 
29
57
  class FEMOpt:
@@ -82,7 +110,6 @@ class FEMOpt:
82
110
  self.monitor_process_future = None
83
111
  self.monitor_server_kwargs = dict()
84
112
  self.monitor_process_worker_name = None
85
- self._is_error_exit = False
86
113
 
87
114
  # multiprocess 時に pickle できないオブジェクト参照の削除
88
115
  def __getstate__(self):
@@ -109,7 +136,8 @@ class FEMOpt:
109
136
  lower_bound: float or None = None,
110
137
  upper_bound: float or None = None,
111
138
  step: float or None = None,
112
- memo: str = ''
139
+ properties: Optional[dict] = None,
140
+ pass_to_fem: Optional[bool] = True,
113
141
  ):
114
142
  """Adds a parameter to the optimization problem.
115
143
 
@@ -119,34 +147,63 @@ class FEMOpt:
119
147
  lower_bound (float or None, optional): The lower bound of the parameter. Defaults to None. However, this argument is required for some algorithms.
120
148
  upper_bound (float or None, optional): The upper bound of the parameter. Defaults to None. However, this argument is required for some algorithms.
121
149
  step (float or None, optional): The step of parameter. Defaults to None.
122
- memo (str, optional): Additional information about the parameter. Defaults to ''.
150
+ properties (dict, optional): Additional information about the parameter. Defaults to None.
151
+ pass_to_fem (bool, optional): If this variable is used directly in FEM model update or not. If False, this parameter can be just used as inpt of expressions. Defaults to True.
123
152
  Raises:
124
153
  ValueError: If initial_value is not specified and the value for the given name is also not specified.
125
154
 
126
155
  """
127
156
 
128
157
  _check_bound(lower_bound, upper_bound, name)
129
- value = self.fem.check_param_value(name)
130
- if initial_value is None:
131
- if value is not None:
132
- initial_value = value
133
- else:
134
- raise ValueError('initial_value を指定してください.')
135
158
 
136
- d = {
137
- 'name': name,
138
- 'value': float(initial_value),
139
- 'lb': float(lower_bound) if lower_bound is not None else None,
140
- 'ub': float(upper_bound) if upper_bound is not None else None,
141
- 'step': float(step) if step is not None else None,
142
- 'memo': memo,
143
- }
144
- pdf = pd.DataFrame(d, index=[0], dtype=object)
145
-
146
- if len(self.opt.parameters) == 0:
147
- self.opt.parameters = pdf
159
+ if pass_to_fem:
160
+ value = self.fem.check_param_value(name)
161
+ if initial_value is None:
162
+ if value is not None:
163
+ initial_value = value
164
+ else:
165
+ raise ValueError('initial_value を指定してください.')
148
166
  else:
149
- self.opt.parameters = pd.concat([self.opt.parameters, pdf], ignore_index=True)
167
+ if initial_value is None:
168
+ raise ValueError('initial_value を指定してください.')
169
+
170
+ prm = Parameter(
171
+ name=name,
172
+ value=float(initial_value),
173
+ lower_bound=float(lower_bound) if lower_bound is not None else None,
174
+ upper_bound=float(upper_bound) if upper_bound is not None else None,
175
+ step=float(step) if step is not None else None,
176
+ pass_to_fem=pass_to_fem,
177
+ properties=properties,
178
+ )
179
+ self.opt.variables.add_parameter(prm)
180
+
181
+ def add_expression(
182
+ self,
183
+ name: str,
184
+ fun: Callable[[Any], float],
185
+ properties=property,
186
+ kwargs: Optional[dict] = None,
187
+ pass_to_fem=True,
188
+ ):
189
+ """Add expression to the optimization problem.
190
+
191
+ Args:
192
+ name (str): The name of the variable.
193
+ fun (Callable[[Any], float]): An expression function. The arguments that you want to use as input variables must be the same with ``name`` of Variable objects added by ``add_parameter()`` or ``add_expression()``. If you use other objects as argument of the function, you must specify ``kwargs``.
194
+ properties ([type], optional): Property names and their values of the variable. Defaults to property.
195
+ kwargs (Optional[dict], optional): Remaining arguments of ``fun``. Defaults to None.
196
+ pass_to_fem (bool, optional): If this variable is used directly in FEM model update or not. If False, this variable can be just used as inpt of other expressions. Defaults to True.
197
+ """
198
+ exp = Expression(
199
+ name=name,
200
+ value=None,
201
+ properties=properties,
202
+ fun=fun,
203
+ kwargs=kwargs if kwargs else {},
204
+ pass_to_fem=pass_to_fem,
205
+ )
206
+ self.opt.variables.add_expression(exp)
150
207
 
151
208
  def add_objective(
152
209
  self,
@@ -250,20 +307,49 @@ class FEMOpt:
250
307
 
251
308
  self.opt.constraints[name] = Constraint(fun, name, lower_bound, upper_bound, strict, args, kwargs)
252
309
 
253
- def get_parameter(self, format='dict') -> pd.DataFrame or dict or np.ndarray:
254
- """Returns the parameter in a specified format.
310
+ def add_parameter_constraint(
311
+ self,
312
+ fun: Callable[[List[float], Any], float],
313
+ name: Optional[str] = None,
314
+ lower_bound: Optional[float] = None,
315
+ upper_bound: Optional[float] = None,
316
+ ):
317
+ """Add constraint in case of parameter-only.
255
318
 
256
319
  Args:
257
- format (str, optional): The desired output format. Defaults to 'dict'. Valid formats are 'values', 'df' and 'dict'.
320
+ fun (Callable[[List[float], Any], float]): Function to constraint. The name of arguments must be one of ones of parameter or variables.
321
+ name (Optional[str], optional): Name of constraint. Defaults to None and then name is set to 'cns_{i}'.
322
+ lower_bound (Optional[float], optional): Lower bound of return value. Defaults to None.
323
+ upper_bound (Optional[float], optional): Upper bound of return value. Defaults to None.
324
+ """
258
325
 
259
- Returns:
260
- pd.DataFrame or dict or np.ndarray: The parameter data converted into the specified format.
326
+ # candidate default name
327
+ if name is None:
328
+ prefix = Constraint.default_name
329
+ i = 0
330
+ while True:
331
+ candidate = f'{prefix}_{str(int(i))}'
332
+ is_existing = candidate in list(self.opt.constraints.keys())
333
+ if not is_existing:
334
+ break
335
+ else:
336
+ i += 1
337
+ name = candidate
261
338
 
262
- Raises:
263
- ValueError: If an invalid format is provided.
339
+ # assert at least 1 bound exist
340
+ assert lower_bound is not None or upper_bound is not None, Msg.ERR_NO_BOUNDS
264
341
 
265
- """
266
- return self.opt.get_parameter(format)
342
+ from pyfemtet.opt._femopt_core import ParameterConstraint
343
+ self.opt.constraints[name] = ParameterConstraint(fun, name, lower_bound, upper_bound, self.opt)
344
+ if hasattr(self.opt, 'add_parameter_constraints'):
345
+ prm_args = [p.name for p in inspect.signature(fun).parameters.values()]
346
+ if lower_bound is not None:
347
+ self.opt.add_parameter_constraints(lambda *args, **kwargs: fun(*args, **kwargs) - lower_bound, prm_args=prm_args)
348
+ if upper_bound is not None:
349
+ self.opt.add_parameter_constraints(lambda *args, **kwargs: upper_bound - fun(*args, **kwargs), prm_args=prm_args)
350
+
351
+ def get_parameter(self, format='dict'):
352
+ raise DeprecationWarning('FEMOpt.get_parameter() was deprecated. Use Femopt.opt.get_parameter() instead.')
267
353
 
268
354
  def set_monitor_host(self, host=None, port=None):
269
355
  """Sets up the monitor server with the specified host and port.
@@ -292,6 +378,7 @@ class FEMOpt:
292
378
  n_parallel=1,
293
379
  timeout=None,
294
380
  wait_setup=True,
381
+ confirm_before_exit=True,
295
382
  ):
296
383
  """Runs the main optimization process.
297
384
 
@@ -300,6 +387,7 @@ class FEMOpt:
300
387
  n_parallel (int, optional): The number of parallel processes. Defaults to 1.
301
388
  timeout (float or None, optional): The maximum amount of time in seconds that each trial can run. Defaults to None.
302
389
  wait_setup (bool, optional): Wait for all workers launching FEM system. Defaults to True.
390
+ confirm_before_exit (bool, optional): Insert stop before exit to continue to show process monitor.
303
391
 
304
392
  Tip:
305
393
  If set_monitor_host() is not executed, a local server for monitoring will be started at localhost:8080.
@@ -338,8 +426,9 @@ class FEMOpt:
338
426
  self.opt.method_checker.check_seed()
339
427
 
340
428
  is_incomplete_bounds = False
341
- for _, row in self.opt.parameters.iterrows():
342
- lb, ub = row['lb'], row['ub']
429
+ prm: Parameter = None
430
+ for prm in self.opt.variables.parameters.values():
431
+ lb, ub = prm.lower_bound, prm.upper_bound
343
432
  is_incomplete_bounds = is_incomplete_bounds + (lb is None) + (ub is None)
344
433
  if is_incomplete_bounds:
345
434
  self.opt.method_checker.check_incomplete_bounds()
@@ -348,6 +437,10 @@ class FEMOpt:
348
437
  self.opt.n_trials = n_trials
349
438
  self.opt.timeout = timeout
350
439
 
440
+ # resolve expression dependencies
441
+ self.opt.variables.resolve()
442
+ self.opt.variables.evaluate()
443
+
351
444
  # クラスターの設定
352
445
  self.opt.is_cluster = self.scheduler_address is not None
353
446
  if self.opt.is_cluster:
@@ -373,16 +466,7 @@ class FEMOpt:
373
466
  # monitor worker の設定
374
467
  logger.info('Launching monitor server. This may take a few seconds.')
375
468
  self.monitor_process_worker_name = datetime.datetime.now().strftime("Monitor%Y%m%d%H%M%S")
376
- current_n_workers = len(self.client.nthreads().keys())
377
- from subprocess import Popen
378
- import sys
379
- Popen(
380
- f'{sys.executable} -m dask worker {self.client.scheduler.address} --nthreads 1 --nworkers 1 --name {self.monitor_process_worker_name} --no-nanny',
381
- shell=True
382
- )
383
-
384
- # monitor 用 worker が増えるまで待つ
385
- self.client.wait_for_workers(n_workers=current_n_workers + 1)
469
+ add_worker(self.client, self.monitor_process_worker_name)
386
470
 
387
471
  else:
388
472
  # ローカルクラスターを構築
@@ -400,200 +484,175 @@ class FEMOpt:
400
484
  self.monitor_process_worker_name = worker_addresses[0]
401
485
  worker_addresses[0] = 'Main'
402
486
 
403
- # Femtet 特有の処理
404
- metadata = None
405
- if isinstance(self.fem, FemtetInterface):
406
- # 結果 csv に記載する femprj に関する情報
407
- metadata = json.dumps(
408
- dict(
409
- femprj_path=self.fem.original_femprj_path,
410
- model_name=self.fem.model_name
487
+ with self.client.cluster as _cluster, self.client as _client:
488
+
489
+ # Femtet 特有の処理
490
+ metadata = None
491
+ if isinstance(self.fem, FemtetInterface):
492
+ # 結果 csv に記載する femprj に関する情報
493
+ metadata = json.dumps(
494
+ dict(
495
+ femprj_path=self.fem.original_femprj_path,
496
+ model_name=self.fem.model_name
497
+ )
411
498
  )
499
+ # Femtet の parametric 設定を目的関数に用いるかどうか
500
+ if self.fem.parametric_output_indexes_use_as_objective is not None:
501
+ from pyfemtet.opt.interface._femtet_parametric import add_parametric_results_as_objectives
502
+ indexes = list(self.fem.parametric_output_indexes_use_as_objective.keys())
503
+ directions = list(self.fem.parametric_output_indexes_use_as_objective.values())
504
+ add_parametric_results_as_objectives(
505
+ self,
506
+ indexes,
507
+ directions,
508
+ )
509
+
510
+ # actor の設定
511
+ self.status = OptimizationStatus(_client)
512
+ self.worker_status_list = [OptimizationStatus(_client, name) for name in worker_addresses] # tqdm 検討
513
+ self.status.set(OptimizationStatus.SETTING_UP)
514
+ self.history = History(
515
+ self.history_path,
516
+ self.opt.variables.get_parameter_names(),
517
+ list(self.opt.objectives.keys()),
518
+ list(self.opt.constraints.keys()),
519
+ _client,
520
+ metadata,
412
521
  )
413
- # Femtet の parametric 設定を目的関数に用いるかどうか
414
- if self.fem.parametric_output_indexes_use_as_objective is not None:
415
- from pyfemtet.opt.interface._femtet_parametric import add_parametric_results_as_objectives
416
- indexes = list(self.fem.parametric_output_indexes_use_as_objective.keys())
417
- directions = list(self.fem.parametric_output_indexes_use_as_objective.values())
418
- add_parametric_results_as_objectives(
419
- self,
420
- indexes,
421
- directions,
422
- )
423
-
424
- # actor の設定
425
- self.status = OptimizationStatus(self.client)
426
- self.worker_status_list = [OptimizationStatus(self.client, name) for name in worker_addresses] # tqdm 検討
427
- self.status.set(OptimizationStatus.SETTING_UP)
428
- self.history = History(
429
- self.history_path,
430
- self.opt.parameters['name'].to_list(),
431
- list(self.opt.objectives.keys()),
432
- list(self.opt.constraints.keys()),
433
- self.client,
434
- metadata,
435
- )
436
522
 
437
- # launch monitor
438
- self.monitor_process_future = self.client.submit(
439
- # func
440
- _start_monitor_server,
441
- # args
442
- self.history,
443
- self.status,
444
- worker_addresses,
445
- self.worker_status_list,
446
- # kwargs
447
- **self.monitor_server_kwargs,
448
- # kwargs of submit
449
- workers=self.monitor_process_worker_name,
450
- allow_other_workers=False
451
- )
452
-
453
- # fem
454
- self.fem._setup_before_parallel(self.client)
455
-
456
- # opt
457
- self.opt.fem_class = type(self.fem)
458
- self.opt.fem_kwargs = self.fem.kwargs
459
- self.opt.entire_status = self.status
460
- self.opt.history = self.history
461
- self.opt._setup_before_parallel()
462
-
463
- # クラスターでの計算開始
464
- self.status.set(OptimizationStatus.LAUNCHING_FEM)
465
- start = time()
466
- calc_futures = self.client.map(
467
- self.opt._run,
468
- subprocess_indices,
469
- [self.worker_status_list] * len(subprocess_indices),
470
- [wait_setup] * len(subprocess_indices),
471
- workers=worker_addresses,
472
- allow_other_workers=False,
473
- )
474
-
475
- t_main = None
476
- if not self.opt.is_cluster:
477
- # ローカルプロセスでの計算(opt._main 相当の処理)
478
- subprocess_idx = 0
479
-
480
- # set_fem
481
- self.opt.fem = self.fem
482
- self.opt._reconstruct_fem(skip_reconstruct=True)
483
-
484
- t_main = Thread(
485
- target=self.opt._run,
486
- args=(
487
- subprocess_idx,
488
- self.worker_status_list,
489
- wait_setup,
490
- ),
491
- kwargs=dict(
492
- skip_set_fem=True,
493
- )
523
+ # launch monitor
524
+ self.monitor_process_future = _client.submit(
525
+ # func
526
+ _start_monitor_server,
527
+ # args
528
+ self.history,
529
+ self.status,
530
+ worker_addresses,
531
+ self.worker_status_list,
532
+ # kwargs
533
+ **self.monitor_server_kwargs,
534
+ # kwargs of submit
535
+ workers=self.monitor_process_worker_name,
536
+ allow_other_workers=False
494
537
  )
495
- t_main.start()
496
-
497
- # save history
498
- def save_history():
499
- while True:
500
- sleep(2)
501
- try:
502
- self.history.save()
503
- except PermissionError:
504
- logger.warning(Msg.WARN_HISTORY_CSV_NOT_ACCESSIBLE)
505
- if self.status.get() >= OptimizationStatus.TERMINATED:
506
- break
507
-
508
- t_save_history = Thread(target=save_history)
509
- t_save_history.start()
510
538
 
511
- # 終了を待つ
512
- local_opt_crashed = False
513
- opt_crashed_list = self.client.gather(calc_futures)
514
- if not self.opt.is_cluster: # 既存の fem を使っているならそれも待つ
515
- if t_main is not None:
516
- t_main.join()
517
- local_opt_crashed = self.opt._is_error_exit
518
- opt_crashed_list.append(local_opt_crashed)
519
- self.status.set(OptimizationStatus.TERMINATED)
520
- end = time()
521
-
522
- # 一応
523
- t_save_history.join()
524
-
525
- # logger.info(f'計算が終了しました. 実行時間は {int(end - start)} 秒でした。ウィンドウを閉じると終了します.')
526
- # logger.info(f'結果は{self.history.path}を確認してください.')
527
- logger.info(Msg.OPTIMIZATION_FINISHED)
528
- logger.info(self.history.path)
529
-
530
- # ひとつでも crashed ならばフラグを立てる
531
- if any(opt_crashed_list):
532
- self._is_error_exit = True
533
-
534
- return self.history.local_data
535
-
536
-
537
- def terminate_all(self):
538
- """Try to terminate all launched processes.
539
-
540
- If distributed computing, Scheduler and Workers will NOT be terminated.
541
-
542
- """
543
-
544
- # monitor が terminated 状態で少なくとも一度更新されなければ running のまま固まる
545
- sleep(1)
546
-
547
- # terminate monitor process
548
- self.status.set(OptimizationStatus.TERMINATE_ALL)
549
- logger.info(self.monitor_process_future.result())
550
- sleep(1)
551
-
552
- # terminate actors
553
- self.client.cancel(self.history._future, force=True)
554
- self.client.cancel(self.status._future, force=True)
555
- for worker_status in self.worker_status_list:
556
- self.client.cancel(worker_status._future, force=True)
557
- logger.info('Terminate actors.')
558
- sleep(1)
559
-
560
- # terminate monitor worker
561
- n_workers = len(self.client.nthreads())
562
-
563
- found_worker_dict = self.client.retire_workers(
564
- names=[self.monitor_process_worker_name], # name
565
- close_workers=True,
566
- remove=True,
567
- )
568
-
569
- if len(found_worker_dict) == 0:
570
- found_worker_dict = self.client.retire_workers(
571
- workers=[self.monitor_process_worker_name], # address
572
- close_workers=True,
573
- remove=True,
539
+ # fem
540
+ self.fem._setup_before_parallel(_client)
541
+
542
+ # opt
543
+ self.opt.fem_class = type(self.fem)
544
+ self.opt.fem_kwargs = self.fem.kwargs
545
+ self.opt.entire_status = self.status
546
+ self.opt.history = self.history
547
+ self.opt._setup_before_parallel()
548
+
549
+ # クラスターでの計算開始
550
+ self.status.set(OptimizationStatus.LAUNCHING_FEM)
551
+ start = time()
552
+ calc_futures = _client.map(
553
+ self.opt._run,
554
+ subprocess_indices,
555
+ [self.worker_status_list] * len(subprocess_indices),
556
+ [wait_setup] * len(subprocess_indices),
557
+ workers=worker_addresses,
558
+ allow_other_workers=False,
574
559
  )
575
560
 
576
- if len(found_worker_dict) > 0:
577
- while n_workers == len(self.client.nthreads()):
578
- sleep(1)
579
- logger.info('Terminate monitor processes worker.')
580
- sleep(1)
581
- else:
582
- logger.warn('Monitor process worker not found.')
583
-
584
- # close FEM (if specified to quit when deconstruct)
585
- del self.fem
586
- logger.info('Terminate FEM.')
587
- sleep(1)
588
-
589
- # close scheduler, other workers(, cluster)
590
- self.client.shutdown()
591
- logger.info('Terminate all relative processes.')
592
- sleep(3)
593
-
594
- # if optimization was crashed, raise Exception
595
- if self._is_error_exit:
596
- raise RuntimeError('At least 1 of optimization processes have been crashed. See console log.')
561
+ t_main = None
562
+ if not self.opt.is_cluster:
563
+ # ローカルプロセスでの計算(opt._main 相当の処理)
564
+ subprocess_idx = 0
565
+
566
+ # set_fem
567
+ self.opt.fem = self.fem
568
+ self.opt._reconstruct_fem(skip_reconstruct=True)
569
+
570
+ t_main = Thread(
571
+ target=self.opt._run,
572
+ args=(
573
+ subprocess_idx,
574
+ self.worker_status_list,
575
+ wait_setup,
576
+ ),
577
+ kwargs=dict(
578
+ skip_set_fem=True,
579
+ )
580
+ )
581
+ t_main.start()
582
+
583
+ # save history
584
+ def save_history():
585
+ while True:
586
+ sleep(2)
587
+ try:
588
+ self.history.save()
589
+ except PermissionError:
590
+ logger.warning(Msg.WARN_HISTORY_CSV_NOT_ACCESSIBLE)
591
+ if self.status.get() >= OptimizationStatus.TERMINATED:
592
+ break
593
+
594
+ t_save_history = Thread(target=save_history)
595
+ t_save_history.start()
596
+
597
+ # ===== 終了 =====
598
+
599
+ # クラスターの Unexpected Exception のリストを取得
600
+ opt_exceptions: list[Exception or None] = _client.gather(calc_futures) # gather() で終了待ちも兼ねる
601
+
602
+ # ローカルの opt で計算している場合、その Exception も取得
603
+ local_opt_exception: Exception or None = None
604
+ if not self.opt.is_cluster:
605
+ if t_main is not None:
606
+ t_main.join() # 終了待ち
607
+ local_opt_exception = self.opt._exception # Exception を取得
608
+ opt_exceptions.append(local_opt_exception)
609
+
610
+ # 終了
611
+ self.status.set(OptimizationStatus.TERMINATED)
612
+ end = time()
613
+
614
+ # 一応
615
+ t_save_history.join()
616
+
617
+ # 結果通知
618
+ logger.info(Msg.OPTIMIZATION_FINISHED)
619
+ logger.info(self.history.path)
620
+
621
+ # monitor worker を終了する準備
622
+ # 実際の終了は monitor worker の終了時
623
+ self.status.set(OptimizationStatus.TERMINATE_ALL)
624
+ logger.info(self.monitor_process_future.result())
625
+ sleep(1) # monitor が terminated 状態で少なくとも一度更新されなければ running のまま固まる
626
+
627
+ # 全ての Exception を再表示
628
+ for i, opt_exception in enumerate(opt_exceptions):
629
+ if opt_exception is not None:
630
+ print()
631
+ print(f'===== unexpected exception raised on worker {i} =====')
632
+ print_exception(opt_exception)
633
+ print()
634
+
635
+ # monitor worker を残してユーザーが結果を確認できるようにする
636
+ if confirm_before_exit:
637
+ print()
638
+ print('='*len(Msg.CONFIRM_BEFORE_EXIT))
639
+ print(Msg.CONFIRM_BEFORE_EXIT)
640
+ print('='*len(Msg.CONFIRM_BEFORE_EXIT))
641
+ input()
642
+
643
+ return self.history.get_df() # with 文を抜けると actor は消えるが .copy() はこの段階では不要
644
+
645
+ @staticmethod
646
+ def terminate_all():
647
+ warnings.warn(
648
+ "terminate_all() is deprecated and will be removed in a future version. "
649
+ "In current and later versions, the equivalent of terminate_all() will be executed when optimize() finishes. "
650
+ "Therefore, you can simply remove terminate_all() from your code. "
651
+ "If you want to stop program before terminating monitor process, "
652
+ "use ``confirm_before_exit`` argument like ``FEMOpt.optimize(confirm_before_exit=True)``",
653
+ DeprecationWarning,
654
+ stacklevel=2
655
+ )
597
656
 
598
657
 
599
658
  def _start_monitor_server(