pyfemtet 0.5.4__py3-none-any.whl → 0.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyfemtet might be problematic. Click here for more details.
- pyfemtet/__init__.py +1 -1
- pyfemtet/_message/locales/ja/LC_MESSAGES/messages.po +1 -1
- pyfemtet/opt/__init__.py +3 -0
- pyfemtet/opt/_femopt.py +73 -14
- pyfemtet/opt/_femopt_core.py +172 -63
- pyfemtet/opt/interface/_femtet.py +15 -4
- pyfemtet/opt/optimizer/__init__.py +5 -1
- pyfemtet/opt/optimizer/_base.py +5 -13
- pyfemtet/opt/optimizer/_optuna/__init__.py +0 -0
- pyfemtet/opt/optimizer/_optuna/_botorch_patch/__init__.py +0 -0
- pyfemtet/opt/optimizer/{_optuna_botorchsampler_parameter_constraint_helper.py → _optuna/_botorch_patch/enable_nonlinear_constraint.py} +9 -126
- pyfemtet/opt/optimizer/{_optuna.py → _optuna/_optuna.py} +89 -18
- pyfemtet/opt/optimizer/_optuna/_pof_botorch.py +1872 -0
- pyfemtet/opt/samples/femprj_sample/constrained_pipe.py +2 -2
- pyfemtet/opt/samples/femprj_sample/her_ex40_parametric.py +2 -2
- {pyfemtet-0.5.4.dist-info → pyfemtet-0.6.1.dist-info}/METADATA +1 -1
- {pyfemtet-0.5.4.dist-info → pyfemtet-0.6.1.dist-info}/RECORD +20 -18
- pyfemtet/opt/samples/femprj_sample/.gitignore +0 -2
- {pyfemtet-0.5.4.dist-info → pyfemtet-0.6.1.dist-info}/LICENSE +0 -0
- {pyfemtet-0.5.4.dist-info → pyfemtet-0.6.1.dist-info}/WHEEL +0 -0
- {pyfemtet-0.5.4.dist-info → pyfemtet-0.6.1.dist-info}/entry_points.txt +0 -0
pyfemtet/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.6.1"
|
|
@@ -80,7 +80,7 @@ msgstr "モデル更新に失敗しました。"
|
|
|
80
80
|
|
|
81
81
|
#: pyfemtet/message/messages.py:60
|
|
82
82
|
msgid "It was detected that the configuration of Femtet python macro constants has not been completed. The configuration was done automatically (python -m win32com.client.makepy FemtetMacro). Please restart the program."
|
|
83
|
-
msgstr "Femtet マクロの定数の設定が未完了であることが検出され、設定コマンド(python -m
|
|
83
|
+
msgstr "Femtet マクロの定数の設定が未完了であることが検出され、設定コマンド(python -m win32com.client.makepy FemtetMacro)が自動実行されました。プログラムを再実行してください。"
|
|
84
84
|
|
|
85
85
|
#: pyfemtet/message/messages.py:61
|
|
86
86
|
msgid "Failed to connect to Femtet."
|
pyfemtet/opt/__init__.py
CHANGED
|
@@ -9,6 +9,8 @@ from pyfemtet.opt.optimizer import AbstractOptimizer
|
|
|
9
9
|
|
|
10
10
|
from pyfemtet.opt._femopt import FEMOpt
|
|
11
11
|
|
|
12
|
+
from pyfemtet.opt._femopt_core import History
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
__all__ = [
|
|
14
16
|
'FEMOpt',
|
|
@@ -21,4 +23,5 @@ __all__ = [
|
|
|
21
23
|
'OptunaOptimizer',
|
|
22
24
|
'ScipyScalarOptimizer',
|
|
23
25
|
'ScipyOptimizer',
|
|
26
|
+
'History',
|
|
24
27
|
]
|
pyfemtet/opt/_femopt.py
CHANGED
|
@@ -12,7 +12,7 @@ from traceback import print_exception
|
|
|
12
12
|
# 3rd-party
|
|
13
13
|
import numpy as np
|
|
14
14
|
import pandas as pd
|
|
15
|
-
from dask.distributed import LocalCluster, Client
|
|
15
|
+
from dask.distributed import LocalCluster, Client
|
|
16
16
|
|
|
17
17
|
# pyfemtet relative
|
|
18
18
|
from pyfemtet.opt.interface import FEMInterface, FemtetInterface
|
|
@@ -32,10 +32,13 @@ from pyfemtet._message import Msg, encoding
|
|
|
32
32
|
from pyfemtet.opt.optimizer.parameter import Parameter, Expression
|
|
33
33
|
from pyfemtet._warning import experimental_feature
|
|
34
34
|
|
|
35
|
+
from dask import config as cfg
|
|
36
|
+
cfg.set({'distributed.scheduler.worker-ttl': None})
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
|
|
39
|
+
def add_worker(client, worker_name, n_workers=1):
|
|
37
40
|
import sys
|
|
38
|
-
from subprocess import Popen, DEVNULL
|
|
41
|
+
from subprocess import Popen, DEVNULL, PIPE
|
|
39
42
|
|
|
40
43
|
current_n_workers = len(client.nthreads().keys())
|
|
41
44
|
|
|
@@ -43,16 +46,16 @@ def add_worker(client, worker_name):
|
|
|
43
46
|
f'{sys.executable} -m dask worker '
|
|
44
47
|
f'{client.scheduler.address} '
|
|
45
48
|
f'--nthreads 1 '
|
|
46
|
-
f'--nworkers
|
|
47
|
-
f'--name {worker_name} '
|
|
48
|
-
|
|
49
|
+
f'--nworkers {n_workers} '
|
|
50
|
+
f'--name {worker_name} ', # A unique name for this worker like ‘worker-1’. If used with -nworkers then the process number will be appended like name-0, name-1, name-2, …
|
|
51
|
+
# --no-nanny option は --nworkers と併用できない
|
|
49
52
|
shell=True,
|
|
50
53
|
stderr=DEVNULL,
|
|
51
54
|
stdout=DEVNULL,
|
|
52
55
|
)
|
|
53
56
|
|
|
54
57
|
# worker が増えるまで待つ
|
|
55
|
-
client.wait_for_workers(n_workers=current_n_workers +
|
|
58
|
+
client.wait_for_workers(n_workers=current_n_workers + n_workers)
|
|
56
59
|
|
|
57
60
|
|
|
58
61
|
class FEMOpt:
|
|
@@ -132,6 +135,7 @@ class FEMOpt:
|
|
|
132
135
|
self.monitor_process_future = None
|
|
133
136
|
self.monitor_server_kwargs = dict()
|
|
134
137
|
self.monitor_process_worker_name = None
|
|
138
|
+
self._hv_reference = None
|
|
135
139
|
|
|
136
140
|
# multiprocess 時に pickle できないオブジェクト参照の削除
|
|
137
141
|
def __getstate__(self):
|
|
@@ -359,6 +363,46 @@ class FEMOpt:
|
|
|
359
363
|
|
|
360
364
|
self.opt.objectives[name] = Objective(fun, name, direction, args, kwargs)
|
|
361
365
|
|
|
366
|
+
def add_objectives(
|
|
367
|
+
self,
|
|
368
|
+
fun: Callable[[Any], "Sequence"["SupportsFloat"]],
|
|
369
|
+
n_return: int,
|
|
370
|
+
names: str or "Sequence"[str] or None = None,
|
|
371
|
+
directions: str or "Sequence"[str] or None = None,
|
|
372
|
+
args: tuple or None = None,
|
|
373
|
+
kwargs: dict or None = None,
|
|
374
|
+
):
|
|
375
|
+
from pyfemtet.opt._femopt_core import ObjectivesFunc
|
|
376
|
+
components = ObjectivesFunc(fun, n_return)
|
|
377
|
+
|
|
378
|
+
if names is not None:
|
|
379
|
+
if isinstance(names, str):
|
|
380
|
+
names = [f'name_{i}' for i in range(n_return)]
|
|
381
|
+
else:
|
|
382
|
+
# names = names
|
|
383
|
+
pass
|
|
384
|
+
else:
|
|
385
|
+
names = [None for _ in range(n_return)]
|
|
386
|
+
|
|
387
|
+
if directions is not None:
|
|
388
|
+
if isinstance(directions, str):
|
|
389
|
+
directions = [directions for _ in range(n_return)]
|
|
390
|
+
else:
|
|
391
|
+
# directions = directions
|
|
392
|
+
pass
|
|
393
|
+
else:
|
|
394
|
+
directions = ['minimize' for _ in range(n_return)]
|
|
395
|
+
|
|
396
|
+
for name, component, direction in zip(names, components, directions):
|
|
397
|
+
self.add_objective(
|
|
398
|
+
fun=component,
|
|
399
|
+
name=name,
|
|
400
|
+
direction=direction,
|
|
401
|
+
args=args,
|
|
402
|
+
kwargs=kwargs,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
|
|
362
406
|
def add_constraint(
|
|
363
407
|
self,
|
|
364
408
|
fun,
|
|
@@ -385,9 +429,9 @@ class FEMOpt:
|
|
|
385
429
|
|
|
386
430
|
Warnings:
|
|
387
431
|
|
|
388
|
-
When ```strict``` == True and using OptunaOptimizer along with
|
|
389
|
-
|
|
390
|
-
During this process, the constraint function fun will be executed at each
|
|
432
|
+
When ```strict``` == True and using OptunaOptimizer along with :class:`PoFBoTorchSampler`,
|
|
433
|
+
PyFemtet will solve an optimization subproblem to propose new variables.
|
|
434
|
+
During this process, the constraint function ```fun``` will be executed at each
|
|
391
435
|
iteration of the subproblem, which may include time-consuming operations such
|
|
392
436
|
as retrieving 3D model information via FEMInterface.
|
|
393
437
|
As a result, it may not be feasible to complete the overall optimization within
|
|
@@ -406,7 +450,7 @@ class FEMOpt:
|
|
|
406
450
|
|
|
407
451
|
Instead, please do the following.
|
|
408
452
|
|
|
409
|
-
>>> def bottom_area(_, opt): #
|
|
453
|
+
>>> def bottom_area(_, opt): # Not to access the 1st argument. # doctest: +SKIP
|
|
410
454
|
... params = opt.get_parameter() # doctest: +SKIP
|
|
411
455
|
... w, d = params['width'], params['depth'] # doctest: +SKIP
|
|
412
456
|
... return w * d # doctest: +SKIP
|
|
@@ -623,14 +667,21 @@ class FEMOpt:
|
|
|
623
667
|
|
|
624
668
|
else:
|
|
625
669
|
# ローカルクラスターを構築
|
|
626
|
-
logger.info('Launching single machine cluster
|
|
670
|
+
logger.info('Launching single machine cluster... This may take tens of seconds.')
|
|
671
|
+
|
|
672
|
+
# Fixed:
|
|
673
|
+
# Nanny の管理機能は必要ないが、Python API では worker_class を Worker にすると
|
|
674
|
+
# processes 引数が無視されて Thread worker が立てられる。
|
|
675
|
+
# これは CLI の --no-nanny オプションも同様らしい。
|
|
676
|
+
|
|
677
|
+
# クラスターの構築
|
|
627
678
|
cluster = LocalCluster(
|
|
628
679
|
processes=True,
|
|
629
|
-
n_workers=n_parallel,
|
|
680
|
+
n_workers=n_parallel,
|
|
630
681
|
threads_per_worker=1,
|
|
631
|
-
worker_class=Worker,
|
|
632
682
|
)
|
|
633
683
|
logger.info('LocalCluster launched successfully.')
|
|
684
|
+
|
|
634
685
|
self.client = Client(cluster, direct_to_workers=False)
|
|
635
686
|
self.scheduler_address = self.client.scheduler.address
|
|
636
687
|
logger.info('Client launched successfully.')
|
|
@@ -655,6 +706,7 @@ class FEMOpt:
|
|
|
655
706
|
model_name=self.fem.model_name
|
|
656
707
|
)
|
|
657
708
|
)
|
|
709
|
+
|
|
658
710
|
# Femtet の parametric 設定を目的関数に用いるかどうか
|
|
659
711
|
if self.fem.parametric_output_indexes_use_as_objective is not None:
|
|
660
712
|
from pyfemtet.opt.interface._femtet_parametric import add_parametric_results_as_objectives
|
|
@@ -665,8 +717,14 @@ class FEMOpt:
|
|
|
665
717
|
indexes,
|
|
666
718
|
directions,
|
|
667
719
|
)
|
|
720
|
+
|
|
721
|
+
# Femtet の confirm_before_exit のセット
|
|
722
|
+
self.fem.confirm_before_exit = confirm_before_exit
|
|
723
|
+
self.fem.kwargs['confirm_before_exit'] = confirm_before_exit
|
|
724
|
+
|
|
668
725
|
logger.info('Femtet loaded successfully.')
|
|
669
726
|
|
|
727
|
+
|
|
670
728
|
# actor の設定
|
|
671
729
|
self.status = OptimizationStatus(_client)
|
|
672
730
|
self.worker_status_list = [OptimizationStatus(_client, name) for name in worker_addresses] # tqdm 検討
|
|
@@ -678,6 +736,7 @@ class FEMOpt:
|
|
|
678
736
|
list(self.opt.constraints.keys()),
|
|
679
737
|
_client,
|
|
680
738
|
metadata,
|
|
739
|
+
self._hv_reference
|
|
681
740
|
)
|
|
682
741
|
logger.info('Status Actor initialized successfully.')
|
|
683
742
|
|
pyfemtet/opt/_femopt_core.py
CHANGED
|
@@ -430,6 +430,79 @@ class Constraint(Function):
|
|
|
430
430
|
super().__init__(fun, name, args, kwargs)
|
|
431
431
|
|
|
432
432
|
|
|
433
|
+
class ObjectivesFunc:
|
|
434
|
+
"""複数の値を返す関数を objective として扱うためのクラス
|
|
435
|
+
|
|
436
|
+
複数の値を返す関数を受け取る。
|
|
437
|
+
|
|
438
|
+
最初に評価されたときに計算を実行し
|
|
439
|
+
そうでない場合は保持した値を返す
|
|
440
|
+
callable のリストを提供する。
|
|
441
|
+
|
|
442
|
+
"""
|
|
443
|
+
|
|
444
|
+
def __init__(self, fun, n_return):
|
|
445
|
+
self._evaluated = [False for _ in range(n_return)]
|
|
446
|
+
self._values = [None for _ in range(n_return)]
|
|
447
|
+
self._i = 0
|
|
448
|
+
self.fun = fun
|
|
449
|
+
self.n_return = n_return
|
|
450
|
+
|
|
451
|
+
def __iter__(self):
|
|
452
|
+
return self
|
|
453
|
+
|
|
454
|
+
def __next__(self):
|
|
455
|
+
|
|
456
|
+
# iter の長さ
|
|
457
|
+
if self._i == self.n_return:
|
|
458
|
+
self._i = 0
|
|
459
|
+
raise StopIteration
|
|
460
|
+
|
|
461
|
+
# iter として提供する callable オブジェクト
|
|
462
|
+
# self の情報にもアクセスする必要があり
|
|
463
|
+
# それぞれが iter された時点での i 番目という
|
|
464
|
+
# 情報も必要なのでこのスコープで定義する必要がある
|
|
465
|
+
class NthFunc:
|
|
466
|
+
def __init__(self_, i):
|
|
467
|
+
# 何番目の要素であるかを保持
|
|
468
|
+
# self._i を直接参照すると
|
|
469
|
+
# 実行時点での ObjectiveFunc の
|
|
470
|
+
# 値を参照してしまう
|
|
471
|
+
self_.i = i
|
|
472
|
+
|
|
473
|
+
def __call__(self_, *args, **kwargs):
|
|
474
|
+
# 何番目の要素であるか
|
|
475
|
+
i = self_.i
|
|
476
|
+
|
|
477
|
+
# 一度も評価されていなければ評価する
|
|
478
|
+
if not any(self._evaluated):
|
|
479
|
+
self._values = tuple(self.fun(*args, **kwargs))
|
|
480
|
+
assert len(self._values) == self.n_return, '予期しない戻り値の数'
|
|
481
|
+
|
|
482
|
+
# 評価したらフラグを立てる
|
|
483
|
+
self._evaluated[i] = True
|
|
484
|
+
|
|
485
|
+
# すべてのフラグが立ったらクリアする
|
|
486
|
+
if all(self._evaluated):
|
|
487
|
+
self._evaluated = [False for _ in range(self.n_return)]
|
|
488
|
+
|
|
489
|
+
# 値を返す
|
|
490
|
+
return self._values[i]
|
|
491
|
+
|
|
492
|
+
@property
|
|
493
|
+
def __globals__(self_):
|
|
494
|
+
# ScapeGoat 実装への対処
|
|
495
|
+
return self.fun.__globals__
|
|
496
|
+
|
|
497
|
+
# callable を作成
|
|
498
|
+
f = NthFunc(self._i)
|
|
499
|
+
|
|
500
|
+
# index を更新
|
|
501
|
+
self._i += 1
|
|
502
|
+
|
|
503
|
+
return f
|
|
504
|
+
|
|
505
|
+
|
|
433
506
|
class _HistoryDfCore:
|
|
434
507
|
"""Class for managing a DataFrame object in a distributed manner."""
|
|
435
508
|
|
|
@@ -453,18 +526,12 @@ class History:
|
|
|
453
526
|
cns_names (List[str], optional): The names of constraints. Defaults to None.
|
|
454
527
|
client (dask.distributed.Client): Dask client.
|
|
455
528
|
additional_metadata (str, optional): metadata of optimization process.
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
ENCODING (str): Encoding of csv file. Default to 'cp932'.
|
|
463
|
-
prm_names (str): User defined names of parameters.
|
|
464
|
-
obj_names (str): User defined names of objectives.
|
|
465
|
-
cns_names (str): User defined names of constraints.
|
|
466
|
-
is_restart (bool): If the optimization process is a continuation of another process or not.
|
|
467
|
-
is_processing (bool): The optimization is running or not.
|
|
529
|
+
hv_reference (str or list[float or np.ndarray, optional):
|
|
530
|
+
The method to calculate hypervolume or
|
|
531
|
+
the reference point itself.
|
|
532
|
+
Valid values are 'dynamic-pareto' or
|
|
533
|
+
'dynamic-nadir' or 'nadir' or 'pareto'
|
|
534
|
+
or fixed point (in objective function space).
|
|
468
535
|
|
|
469
536
|
"""
|
|
470
537
|
|
|
@@ -487,7 +554,10 @@ class History:
|
|
|
487
554
|
cns_names=None,
|
|
488
555
|
client=None,
|
|
489
556
|
additional_metadata=None,
|
|
557
|
+
hv_reference=None,
|
|
490
558
|
):
|
|
559
|
+
# hypervolume 計算メソッド
|
|
560
|
+
self._hv_reference = hv_reference or 'dynamic-pareto'
|
|
491
561
|
|
|
492
562
|
# 引数の処理
|
|
493
563
|
self.path = history_path # .csv
|
|
@@ -739,59 +809,98 @@ class History:
|
|
|
739
809
|
|
|
740
810
|
def _calc_hypervolume(self, objectives, df):
|
|
741
811
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
# 計算結果を履歴の一部に割り当て
|
|
778
|
-
# idx: [False, True, False, True, True, False, ...]
|
|
779
|
-
# hvs: [ 1, 2, 3, ...]
|
|
780
|
-
# want:[ 0, 1, 1, 2, 3, 3, ...]
|
|
781
|
-
hvs_index = -1
|
|
782
|
-
for i in range(len(df)):
|
|
783
|
-
|
|
784
|
-
# process hvs index
|
|
785
|
-
if idx[i]:
|
|
786
|
-
hvs_index += 1
|
|
787
|
-
|
|
788
|
-
# get hv
|
|
789
|
-
if hvs_index < 0:
|
|
790
|
-
hypervolume = 0.
|
|
812
|
+
if len(objectives) < 2:
|
|
813
|
+
df.loc[len(df) - 1, 'hypervolume'] = 0.
|
|
814
|
+
return
|
|
815
|
+
|
|
816
|
+
# 最小化問題に変換された objective values を取得
|
|
817
|
+
raw_objective_values = df[self.obj_names].values
|
|
818
|
+
objective_values = np.empty_like(raw_objective_values)
|
|
819
|
+
for n_trial in range(len(raw_objective_values)):
|
|
820
|
+
for obj_idx, (_, objective) in enumerate(objectives.items()):
|
|
821
|
+
objective_values[n_trial, obj_idx] = objective.convert(raw_objective_values[n_trial, obj_idx])
|
|
822
|
+
|
|
823
|
+
# pareto front を取得
|
|
824
|
+
def get_pareto(objective_values_, with_partial=False):
|
|
825
|
+
ret = None
|
|
826
|
+
if with_partial:
|
|
827
|
+
ret = []
|
|
828
|
+
|
|
829
|
+
pareto_set_ = np.empty((0, len(self.obj_names)))
|
|
830
|
+
for i in range(len(objective_values_)):
|
|
831
|
+
target = objective_values_[i]
|
|
832
|
+
dominated = False
|
|
833
|
+
# TODO: Array の計算に直して高速化する
|
|
834
|
+
for j in range(len(objective_values_)):
|
|
835
|
+
compare = objective_values_[j]
|
|
836
|
+
if all(target > compare):
|
|
837
|
+
dominated = True
|
|
838
|
+
break
|
|
839
|
+
if not dominated:
|
|
840
|
+
pareto_set_ = np.concatenate([pareto_set_, [target]], axis=0)
|
|
841
|
+
|
|
842
|
+
if ret is not None:
|
|
843
|
+
ret.append(np.array(pareto_set_))
|
|
844
|
+
|
|
845
|
+
if ret is not None:
|
|
846
|
+
return pareto_set_, ret
|
|
791
847
|
else:
|
|
792
|
-
|
|
848
|
+
return pareto_set_
|
|
849
|
+
|
|
850
|
+
if self._hv_reference == 'dynamic-pareto':
|
|
851
|
+
pareto_set, pareto_set_list = get_pareto(objective_values, with_partial=True)
|
|
852
|
+
for i, partial_pareto_set in enumerate(pareto_set_list):
|
|
853
|
+
ref_point = pareto_set.max(axis=0) + 1e-8
|
|
854
|
+
hv = compute_hypervolume(partial_pareto_set, ref_point)
|
|
855
|
+
df.loc[i, 'hypervolume'] = hv
|
|
856
|
+
return
|
|
857
|
+
|
|
858
|
+
elif self._hv_reference == 'dynamic-nadir':
|
|
859
|
+
_, pareto_set_list = get_pareto(objective_values, with_partial=True)
|
|
860
|
+
for i, partial_pareto_set in enumerate(pareto_set_list):
|
|
861
|
+
ref_point = objective_values.max(axis=0) + 1e-8
|
|
862
|
+
hv = compute_hypervolume(partial_pareto_set, ref_point)
|
|
863
|
+
df.loc[i, 'hypervolume'] = hv
|
|
864
|
+
return
|
|
865
|
+
|
|
866
|
+
elif self._hv_reference == 'nadir':
|
|
867
|
+
pareto_set = get_pareto(objective_values)
|
|
868
|
+
ref_point = objective_values.max(axis=0) + 1e-8
|
|
869
|
+
hv = compute_hypervolume(pareto_set, ref_point)
|
|
870
|
+
df.loc[len(df) - 1, 'hypervolume'] = hv
|
|
871
|
+
return
|
|
872
|
+
|
|
873
|
+
elif self._hv_reference == 'pareto':
|
|
874
|
+
pareto_set = get_pareto(objective_values)
|
|
875
|
+
ref_point = pareto_set.max(axis=0) + 1e-8
|
|
876
|
+
hv = compute_hypervolume(pareto_set, ref_point)
|
|
877
|
+
df.loc[len(df) - 1, 'hypervolume'] = hv
|
|
878
|
+
return
|
|
879
|
+
|
|
880
|
+
elif (
|
|
881
|
+
isinstance(self._hv_reference, np.ndarray)
|
|
882
|
+
or isinstance(self._hv_reference, list)
|
|
883
|
+
):
|
|
884
|
+
_buff = np.array(self._hv_reference)
|
|
885
|
+
assert _buff.shape == (len(self.obj_names),)
|
|
886
|
+
|
|
887
|
+
ref_point = np.array(
|
|
888
|
+
[obj.convert(raw_value) for obj, raw_value in zip(objectives.values(), _buff)]
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
_buff = get_pareto(objective_values)
|
|
793
892
|
|
|
794
|
-
|
|
893
|
+
pareto_set = np.empty((0, len(objectives)))
|
|
894
|
+
for pareto_sol in _buff:
|
|
895
|
+
if all(pareto_sol < ref_point):
|
|
896
|
+
pareto_set = np.concatenate([pareto_set, [pareto_sol]], axis=0)
|
|
897
|
+
|
|
898
|
+
hv = compute_hypervolume(pareto_set, ref_point)
|
|
899
|
+
df.loc[len(df) - 1, 'hypervolume'] = hv
|
|
900
|
+
return
|
|
901
|
+
|
|
902
|
+
else:
|
|
903
|
+
raise NotImplementedError(f'Invalid Hypervolume reference point calculation method: {self._hv_reference}')
|
|
795
904
|
|
|
796
905
|
def save(self, _f=None):
|
|
797
906
|
"""Save csv file."""
|
|
@@ -78,6 +78,10 @@ class FemtetInterface(FEMInterface):
|
|
|
78
78
|
it will be None and no parametric outputs are used
|
|
79
79
|
as objectives.
|
|
80
80
|
|
|
81
|
+
confirm_before_exit (bool):
|
|
82
|
+
Whether to confirm before (abnormal) termination.
|
|
83
|
+
Default is True.
|
|
84
|
+
|
|
81
85
|
**kwargs: Additional arguments from inherited classes.
|
|
82
86
|
|
|
83
87
|
Warning:
|
|
@@ -103,6 +107,7 @@ class FemtetInterface(FEMInterface):
|
|
|
103
107
|
allow_without_project: bool = False, # main でのみ True を許容したいので super() の引数にしない。
|
|
104
108
|
open_result_with_gui: bool = True,
|
|
105
109
|
parametric_output_indexes_use_as_objective: dict[int, str or float] = None,
|
|
110
|
+
confirm_before_exit: bool = True,
|
|
106
111
|
**kwargs # 継承されたクラスからの引数
|
|
107
112
|
):
|
|
108
113
|
|
|
@@ -132,6 +137,7 @@ class FemtetInterface(FEMInterface):
|
|
|
132
137
|
self.parametric_output_indexes_use_as_objective = parametric_output_indexes_use_as_objective
|
|
133
138
|
self._original_autosave_enabled = _get_autosave_enabled()
|
|
134
139
|
_set_autosave_enabled(False)
|
|
140
|
+
self.confirm_before_exit = confirm_before_exit
|
|
135
141
|
|
|
136
142
|
# dask サブプロセスのときは femprj を更新し connect_method を new にする
|
|
137
143
|
try:
|
|
@@ -169,6 +175,7 @@ class FemtetInterface(FEMInterface):
|
|
|
169
175
|
open_result_with_gui=self.open_result_with_gui,
|
|
170
176
|
parametric_output_indexes_use_as_objective=self.parametric_output_indexes_use_as_objective,
|
|
171
177
|
save_pdt=self.save_pdt,
|
|
178
|
+
confirm_before_exit=self.confirm_before_exit,
|
|
172
179
|
**kwargs
|
|
173
180
|
)
|
|
174
181
|
|
|
@@ -241,7 +248,8 @@ class FemtetInterface(FEMInterface):
|
|
|
241
248
|
print('================')
|
|
242
249
|
print(message)
|
|
243
250
|
print('================')
|
|
244
|
-
|
|
251
|
+
if self.confirm_before_exit:
|
|
252
|
+
input('Press enter to finish...')
|
|
245
253
|
raise RuntimeError(message)
|
|
246
254
|
|
|
247
255
|
if self.Femtet is None:
|
|
@@ -532,7 +540,8 @@ class FemtetInterface(FEMInterface):
|
|
|
532
540
|
logger.error(Msg.ERR_CANNOT_ACCESS_API + 'GetVariableNames_py')
|
|
533
541
|
logger.error(Msg.CERTIFY_MACRO_VERSION)
|
|
534
542
|
print('================')
|
|
535
|
-
|
|
543
|
+
if self.confirm_before_exit:
|
|
544
|
+
input(Msg.ENTER_TO_QUIT)
|
|
536
545
|
raise e
|
|
537
546
|
|
|
538
547
|
if variable_names is not None:
|
|
@@ -543,7 +552,8 @@ class FemtetInterface(FEMInterface):
|
|
|
543
552
|
logger.error(message)
|
|
544
553
|
logger.error(f'`{param_name}` not in {variable_names}')
|
|
545
554
|
print('================')
|
|
546
|
-
|
|
555
|
+
if self.confirm_before_exit:
|
|
556
|
+
input(Msg.ENTER_TO_QUIT)
|
|
547
557
|
raise RuntimeError(message)
|
|
548
558
|
else:
|
|
549
559
|
return None
|
|
@@ -736,7 +746,8 @@ class FemtetInterface(FEMInterface):
|
|
|
736
746
|
logger.error(Msg.ERR_CANNOT_ACCESS_API + 'Femtet.Exit()')
|
|
737
747
|
logger.error(Msg.CERTIFY_MACRO_VERSION)
|
|
738
748
|
print('================')
|
|
739
|
-
|
|
749
|
+
if self.confirm_before_exit:
|
|
750
|
+
input(Msg.ENTER_TO_QUIT)
|
|
740
751
|
raise e
|
|
741
752
|
|
|
742
753
|
else:
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
from pyfemtet.opt.optimizer._base import AbstractOptimizer, logger, OptimizationMethodChecker
|
|
2
|
-
from pyfemtet.opt.optimizer._optuna import OptunaOptimizer
|
|
2
|
+
from pyfemtet.opt.optimizer._optuna._optuna import OptunaOptimizer
|
|
3
3
|
from pyfemtet.opt.optimizer._scipy import ScipyOptimizer
|
|
4
4
|
from pyfemtet.opt.optimizer._scipy_scalar import ScipyScalarOptimizer
|
|
5
5
|
|
|
6
|
+
from pyfemtet.opt.optimizer._optuna._pof_botorch import PoFBoTorchSampler, PoFConfig
|
|
7
|
+
|
|
6
8
|
__all__ = [
|
|
7
9
|
'ScipyScalarOptimizer',
|
|
8
10
|
'ScipyOptimizer',
|
|
9
11
|
'OptunaOptimizer',
|
|
10
12
|
'AbstractOptimizer',
|
|
11
13
|
'logger',
|
|
14
|
+
'PoFBoTorchSampler',
|
|
15
|
+
'PoFConfig',
|
|
12
16
|
]
|
pyfemtet/opt/optimizer/_base.py
CHANGED
|
@@ -142,6 +142,7 @@ class AbstractOptimizer(ABC):
|
|
|
142
142
|
self.subprocess_idx = None
|
|
143
143
|
self._exception = None
|
|
144
144
|
self.method_checker: OptimizationMethodChecker = OptimizationMethodChecker(self)
|
|
145
|
+
self._retry_counter = 0
|
|
145
146
|
|
|
146
147
|
# ===== algorithm specific methods =====
|
|
147
148
|
@abstractmethod
|
|
@@ -176,13 +177,11 @@ class AbstractOptimizer(ABC):
|
|
|
176
177
|
|
|
177
178
|
# Optimizer の x の更新
|
|
178
179
|
self.set_parameter_values(x)
|
|
179
|
-
|
|
180
|
-
logger.info('---------------------')
|
|
181
180
|
logger.info(f'input: {x}')
|
|
182
181
|
|
|
183
182
|
# FEM の更新
|
|
184
|
-
logger.debug('fem.update() start')
|
|
185
183
|
try:
|
|
184
|
+
logger.info(f'Solving FEM...')
|
|
186
185
|
df_to_fem = self.variables.get_variables(
|
|
187
186
|
format='df',
|
|
188
187
|
filter_pass_to_fem=True
|
|
@@ -193,25 +192,20 @@ class AbstractOptimizer(ABC):
|
|
|
193
192
|
logger.info(f'{type(e).__name__} : {e}')
|
|
194
193
|
logger.info(Msg.INFO_EXCEPTION_DURING_FEM_ANALYSIS)
|
|
195
194
|
logger.info(x)
|
|
196
|
-
raise e # may be just a ModelError, etc.
|
|
195
|
+
raise e # may be just a ModelError, etc. Handling them in Concrete classes.
|
|
197
196
|
|
|
198
197
|
# y, _y, c の更新
|
|
199
|
-
logger.debug('calculate y start')
|
|
200
198
|
y = [obj.calc(self.fem) for obj in self.objectives.values()]
|
|
201
199
|
|
|
202
|
-
logger.debug('calculate _y start')
|
|
203
200
|
_y = [obj.convert(value) for obj, value in zip(self.objectives.values(), y)]
|
|
204
201
|
|
|
205
|
-
logger.debug('calculate c start')
|
|
206
202
|
c = [cns.calc(self.fem) for cns in self.constraints.values()]
|
|
207
203
|
|
|
208
|
-
|
|
209
|
-
|
|
204
|
+
# register to history
|
|
210
205
|
df_to_opt = self.variables.get_variables(
|
|
211
206
|
format='df',
|
|
212
207
|
filter_parameter=True,
|
|
213
208
|
)
|
|
214
|
-
|
|
215
209
|
self.history.record(
|
|
216
210
|
df_to_opt,
|
|
217
211
|
self.objectives,
|
|
@@ -223,9 +217,7 @@ class AbstractOptimizer(ABC):
|
|
|
223
217
|
postprocess_args=self.fem._create_postprocess_args(),
|
|
224
218
|
)
|
|
225
219
|
|
|
226
|
-
logger.
|
|
227
|
-
|
|
228
|
-
logger.info(f'output: {_y}')
|
|
220
|
+
logger.info(f'output: {y}')
|
|
229
221
|
|
|
230
222
|
return np.array(y), np.array(_y), np.array(c)
|
|
231
223
|
|
|
File without changes
|
|
File without changes
|