pyfemtet 0.4.25__py3-none-any.whl → 0.5.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.

Files changed (117) 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 +14 -33
  17. pyfemtet/opt/optimizer/_optuna_botorchsampler_parameter_constraint_helper.py +325 -0
  18. pyfemtet/opt/{opt → optimizer}/_scipy.py +10 -6
  19. pyfemtet/opt/{opt → optimizer}/_scipy_scalar.py +24 -12
  20. pyfemtet/opt/samples/femprj_sample/constrained_pipe.femprj +0 -0
  21. pyfemtet/opt/samples/femprj_sample/constrained_pipe.py +97 -0
  22. pyfemtet/opt/samples/femprj_sample/constrained_pipe_test_result.reccsv +13 -0
  23. pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric_test_result.reccsv +1 -1
  24. pyfemtet/opt/{femprj_sample → samples/femprj_sample}/her_ex40_parametric.py +10 -8
  25. pyfemtet/opt/samples/femprj_sample/her_ex40_parametric_test_result.reccsv +18 -0
  26. pyfemtet/opt/samples/femprj_sample_jp/constrained_pipe_jp.py +93 -0
  27. pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/her_ex40_parametric_jp.py +10 -8
  28. pyfemtet/opt/visualization/complex_components/main_figure_creator.py +69 -0
  29. pyfemtet/opt/visualization/complex_components/main_graph.py +299 -14
  30. pyfemtet/opt/visualization/process_monitor/pages.py +1 -1
  31. pyfemtet/opt/visualization/result_viewer/application.py +1 -1
  32. pyfemtet/opt/visualization/result_viewer/pages.py +9 -9
  33. {pyfemtet-0.4.25.dist-info → pyfemtet-0.5.1.dist-info}/METADATA +1 -1
  34. pyfemtet-0.5.1.dist-info/RECORD +115 -0
  35. pyfemtet/_test_util.py +0 -135
  36. pyfemtet/opt/femprj_sample/ParametricIF - True.femprj +0 -0
  37. pyfemtet/opt/femprj_sample/her_ex40_parametric_test_result.reccsv +0 -18
  38. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14.pdt +0 -0
  39. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
  40. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
  41. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
  42. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
  43. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
  44. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
  45. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
  46. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
  47. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
  48. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
  49. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
  50. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
  51. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
  52. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
  53. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
  54. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
  55. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
  56. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
  57. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
  58. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
  59. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
  60. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
  61. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
  62. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
  63. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
  64. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
  65. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
  66. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
  67. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
  68. pyfemtet/opt/femprj_sample/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
  69. pyfemtet/opt/opt/__init__.py +0 -12
  70. pyfemtet/opt/opt/_optuna_botorch_helper.py +0 -209
  71. pyfemtet-0.4.25.dist-info/RECORD +0 -140
  72. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/.gitignore +0 -0
  73. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF.femprj +0 -0
  74. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF.py +0 -0
  75. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/ParametricIF_test_result.reccsv +0 -0
  76. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.femprj +0 -0
  77. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.prt +0 -0
  78. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX.py +0 -0
  79. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_NX_test_result.reccsv +0 -0
  80. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.SLDPRT +0 -0
  81. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.femprj +0 -0
  82. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW.py +0 -0
  83. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/cad_ex01_SW_test_result.reccsv +0 -0
  84. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric.femprj +0 -0
  85. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gal_ex58_parametric.py +0 -0
  86. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric.femprj +0 -0
  87. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric.py +0 -0
  88. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/gau_ex08_parametric_test_result.reccsv +0 -0
  89. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/her_ex40_parametric.femprj +0 -0
  90. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric.femprj +0 -0
  91. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric.py +0 -0
  92. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric_parallel.py +0 -0
  93. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/paswat_ex1_parametric_test_result.reccsv +0 -0
  94. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric.femprj +0 -0
  95. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric.py +0 -0
  96. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric_parallel.py +0 -0
  97. /pyfemtet/opt/{femprj_sample → samples/femprj_sample}/wat_ex14_parametric_test_result.reccsv +0 -0
  98. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/ParametricIF_jp.femprj +0 -0
  99. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/ParametricIF_jp.py +0 -0
  100. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_NX_jp.femprj +0 -0
  101. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_NX_jp.py +0 -0
  102. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_SW_jp.femprj +0 -0
  103. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/cad_ex01_SW_jp.py +0 -0
  104. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gal_ex58_parametric_jp.femprj +0 -0
  105. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gal_ex58_parametric_jp.py +0 -0
  106. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gau_ex08_parametric_jp.femprj +0 -0
  107. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/gau_ex08_parametric_jp.py +0 -0
  108. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/her_ex40_parametric_jp.femprj +0 -0
  109. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_jp.femprj +0 -0
  110. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_jp.py +0 -0
  111. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/paswat_ex1_parametric_parallel_jp.py +0 -0
  112. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_jp.femprj +0 -0
  113. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_jp.py +0 -0
  114. /pyfemtet/opt/{femprj_sample_jp → samples/femprj_sample_jp}/wat_ex14_parametric_parallel_jp.py +0 -0
  115. {pyfemtet-0.4.25.dist-info → pyfemtet-0.5.1.dist-info}/LICENSE +0 -0
  116. {pyfemtet-0.4.25.dist-info → pyfemtet-0.5.1.dist-info}/WHEEL +0 -0
  117. {pyfemtet-0.4.25.dist-info → pyfemtet-0.5.1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,325 @@
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
+ search_space = study.sampler.infer_relative_search_space(study, None)
139
+ trans = _SearchSpaceTransform(search_space, transform_0_1=True, transform_log=False, transform_step=False)
140
+ params = trans.untransform(norm_x)
141
+
142
+ # ===== update OptunaOptimizer and FEMInterface who is referenced by cns =====
143
+
144
+ # opt
145
+ opt.set_parameter(params)
146
+
147
+ # fem
148
+ if cns.using_fem:
149
+ df_to_fem = opt.variables.get_variables(format='df', filter_pass_to_fem=True)
150
+ opt.fem.update_parameter(df_to_fem)
151
+
152
+ # ===== calc cns =====
153
+ return cns.calc(opt.fem)
154
+
155
+
156
+ class NonlinearInequalityConstraints:
157
+ """botorch の optimize_acqf に parameter constraints を設定するための引数を作成します。"""
158
+
159
+ def __init__(self, study: Study, constraints: list[Constraint], opt: OptunaOptimizer):
160
+ self._study = study
161
+ self._constraints = constraints
162
+ self._opt = opt
163
+
164
+ self._nonlinear_inequality_constraints = []
165
+ cns: Constraint
166
+ for cns in self._constraints:
167
+ if cns.lb is not None:
168
+ cns_botorch = ConvertedConstraint(cns, self._study, 'lb', self._opt)
169
+ item = (lambda x: GeneralFunctionWithForwardDifference.apply(cns_botorch, x), True)
170
+ self._nonlinear_inequality_constraints.append(item)
171
+ if cns.ub is not None:
172
+ cns_botorch = ConvertedConstraint(cns, self._study, 'ub', self._opt)
173
+ item = (lambda x: GeneralFunctionWithForwardDifference.apply(cns_botorch, x), True)
174
+ self._nonlinear_inequality_constraints.append(item)
175
+
176
+
177
+ def _filter_feasible_conditions(self, ic_batch):
178
+ # List to store feasible initial conditions
179
+ feasible_ic_list = []
180
+
181
+ for each_num_restarts in ic_batch:
182
+ feasible_q_list = []
183
+ for each_q in each_num_restarts:
184
+ norm_x: np.ndarray = each_q.numpy() # normalized parameters
185
+
186
+ if is_feasible(self._study, self._constraints, norm_x, self._opt):
187
+ feasible_q_list.append(each_q) # Keep only feasible rows
188
+
189
+ if feasible_q_list: # Only add if there are feasible rows
190
+ feasible_ic_list.append(torch.stack(feasible_q_list))
191
+
192
+ # Stack feasible conditions back into tensor format
193
+ if feasible_ic_list:
194
+ return torch.stack(feasible_ic_list)
195
+ else:
196
+ return None # Return None if none are feasible
197
+
198
+ @staticmethod
199
+ def _generate_random_initial_conditions(shape):
200
+ # Generates random initial conditions with the same shape as ic_batch
201
+ return torch.rand(shape)
202
+
203
+ def _generate_feasible_initial_conditions(self, *args, **kwargs):
204
+ # A `num_restarts x q x d` tensor of initial conditions.
205
+ ic_batch = gen_batch_initial_conditions(*args, **kwargs)
206
+ feasible_ic_batch = self._filter_feasible_conditions(ic_batch)
207
+
208
+ while feasible_ic_batch is None:
209
+ # Generate new random ic_batch with the same shape
210
+ print('警告: gen_batch_initial_conditions() は feasible な初期値を提案しませんでした。'
211
+ 'パラメータ提案を探索するための初期値をランダムに選定します。')
212
+ random_ic_batch = self._generate_random_initial_conditions(ic_batch.shape)
213
+ feasible_ic_batch = self._filter_feasible_conditions(random_ic_batch)
214
+
215
+ return feasible_ic_batch
216
+
217
+ def create_kwargs(self) -> dict:
218
+ """
219
+ nonlinear_inequality_constraints:
220
+ 非線形不等式制約を表すタプルのリスト。
221
+ タプルの最初の要素は、`callable(x) >= 0` という形式の制約を表す呼び出し可能オブジェクトです。
222
+ 2 番目の要素はブール値で、点内制約の場合は `True`
223
+ 制約は後で scipy ソルバーに渡されます。
224
+ この場合、`batch_initial_conditions` を渡す必要があります。
225
+ 非線形不等式制約を使用するには、`batch_limit` を 1 に設定する必要もあります。
226
+ これは、`options` で指定されていない場合は自動的に行われます。
227
+ """
228
+ return dict(
229
+ q=1,
230
+ options=dict(
231
+ batch_limit=1,
232
+ ),
233
+ nonlinear_inequality_constraints=self._nonlinear_inequality_constraints,
234
+ ic_generator=self._generate_feasible_initial_conditions,
235
+ )
236
+
237
+
238
+ class AcquisitionFunctionWithPenalty(AcquisitionFunction):
239
+ """獲得関数に infeasible 項を追加します。"""
240
+
241
+ # noinspection PyAttributeOutsideInit
242
+ def set_acqf(self, acqf):
243
+ self._acqf = acqf
244
+
245
+ # noinspection PyAttributeOutsideInit
246
+ def set_constraints(self, constraints: list[Constraint]):
247
+ self._constraints: list[Constraint] = constraints
248
+
249
+ # noinspection PyAttributeOutsideInit
250
+ def set_study(self, study: Study):
251
+ self._study: Study = study
252
+
253
+ # noinspection PyAttributeOutsideInit
254
+ def set_opt(self, opt: OptunaOptimizer):
255
+ self._opt = opt
256
+
257
+ def forward(self, X: "Tensor") -> "Tensor":
258
+ """
259
+
260
+ Args:
261
+ X (Tensor): batch_size x 1 x n_params tensor.
262
+
263
+ Returns:
264
+ Tensor: batch_size tensor.
265
+
266
+ """
267
+ base = self._acqf.forward(X)
268
+
269
+ norm_x: np.ndarray
270
+ for i, _norm_x in enumerate(X.detach().numpy()):
271
+
272
+ cns: Constraint
273
+ for cns in self._constraints:
274
+ feasible = is_feasible(self._study, [cns], _norm_x[0], self._opt)
275
+ if not feasible:
276
+ base[i] = base[i] * 0. # ペナルティ
277
+ break
278
+
279
+ return base
280
+
281
+
282
+ class OptimizeReplacedACQF(partial):
283
+ """optimize_acqf をこの partial 関数に置き換えます。"""
284
+
285
+ # noinspection PyAttributeOutsideInit
286
+ def set_constraints(self, constraints: list[Constraint]):
287
+ self._constraints: list[Constraint] = constraints
288
+
289
+ # noinspection PyAttributeOutsideInit
290
+ def set_study(self, study: Study):
291
+ self._study: Study = study
292
+
293
+ # noinspection PyAttributeOutsideInit
294
+ def set_opt(self, opt: OptunaOptimizer):
295
+ self._opt = opt
296
+
297
+ def __call__(self, *args, **kwargs):
298
+ """置き換え先の関数の処理内容です。
299
+
300
+ kwargs を横入りして追記することで拘束を実現します。
301
+ """
302
+
303
+ logger.info(Msg.START_CANDIDATE_WITH_PARAMETER_CONSTRAINT)
304
+
305
+ # FEM の更新が必要な場合、時間がかかることが多いので警告を出す
306
+ if any([cns.using_fem for cns in self._constraints]):
307
+ logger.warning(Msg.WARN_UPDATE_FEM_PARAMETER_TOOK_A_LONG_TIME)
308
+
309
+ # 獲得関数に infeasible な場合のペナルティ項を追加します。
310
+ acqf = kwargs['acq_function']
311
+ new_acqf = AcquisitionFunctionWithPenalty(...)
312
+ new_acqf.set_acqf(acqf)
313
+ new_acqf.set_constraints(self._constraints)
314
+ new_acqf.set_study(self._study)
315
+ new_acqf.set_opt(self._opt)
316
+ kwargs['acq_function'] = new_acqf
317
+
318
+ # optimize_acqf の探索に parameter constraints を追加します。
319
+ nlic = NonlinearInequalityConstraints(self._study, self._constraints, self._opt)
320
+ kwargs.update(nlic.create_kwargs())
321
+
322
+ # replace other arguments
323
+ ...
324
+
325
+ 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:
@@ -0,0 +1,97 @@
1
+ """A sample to implement constrained optimization.
2
+
3
+ This section describes the types of constraints and
4
+ the steps to run optimization on models that require them.
5
+
6
+ """
7
+
8
+ from optuna_integration import BoTorchSampler
9
+ from pyfemtet.opt import FEMOpt, OptunaOptimizer
10
+
11
+
12
+ def mises_stress(Femtet):
13
+ """Calculate the von Mises stress as the objective function.
14
+
15
+ This function is called automatically by the FEMOpt
16
+ object while the optimization is running.
17
+
18
+ Args:
19
+ Femtet: When defining an objective or constraint
20
+ function using PyFemtet, the first argument
21
+ must take a Femtet instance.
22
+
23
+ Returns:
24
+ float: A single float representing the expression value you want to constrain.
25
+ """
26
+ return Femtet.Gogh.Galileo.GetMaxStress_py()[2]
27
+
28
+
29
+ def radius_diff(Femtet, opt):
30
+ """Calculate the difference between the outer and inner radii of the pipe.
31
+
32
+ This constraint is called to ensure that the
33
+ inner radius of the pipe does not exceed the
34
+ outer radius while the optimization is running.
35
+
36
+ Note:
37
+ If you are using BoTorchSampler of OptunaOptimizer
38
+ and use strict constraints, be aware that accessing
39
+ the Femtet can be very slow, as it requires repeated
40
+ calculations to propose parameters.
41
+ We recommend that you do not access the Femtet,
42
+ but rather get the parameters and perform the
43
+ calculations via the Optimizer object, as in this
44
+ function example.
45
+
46
+ NOT recommended::
47
+
48
+ p = Femtet.GetVariableValue('p')
49
+
50
+ instead, use optimizer::
51
+
52
+ params = opt.get_parameter()
53
+ p = params['p']
54
+
55
+ Args:
56
+ Femtet: When defining an objective or constraint
57
+ function using PyFemtet, the first argument
58
+ must take a Femtet instance.
59
+ opt: This object allows you to obtain the outer
60
+ radius and inner radius values without going
61
+ through Femtet.
62
+ """
63
+ params = opt.get_parameter()
64
+ internal_r = params['internal_r']
65
+ external_r = params['external_r']
66
+ return external_r - internal_r
67
+
68
+
69
+ if __name__ == '__main__':
70
+ # Setup optimization method
71
+ opt = OptunaOptimizer(
72
+ sampler_class=BoTorchSampler,
73
+ sampler_kwargs=dict(
74
+ n_startup_trials=3, # The first three samples are randomly sampled.
75
+ )
76
+ )
77
+ femopt = FEMOpt(opt=opt)
78
+
79
+ # Add parameters
80
+ femopt.add_parameter("external_r", 10, lower_bound=0.1, upper_bound=10)
81
+ femopt.add_parameter("internal_r", 5, lower_bound=0.1, upper_bound=10)
82
+
83
+ # Add the strict constraint not to exceed the
84
+ # outer radius while the optimization is running.
85
+ femopt.add_constraint(
86
+ radius_diff, # Constraint function (returns external radius - internal radius).
87
+ name='wall thickness', # You can name the function anything you want.
88
+ lower_bound=1, # Lower bound of constraint function (set minimum wall thickness is 1).
89
+ args=(femopt.opt,) # Additional arguments passed to the function.
90
+ )
91
+
92
+ # Add the objective
93
+ femopt.add_objective(mises_stress, name='Mises Stress')
94
+
95
+ # Run optimization.
96
+ femopt.set_random_seed(42)
97
+ femopt.optimize(n_trials=10)
@@ -0,0 +1,13 @@
1
+ "",prm,prm_lb,prm_ub,prm,prm_lb,prm_ub,obj,obj_direction,,cns,cns_lb,cns_ub,,,,
2
+ ,,,,,,,,,,,,,,,,
3
+ trial,external_r,external_r_lower_bound,external_r_upper_bound,internal_r,internal_r_lower_bound,internal_r_upper_bound,Mises Stress,Mises Stress_direction,non_domi,wall thickness,wall thickness_lower_bound,wall thickness_upper_bound,feasible,hypervolume,message,time
4
+ 1,10.0,0.1,10.0,5.0,0.1,10.0,2369132.9299968677,minimize,False,5.0,1,,True,-1.0,initial,2024-09-17 14:52:32.436730
5
+ 2,7.34674002393291,0.1,10.0,6.026718993550662,0.1,10.0,5797829.750527166,minimize,False,1.3200210303822484,1,,True,-1.0,,2024-09-17 14:52:42.927138
6
+ 3,8.341182143924176,0.1,10.0,2.202157195714934,0.1,10.0,1977631.590419345,minimize,False,6.139024948209242,1,,True,-1.0,,2024-09-17 14:52:54.382893
7
+ 4,9.999999999999995,0.1,10.0,0.6764719819158711,0.1,10.0,1514026.80705058,minimize,False,9.323528018084124,1,,True,-1.0,,2024-09-17 14:53:32.128331
8
+ 5,7.61174007452904,0.1,10.0,0.10000000000001566,0.1,10.0,398224.5447358758,minimize,True,7.511740074529024,1,,True,-1.0,,2024-09-17 14:54:08.541865
9
+ 6,5.026961232239628,0.1,10.0,0.10000000000001101,0.1,10.0,485322.32959281537,minimize,False,4.926961232239617,1,,True,-1.0,,2024-09-17 14:54:39.695340
10
+ 7,1.1000000916874046,0.1,10.0,0.1,0.1,10.0,613872.6231008903,minimize,False,1.0000000916874046,1,,True,-1.0,,2024-09-17 14:55:07.711879
11
+ 8,3.122874622702614,0.1,10.0,0.1,0.1,10.0,497544.0108231131,minimize,False,3.0228746227026138,1,,True,-1.0,,2024-09-17 14:55:41.036763
12
+ 9,9.999999999999998,0.1,10.0,3.7062659141309533,0.1,10.0,2092780.7380693094,minimize,False,6.293734085869045,1,,True,-1.0,,2024-09-17 14:56:28.269065
13
+ 10,6.601571760977393,0.1,10.0,0.10000000004171199,0.1,10.0,456263.04356135987,minimize,False,6.501571760935681,1,,True,-1.0,,2024-09-17 14:57:04.427906
@@ -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