pyfemtet 1.0.10__py3-none-any.whl → 1.1.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/opt/femopt.py CHANGED
@@ -168,6 +168,12 @@ class FEMOpt:
168
168
  ):
169
169
  self.opt.add_other_output(name, fun, args, kwargs)
170
170
 
171
+ def add_trial(
172
+ self,
173
+ parameters: dict[str, SupportedVariableTypes],
174
+ ):
175
+ self.opt.add_trial(parameters)
176
+
171
177
  def add_sub_fidelity_model(
172
178
  self,
173
179
  name: str,
@@ -116,9 +116,9 @@ class TrialState(StrEnum):
116
116
  elif state == TrialState.unknown_error:
117
117
  e = Exception()
118
118
  elif state == TrialState.hard_constraint_violation:
119
- e = HardConstraintViolation
119
+ e = HardConstraintViolation()
120
120
  elif state == TrialState.skipped:
121
- e = SkipSolve
121
+ e = SkipSolve()
122
122
  else:
123
123
  e = None
124
124
  return e
@@ -14,6 +14,7 @@ from pyfemtet.opt.exceptions import *
14
14
  from pyfemtet.opt.worker_status import *
15
15
  from pyfemtet.opt.problem.problem import *
16
16
  from pyfemtet.opt.problem.variable_manager import *
17
+ from pyfemtet.opt.optimizer._trial_queue import *
17
18
  from pyfemtet.logger import get_module_logger
18
19
 
19
20
  __all__ = [
@@ -68,6 +69,7 @@ class AbstractOptimizer:
68
69
  n_trials: int | None
69
70
  timeout: float | None
70
71
  seed: int | None
72
+ include_queued_in_n_trials: bool
71
73
 
72
74
  # problem
73
75
  variable_manager: VariableManager
@@ -94,6 +96,7 @@ class AbstractOptimizer:
94
96
  self.seed = None
95
97
  self.n_trials = None
96
98
  self.timeout = None
99
+ self.include_queued_in_n_trials = False
97
100
 
98
101
  # Problem
99
102
  self.variable_manager = VariableManager()
@@ -113,6 +116,7 @@ class AbstractOptimizer:
113
116
  self.entire_status: WorkerStatus = WorkerStatus(ENTIRE_PROCESS_STATUS_KEY)
114
117
  self.worker_status: WorkerStatus = WorkerStatus('worker-status')
115
118
  self.worker_status_list: list[WorkerStatus] = [self.worker_status]
119
+ self.trial_queue: TrialQueue = TrialQueue()
116
120
  self._done_setup_before_parallel = False
117
121
  self._done_load_problem_from_fem = False
118
122
  self._worker_index: int | str | None = None
@@ -368,6 +372,12 @@ class AbstractOptimizer:
368
372
  _duplicated_name_check(name, self.sub_fidelity_models.keys())
369
373
  self.sub_fidelity_models._update(name, sub_fidelity_model, fidelity)
370
374
 
375
+ def add_trial(
376
+ self,
377
+ parameters: dict[str, SupportedVariableTypes],
378
+ ):
379
+ self.trial_queue.enqueue(parameters)
380
+
371
381
  def get_variables(self, format: Literal['dict', 'values', 'raw'] = 'dict'):
372
382
  return self.variable_manager.get_variables(
373
383
  format=format,
@@ -383,6 +393,21 @@ class AbstractOptimizer:
383
393
 
384
394
  # ===== private =====
385
395
 
396
+ def _setup_enqueued_trials(self):
397
+ # Insert initial trial
398
+ params: dict = self.variable_manager.get_variables(format='dict', filter='parameter')
399
+ self.trial_queue.enqueue_first(params, flags={_IS_INITIAL_TRIAL_FLAG_KEY: True})
400
+
401
+ # Remove trials included in history
402
+ tried: list[dict] = get_tried_list_from_history(
403
+ self.history,
404
+ equality_filters=MAIN_FILTER,
405
+ )
406
+ self.trial_queue.remove_tried(tried)
407
+
408
+ # Remove duplicated
409
+ self.trial_queue.remove_duplicated()
410
+
386
411
  def _should_solve(self, history):
387
412
  return self.solve_condition(history)
388
413
 
@@ -921,6 +946,61 @@ class AbstractOptimizer:
921
946
  # resolve evaluation order
922
947
  self.variable_manager.resolve()
923
948
 
949
+ # check the enqueued trials is
950
+ # compatible with current optimization
951
+ # problem setup
952
+ history_set = set(self.history.prm_names)
953
+ for t in self.trial_queue.queue:
954
+ params: dict = t.d
955
+ enqueued_set: set = set(params.keys())
956
+
957
+ # Warning if over
958
+ if len(enqueued_set - history_set) > 0:
959
+ logger.warning(
960
+ _(
961
+ en_message='Enqueued parameter set contains '
962
+ 'more parameters than the optimization '
963
+ 'problem setup. The extra parameters '
964
+ 'will be ignored.\n'
965
+ 'Enqueued set: {enqueued_set}\n'
966
+ 'Setup set: {history_set}\n'
967
+ 'Parameters ignored: {over_set}',
968
+ jp_message='予約された入力変数セットは'
969
+ '最適化のセットアップで指定されたよりも'
970
+ '多くの変数を含んでいます。'
971
+ 'そのような変数は無視されます。\n'
972
+ '予約された入力変数: {enqueued_set}\n'
973
+ '最適化する変数: {history_set}\n'
974
+ '無視される変数: {over_set}',
975
+ enqueued_set=enqueued_set,
976
+ history_set=history_set,
977
+ over_set=enqueued_set - history_set,
978
+ )
979
+ )
980
+
981
+ # Error if not enough
982
+ if len(history_set - enqueued_set) > 0:
983
+ raise ValueError(
984
+ _(
985
+ en_message='The enqueued parameter set lacks '
986
+ 'some parameters to be optimized.\n'
987
+ 'Enqueued set: {enqueued_set}\n'
988
+ 'Parameters to optimize: {history_set}\n'
989
+ 'Lacked set: {lacked_set}',
990
+ jp_message='予約された入力変数セットに'
991
+ '変数が不足しています。\n'
992
+ '予約された変数: {enqueued_set}\n'
993
+ '最適化する変数: {history_set}\n'
994
+ '足りない変数: {lacked_set}',
995
+ enqueued_set=enqueued_set,
996
+ history_set=history_set,
997
+ lacked_set=history_set - enqueued_set,
998
+ )
999
+ )
1000
+
1001
+ # remove duplicated enqueued trials
1002
+ self._setup_enqueued_trials()
1003
+
924
1004
  self._done_setup_before_parallel = True
925
1005
 
926
1006
  def _setup_after_parallel(self):
@@ -0,0 +1,101 @@
1
+ from typing import Sequence
2
+ from frozendict import frozendict
3
+
4
+ from pyfemtet.opt.history import History, TrialState
5
+ from pyfemtet.opt.problem.variable_manager import SupportedVariableTypes
6
+
7
+
8
+ __all__ = [
9
+ 'TrialQueue',
10
+ 'get_tried_list_from_history',
11
+ '_IS_INITIAL_TRIAL_FLAG_KEY',
12
+ ]
13
+
14
+
15
+ _IS_INITIAL_TRIAL_FLAG_KEY = 'is_initial_trial'
16
+
17
+
18
+ class EnqueuedTrial:
19
+
20
+ def __init__(self, d: dict[str, SupportedVariableTypes], flags: dict = None):
21
+ self.d = d
22
+ self.flags = flags or {}
23
+
24
+ @staticmethod
25
+ def d_type_verified(d) -> dict:
26
+ out = {}
27
+ for k, v in d.items():
28
+ if isinstance(v, str):
29
+ out.update({k: v})
30
+ else:
31
+ out.update({k: float(v)})
32
+ return out
33
+
34
+ def get_hashed_id(self):
35
+ return hash(frozendict(self.d_type_verified(self.d)))
36
+
37
+
38
+ class TrialQueue:
39
+
40
+ def __init__(self):
41
+ self.queue: list[EnqueuedTrial] = []
42
+
43
+ def __len__(self):
44
+ return len(self.queue)
45
+
46
+ def enqueue_first(self, d: dict[str, SupportedVariableTypes], flags: dict = None):
47
+ self.queue.insert(0, EnqueuedTrial(d, flags=flags))
48
+
49
+ def enqueue(self, d: dict[str, SupportedVariableTypes], flags: dict = None):
50
+ self.queue.append(EnqueuedTrial(d, flags=flags))
51
+
52
+ def dequeue(self) -> dict[str, SupportedVariableTypes] | None:
53
+ if len(self.queue) == 0:
54
+ return None
55
+ return self.queue.pop(0).d
56
+
57
+ def remove_duplicated(self):
58
+ indices_to_remove = []
59
+ all_ids = [t.get_hashed_id() for t in self.queue]
60
+
61
+ # 先に queue に入れられたものから
62
+ # 順に見ていく
63
+ # indices は sorted となるはず
64
+ seen_ids = set()
65
+ for i, id in enumerate(all_ids):
66
+ if id in seen_ids:
67
+ indices_to_remove.append(i)
68
+ else:
69
+ seen_ids.add(id)
70
+
71
+ # 削除
72
+ for i in indices_to_remove[::-1]:
73
+ self.queue.pop(i)
74
+
75
+ def remove_tried(self, tried_list: Sequence[dict[str, SupportedVariableTypes]]):
76
+ indices_to_remove = []
77
+ tried_id = [EnqueuedTrial(tried).get_hashed_id() for tried in tried_list]
78
+ for i, t in enumerate(self.queue):
79
+ if t.get_hashed_id() in tried_id:
80
+ indices_to_remove.append(i)
81
+ for i in indices_to_remove[::-1]:
82
+ self.queue.pop(i)
83
+
84
+
85
+ def get_tried_list_from_history(
86
+ history: History,
87
+ equality_filters=None,
88
+ ) -> list[dict[str, SupportedVariableTypes]]:
89
+ out = []
90
+ df = history.get_df(equality_filters=equality_filters)
91
+ for _, row in df.iterrows():
92
+ # Want to retry if unknown error
93
+ # so don't count it as tried.
94
+ if row['state'] in (
95
+ TrialState.unknown_error,
96
+ TrialState.undefined,
97
+ ):
98
+ continue
99
+ d = row[history.prm_names].to_dict()
100
+ out.append(d)
101
+ return out
@@ -12,7 +12,8 @@ import numpy as np
12
12
 
13
13
  import optuna
14
14
  from optuna.samplers import TPESampler
15
- from optuna.study import MaxTrialsCallback
15
+ from optuna.study import Study, MaxTrialsCallback
16
+ from optuna.trial import FrozenTrial, TrialState as OptunaTrialState
16
17
  from optuna_integration.dask import DaskStorage
17
18
 
18
19
  from pyfemtet._i18n import _
@@ -28,8 +29,9 @@ from pyfemtet.opt.optimizer._base_optimizer import *
28
29
  from pyfemtet.opt.optimizer.optuna_optimizer._optuna_attribute import OptunaAttribute
29
30
  from pyfemtet.opt.optimizer.optuna_optimizer._pof_botorch.pof_botorch_sampler import PoFBoTorchSampler
30
31
  from pyfemtet.opt.worker_status import WorkerStatus
32
+ from pyfemtet.opt.optimizer._trial_queue import _IS_INITIAL_TRIAL_FLAG_KEY
31
33
 
32
- logger = get_module_logger('opt.optimizer', False)
34
+ logger = get_module_logger('opt.optimizer', True)
33
35
 
34
36
  remove_all_output(get_optuna_logger())
35
37
 
@@ -38,6 +40,69 @@ warnings.filterwarnings('ignore', 'Argument ``constraints_func`` is an experimen
38
40
  optuna.exceptions.ExperimentalWarning)
39
41
 
40
42
 
43
+ _MESSAGE_ENQUEUED = 'Enqueued trial.'
44
+
45
+
46
+ class MaxTrialsCallbackExcludingEnqueued(MaxTrialsCallback):
47
+ def __call__(self, study: Study, trial: FrozenTrial) -> None:
48
+ """
49
+ queue を考慮して終了条件を決定する
50
+
51
+ if n_trials <= 0:
52
+ -q_wait >= n_trials
53
+
54
+ ```
55
+ q_comp q_wait
56
+ | |
57
+ v v
58
+ f=====....0.............
59
+ ------|----+------------->
60
+ n_trials=-4 (<=0)
61
+ ```
62
+
63
+ else:
64
+ (s_comp := comp - q_comp) >= n_trials
65
+
66
+ ```
67
+ q_comp s_comp
68
+ | |
69
+ v v
70
+ f========0===..........
71
+ -----------+--|---------->
72
+ n_trials=4 (>0)
73
+ ```
74
+
75
+ """
76
+
77
+ # 与えられた state (=COMPLETE) の trials を取得
78
+ trials = study.get_trials(deepcopy=False, states=self._states)
79
+
80
+ # n_trials が負か 0 なら残り WAITING 数と比較
81
+ if self._n_trials <= 0:
82
+ waiting_trials = study.get_trials(deepcopy=False, states=(OptunaTrialState.WAITING,))
83
+ enqueued_trials = [trial for trial in waiting_trials if trial.user_attrs.get('enqueued')]
84
+
85
+ if -len(enqueued_trials) >= self._n_trials:
86
+ study.stop()
87
+
88
+ # 正なら (初回を除く enqueued trial) を除く
89
+ # COMPLETE 数 (=初回 + sampled_complete) と比較
90
+ else:
91
+
92
+ n_complete = len(trials)
93
+ n_enqueued_complete = len([
94
+ trial for trial in trials
95
+ if (
96
+ trial.user_attrs.get('enqueued', False)
97
+ and not trial.user_attrs.get(_IS_INITIAL_TRIAL_FLAG_KEY, False)
98
+ )
99
+ ])
100
+ n_sampled_complete = n_complete - n_enqueued_complete
101
+
102
+ if n_sampled_complete >= self._n_trials:
103
+ study.stop()
104
+
105
+
41
106
  class OptunaOptimizer(AbstractOptimizer):
42
107
  """
43
108
  An optimizer class utilizing Optuna for hyperparameter optimization.
@@ -274,13 +339,40 @@ class OptunaOptimizer(AbstractOptimizer):
274
339
 
275
340
  def _get_callback(self, n_trials: int):
276
341
 
342
+ states = (optuna.trial.TrialState.COMPLETE,)
343
+
277
344
  # restart である場合、追加 N 回と見做す
278
345
  if self.history.is_restart:
279
- df = self.history.get_df(equality_filters=MAIN_FILTER)
280
- n_existing_succeeded_trials = len(df[df['state'] == TrialState.succeeded])
281
- n_trials = n_trials + n_existing_succeeded_trials
282
346
 
283
- return MaxTrialsCallback(n_trials, states=(optuna.trial.TrialState.COMPLETE,))
347
+ study = optuna.load_study(
348
+ study_name=self.study_name,
349
+ storage=self.storage,
350
+ )
351
+ trials = study.get_trials(deepcopy=False, states=states)
352
+ n_complete = len(trials)
353
+
354
+ if self.include_queued_in_n_trials:
355
+ # 追加 n_trials 回と見做す
356
+ n_trials = n_trials + n_complete
357
+
358
+ else:
359
+ if n_trials > 0:
360
+ # 追加 n_trials 回と見做すが、
361
+ # Callback で n_enqueued_complete を足しているので
362
+ # そのぶんを引かなければならない
363
+ n_enqueued_complete = len([trial for trial in trials if trial.user_attrs.get('enqueued')])
364
+ n_trials = n_trials + n_complete - n_enqueued_complete
365
+
366
+ else:
367
+ # queue_wait と比較するので
368
+ # 何も補正しなくていい
369
+ pass
370
+
371
+ if self.include_queued_in_n_trials:
372
+ Class = MaxTrialsCallback
373
+ else:
374
+ Class = MaxTrialsCallbackExcludingEnqueued
375
+ return Class(n_trials, states=states)
284
376
 
285
377
  def _setup_before_parallel(self):
286
378
 
@@ -321,30 +413,44 @@ class OptunaOptimizer(AbstractOptimizer):
321
413
  # if TPESampler and re-starting,
322
414
  # create temporary study to avoid error
323
415
  # with many pruned trials.
324
- if issubclass(self.sampler_class, optuna.samplers.TPESampler) \
325
- and self.history.is_restart:
326
- # get unique tmp file
327
- tmp_storage_path = tempfile.mktemp(suffix='.db')
328
- self._existing_storage_path = self.storage_path
329
- self.storage_path = tmp_storage_path
416
+ if (
417
+ issubclass(self.sampler_class, optuna.samplers.TPESampler)
418
+ and self.history.is_restart
419
+ ):
420
+
421
+ # If there is a WAITING trial in queue,
422
+ # do nothing (because the callback cannot
423
+ # update the WAITING trial in the existing
424
+ # study.)
330
425
 
331
426
  # load existing study
332
427
  existing_study = optuna.load_study(
333
- study_name=self.study_name,
334
- storage=f'sqlite:///{self._existing_storage_path}',
335
- )
336
-
337
- # create new study
338
- tmp_study = optuna.create_study(
339
428
  study_name=self.study_name,
340
429
  storage=f'sqlite:///{self.storage_path}',
341
- load_if_exists=True,
342
- directions=['minimize'] * len(self.objectives),
343
430
  )
344
431
 
345
- # Copy COMPLETE trials to temporary study.
346
- existing_trials = existing_study.get_trials(states=(optuna.trial.TrialState.COMPLETE,))
347
- tmp_study.add_trials(existing_trials)
432
+ if len(existing_study.get_trials(deepcopy=False, states=(OptunaTrialState.WAITING,))) == 0:
433
+
434
+ # get unique tmp file
435
+ tmp_storage_path = tempfile.mktemp(suffix='.db')
436
+ self._existing_storage_path = self.storage_path
437
+ self.storage_path = tmp_storage_path
438
+
439
+ # create new study
440
+ tmp_study = optuna.create_study(
441
+ study_name=self.study_name,
442
+ storage=f'sqlite:///{self.storage_path}',
443
+ load_if_exists=True,
444
+ directions=['minimize'] * len(self.objectives),
445
+ )
446
+
447
+ # Copy COMPLETE trials to temporary study.
448
+ existing_trials = existing_study.get_trials(
449
+ states=(
450
+ optuna.trial.TrialState.COMPLETE,
451
+ )
452
+ )
453
+ tmp_study.add_trials(existing_trials)
348
454
 
349
455
  # setup storage
350
456
  client = get_client()
@@ -368,9 +474,18 @@ class OptunaOptimizer(AbstractOptimizer):
368
474
  # set objective names
369
475
  study.set_metric_names(list(self.objectives.keys()))
370
476
 
371
- # initial trial
372
- params = self.variable_manager.get_variables(format='dict', filter='parameter')
373
- study.enqueue_trial(params, user_attrs={"message": "Initial values"})
477
+ # Add enqueued trials
478
+ for t in self.trial_queue.queue:
479
+ is_initial_step = t.flags.get(_IS_INITIAL_TRIAL_FLAG_KEY, False)
480
+ params: dict[str, SupportedVariableTypes] = t.d
481
+ study.enqueue_trial(
482
+ params,
483
+ user_attrs={
484
+ 'message': _MESSAGE_ENQUEUED,
485
+ 'enqueued': True,
486
+ _IS_INITIAL_TRIAL_FLAG_KEY: is_initial_step,
487
+ },
488
+ )
374
489
 
375
490
  def _setup_after_parallel(self):
376
491
  # reseed
@@ -539,6 +654,9 @@ class OptunaOptimizer(AbstractOptimizer):
539
654
  # and add callback to copy-back
540
655
  # from processing study to existing one.
541
656
  def copy_back(_, trial):
657
+ # ここで existing_study 内の WAITING を
658
+ # 更新したり削除したりできないので
659
+ # tpe_addressing は WAITING がない状態でしか使わない
542
660
  existing_study.add_trial(trial)
543
661
 
544
662
  self.callbacks.append(copy_back)
@@ -92,6 +92,12 @@ class ScipyOptimizer(AbstractOptimizer):
92
92
  jp_message='`ScipyOptimizer` では n_trials は指定できません。'
93
93
  ))
94
94
 
95
+ def add_trial(self, parameters: dict[str, SupportedVariableTypes]):
96
+ raise NotImplementedError(_(
97
+ en_message='You cannot use `add_trial()` in `ScipyOptimizer`.',
98
+ jp_message='`ScipyOptimizer` では `add_trial()` は使えません。',
99
+ ))
100
+
95
101
  def _get_x0(self) -> np.ndarray:
96
102
 
97
103
  # params を取得
@@ -1,3 +1,5 @@
1
+ from collections import defaultdict
2
+
1
3
  import numpy as np
2
4
  import pandas as pd
3
5
  import plotly.graph_objs as go
@@ -6,7 +8,7 @@ import plotly.express as px
6
8
  from pyfemtet._util.df_util import *
7
9
  from pyfemtet.opt.history import *
8
10
  from pyfemtet.opt.problem.problem import MAIN_FIDELITY_NAME
9
- from pyfemtet._i18n import Msg
11
+ from pyfemtet._i18n import Msg, _
10
12
 
11
13
 
12
14
  __all__ = [
@@ -15,51 +17,83 @@ __all__ = [
15
17
  'get_objective_plot',
16
18
  ]
17
19
 
20
+ # ===== i18n =====
21
+ def _i18n_not_defined_default():
22
+ return _('Not included in the evaluation.',
23
+ '評価対象外')
24
+
25
+
26
+ # optimality
27
+ _LOCALIZED_OPTIMALITY = _('Optimality', '最適かどうか')
28
+
29
+
30
+ def _i18n_optimality(value, is_single_objective) -> str:
31
+ if value is True:
32
+ if is_single_objective:
33
+ return _('optimal', '最適解')
34
+ else:
35
+ return _('non dominated', '非劣解')
36
+ elif value is False:
37
+ if is_single_objective:
38
+ return _('not optimal', '最適でない解')
39
+ else:
40
+ return _('dominated', '劣解')
41
+ else:
42
+ return _i18n_not_defined_default()
43
+
44
+
45
+ (_color_mapping_single_objective := defaultdict(lambda: '#888888')).update({
46
+ _i18n_optimality(True, True): "#007bff",
47
+ _i18n_optimality(False, True): "#6c757d",
48
+ })
49
+ (_color_mapping_multi_objective := defaultdict(lambda: '#888888')).update({
50
+ _i18n_optimality(True, False): "#007bff",
51
+ _i18n_optimality(False, False): "#6c757d",
52
+ })
18
53
 
19
- class _ColorSet:
20
- non_domi = {True: '#007bff', False: '#6c757d'} # color
21
54
 
55
+ # feasibility
56
+ _LOCALIZED_FEASIBILITY = _('Feasibility', '実行可能性')
22
57
 
23
- class _SymbolSet:
24
- feasible = {True: 'circle', False: 'circle-open'} # style
25
58
 
59
+ def _i18n_feasibility(value) -> str:
60
+ if value is True:
61
+ return _('feasible', '実行可能')
62
+ elif value is False:
63
+ return _('infeasible', '実行不可能')
64
+ else:
65
+ return _i18n_not_defined_default()
26
66
 
27
- class _LanguageSet:
28
67
 
29
- feasible = {'label': 'feasibility', True: True, False: False}
30
- non_domi = {'label': 'optimality', True: True, False: False}
68
+ (_symbol_mapping := defaultdict(lambda: 'square-open')).update({
69
+ _i18n_feasibility(True): 'circle',
70
+ _i18n_feasibility(False): 'circle-open',
71
+ })
31
72
 
32
- def __init__(self):
33
- self.feasible = {
34
- 'label': Msg.LEGEND_LABEL_CONSTRAINT,
35
- True: Msg.LEGEND_LABEL_FEASIBLE,
36
- False: Msg.LEGEND_LABEL_INFEASIBLE,
37
- }
38
- self.non_domi = {
39
- 'label': Msg.LEGEND_LABEL_OPTIMAL,
40
- True: Msg.LEGEND_LABEL_NON_DOMI,
41
- False: Msg.LEGEND_LABEL_DOMI,
42
- }
43
73
 
44
- def localize(self, df):
45
- # 元のオブジェクトを変更しないようにコピー
46
- cdf = df.copy()
74
+ # common
75
+ def _i18n_df(df, is_single_objective):
76
+ # 元の df を変更しないようにコピー
77
+ df = df.copy()
47
78
 
48
- # feasible, non_domi の localize
49
- cdf[self.feasible['label']] = [self.feasible[v] for v in cdf['feasibility']]
50
- cdf[self.non_domi['label']] = [self.non_domi[v] for v in cdf['optimality']]
79
+ # feasibility 列のデータに対し、
80
+ # <翻訳された列名>: <グラフに表示するデータ>
81
+ # 変換した df を作る。
82
+ df[_LOCALIZED_FEASIBILITY] = [_i18n_feasibility(v)
83
+ for v in df['feasibility']]
51
84
 
52
- return cdf
85
+ # optimality も同様
86
+ df[_LOCALIZED_OPTIMALITY] = [_i18n_optimality(v, is_single_objective)
87
+ for v in df['optimality']]
53
88
 
89
+ return df
54
90
 
55
- _ls = _LanguageSet()
56
- _cs = _ColorSet()
57
- _ss = _SymbolSet()
58
91
 
92
+ # ===== main =====
59
93
 
60
94
  def get_hypervolume_plot(_: History, df: pd.DataFrame) -> go.Figure:
61
95
 
62
- df = _ls.localize(df)
96
+ df = _i18n_df(df, is_single_objective=False)
63
97
 
64
98
  # メインデータを抽出
65
99
  df = get_partial_df(df, equality_filters=MAIN_FILTER)
@@ -113,7 +147,7 @@ def get_default_figure(history, df) -> go.Figure:
113
147
 
114
148
  def _get_single_objective_plot(history: History, df: pd.DataFrame):
115
149
 
116
- df: pd.DataFrame = _ls.localize(df)
150
+ df: pd.DataFrame = _i18n_df(df, is_single_objective=True)
117
151
  obj_name = history.obj_names[0]
118
152
 
119
153
  # 「成功した試行数」を表示するために
@@ -275,7 +309,7 @@ def _get_single_objective_plot(history: History, df: pd.DataFrame):
275
309
 
276
310
  def _get_multi_objective_pairplot(history: History, df: pd.DataFrame):
277
311
 
278
- df = _ls.localize(df)
312
+ df = _i18n_df(df, is_single_objective=False)
279
313
  df_main = get_partial_df(df, equality_filters=MAIN_FILTER)
280
314
 
281
315
  obj_names = history.obj_names
@@ -289,21 +323,27 @@ def _get_multi_objective_pairplot(history: History, df: pd.DataFrame):
289
323
  history.obj_names for _ in sub_fidelity_names
290
324
  ]
291
325
 
326
+ print('===== _color_mapping_multi_objective =====')
327
+ print(df_main)
328
+ print(f'{_color_mapping_multi_objective=}')
329
+
292
330
  common_kwargs = dict(
293
- color=_ls.non_domi['label'],
294
- color_discrete_map={
295
- _ls.non_domi[True]: _cs.non_domi[True],
296
- _ls.non_domi[False]: _cs.non_domi[False],
297
- },
298
- symbol=_ls.feasible['label'],
299
- symbol_map={
300
- _ls.feasible[True]: _ss.feasible[True],
301
- _ls.feasible[False]: _ss.feasible[False],
302
- },
331
+ color=_LOCALIZED_OPTIMALITY,
332
+ color_discrete_map=_color_mapping_multi_objective,
333
+ symbol=_LOCALIZED_FEASIBILITY,
334
+ symbol_map=_symbol_mapping,
303
335
  custom_data=['trial'],
304
336
  category_orders={
305
- _ls.feasible['label']: (_ls.feasible[False], _ls.feasible[True]),
306
- _ls.non_domi['label']: (_ls.non_domi[False], _ls.non_domi[True]),
337
+ _LOCALIZED_FEASIBILITY: (
338
+ _i18n_feasibility(None), # True, False でなければなんでもよい
339
+ _i18n_feasibility(False),
340
+ _i18n_feasibility(True),
341
+ ),
342
+ _LOCALIZED_OPTIMALITY: (
343
+ _i18n_optimality(None, False),
344
+ _i18n_optimality(False, False),
345
+ _i18n_optimality(True, False),
346
+ ),
307
347
  },
308
348
  )
309
349
 
@@ -404,7 +444,7 @@ def _get_multi_objective_pairplot(history: History, df: pd.DataFrame):
404
444
 
405
445
  def get_objective_plot(history: History, df: pd.DataFrame, obj_names: list[str]) -> go.Figure:
406
446
 
407
- df = _ls.localize(df)
447
+ df = _i18n_df(df, is_single_objective=False)
408
448
 
409
449
  df_main = get_partial_df(df, equality_filters=MAIN_FILTER)
410
450
 
@@ -418,20 +458,22 @@ def get_objective_plot(history: History, df: pd.DataFrame, obj_names: list[str])
418
458
  ]
419
459
 
420
460
  common_kwargs = dict(
421
- color=_ls.non_domi['label'],
422
- color_discrete_map={
423
- _ls.non_domi[True]: _cs.non_domi[True],
424
- _ls.non_domi[False]: _cs.non_domi[False],
425
- },
426
- symbol=_ls.feasible['label'],
427
- symbol_map={
428
- _ls.feasible[True]: _ss.feasible[True],
429
- _ls.feasible[False]: _ss.feasible[False],
430
- },
461
+ color=_LOCALIZED_OPTIMALITY,
462
+ color_discrete_map=_color_mapping_multi_objective,
463
+ symbol=_LOCALIZED_FEASIBILITY,
464
+ symbol_map=_symbol_mapping,
431
465
  custom_data=['trial'],
432
466
  category_orders={
433
- _ls.feasible['label']: (_ls.feasible[False], _ls.feasible[True]),
434
- _ls.non_domi['label']: (_ls.non_domi[False], _ls.non_domi[True]),
467
+ _LOCALIZED_FEASIBILITY: (
468
+ _i18n_feasibility(None), # True, False でなければなんでもよい
469
+ _i18n_feasibility(False),
470
+ _i18n_feasibility(True),
471
+ ),
472
+ _LOCALIZED_OPTIMALITY: (
473
+ _i18n_optimality(None, False),
474
+ _i18n_optimality(False, False),
475
+ _i18n_optimality(True, False),
476
+ ),
435
477
  },
436
478
  )
437
479
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pyfemtet
3
- Version: 1.0.10
3
+ Version: 1.1.0
4
4
  Summary: Design parameter optimization using Femtet.
5
5
  License: MIT
6
6
  Author: pyfemtet
@@ -21,6 +21,7 @@ Requires-Dist: dask (>=2024.12.1,<2024.13.0)
21
21
  Requires-Dist: distributed (>=2024.12.1,<2024.13.0)
22
22
  Requires-Dist: femtetutils (>=1.0.0,<2) ; sys_platform == "win32"
23
23
  Requires-Dist: fire (>=0.7)
24
+ Requires-Dist: frozendict (>=2.4.6)
24
25
  Requires-Dist: numpy (>=2.0.0,<3)
25
26
  Requires-Dist: openpyxl (>=3.1.2,<4)
26
27
  Requires-Dist: optuna (>=3.4.0,<5.0.0)
@@ -29,9 +29,9 @@ pyfemtet/logger/__init__.py,sha256=lofBrZHr0P1hsxPUiPG1SQqKxCuSBk8zGnR7vUfCHYw,5
29
29
  pyfemtet/logger/_impl.py,sha256=uJ9el3kR-A4W2DvO_G5A6k9z2u1lkyl7GSvZ68uPy-U,6321
30
30
  pyfemtet/opt/__init__.py,sha256=1LcwTddtoi8plemxkzmX0YEKiNpAZvKn9OoNQysyDLE,339
31
31
  pyfemtet/opt/exceptions.py,sha256=M_O7jm20Y4e_QxsKF6tnEl-OrAtErUOj6hNT7eEXCO4,1327
32
- pyfemtet/opt/femopt.py,sha256=etPQ1vJAsnv8nLV4ddJBK56__a55Bsaz68AJNF6NJQM,23573
32
+ pyfemtet/opt/femopt.py,sha256=Zht_H_eaTUwUcA7XFlzod0mGoJzTOZckqRK3VuwjaHM,23716
33
33
  pyfemtet/opt/history/__init__.py,sha256=pUp3SO4R7RGzmpNDLBg_pQH0X2yzBd-oqsHXWmB33os,201
34
- pyfemtet/opt/history/_history.py,sha256=A8kygZXNXtySzpXqsceoFDI912W6NbTdd4KoMjbS8bk,54154
34
+ pyfemtet/opt/history/_history.py,sha256=6n4EZnyBZxGaP7jcHv5PgryFtdE9HotLa6NN1zE6Ops,54158
35
35
  pyfemtet/opt/history/_hypervolume.py,sha256=_IvGH71ZNreWvDQCG815Q2hS1OEvPFPQhUnNXf1UxRQ,4449
36
36
  pyfemtet/opt/history/_optimality.py,sha256=6vLySZmrrklr04Qir0hGethTykf8NYFod88NDGrBrG0,2407
37
37
  pyfemtet/opt/interface/__init__.py,sha256=b2lfkBL-UPbzJppIqSgtUqHSHitPLQa6DRuH91nDZK8,1905
@@ -64,17 +64,18 @@ pyfemtet/opt/meta_script/__main__.py,sha256=9-QM6eZOLpZ_CxERpRu3RAMqpudorSJdPCiK
64
64
  pyfemtet/opt/meta_script/sample/sample.bas,sha256=2iuSYMgPDyAdiSDVGxRu3avjcZYnULz0l8e25YBa7SQ,27966
65
65
  pyfemtet/opt/meta_script/sample/sample.femprj,sha256=6_0ywhgXxZjdzZzQFog8mgMUEjKNCFVNlEgAWoptovk,292885
66
66
  pyfemtet/opt/optimizer/__init__.py,sha256=A4QYeF0KHEFdwoxLfkDND7ikDQ186Ryy3oXEGdakFSg,463
67
- pyfemtet/opt/optimizer/_base_optimizer.py,sha256=_FmdtisooWgKP3aAqT2MEoqMMxZ6sSZOR9QGiJMbtks,33533
67
+ pyfemtet/opt/optimizer/_base_optimizer.py,sha256=o7XYqAJywX-gnXu2iyKXRKAhyIklZxbAuLitTn23_-Q,37445
68
+ pyfemtet/opt/optimizer/_trial_queue.py,sha256=Yv6JlfVCYOiCukllfxk79xU4_utmxwRA3gcCWpdyG9k,2919
68
69
  pyfemtet/opt/optimizer/optuna_optimizer/__init__.py,sha256=u2Bwc79tkZTU5dMbhzzrPQi0RlFg22UgXc-m9K9G6wQ,242
69
70
  pyfemtet/opt/optimizer/optuna_optimizer/_optuna_attribute.py,sha256=7eZsruVCGgMlcnf3a9Vf55FOEE-D7V777MJQajI12Cw,1842
70
- pyfemtet/opt/optimizer/optuna_optimizer/_optuna_optimizer.py,sha256=rnQjWoxXVNDFRj8Htjb8AXa12_EWbP3qUiR3RsK5sh0,28810
71
+ pyfemtet/opt/optimizer/optuna_optimizer/_optuna_optimizer.py,sha256=2hWxS-PZ-oQyPw1cDfW5e6Tiu2bAUmE9Wo9WR01e2zc,32863
71
72
  pyfemtet/opt/optimizer/optuna_optimizer/_pof_botorch/__init__.py,sha256=BFbMNvdXqV9kl1h340pW2sq0-cwNFV5dfTo6UnNnX2M,179
72
73
  pyfemtet/opt/optimizer/optuna_optimizer/_pof_botorch/debug-pof-botorch.reccsv,sha256=K6oI9jPi_5yayhBrI9Tm1RX3PoWWKo74TOdqnaPsIy8,1746
73
74
  pyfemtet/opt/optimizer/optuna_optimizer/_pof_botorch/enable_nonlinear_constraint.py,sha256=jq8cfkZuEkdd8Gvlr3Do4dl48bKSm9Uu7AcEy1dAf6U,9901
74
75
  pyfemtet/opt/optimizer/optuna_optimizer/_pof_botorch/pof_botorch_sampler.py,sha256=qOAQOiC7xW1aGZ7JbMPYVjc9FEHi3hYffOfcXpy4rhI,48642
75
76
  pyfemtet/opt/optimizer/optuna_optimizer/wat_ex14_parametric_jp.femprj,sha256=-M54MTNrV7muZWPm9Tjptd6HDdtgUFBsRroC6ytyqa0,180970
76
77
  pyfemtet/opt/optimizer/scipy_optimizer/__init__.py,sha256=oXx2JAVLvgz0WwIXAknuV4p2MupaiutYYvjI8hXcFwc,45
77
- pyfemtet/opt/optimizer/scipy_optimizer/_scipy_optimizer.py,sha256=sZxpb-qnDn1KmwgvZmFgVwCKpTPTGw7Yrlmq3DNin0Q,12796
78
+ pyfemtet/opt/optimizer/scipy_optimizer/_scipy_optimizer.py,sha256=BhlvVi3fugDjaDqoyi3glUR0N7NOxne9E92V-BvBOt4,13079
78
79
  pyfemtet/opt/prediction/__init__.py,sha256=-XYo-l5YFjExMtqMKj1YUAhmGSQq_0YITS0qizj2Xbs,104
79
80
  pyfemtet/opt/prediction/_botorch_utils.py,sha256=cXQlQ_oC6cQaI5kGIYANlqDJPdkGEA_zK2gtJpluJNE,6001
80
81
  pyfemtet/opt/prediction/_gpytorch_modules_extension.py,sha256=B_qUtFn06dQENOmUOObbCpkeASUKI5JpXROx8zYeaq0,5224
@@ -165,14 +166,14 @@ pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_
165
166
  pyfemtet/opt/visualization/history_viewer/result_viewer/tutorial_files/tutorial_gau_ex08_parametric.femprj,sha256=uPqOara56FAt_T4x2P6tvcIXnISw_fX7RECbVoJlAFs,280214
166
167
  pyfemtet/opt/visualization/plotter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
167
168
  pyfemtet/opt/visualization/plotter/contour_creator.py,sha256=bvGGWNBLZawHq0fAIHfZOUAXw0f9GTlNROiJThoXZ2A,3043
168
- pyfemtet/opt/visualization/plotter/main_figure_creator.py,sha256=9RXz6Wt52MiSz3Hbew6sDq6KZrYU8ojvhCTHSEup6Ys,16403
169
+ pyfemtet/opt/visualization/plotter/main_figure_creator.py,sha256=a_HK6EUruQBncI8vEIZnrDorPr2EdPICLKU4Er97WtQ,17761
169
170
  pyfemtet/opt/visualization/plotter/parallel_plot_creator.py,sha256=VRhT0CUG1mCHDoVO8e6LR_-FiD0QB3GsB95016iXmYc,802
170
171
  pyfemtet/opt/visualization/plotter/pm_graph_creator.py,sha256=V26HIbPF1jEVAZ9r1Izsi2zcVz84l4RhZUTJV9jPqp8,11019
171
172
  pyfemtet/opt/wat_ex14_parametric_jp.femprj,sha256=dMwQMt6yok_PbZLyxPYdmg5wJQwgQDZ4RhS76zdGLGk,177944
172
173
  pyfemtet/opt/worker_status.py,sha256=xSVW9lcw5jzYBwnmlVzk-1zCCyvmXVOH6EoRjqVbE9M,3605
173
- pyfemtet-1.0.10.dist-info/LICENSE,sha256=LWUL5LlMGjSRTvsalS8_fFuwS4VMw18fJSNWFwDK8pc,1060
174
- pyfemtet-1.0.10.dist-info/LICENSE_THIRD_PARTY.txt,sha256=8_9-cgzTpmeuCqItPZb9-lyAZcH2Qp9sZTU_hYuOZIQ,191
175
- pyfemtet-1.0.10.dist-info/METADATA,sha256=rWboOw8yO1pC_MZQEdb7He_jMw2V4oEEBK9rJ_hQDP8,3468
176
- pyfemtet-1.0.10.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
177
- pyfemtet-1.0.10.dist-info/entry_points.txt,sha256=Tsb_l_8Z6pyyq2tRfuKiwfJUV3nq_cHoLS61foALtsg,134
178
- pyfemtet-1.0.10.dist-info/RECORD,,
174
+ pyfemtet-1.1.0.dist-info/LICENSE,sha256=LWUL5LlMGjSRTvsalS8_fFuwS4VMw18fJSNWFwDK8pc,1060
175
+ pyfemtet-1.1.0.dist-info/LICENSE_THIRD_PARTY.txt,sha256=8_9-cgzTpmeuCqItPZb9-lyAZcH2Qp9sZTU_hYuOZIQ,191
176
+ pyfemtet-1.1.0.dist-info/METADATA,sha256=U-_Mn8IbOEgQjZ6IGa85S6pccNEW6Mq_t8XtkW2svVY,3503
177
+ pyfemtet-1.1.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
178
+ pyfemtet-1.1.0.dist-info/entry_points.txt,sha256=Tsb_l_8Z6pyyq2tRfuKiwfJUV3nq_cHoLS61foALtsg,134
179
+ pyfemtet-1.1.0.dist-info/RECORD,,