pyfemtet 0.4.25__py3-none-any.whl → 0.5.0__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.mo +0 -0
- pyfemtet/message/locales/ja/LC_MESSAGES/messages.po +114 -62
- pyfemtet/message/locales/messages.pot +114 -62
- pyfemtet/message/messages.py +6 -2
- pyfemtet/opt/__init__.py +2 -2
- pyfemtet/opt/_femopt.py +27 -46
- pyfemtet/opt/_femopt_core.py +50 -33
- pyfemtet/opt/_test_utils/__init__.py +0 -0
- pyfemtet/opt/_test_utils/control_femtet.py +45 -0
- pyfemtet/opt/_test_utils/hyper_sphere.py +24 -0
- pyfemtet/opt/_test_utils/record_history.py +72 -0
- pyfemtet/opt/interface/_femtet.py +39 -1
- pyfemtet/opt/optimizer/__init__.py +12 -0
- pyfemtet/opt/{opt → optimizer}/_base.py +30 -9
- pyfemtet/opt/{opt → optimizer}/_optuna.py +15 -34
- pyfemtet/opt/optimizer/_optuna_botorchsampler_parameter_constraint_helper.py +331 -0
- pyfemtet/opt/{opt → optimizer}/_scipy.py +10 -6
- pyfemtet/opt/{opt → optimizer}/_scipy_scalar.py +24 -12
- pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric_test_result.reccsv +1 -1
- pyfemtet/opt/{femprj_sample → samples/femprj_sample}/her_ex40_parametric.py +10 -8
- pyfemtet/opt/samples/femprj_sample/her_ex40_parametric_test_result.reccsv +18 -0
- pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/her_ex40_parametric_jp.py +10 -8
- pyfemtet/opt/visualization/complex_components/main_figure_creator.py +69 -0
- pyfemtet/opt/visualization/complex_components/main_graph.py +299 -14
- pyfemtet/opt/visualization/process_monitor/pages.py +1 -1
- pyfemtet/opt/visualization/result_viewer/application.py +1 -1
- pyfemtet/opt/visualization/result_viewer/pages.py +9 -9
- {pyfemtet-0.4.25.dist-info → pyfemtet-0.5.0.dist-info}/METADATA +1 -1
- pyfemtet-0.5.0.dist-info/RECORD +112 -0
- pyfemtet/_test_util.py +0 -135
- pyfemtet/opt/femprj_sample/her_ex40_parametric_test_result.reccsv +0 -18
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
- pyfemtet/opt/opt/__init__.py +0 -12
- pyfemtet/opt/opt/_optuna_botorch_helper.py +0 -209
- pyfemtet-0.4.25.dist-info/RECORD +0 -140
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/.gitignore +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF - True.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.prt +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.SLDPRT +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/her_ex40_parametric.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric_parallel.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric.femprj +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric_parallel.py +0 -0
- /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric_test_result.reccsv +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/ParametricIF_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/ParametricIF_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_NX_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_NX_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_SW_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_SW_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gal_ex58_parametric_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gal_ex58_parametric_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gau_ex08_parametric_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gau_ex08_parametric_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/her_ex40_parametric_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_parallel_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_jp.femprj +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_jp.py +0 -0
- /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_parallel_jp.py +0 -0
- {pyfemtet-0.4.25.dist-info → pyfemtet-0.5.0.dist-info}/LICENSE +0 -0
- {pyfemtet-0.4.25.dist-info → pyfemtet-0.5.0.dist-info}/WHEEL +0 -0
- {pyfemtet-0.4.25.dist-info → pyfemtet-0.5.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
from torch import Tensor
|
|
8
|
+
|
|
9
|
+
from optuna.study import Study
|
|
10
|
+
from optuna.trial import Trial
|
|
11
|
+
from optuna._transform import _SearchSpaceTransform
|
|
12
|
+
|
|
13
|
+
from botorch.acquisition import AcquisitionFunction
|
|
14
|
+
from botorch.optim.initializers import gen_batch_initial_conditions
|
|
15
|
+
|
|
16
|
+
from pyfemtet.opt._femopt_core import Constraint
|
|
17
|
+
from pyfemtet.opt.optimizer import OptunaOptimizer, logger
|
|
18
|
+
from pyfemtet.message import Msg
|
|
19
|
+
|
|
20
|
+
from time import time
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__all__ = ['do_patch']
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
BotorchConstraint = Callable[[Tensor], Tensor]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def do_patch(
|
|
30
|
+
study: Study,
|
|
31
|
+
constraints: dict[str, Constraint],
|
|
32
|
+
opt: OptunaOptimizer,
|
|
33
|
+
):
|
|
34
|
+
"""BoTorchSampler の optimize_acqf をパッチし、パラメータ拘束が実施できるようにします。
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
study (Study): Optuna study. Use to calculate bounds.
|
|
38
|
+
constraints (dict[str, Constraint]): Constraints.
|
|
39
|
+
opt (OptunaOptimizer): OptunaOptimizer.
|
|
40
|
+
"""
|
|
41
|
+
import optuna_integration
|
|
42
|
+
target_fun = optuna_integration.botorch.optimize_acqf
|
|
43
|
+
new_fun: callable = OptimizeReplacedACQF(target_fun)
|
|
44
|
+
new_fun.set_constraints(list(constraints.values()))
|
|
45
|
+
new_fun.set_study(study)
|
|
46
|
+
new_fun.set_opt(opt)
|
|
47
|
+
optuna_integration.botorch.optimize_acqf = new_fun
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class GeneralFunctionWithForwardDifference(torch.autograd.Function):
|
|
51
|
+
"""自作関数を pytorch で自動微分するためのクラスです。
|
|
52
|
+
|
|
53
|
+
ユーザー定義関数を botorch 形式に変換する過程で微分の計算ができなくなるのでこれが必要です。
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def forward(ctx, f, xs):
|
|
58
|
+
ys = f(xs)
|
|
59
|
+
ctx.save_for_backward(xs, ys)
|
|
60
|
+
ctx.f = f
|
|
61
|
+
return ys
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def backward(ctx, grad_output):
|
|
65
|
+
xs, ys = ctx.saved_tensors
|
|
66
|
+
f = ctx.f
|
|
67
|
+
dx = 0.001 # 入力は normalized なので決め打ちでよい
|
|
68
|
+
diff = []
|
|
69
|
+
xs = xs.detach() # xs に余計な計算履歴を残さないために、detachする。
|
|
70
|
+
for i in range(len(xs)):
|
|
71
|
+
xs[i] += dx
|
|
72
|
+
diff.append(torch.sum(grad_output * (f(xs) - ys)))
|
|
73
|
+
xs[i] -= dx
|
|
74
|
+
diff = torch.tensor(diff) / dx
|
|
75
|
+
return None, diff
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ConvertedConstraint:
|
|
79
|
+
"""ユーザーが定義した Constraint を botorch で処理できる形式に変換します。
|
|
80
|
+
|
|
81
|
+
`callable()` は形状 `d` の 1 次元テンソルを受け取り、スカラーを返します。
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(self, constraint: Constraint, study: Study, bound: str, opt: OptunaOptimizer):
|
|
85
|
+
self._constraint: Constraint = constraint
|
|
86
|
+
self._study = study
|
|
87
|
+
self._bound = bound
|
|
88
|
+
self._opt = opt
|
|
89
|
+
|
|
90
|
+
def __call__(self, x: Tensor) -> Tensor: # BotorchConstraint
|
|
91
|
+
"""optimize_acqf() に渡される非線形拘束関数の処理です。
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
x (Tensor): Normalized parameters. Its length is d (== len(prm)).
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
float Tensor. >= 0 is feasible.
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
norm_x = x.detach().numpy()
|
|
102
|
+
c = evaluate_pyfemtet_cns(self._study, self._constraint, norm_x, self._opt)
|
|
103
|
+
if self._bound == 'lb':
|
|
104
|
+
return Tensor([c - self._constraint.lb])
|
|
105
|
+
elif self._bound == 'ub':
|
|
106
|
+
return Tensor([self._constraint.ub - c])
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def is_feasible(study: Study, constraints: list[Constraint], norm_x: np.ndarray, opt: OptunaOptimizer) -> bool:
|
|
110
|
+
feasible = True
|
|
111
|
+
cns: Constraint
|
|
112
|
+
for cns in constraints:
|
|
113
|
+
c = evaluate_pyfemtet_cns(study, cns, norm_x, opt)
|
|
114
|
+
if cns.lb is not None:
|
|
115
|
+
if cns.lb > c:
|
|
116
|
+
feasible = False
|
|
117
|
+
break
|
|
118
|
+
if cns.ub is not None:
|
|
119
|
+
if cns.ub < c:
|
|
120
|
+
feasible = False
|
|
121
|
+
break
|
|
122
|
+
return feasible
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def evaluate_pyfemtet_cns(study: Study, cns: Constraint, norm_x: np.ndarray, opt: OptunaOptimizer) -> float:
|
|
126
|
+
"""Evaluate given constraint function by given NORMALIZED x.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
study (Study): Optuna study. Use to detect search space from last trial's Distribution objects.
|
|
130
|
+
cns (Constraint): PyFemtet's format constraint.
|
|
131
|
+
norm_x (np.ndarray): NORMALIZED values of all parameters.
|
|
132
|
+
opt (OptunaOptimizer): PyFemtet's optimizer. Used for update values of `opt` and `fem` who may be used in `cns`.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
bool: feasible or not.
|
|
136
|
+
"""
|
|
137
|
+
# ===== unnormalize x =====
|
|
138
|
+
trial: Trial
|
|
139
|
+
for trial in study.trials[-1::-1]:
|
|
140
|
+
if len(trial.distributions) > 0:
|
|
141
|
+
break
|
|
142
|
+
else:
|
|
143
|
+
raise RuntimeError('Cannot detect bounds because of no COMPLETE trials. Certify 1 or more sampling excluding initial conditions.')
|
|
144
|
+
search_space = study.sampler.infer_relative_search_space(study, trial)
|
|
145
|
+
trans = _SearchSpaceTransform(trial.distributions, transform_0_1=True, transform_log=False, transform_step=False)
|
|
146
|
+
params = trans.untransform(norm_x)
|
|
147
|
+
|
|
148
|
+
# ===== update OptunaOptimizer and FEMInterface who is referenced by cns =====
|
|
149
|
+
|
|
150
|
+
# opt
|
|
151
|
+
opt.set_parameter(params)
|
|
152
|
+
|
|
153
|
+
# fem
|
|
154
|
+
if cns.using_fem:
|
|
155
|
+
df_to_fem = opt.variables.get_variables(format='df', filter_pass_to_fem=True)
|
|
156
|
+
opt.fem.update_parameter(df_to_fem)
|
|
157
|
+
|
|
158
|
+
# ===== calc cns =====
|
|
159
|
+
return cns.calc(opt.fem)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class NonlinearInequalityConstraints:
|
|
163
|
+
"""botorch の optimize_acqf に parameter constraints を設定するための引数を作成します。"""
|
|
164
|
+
|
|
165
|
+
def __init__(self, study: Study, constraints: list[Constraint], opt: OptunaOptimizer):
|
|
166
|
+
self._study = study
|
|
167
|
+
self._constraints = constraints
|
|
168
|
+
self._opt = opt
|
|
169
|
+
|
|
170
|
+
self._nonlinear_inequality_constraints = []
|
|
171
|
+
cns: Constraint
|
|
172
|
+
for cns in self._constraints:
|
|
173
|
+
if cns.lb is not None:
|
|
174
|
+
cns_botorch = ConvertedConstraint(cns, self._study, 'lb', self._opt)
|
|
175
|
+
item = (lambda x: GeneralFunctionWithForwardDifference.apply(cns_botorch, x), True)
|
|
176
|
+
self._nonlinear_inequality_constraints.append(item)
|
|
177
|
+
if cns.ub is not None:
|
|
178
|
+
cns_botorch = ConvertedConstraint(cns, self._study, 'ub', self._opt)
|
|
179
|
+
item = (lambda x: GeneralFunctionWithForwardDifference.apply(cns_botorch, x), True)
|
|
180
|
+
self._nonlinear_inequality_constraints.append(item)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _filter_feasible_conditions(self, ic_batch):
|
|
184
|
+
# List to store feasible initial conditions
|
|
185
|
+
feasible_ic_list = []
|
|
186
|
+
|
|
187
|
+
for each_num_restarts in ic_batch:
|
|
188
|
+
feasible_q_list = []
|
|
189
|
+
for each_q in each_num_restarts:
|
|
190
|
+
norm_x: np.ndarray = each_q.numpy() # normalized parameters
|
|
191
|
+
|
|
192
|
+
if is_feasible(self._study, self._constraints, norm_x, self._opt):
|
|
193
|
+
feasible_q_list.append(each_q) # Keep only feasible rows
|
|
194
|
+
|
|
195
|
+
if feasible_q_list: # Only add if there are feasible rows
|
|
196
|
+
feasible_ic_list.append(torch.stack(feasible_q_list))
|
|
197
|
+
|
|
198
|
+
# Stack feasible conditions back into tensor format
|
|
199
|
+
if feasible_ic_list:
|
|
200
|
+
return torch.stack(feasible_ic_list)
|
|
201
|
+
else:
|
|
202
|
+
return None # Return None if none are feasible
|
|
203
|
+
|
|
204
|
+
@staticmethod
|
|
205
|
+
def _generate_random_initial_conditions(shape):
|
|
206
|
+
# Generates random initial conditions with the same shape as ic_batch
|
|
207
|
+
return torch.rand(shape)
|
|
208
|
+
|
|
209
|
+
def _generate_feasible_initial_conditions(self, *args, **kwargs):
|
|
210
|
+
# A `num_restarts x q x d` tensor of initial conditions.
|
|
211
|
+
ic_batch = gen_batch_initial_conditions(*args, **kwargs)
|
|
212
|
+
feasible_ic_batch = self._filter_feasible_conditions(ic_batch)
|
|
213
|
+
|
|
214
|
+
while feasible_ic_batch is None:
|
|
215
|
+
# Generate new random ic_batch with the same shape
|
|
216
|
+
print('警告: gen_batch_initial_conditions() は feasible な初期値を提案しませんでした。'
|
|
217
|
+
'パラメータ提案を探索するための初期値をランダムに選定します。')
|
|
218
|
+
random_ic_batch = self._generate_random_initial_conditions(ic_batch.shape)
|
|
219
|
+
feasible_ic_batch = self._filter_feasible_conditions(random_ic_batch)
|
|
220
|
+
|
|
221
|
+
return feasible_ic_batch
|
|
222
|
+
|
|
223
|
+
def create_kwargs(self) -> dict:
|
|
224
|
+
"""
|
|
225
|
+
nonlinear_inequality_constraints:
|
|
226
|
+
非線形不等式制約を表すタプルのリスト。
|
|
227
|
+
タプルの最初の要素は、`callable(x) >= 0` という形式の制約を表す呼び出し可能オブジェクトです。
|
|
228
|
+
2 番目の要素はブール値で、点内制約の場合は `True`
|
|
229
|
+
制約は後で scipy ソルバーに渡されます。
|
|
230
|
+
この場合、`batch_initial_conditions` を渡す必要があります。
|
|
231
|
+
非線形不等式制約を使用するには、`batch_limit` を 1 に設定する必要もあります。
|
|
232
|
+
これは、`options` で指定されていない場合は自動的に行われます。
|
|
233
|
+
"""
|
|
234
|
+
return dict(
|
|
235
|
+
q=1,
|
|
236
|
+
options=dict(
|
|
237
|
+
batch_limit=1,
|
|
238
|
+
),
|
|
239
|
+
nonlinear_inequality_constraints=self._nonlinear_inequality_constraints,
|
|
240
|
+
ic_generator=self._generate_feasible_initial_conditions,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class AcquisitionFunctionWithPenalty(AcquisitionFunction):
|
|
245
|
+
"""獲得関数に infeasible 項を追加します。"""
|
|
246
|
+
|
|
247
|
+
# noinspection PyAttributeOutsideInit
|
|
248
|
+
def set_acqf(self, acqf):
|
|
249
|
+
self._acqf = acqf
|
|
250
|
+
|
|
251
|
+
# noinspection PyAttributeOutsideInit
|
|
252
|
+
def set_constraints(self, constraints: list[Constraint]):
|
|
253
|
+
self._constraints: list[Constraint] = constraints
|
|
254
|
+
|
|
255
|
+
# noinspection PyAttributeOutsideInit
|
|
256
|
+
def set_study(self, study: Study):
|
|
257
|
+
self._study: Study = study
|
|
258
|
+
|
|
259
|
+
# noinspection PyAttributeOutsideInit
|
|
260
|
+
def set_opt(self, opt: OptunaOptimizer):
|
|
261
|
+
self._opt = opt
|
|
262
|
+
|
|
263
|
+
def forward(self, X: "Tensor") -> "Tensor":
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
X (Tensor): batch_size x 1 x n_params tensor.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Tensor: batch_size tensor.
|
|
271
|
+
|
|
272
|
+
"""
|
|
273
|
+
base = self._acqf.forward(X)
|
|
274
|
+
|
|
275
|
+
norm_x: np.ndarray
|
|
276
|
+
for i, _norm_x in enumerate(X.detach().numpy()):
|
|
277
|
+
|
|
278
|
+
cns: Constraint
|
|
279
|
+
for cns in self._constraints:
|
|
280
|
+
feasible = is_feasible(self._study, [cns], _norm_x[0], self._opt)
|
|
281
|
+
if not feasible:
|
|
282
|
+
base[i] = base[i] * 0. # ペナルティ
|
|
283
|
+
break
|
|
284
|
+
|
|
285
|
+
return base
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class OptimizeReplacedACQF(partial):
|
|
289
|
+
"""optimize_acqf をこの partial 関数に置き換えます。"""
|
|
290
|
+
|
|
291
|
+
# noinspection PyAttributeOutsideInit
|
|
292
|
+
def set_constraints(self, constraints: list[Constraint]):
|
|
293
|
+
self._constraints: list[Constraint] = constraints
|
|
294
|
+
|
|
295
|
+
# noinspection PyAttributeOutsideInit
|
|
296
|
+
def set_study(self, study: Study):
|
|
297
|
+
self._study: Study = study
|
|
298
|
+
|
|
299
|
+
# noinspection PyAttributeOutsideInit
|
|
300
|
+
def set_opt(self, opt: OptunaOptimizer):
|
|
301
|
+
self._opt = opt
|
|
302
|
+
|
|
303
|
+
def __call__(self, *args, **kwargs):
|
|
304
|
+
"""置き換え先の関数の処理内容です。
|
|
305
|
+
|
|
306
|
+
kwargs を横入りして追記することで拘束を実現します。
|
|
307
|
+
"""
|
|
308
|
+
|
|
309
|
+
logger.info(Msg.START_CANDIDATE_WITH_PARAMETER_CONSTRAINT)
|
|
310
|
+
|
|
311
|
+
# FEM の更新が必要な場合、時間がかかることが多いので警告を出す
|
|
312
|
+
if any([cns.using_fem for cns in self._constraints]):
|
|
313
|
+
logger.warning(Msg.WARN_UPDATE_FEM_PARAMETER_TOOK_A_LONG_TIME)
|
|
314
|
+
|
|
315
|
+
# 獲得関数に infeasible な場合のペナルティ項を追加します。
|
|
316
|
+
acqf = kwargs['acq_function']
|
|
317
|
+
new_acqf = AcquisitionFunctionWithPenalty(...)
|
|
318
|
+
new_acqf.set_acqf(acqf)
|
|
319
|
+
new_acqf.set_constraints(self._constraints)
|
|
320
|
+
new_acqf.set_study(self._study)
|
|
321
|
+
new_acqf.set_opt(self._opt)
|
|
322
|
+
kwargs['acq_function'] = new_acqf
|
|
323
|
+
|
|
324
|
+
# optimize_acqf の探索に parameter constraints を追加します。
|
|
325
|
+
nlic = NonlinearInequalityConstraints(self._study, self._constraints, self._opt)
|
|
326
|
+
kwargs.update(nlic.create_kwargs())
|
|
327
|
+
|
|
328
|
+
# replace other arguments
|
|
329
|
+
...
|
|
330
|
+
|
|
331
|
+
return super().__call__(*args, **kwargs)
|
|
@@ -13,7 +13,7 @@ from scipy.optimize import minimize, OptimizeResult
|
|
|
13
13
|
|
|
14
14
|
# pyfemtet relative
|
|
15
15
|
from pyfemtet.opt._femopt_core import OptimizationStatus, generate_lhs
|
|
16
|
-
from pyfemtet.opt.
|
|
16
|
+
from pyfemtet.opt.optimizer import AbstractOptimizer, logger, OptimizationMethodChecker
|
|
17
17
|
from pyfemtet.core import MeshError, ModelError, SolveError
|
|
18
18
|
from pyfemtet.message import Msg
|
|
19
19
|
|
|
@@ -51,6 +51,9 @@ class StopIterationCallback:
|
|
|
51
51
|
|
|
52
52
|
class ScipyMethodChecker(OptimizationMethodChecker):
|
|
53
53
|
def check_incomplete_bounds(self, raise_error=True): return True
|
|
54
|
+
def check_seed(self, raise_error=True):
|
|
55
|
+
logger.warning(Msg.WARN_SCIPY_DOESNT_NEED_SEED)
|
|
56
|
+
return True
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
class ScipyOptimizer(AbstractOptimizer):
|
|
@@ -76,8 +79,9 @@ class ScipyOptimizer(AbstractOptimizer):
|
|
|
76
79
|
|
|
77
80
|
def _objective(self, x: np.ndarray): # x: candidate parameter
|
|
78
81
|
# update parameter
|
|
79
|
-
self.
|
|
80
|
-
|
|
82
|
+
df = self.get_parameter('df')
|
|
83
|
+
df['value'] = x
|
|
84
|
+
self.fem.update_parameter(df)
|
|
81
85
|
|
|
82
86
|
# strict constraints
|
|
83
87
|
...
|
|
@@ -108,13 +112,13 @@ class ScipyOptimizer(AbstractOptimizer):
|
|
|
108
112
|
def run(self):
|
|
109
113
|
|
|
110
114
|
# create init
|
|
111
|
-
x0 = self.
|
|
115
|
+
x0 = self.get_parameter('values')
|
|
112
116
|
|
|
113
117
|
# create bounds
|
|
114
118
|
if 'bounds' not in self.minimize_kwargs.keys():
|
|
115
119
|
bounds = []
|
|
116
|
-
for i, row in self.
|
|
117
|
-
lb, ub = row['
|
|
120
|
+
for i, row in self.get_parameter('df').iterrows():
|
|
121
|
+
lb, ub = row['lower_bound'], row['upper_bound']
|
|
118
122
|
if lb is None: lb = -np.inf
|
|
119
123
|
if ub is None: ub = np.inf
|
|
120
124
|
bounds.append([lb, ub])
|
|
@@ -13,12 +13,16 @@ from scipy.optimize import minimize_scalar, OptimizeResult
|
|
|
13
13
|
|
|
14
14
|
# pyfemtet relative
|
|
15
15
|
from pyfemtet.opt._femopt_core import OptimizationStatus, generate_lhs
|
|
16
|
-
from pyfemtet.opt.
|
|
16
|
+
from pyfemtet.opt.optimizer import AbstractOptimizer, logger, OptimizationMethodChecker
|
|
17
17
|
from pyfemtet.core import MeshError, ModelError, SolveError
|
|
18
|
+
from pyfemtet.message import Msg
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class ScipyScalarMethodChecker(OptimizationMethodChecker):
|
|
21
22
|
def check_incomplete_bounds(self, raise_error=True): return True
|
|
23
|
+
def check_seed(self, raise_error=True):
|
|
24
|
+
logger.warning(Msg.WARN_SCIPY_DOESNT_NEED_SEED)
|
|
25
|
+
return True
|
|
22
26
|
|
|
23
27
|
|
|
24
28
|
class ScipyScalarOptimizer(AbstractOptimizer):
|
|
@@ -41,8 +45,10 @@ class ScipyScalarOptimizer(AbstractOptimizer):
|
|
|
41
45
|
|
|
42
46
|
def _objective(self, x: float): # x: candidate parameter
|
|
43
47
|
# update parameter
|
|
44
|
-
|
|
45
|
-
self.
|
|
48
|
+
|
|
49
|
+
df = self.get_parameter('df')
|
|
50
|
+
df['value'] = x
|
|
51
|
+
self.fem.update_parameter(df)
|
|
46
52
|
|
|
47
53
|
# strict constraints
|
|
48
54
|
...
|
|
@@ -72,19 +78,25 @@ class ScipyScalarOptimizer(AbstractOptimizer):
|
|
|
72
78
|
def run(self):
|
|
73
79
|
|
|
74
80
|
# create init
|
|
75
|
-
|
|
81
|
+
params = self.get_parameter()
|
|
82
|
+
assert len(params) == 1, print(f'{params} parameter(s) are passed.')
|
|
76
83
|
|
|
77
84
|
# create bounds
|
|
78
85
|
if 'bounds' not in self.minimize_kwargs.keys():
|
|
79
86
|
bounds = []
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
|
|
88
|
+
row = self.get_parameter('df')
|
|
89
|
+
lb, ub = row['lower_bound'].iloc[0], row['upper_bound'].iloc[0]
|
|
90
|
+
|
|
91
|
+
if lb is None and ub is None:
|
|
92
|
+
pass
|
|
93
|
+
elif lb is None or ub is None:
|
|
94
|
+
raise ValueError('Both lower and upper bounds must be set.')
|
|
95
|
+
else:
|
|
96
|
+
bounds = [lb, ub]
|
|
97
|
+
self.minimize_kwargs.update(
|
|
98
|
+
{'bounds': bounds}
|
|
99
|
+
)
|
|
88
100
|
|
|
89
101
|
# run optimize
|
|
90
102
|
try:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"
|
|
1
|
+
"",prm,prm_lb,prm_ub,obj,obj_direction,,,,,
|
|
2
2
|
,,,,,,,,,,
|
|
3
3
|
trial,rot,rot_lower_bound,rot_upper_bound,final angle (degree),final angle (degree)_direction,non_domi,feasible,hypervolume,message,time
|
|
4
4
|
1,90.0,80.0,100.0,81.62540666214329,90,False,True,-1.0,initial,2024-07-24 10:57:24.248429
|
|
@@ -80,7 +80,7 @@ class SParameterCalculator:
|
|
|
80
80
|
return self.resonance_frequency # unit: Hz
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def antenna_is_smaller_than_substrate(Femtet):
|
|
83
|
+
def antenna_is_smaller_than_substrate(Femtet, opt):
|
|
84
84
|
"""Calculate the relationship between antenna size and board size.
|
|
85
85
|
|
|
86
86
|
This function is used to constrain the model
|
|
@@ -89,15 +89,17 @@ def antenna_is_smaller_than_substrate(Femtet):
|
|
|
89
89
|
Returns:
|
|
90
90
|
float: Difference between the substrate size and antenna size. Must be equal to or grater than 1 mm.
|
|
91
91
|
"""
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
params = opt.get_parameter()
|
|
93
|
+
r = params['antenna_radius']
|
|
94
|
+
w = params['substrate_w']
|
|
94
95
|
return w / 2 - r # unit: mm
|
|
95
96
|
|
|
96
97
|
|
|
97
|
-
def port_is_inside_antenna(Femtet):
|
|
98
|
+
def port_is_inside_antenna(Femtet, opt):
|
|
98
99
|
"""Calculate the relationship between the feed port location and antenna size."""
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
params = opt.get_parameter()
|
|
101
|
+
r = params['antenna_radius']
|
|
102
|
+
x = params['port_x']
|
|
101
103
|
return r - x # unit: mm. Must be equal to or grater than 1 mm.
|
|
102
104
|
|
|
103
105
|
|
|
@@ -125,8 +127,8 @@ if __name__ == '__main__':
|
|
|
125
127
|
femopt.add_parameter('port_x', 5, 1, 20)
|
|
126
128
|
|
|
127
129
|
# Add the constraint function to the optimization problem.
|
|
128
|
-
femopt.add_constraint(antenna_is_smaller_than_substrate, 'antenna and substrate clearance', lower_bound=1)
|
|
129
|
-
femopt.add_constraint(port_is_inside_antenna, 'antenna and port clearance', lower_bound=1)
|
|
130
|
+
femopt.add_constraint(antenna_is_smaller_than_substrate, 'antenna and substrate clearance', lower_bound=1, args=(opt,))
|
|
131
|
+
femopt.add_constraint(port_is_inside_antenna, 'antenna and port clearance', lower_bound=1, args=(opt,))
|
|
130
132
|
|
|
131
133
|
# Add the objective function to the optimization problem.
|
|
132
134
|
# The target frequency is 3.0 GHz.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"{""femprj_path"": ""E:\\pyfemtet\\pyfemtet\\pyfemtet\\opt\\femprj_sample\\her_ex40_parametric.femprj"", ""model_name"": ""Harm""}",prm,prm_lb,prm_ub,prm,prm_lb,prm_ub,prm,prm_lb,prm_ub,obj,obj_direction,,cns,cns_lb,cns_ub,cns,cns_lb,cns_ub,,,,
|
|
2
|
+
,,,,,,,,,,,,,,,,,,,,,,
|
|
3
|
+
trial,antenna_radius,antenna_radius_lower_bound,antenna_radius_upper_bound,substrate_w,substrate_w_lower_bound,substrate_w_upper_bound,port_x,port_x_lower_bound,port_x_upper_bound,first resonant frequency(Hz),first resonant frequency(Hz)_direction,non_domi,antenna and substrate clearance,antenna and substrate clearance_lower_bound,antenna and substrate clearance_upper_bound,antenna and port clearance,antenna and port clearance_lower_bound,antenna and port clearance_upper_bound,feasible,hypervolume,message,time
|
|
4
|
+
1,10.0,5.0,20.0,50.0,40.0,60.0,5.0,1.0,20.0,4289000000.0,3000000000.0,False,15.0,1,,5.0,1,,True,-1.0,initial,2024-09-11 15:22:45.703728
|
|
5
|
+
2,13.979877262955549,5.0,20.0,43.12037280884873,40.0,60.0,3.96389588638785,1.0,20.0,3050000000.0,3000000000.0,False,7.580309141468815,1,,10.015981376567698,1,,True,-1.0,,2024-09-11 15:23:00.502240
|
|
6
|
+
3,17.48663961200633,5.0,20.0,44.246782213565524,40.0,60.0,4.4546743769349115,1.0,20.0,2460000000.0,3000000000.0,False,4.6367514947764334,1,,13.031965235071418,1,,True,-1.0,,2024-09-11 15:23:19.497216
|
|
7
|
+
4,11.841049763255539,5.0,20.0,55.70351922786027,40.0,60.0,4.793801861008835,1.0,20.0,3581000000.0,3000000000.0,False,16.010709850674594,1,,7.047247902246704,1,,True,-1.0,,2024-09-11 15:23:38.392090
|
|
8
|
+
5,12.713516576204174,5.0,20.0,51.84829137724085,40.0,60.0,1.8825578416799567,1.0,20.0,2932000000.0,3000000000.0,False,13.21062911241625,1,,10.830958734524218,1,,True,-1.0,,2024-09-11 15:23:50.146083
|
|
9
|
+
6,14.113172778521575,5.0,20.0,43.41048247374583,40.0,60.0,2.235980266720311,1.0,20.0,2991000000.0,3000000000.0,True,7.59206845835134,1,,11.877192511801264,1,,True,-1.0,,2024-09-11 15:24:04.616007
|
|
10
|
+
7,19.233283058799998,5.0,20.0,59.31264066149119,40.0,60.0,16.35954961421276,1.0,20.0,2283000000.0,3000000000.0,False,10.423037271945596,1,,2.873733444587238,1,,True,-1.0,,2024-09-11 15:24:33.762831
|
|
11
|
+
8,11.60228740609402,5.0,20.0,42.44076469689558,40.0,60.0,10.408361292114133,1.0,20.0,3758000000.0,3000000000.0,False,9.61809494235377,1,,1.1939261139798862,1,,True,-1.0,,2024-09-11 15:24:53.700769
|
|
12
|
+
9,14.93783426530973,5.0,20.0,46.23422152178822,40.0,60.0,10.881292402378406,1.0,20.0,2932000000.0,3000000000.0,False,8.17927649558438,1,,4.056541862931324,1,,True,-1.0,,2024-09-11 15:25:17.611571
|
|
13
|
+
10,13.968499682166277,5.0,20.0,58.437484700462335,40.0,60.0,2.6813575389864703,1.0,20.0,2991000000.0,3000000000.0,True,15.25024266806489,1,,11.287142143179807,1,,True,-1.0,,2024-09-11 15:25:33.276468
|
|
14
|
+
11,14.05789863217095,5.0,20.0,51.01059902831617,40.0,60.0,1.737571530783426,1.0,20.0,2991000000.0,3000000000.0,True,11.447400881987136,1,,12.320327101387523,1,,True,-1.0,,2024-09-11 15:26:05.718398
|
|
15
|
+
12,14.201356537883626,5.0,20.0,52.843533197525375,40.0,60.0,7.413439727286647,1.0,20.0,3050000000.0,3000000000.0,False,12.220410060879061,1,,6.787916810596979,1,,True,-1.0,,2024-09-11 15:26:37.472938
|
|
16
|
+
13,13.425463212064672,5.0,20.0,52.02680338112066,40.0,60.0,1.0,1.0,20.0,3109000000.0,3000000000.0,False,12.587938478495657,1,,12.425463212064672,1,,True,-1.0,,2024-09-11 15:27:05.737378
|
|
17
|
+
14,14.859313853083268,5.0,20.0,48.76719051860414,40.0,60.0,4.518838210753849,1.0,20.0,2873000000.0,3000000000.0,False,9.524281406218801,1,,10.34047564232942,1,,True,-1.0,,2024-09-11 15:27:37.696689
|
|
18
|
+
15,13.72477890084936,5.0,20.0,49.21346368364997,40.0,60.0,4.707562522313161,1.0,20.0,3050000000.0,3000000000.0,False,10.881952940975625,1,,9.0172163785362,1,,True,-1.0,,2024-09-11 15:28:09.261355
|
|
@@ -80,7 +80,7 @@ class SParameterCalculator:
|
|
|
80
80
|
return self.resonance_frequency # 単位: Hz
|
|
81
81
|
|
|
82
82
|
|
|
83
|
-
def antenna_is_smaller_than_substrate(Femtet):
|
|
83
|
+
def antenna_is_smaller_than_substrate(Femtet, opt):
|
|
84
84
|
"""アンテナの大きさと基板の大きさの関係を計算します。
|
|
85
85
|
|
|
86
86
|
この関数は、変数の更新によってモデル形状が破綻しないように
|
|
@@ -92,15 +92,17 @@ def antenna_is_smaller_than_substrate(Femtet):
|
|
|
92
92
|
Returns:
|
|
93
93
|
float: 基板エッジとアンテナエッジの間隙。1 mm 以上が必要です。
|
|
94
94
|
"""
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
params = opt.get_parameter()
|
|
96
|
+
r = params['antenna_radius']
|
|
97
|
+
w = params['substrate_w']
|
|
97
98
|
return w / 2 - r # 単位: mm
|
|
98
99
|
|
|
99
100
|
|
|
100
|
-
def port_is_inside_antenna(Femtet):
|
|
101
|
+
def port_is_inside_antenna(Femtet, opt):
|
|
101
102
|
"""給電ポートの位置とアンテナの大きさの関係を計算します。"""
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
params = opt.get_parameter()
|
|
104
|
+
r = params['antenna_radius']
|
|
105
|
+
x = params['port_x']
|
|
104
106
|
return r - x # 単位: mm。1 mm 以上が必要です。
|
|
105
107
|
|
|
106
108
|
|
|
@@ -125,8 +127,8 @@ if __name__ == '__main__':
|
|
|
125
127
|
femopt.add_parameter('port_x', 5, 1, 20)
|
|
126
128
|
|
|
127
129
|
# 拘束関数を最適化問題に追加
|
|
128
|
-
femopt.add_constraint(antenna_is_smaller_than_substrate, 'アンテナと基板エッジの間隙', lower_bound=1)
|
|
129
|
-
femopt.add_constraint(port_is_inside_antenna, 'アンテナエッジと給電ポートの間隙', lower_bound=1)
|
|
130
|
+
femopt.add_constraint(antenna_is_smaller_than_substrate, 'アンテナと基板エッジの間隙', lower_bound=1, args=(opt,))
|
|
131
|
+
femopt.add_constraint(port_is_inside_antenna, 'アンテナエッジと給電ポートの間隙', lower_bound=1, args=(opt,))
|
|
130
132
|
|
|
131
133
|
# 目的関数を最適化問題に追加
|
|
132
134
|
# 共振周波数の目標は 3.0 GHz です。
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import pandas as pd
|
|
1
2
|
import plotly.graph_objs as go
|
|
2
3
|
import plotly.express as px
|
|
3
4
|
|
|
@@ -200,3 +201,71 @@ def _get_multi_objective_pairplot(history, df):
|
|
|
200
201
|
)
|
|
201
202
|
|
|
202
203
|
return fig
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def get_objective_plot(history: History, df: pd.DataFrame, obj_names: list[str]) -> go.Figure:
|
|
207
|
+
df = _ls.localize(df)
|
|
208
|
+
df.columns = [c.replace(' / ', '<BR>/ ') for c in df.columns]
|
|
209
|
+
obj_names = [o.replace(' / ', '<BR>/ ') for o in obj_names]
|
|
210
|
+
|
|
211
|
+
common_kwargs = dict(
|
|
212
|
+
color=_ls.non_domi['label'],
|
|
213
|
+
color_discrete_map={
|
|
214
|
+
_ls.non_domi[True]: _cs.non_domi[True],
|
|
215
|
+
_ls.non_domi[False]: _cs.non_domi[False],
|
|
216
|
+
},
|
|
217
|
+
symbol=_ls.feasible['label'],
|
|
218
|
+
symbol_map={
|
|
219
|
+
_ls.feasible[True]: _ss.feasible[True],
|
|
220
|
+
_ls.feasible[False]: _ss.feasible[False],
|
|
221
|
+
},
|
|
222
|
+
custom_data=['trial'],
|
|
223
|
+
category_orders={
|
|
224
|
+
_ls.feasible['label']: (_ls.feasible[False], _ls.feasible[True]),
|
|
225
|
+
_ls.non_domi['label']: (_ls.non_domi[False], _ls.non_domi[True]),
|
|
226
|
+
},
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if len(obj_names) == 2:
|
|
230
|
+
fig = px.scatter(
|
|
231
|
+
data_frame=df,
|
|
232
|
+
x=obj_names[0],
|
|
233
|
+
y=obj_names[1],
|
|
234
|
+
**common_kwargs,
|
|
235
|
+
)
|
|
236
|
+
fig.update_layout(
|
|
237
|
+
dict(
|
|
238
|
+
xaxis_title=obj_names[0],
|
|
239
|
+
yaxis_title=obj_names[1],
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
elif len(obj_names) == 3:
|
|
244
|
+
fig = px.scatter_3d(
|
|
245
|
+
df,
|
|
246
|
+
x=obj_names[0],
|
|
247
|
+
y=obj_names[1],
|
|
248
|
+
z=obj_names[2],
|
|
249
|
+
**common_kwargs,
|
|
250
|
+
)
|
|
251
|
+
fig.update_layout(
|
|
252
|
+
margin=dict(l=0, r=0, b=0, t=30),
|
|
253
|
+
)
|
|
254
|
+
fig.update_traces(
|
|
255
|
+
marker=dict(
|
|
256
|
+
size=3,
|
|
257
|
+
),
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
else:
|
|
261
|
+
raise Exception
|
|
262
|
+
|
|
263
|
+
fig.update_layout(
|
|
264
|
+
dict(
|
|
265
|
+
title_text="Objective plot",
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
fig.update_traces(hoverinfo="none", hovertemplate=None)
|
|
270
|
+
|
|
271
|
+
return fig
|