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.

Files changed (113) hide show
  1. pyfemtet/__init__.py +1 -1
  2. pyfemtet/message/locales/ja/LC_MESSAGES/messages.mo +0 -0
  3. pyfemtet/message/locales/ja/LC_MESSAGES/messages.po +114 -62
  4. pyfemtet/message/locales/messages.pot +114 -62
  5. pyfemtet/message/messages.py +6 -2
  6. pyfemtet/opt/__init__.py +2 -2
  7. pyfemtet/opt/_femopt.py +27 -46
  8. pyfemtet/opt/_femopt_core.py +50 -33
  9. pyfemtet/opt/_test_utils/__init__.py +0 -0
  10. pyfemtet/opt/_test_utils/control_femtet.py +45 -0
  11. pyfemtet/opt/_test_utils/hyper_sphere.py +24 -0
  12. pyfemtet/opt/_test_utils/record_history.py +72 -0
  13. pyfemtet/opt/interface/_femtet.py +39 -1
  14. pyfemtet/opt/optimizer/__init__.py +12 -0
  15. pyfemtet/opt/{opt → optimizer}/_base.py +30 -9
  16. pyfemtet/opt/{opt → optimizer}/_optuna.py +15 -34
  17. pyfemtet/opt/optimizer/_optuna_botorchsampler_parameter_constraint_helper.py +331 -0
  18. pyfemtet/opt/{opt → optimizer}/_scipy.py +10 -6
  19. pyfemtet/opt/{opt → optimizer}/_scipy_scalar.py +24 -12
  20. pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric_test_result.reccsv +1 -1
  21. pyfemtet/opt/{femprj_sample → samples/femprj_sample}/her_ex40_parametric.py +10 -8
  22. pyfemtet/opt/samples/femprj_sample/her_ex40_parametric_test_result.reccsv +18 -0
  23. pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/her_ex40_parametric_jp.py +10 -8
  24. pyfemtet/opt/visualization/complex_components/main_figure_creator.py +69 -0
  25. pyfemtet/opt/visualization/complex_components/main_graph.py +299 -14
  26. pyfemtet/opt/visualization/process_monitor/pages.py +1 -1
  27. pyfemtet/opt/visualization/result_viewer/application.py +1 -1
  28. pyfemtet/opt/visualization/result_viewer/pages.py +9 -9
  29. {pyfemtet-0.4.25.dist-info → pyfemtet-0.5.0.dist-info}/METADATA +1 -1
  30. pyfemtet-0.5.0.dist-info/RECORD +112 -0
  31. pyfemtet/_test_util.py +0 -135
  32. pyfemtet/opt/femprj_sample/her_ex40_parametric_test_result.reccsv +0 -18
  33. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14.pdt +0 -0
  34. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
  35. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
  36. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
  37. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
  38. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
  39. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
  40. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
  41. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
  42. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
  43. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
  44. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
  45. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
  46. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
  47. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
  48. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
  49. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
  50. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
  51. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
  52. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
  53. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
  54. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
  55. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
  56. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
  57. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
  58. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
  59. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
  60. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
  61. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
  62. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
  63. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
  64. pyfemtet/opt/opt/__init__.py +0 -12
  65. pyfemtet/opt/opt/_optuna_botorch_helper.py +0 -209
  66. pyfemtet-0.4.25.dist-info/RECORD +0 -140
  67. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/.gitignore +0 -0
  68. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF - True.femprj +0 -0
  69. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF.femprj +0 -0
  70. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF.py +0 -0
  71. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF_test_result.reccsv +0 -0
  72. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.femprj +0 -0
  73. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.prt +0 -0
  74. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.py +0 -0
  75. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX_test_result.reccsv +0 -0
  76. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.SLDPRT +0 -0
  77. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.femprj +0 -0
  78. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.py +0 -0
  79. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW_test_result.reccsv +0 -0
  80. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric.femprj +0 -0
  81. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric.py +0 -0
  82. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric.femprj +0 -0
  83. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric.py +0 -0
  84. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric_test_result.reccsv +0 -0
  85. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/her_ex40_parametric.femprj +0 -0
  86. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric.femprj +0 -0
  87. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric.py +0 -0
  88. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric_parallel.py +0 -0
  89. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric_test_result.reccsv +0 -0
  90. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric.femprj +0 -0
  91. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric.py +0 -0
  92. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric_parallel.py +0 -0
  93. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric_test_result.reccsv +0 -0
  94. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/ParametricIF_jp.femprj +0 -0
  95. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/ParametricIF_jp.py +0 -0
  96. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_NX_jp.femprj +0 -0
  97. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_NX_jp.py +0 -0
  98. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_SW_jp.femprj +0 -0
  99. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_SW_jp.py +0 -0
  100. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gal_ex58_parametric_jp.femprj +0 -0
  101. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gal_ex58_parametric_jp.py +0 -0
  102. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gau_ex08_parametric_jp.femprj +0 -0
  103. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gau_ex08_parametric_jp.py +0 -0
  104. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/her_ex40_parametric_jp.femprj +0 -0
  105. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_jp.femprj +0 -0
  106. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_jp.py +0 -0
  107. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_parallel_jp.py +0 -0
  108. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_jp.femprj +0 -0
  109. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_jp.py +0 -0
  110. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_parallel_jp.py +0 -0
  111. {pyfemtet-0.4.25.dist-info → pyfemtet-0.5.0.dist-info}/LICENSE +0 -0
  112. {pyfemtet-0.4.25.dist-info → pyfemtet-0.5.0.dist-info}/WHEEL +0 -0
  113. {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.opt import AbstractOptimizer, logger, OptimizationMethodChecker
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.parameters['value'] = x
80
- self.fem.update_parameter(self.parameters)
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.parameters['value'].values
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.parameters.iterrows():
117
- lb, ub = row['lower_buond'], row['upper_bound']
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.opt import AbstractOptimizer, logger, OptimizationMethodChecker
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
- self.parameters['value'] = x
45
- self.fem.update_parameter(self.parameters)
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
- assert len(self.parameters) == 1
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
- for i, row in self.parameters.iterrows():
81
- lb, ub = row['lower_bound'], row['upper_bound']
82
- if lb is None: lb = -np.inf
83
- if ub is None: ub = np.inf
84
- bounds.append([lb, ub])
85
- self.minimize_kwargs.update(
86
- {'bounds': bounds}
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
- "{""femprj_path"": ""E:\\pyfemtet\\pyfemtet\\pyfemtet\\opt\\femprj_sample\\gal_ex58_parametric.femprj"", ""model_name"": ""ex58_UnLoad""}",prm,prm_lb,prm_ub,obj,obj_direction,,,,,
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
- r = Femtet.GetVariableValue('antenna_radius')
93
- w = Femtet.GetVariableValue('substrate_w')
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
- r = Femtet.GetVariableValue('antenna_radius')
100
- x = Femtet.GetVariableValue('port_x')
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
- r = Femtet.GetVariableValue('antenna_radius')
96
- w = Femtet.GetVariableValue('substrate_w')
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
- r = Femtet.GetVariableValue('antenna_radius')
103
- x = Femtet.GetVariableValue('port_x')
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