pyfemtet 0.5.3__py3-none-any.whl → 0.6.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 (62) hide show
  1. pyfemtet/__init__.py +1 -1
  2. pyfemtet/{message → _message}/locales/ja/LC_MESSAGES/messages.po +89 -77
  3. pyfemtet/{message → _message}/locales/messages.pot +88 -76
  4. pyfemtet/{message → _message}/messages.py +1 -1
  5. pyfemtet/_warning.py +23 -0
  6. pyfemtet/dispatch_extensions/__init__.py +12 -0
  7. pyfemtet/{dispatch_extensions.py → dispatch_extensions/_impl.py} +45 -43
  8. pyfemtet/logger/__init__.py +3 -0
  9. pyfemtet/{logger.py → logger/_impl.py} +12 -6
  10. pyfemtet/opt/__init__.py +3 -0
  11. pyfemtet/opt/_femopt.py +265 -68
  12. pyfemtet/opt/_femopt_core.py +111 -68
  13. pyfemtet/opt/_test_utils/record_history.py +1 -1
  14. pyfemtet/opt/interface/__init__.py +0 -1
  15. pyfemtet/opt/interface/_base.py +3 -3
  16. pyfemtet/opt/interface/_femtet.py +116 -59
  17. pyfemtet/opt/interface/_femtet_with_nx/_interface.py +35 -12
  18. pyfemtet/opt/interface/_femtet_with_sldworks.py +22 -2
  19. pyfemtet/opt/optimizer/__init__.py +5 -1
  20. pyfemtet/opt/optimizer/_base.py +81 -55
  21. pyfemtet/opt/optimizer/{_optuna_botorchsampler_parameter_constraint_helper.py → _optuna/_botorch_patch/enable_nonlinear_constraint.py} +10 -127
  22. pyfemtet/opt/optimizer/{_optuna.py → _optuna/_optuna.py} +122 -19
  23. pyfemtet/opt/optimizer/_optuna/_pof_botorch.py +1833 -0
  24. pyfemtet/opt/optimizer/_scipy.py +20 -5
  25. pyfemtet/opt/optimizer/_scipy_scalar.py +20 -5
  26. pyfemtet/opt/prediction/{base.py → _base.py} +3 -2
  27. pyfemtet/opt/prediction/single_task_gp.py +10 -5
  28. pyfemtet/opt/samples/femprj_sample/constrained_pipe.py +2 -2
  29. pyfemtet/opt/samples/femprj_sample/her_ex40_parametric.py +2 -2
  30. pyfemtet/opt/visualization/{base.py → _base.py} +1 -1
  31. pyfemtet/opt/visualization/{complex_components → _complex_components}/alert_region.py +2 -2
  32. pyfemtet/opt/visualization/{complex_components → _complex_components}/control_femtet.py +3 -3
  33. pyfemtet/opt/visualization/{complex_components → _complex_components}/main_figure_creator.py +1 -1
  34. pyfemtet/opt/visualization/{complex_components → _complex_components}/main_graph.py +5 -5
  35. pyfemtet/opt/visualization/{complex_components → _complex_components}/pm_graph.py +5 -5
  36. pyfemtet/opt/visualization/{complex_components → _complex_components}/pm_graph_creator.py +2 -2
  37. pyfemtet/opt/visualization/_create_wrapped_components.py +2 -2
  38. pyfemtet/opt/visualization/_process_monitor/__init__.py +0 -0
  39. pyfemtet/opt/visualization/{process_monitor → _process_monitor}/application.py +3 -3
  40. pyfemtet/opt/visualization/{process_monitor → _process_monitor}/pages.py +10 -10
  41. pyfemtet/opt/visualization/_wrapped_components/__init__.py +0 -0
  42. pyfemtet/opt/visualization/{wrapped_components → _wrapped_components}/dbc.py +1 -1
  43. pyfemtet/opt/visualization/{wrapped_components → _wrapped_components}/dcc.py +1 -1
  44. pyfemtet/opt/visualization/{wrapped_components → _wrapped_components}/html.py +1 -1
  45. pyfemtet/opt/visualization/result_viewer/application.py +4 -4
  46. pyfemtet/opt/visualization/result_viewer/pages.py +9 -9
  47. {pyfemtet-0.5.3.dist-info → pyfemtet-0.6.0.dist-info}/METADATA +2 -2
  48. {pyfemtet-0.5.3.dist-info → pyfemtet-0.6.0.dist-info}/RECORD +60 -56
  49. {pyfemtet-0.5.3.dist-info → pyfemtet-0.6.0.dist-info}/WHEEL +1 -1
  50. pyfemtet/message/locales/ja/LC_MESSAGES/messages.mo +0 -0
  51. pyfemtet/opt/samples/femprj_sample/.gitignore +0 -2
  52. /pyfemtet/{message → _message}/1. make_pot.bat +0 -0
  53. /pyfemtet/{message → _message}/2. make_mo.bat +0 -0
  54. /pyfemtet/{message → _message}/__init__.py +0 -0
  55. /pyfemtet/{message → _message}/babel.cfg +0 -0
  56. /pyfemtet/opt/{visualization/complex_components → optimizer/_optuna}/__init__.py +0 -0
  57. /pyfemtet/opt/{visualization/process_monitor → optimizer/_optuna/_botorch_patch}/__init__.py +0 -0
  58. /pyfemtet/opt/{parameter.py → optimizer/parameter.py} +0 -0
  59. /pyfemtet/opt/visualization/{wrapped_components → _complex_components}/__init__.py +0 -0
  60. /pyfemtet/opt/visualization/{wrapped_components → _wrapped_components}/str_enum.py +0 -0
  61. {pyfemtet-0.5.3.dist-info → pyfemtet-0.6.0.dist-info}/LICENSE +0 -0
  62. {pyfemtet-0.5.3.dist-info → pyfemtet-0.6.0.dist-info}/entry_points.txt +0 -0
@@ -3,18 +3,18 @@ from typing import Iterable
3
3
 
4
4
  # built-in
5
5
  import os
6
+ import inspect
6
7
 
7
8
  # 3rd-party
8
- import numpy as np
9
9
  import optuna
10
10
  from optuna.trial import TrialState
11
11
  from optuna.study import MaxTrialsCallback
12
12
 
13
13
  # pyfemtet relative
14
- from pyfemtet.opt._femopt_core import OptimizationStatus, generate_lhs, Constraint
14
+ from pyfemtet.opt._femopt_core import OptimizationStatus, generate_lhs
15
15
  from pyfemtet.opt.optimizer import AbstractOptimizer, logger, OptimizationMethodChecker
16
16
  from pyfemtet.core import MeshError, ModelError, SolveError
17
- from pyfemtet.message import Msg
17
+ from pyfemtet._message import Msg
18
18
 
19
19
  # filter warnings
20
20
  import warnings
@@ -37,6 +37,38 @@ class OptunaMethodChecker(OptimizationMethodChecker):
37
37
 
38
38
 
39
39
  class OptunaOptimizer(AbstractOptimizer):
40
+ """Optimizer using ```optuna```.
41
+
42
+ This class provides an interface for the optimization
43
+ engine using Optuna. For more details, please refer to
44
+ the Optuna documentation.
45
+
46
+ See Also:
47
+ https://optuna.readthedocs.io/en/stable/reference/index.html
48
+
49
+ Args:
50
+ sampler_class (optuna.samplers.BaseSampler, optional):
51
+ A sampler class from Optuna. If not specified,
52
+ ```optuna.samplers.TPESampler``` is specified.
53
+ This defines the sampling strategy used during
54
+ optimization. Defaults to None.
55
+ sampler_kwargs (dict, optional):
56
+ A dictionary of keyword arguments to be passed to
57
+ the sampler class. This allows for customization
58
+ of the sampling process. Defaults to None.
59
+ add_init_method (str or Iterable[str], optional):
60
+ A method or a collection of methods to be added
61
+ during initialization. This can be used to specify
62
+ additional setup procedures.
63
+ Currently, the only valid value is 'LHS'
64
+ (using Latin Hypercube Sampling).
65
+ Defaults to None.
66
+
67
+ Warnings:
68
+ Do not include ```constraints_func``` in ```sampler_kwargs```.
69
+ It is generated and provided by :func:`FEMOpt.add_constraint`.
70
+
71
+ """
40
72
 
41
73
  def __init__(
42
74
  self,
@@ -54,17 +86,24 @@ class OptunaOptimizer(AbstractOptimizer):
54
86
  self.additional_initial_parameter = []
55
87
  self.additional_initial_methods = add_init_method if hasattr(add_init_method, '__iter__') else [add_init_method]
56
88
  self.method_checker = OptunaMethodChecker(self)
57
- self._do_monkey_patch = False
58
89
 
59
90
  def _objective(self, trial):
60
91
 
92
+ logger.info('')
93
+ if self._retry_counter == 0:
94
+ logger.info(f'===== trial {1 + len(self.history.get_df())} start =====')
95
+ else:
96
+ logger.info(f'===== trial {1 + len(self.history.get_df())} (retry {self._retry_counter}) start =====')
97
+
61
98
  # 中断の確認 (FAIL loop に陥る対策)
62
99
  if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
63
100
  self.worker_status.set(OptimizationStatus.INTERRUPTING)
64
101
  trial.study.stop() # 現在実行中の trial を最後にする
102
+ self._retry_counter = 0
65
103
  return None # set TrialState FAIL
66
104
 
67
105
  # candidate x and update parameters
106
+ logger.info('Searching new parameter set...')
68
107
  for prm in self.variables.get_variables(format='raw', filter_parameter=True):
69
108
  value = trial.suggest_float(
70
109
  name=prm.name,
@@ -94,9 +133,11 @@ class OptunaOptimizer(AbstractOptimizer):
94
133
  if cns.ub is not None:
95
134
  feasible = feasible and (cns.ub >= cns_value)
96
135
  if not feasible:
136
+ logger.info('----- Out of constraint! -----')
97
137
  logger.info(Msg.INFO_INFEASIBLE)
98
138
  logger.info(f'Constraint: {cns.name}')
99
139
  logger.info(self.variables.get_variables('dict', filter_parameter=True))
140
+ self._retry_counter += 1
100
141
  raise optuna.TrialPruned() # set TrialState PRUNED because FAIL causes similar candidate loop.
101
142
 
102
143
  # 計算
@@ -110,6 +151,15 @@ class OptunaOptimizer(AbstractOptimizer):
110
151
  trial.study.stop() # 現在実行中の trial を最後にする
111
152
  return None # set TrialState FAIL
112
153
 
154
+ logger.warning('----- Infeasible! -----')
155
+ logger.warning(Msg.INFO_INFEASIBLE)
156
+ logger.warning(f'Hidden Constraint ({type(e).__name__})')
157
+ logger.warning(self.variables.get_variables('dict', filter_parameter=True))
158
+ logger.warning('Please consider to determine the cause'
159
+ 'of the above error and modify the model'
160
+ 'or analysis.')
161
+
162
+ self._retry_counter += 1
113
163
  raise optuna.TrialPruned() # set TrialState PRUNED because FAIL causes similar candidate loop.
114
164
 
115
165
  # 拘束 attr の更新
@@ -127,9 +177,11 @@ class OptunaOptimizer(AbstractOptimizer):
127
177
  if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
128
178
  self.worker_status.set(OptimizationStatus.INTERRUPTING)
129
179
  trial.study.stop() # 現在実行中の trial を最後にする
180
+ self._retry_counter = 0
130
181
  return None # set TrialState FAIL
131
182
 
132
183
  # 結果
184
+ self._retry_counter = 0
133
185
  return tuple(_y)
134
186
 
135
187
  def _constraint(self, trial):
@@ -263,10 +315,20 @@ class OptunaOptimizer(AbstractOptimizer):
263
315
  self.sampler_kwargs.update(
264
316
  seed=seed
265
317
  )
318
+ parameters = inspect.signature(self.sampler_class.__init__).parameters
319
+ sampler_kwargs = dict()
320
+ for k, v in self.sampler_kwargs.items():
321
+ if k in parameters.keys():
322
+ sampler_kwargs.update({k: v})
266
323
  sampler = self.sampler_class(
267
- **self.sampler_kwargs
324
+ **sampler_kwargs
268
325
  )
269
326
 
327
+ from pyfemtet.opt.optimizer._optuna._pof_botorch import PoFBoTorchSampler
328
+ if isinstance(sampler, PoFBoTorchSampler):
329
+ sampler._pyfemtet_constraints = [cns for cns in self.constraints.values() if cns.strict]
330
+ sampler._pyfemtet_optimizer = self
331
+
270
332
  # load study
271
333
  study = optuna.load_study(
272
334
  study_name=self.study_name,
@@ -274,21 +336,62 @@ class OptunaOptimizer(AbstractOptimizer):
274
336
  sampler=sampler,
275
337
  )
276
338
 
277
- # monkey patch
278
- if self._do_monkey_patch:
279
- assert isinstance(sampler, optuna.integration.BoTorchSampler), Msg.ERR_PARAMETER_CONSTRAINT_ONLY_BOTORCH
339
+ # 一時的な実装。
340
+ # TPESampler の場合、リスタート時などの場合で、
341
+ # Pruned が多いとエラーを起こす挙動があるので、
342
+ # Pruned な Trial は remove したい。
343
+ # study.remove_trial がないので、一度ダミー
344
+ # study を作成して最適化終了後に結果をコピーする。
345
+ if isinstance(sampler, optuna.samplers.TPESampler):
346
+ tmp_db = f"tmp{self.subprocess_idx}.db"
347
+ if os.path.exists(tmp_db):
348
+ os.remove(tmp_db)
349
+
350
+ _study = optuna.create_study(
351
+ study_name="tmp",
352
+ storage=f"sqlite:///{tmp_db}",
353
+ sampler=sampler,
354
+ directions=['minimize']*len(self.objectives),
355
+ load_if_exists=False,
356
+ )
280
357
 
281
- from pyfemtet.opt.optimizer._optuna_botorchsampler_parameter_constraint_helper import do_patch
358
+ # 既存の trials のうち COMPLETE のものを取得
359
+ existing_trials = study.get_trials(states=(optuna.trial.TrialState.COMPLETE,))
360
+ _study.add_trials(existing_trials)
282
361
 
283
- do_patch(
284
- study,
285
- self.constraints,
286
- self
362
+ # run
363
+ _study.optimize(
364
+ self._objective,
365
+ timeout=self.timeout,
366
+ callbacks=self.optimize_callbacks,
287
367
  )
288
368
 
289
- # run
290
- study.optimize(
291
- self._objective,
292
- timeout=self.timeout,
293
- callbacks=self.optimize_callbacks,
294
- )
369
+ # trial.number と trial_id は _study への add_trials 時に
370
+ # 振りなおされるため重複したものをフィルタアウトするために
371
+ # datetime_start を利用。
372
+ added_trials = []
373
+ for _trial in _study.get_trials():
374
+ if _trial.datetime_start not in [t.datetime_start for t in existing_trials]:
375
+ added_trials.append(_trial)
376
+
377
+ # Write back added trials to the existing study.
378
+ study.add_trials(added_trials)
379
+
380
+ # clean up
381
+ from optuna.storages import get_storage
382
+ storage = get_storage(f"sqlite:///{tmp_db}")
383
+ storage.remove_session()
384
+ del _study
385
+ del storage
386
+ import gc
387
+ gc.collect()
388
+ if os.path.exists(tmp_db):
389
+ os.remove(tmp_db)
390
+
391
+ else:
392
+ # run
393
+ study.optimize(
394
+ self._objective,
395
+ timeout=self.timeout,
396
+ callbacks=self.optimize_callbacks,
397
+ )