pyfemtet 0.4.20__py3-none-any.whl → 0.4.23__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 (61) hide show
  1. pyfemtet/__init__.py +1 -1
  2. pyfemtet/_test_util.py +0 -2
  3. pyfemtet/message/locales/ja/LC_MESSAGES/messages.mo +0 -0
  4. pyfemtet/message/locales/ja/LC_MESSAGES/messages.po +107 -96
  5. pyfemtet/message/locales/messages.pot +104 -96
  6. pyfemtet/message/messages.py +15 -1
  7. pyfemtet/opt/_femopt.py +289 -230
  8. pyfemtet/opt/_femopt_core.py +118 -49
  9. pyfemtet/opt/femprj_sample/ParametricIF.py +0 -2
  10. pyfemtet/opt/femprj_sample/cad_ex01_NX.py +0 -8
  11. pyfemtet/opt/femprj_sample/cad_ex01_SW.py +0 -8
  12. pyfemtet/opt/femprj_sample/gal_ex58_parametric.py +0 -8
  13. pyfemtet/opt/femprj_sample/gau_ex08_parametric.py +0 -8
  14. pyfemtet/opt/femprj_sample/her_ex40_parametric.py +0 -8
  15. pyfemtet/opt/femprj_sample/paswat_ex1_parametric.py +0 -8
  16. pyfemtet/opt/femprj_sample/paswat_ex1_parametric_parallel.py +0 -8
  17. pyfemtet/opt/femprj_sample/wat_ex14_parametric.py +0 -8
  18. pyfemtet/opt/femprj_sample/wat_ex14_parametric_parallel.py +0 -8
  19. pyfemtet/opt/femprj_sample_jp/ParametricIF_jp.py +0 -2
  20. pyfemtet/opt/femprj_sample_jp/cad_ex01_NX_jp.py +0 -8
  21. pyfemtet/opt/femprj_sample_jp/cad_ex01_SW_jp.py +0 -8
  22. pyfemtet/opt/femprj_sample_jp/gal_ex58_parametric_jp.py +0 -8
  23. pyfemtet/opt/femprj_sample_jp/gau_ex08_parametric_jp.py +0 -8
  24. pyfemtet/opt/femprj_sample_jp/her_ex40_parametric_jp.py +0 -8
  25. pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_jp.py +0 -8
  26. pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_parallel_jp.py +0 -8
  27. pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_jp.py +0 -8
  28. pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_parallel_jp.py +0 -8
  29. pyfemtet/opt/interface/_femtet.py +77 -24
  30. pyfemtet/opt/opt/_base.py +25 -18
  31. pyfemtet/opt/opt/_optuna.py +53 -14
  32. pyfemtet/opt/opt/_optuna_botorch_helper.py +209 -0
  33. pyfemtet/opt/opt/_scipy.py +1 -1
  34. pyfemtet/opt/opt/_scipy_scalar.py +1 -1
  35. pyfemtet/opt/parameter.py +113 -0
  36. pyfemtet/opt/visualization/complex_components/main_graph.py +22 -5
  37. pyfemtet/opt/visualization/complex_components/pm_graph.py +77 -25
  38. pyfemtet/opt/visualization/complex_components/pm_graph_creator.py +7 -0
  39. pyfemtet/opt/visualization/process_monitor/application.py +10 -6
  40. pyfemtet/opt/visualization/process_monitor/pages.py +102 -0
  41. pyfemtet/opt/visualization/result_viewer/application.py +6 -0
  42. pyfemtet/opt/visualization/result_viewer/pages.py +1 -1
  43. {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/METADATA +3 -4
  44. {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/RECORD +47 -59
  45. pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.femprj +0 -0
  46. pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.prt +0 -0
  47. pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.py +0 -118
  48. pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.SLDPRT +0 -0
  49. pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.femprj +0 -0
  50. pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.py +0 -121
  51. pyfemtet/FemtetPJTSample/_her_ex40_parametric.py +0 -148
  52. pyfemtet/FemtetPJTSample/gau_ex08_parametric.femprj +0 -0
  53. pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +0 -58
  54. pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
  55. pyfemtet/FemtetPJTSample/her_ex40_parametric.py +0 -148
  56. pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +0 -65
  57. pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
  58. pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +0 -64
  59. {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/LICENSE +0 -0
  60. {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/WHEEL +0 -0
  61. {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,113 @@
1
+ from graphlib import TopologicalSorter
2
+ from dataclasses import dataclass
3
+ import inspect
4
+ from typing import Optional, Callable, Any, Tuple, Dict
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+
10
+ @dataclass
11
+ class Variable:
12
+ name: str
13
+ value: float
14
+ pass_to_fem: Optional[bool] = True
15
+ properties: Optional[dict[Any]] = None
16
+
17
+
18
+ @dataclass
19
+ class Parameter(Variable):
20
+ lower_bound: Optional[float] = None
21
+ upper_bound: Optional[float] = None
22
+ step: Optional[float] = None
23
+
24
+
25
+ @dataclass
26
+ class Expression(Variable):
27
+ # fun に params を自動で代入するので positional args は実装しない
28
+ fun: Optional[Callable] = None
29
+ kwargs: Optional[Dict] = None
30
+
31
+
32
+ class ExpressionEvaluator:
33
+ def __init__(self):
34
+ self.variables = {} # Parameter 又は計算された Expression が入る
35
+ self.parameters = {}
36
+ self.expressions = {}
37
+ self.dependencies = {}
38
+ self.evaluation_order = []
39
+
40
+ def add_parameter(self, prm: Parameter):
41
+ self.variables[prm.name] = prm
42
+ self.parameters[prm.name] = prm
43
+ self.dependencies[prm.name] = set()
44
+
45
+ def add_expression(self, exp: Expression):
46
+ self.expressions[exp.name] = exp
47
+
48
+ # params は Python 変数として使える文字のみからなる文字列のリスト
49
+ params = inspect.signature(exp.fun).parameters
50
+ self.dependencies[exp.name] = set(params) - set(exp.kwargs.keys())
51
+
52
+ def resolve(self):
53
+ ts = TopologicalSorter(self.dependencies)
54
+ self.evaluation_order = list(ts.static_order())
55
+
56
+ def evaluate(self):
57
+ # order 順に見ていき、expression なら計算して variables を更新する
58
+ for var_name in self.evaluation_order:
59
+ if var_name in self.expressions.keys():
60
+ # 現在の expression に関して parameter 部分の引数 kwargs を作成
61
+ kwargs = {param: self.variables[param].value for param in self.dependencies[var_name]}
62
+
63
+ # fun に すべての kwargs を入れて expression の value を更新
64
+ exp: Expression = self.expressions[var_name]
65
+ kwargs.update(exp.kwargs)
66
+ exp.value = exp.fun(**kwargs)
67
+
68
+ # 計算済み variables に追加
69
+ self.variables[var_name] = exp
70
+
71
+ def get_variables(self, format='dict', filter_pass_to_fem=False, filter_parameter=False):
72
+ """format: dict, values, df, raw(list of Variable object)"""
73
+
74
+ # リストを作成
75
+ vars = [self.variables[name] for name in self.evaluation_order if name in self.variables]
76
+
77
+ # 必要なら FEM に直接使うもののみ取り出し
78
+ if filter_pass_to_fem:
79
+ vars = [var for var in vars if var.pass_to_fem]
80
+
81
+ # 必要なら parameter のみ取り出し
82
+ if filter_parameter:
83
+ vars = [var for var in vars if isinstance(var, Parameter)]
84
+
85
+ if format == 'raw':
86
+ return vars
87
+
88
+ elif format == 'dict':
89
+ return {var.name: var.value for var in vars}
90
+
91
+ elif format == 'values':
92
+ return np.array([var.value for var in vars]).astype(float)
93
+
94
+ elif format == 'df':
95
+ data = dict(
96
+ name=[var.name for var in vars],
97
+ value=[var.value for var in vars],
98
+ properties=[var.properties for var in vars],
99
+ )
100
+ if filter_parameter:
101
+ data.update(
102
+ dict(
103
+ lower_bound=[var.lower_bound for var in vars],
104
+ upper_bound=[var.upper_bound for var in vars],
105
+ )
106
+ )
107
+ return pd.DataFrame(data)
108
+
109
+ else:
110
+ raise NotImplementedError(f'invalid format: {format}. Valid formats are `dict`, `values`, `df` and `raw`(= list of Variables).')
111
+
112
+ def get_parameter_names(self):
113
+ return list(self.parameters.keys())
@@ -184,16 +184,18 @@ class MainGraph(AbstractPage):
184
184
  # create component
185
185
  title_component = html.H3(f"trial{trial}", style={"color": "darkblue"})
186
186
  img_component = self.create_image_content_if_femtet(trial)
187
- tbl_component = self.create_formatted_parameter(row)
187
+ tbl_component_prm = self.create_formatted_parameter(row)
188
+ tbl_component_obj = self.create_formatted_objective(row)
188
189
 
189
190
  # create layout
190
191
  description = html.Div([
191
192
  title_component,
192
- tbl_component,
193
+ tbl_component_prm,
193
194
  ])
194
195
  tooltip_layout = html.Div([
195
196
  html.Div(img_component, style={'display': 'inline-block', 'margin-right': '10px', 'vertical-align': 'top'}),
196
- html.Div(description, style={'display': 'inline-block', 'margin-right': '10px'})
197
+ html.Div(description, style={'display': 'inline-block', 'margin-right': '10px'}),
198
+ html.Div(tbl_component_obj, style={'display': 'inline-block', 'margin-right': '10px'}),
197
199
  ])
198
200
 
199
201
  return True, bbox, tooltip_layout
@@ -205,7 +207,22 @@ class MainGraph(AbstractPage):
205
207
  names = parameters.columns
206
208
  values = [f'{value:.3e}' for value in parameters.values.ravel()]
207
209
  data = pd.DataFrame(dict(
208
- name=names, value=values
210
+ parameter=names, value=values
211
+ ))
212
+ table = dash_table.DataTable(
213
+ columns=[{'name': col, 'id': col} for col in data.columns],
214
+ data=data.to_dict('records')
215
+ )
216
+ return table
217
+
218
+ def create_formatted_objective(self, row) -> Component:
219
+ metadata = self.application.history.metadata
220
+ pd.options.display.float_format = '{:.4e}'.format
221
+ objectives = row.iloc[:, np.where(np.array(metadata) == 'obj')[0]]
222
+ names = objectives.columns
223
+ values = [f'{value:.3e}' for value in objectives.values.ravel()]
224
+ data = pd.DataFrame(dict(
225
+ objective=names, value=values
209
226
  ))
210
227
  table = dash_table.DataTable(
211
228
  columns=[{'name': col, 'id': col} for col in data.columns],
@@ -258,5 +275,5 @@ class MainGraph(AbstractPage):
258
275
  if isinstance(self.application, ProcessMonitorApplication):
259
276
  df = self.application.local_data
260
277
  else:
261
- df = self.application.history.local_data
278
+ df = self.application.history.get_df()
262
279
  return df
@@ -129,6 +129,19 @@ class PredictionModelGraph(AbstractPage):
129
129
  self.slider_stack_data = html.Data(**{self.slider_stack_data_prop: {}})
130
130
  self.slider_container = html.Div()
131
131
 
132
+ # 2d or 3d
133
+ self.switch_3d = dbc.Checklist(
134
+ options=[
135
+ dict(
136
+ label=Msg.LABEL_SWITCH_PREDICTION_MODEL_3D,
137
+ disabled=False,
138
+ value=False,
139
+ )
140
+ ],
141
+ switch=True,
142
+ value=[],
143
+ )
144
+
132
145
  def setup_layout(self):
133
146
  self.card_header = dbc.CardHeader(self.tabs)
134
147
 
@@ -151,6 +164,7 @@ class PredictionModelGraph(AbstractPage):
151
164
  self.command_manager
152
165
  ],
153
166
  direction='horizontal', gap=2),
167
+ self.switch_3d,
154
168
  *dropdown_rows,
155
169
  self.slider_container,
156
170
  self.slider_stack_data,
@@ -193,13 +207,15 @@ class PredictionModelGraph(AbstractPage):
193
207
  Output(self.redraw_graph_button_spinner, 'spinner_style', allow_duplicate=True),
194
208
  Output(self.redraw_graph_button, 'disabled', allow_duplicate=True),
195
209
  Output(self.command_manager, self.command_manager_prop, allow_duplicate=True),
210
+ Output(self.switch_3d, 'options', allow_duplicate=True),
196
211
  Input(self.fit_rsm_button, 'n_clicks'),
197
212
  Input(self.redraw_graph_button, 'n_clicks'),
198
213
  State(self.fit_rsm_button_spinner, 'spinner_style'),
199
214
  State(self.redraw_graph_button_spinner, 'spinner_style'),
215
+ State(self.switch_3d, 'options'),
200
216
  prevent_initial_call=True,
201
217
  )
202
- def disable_fit_button(_1, _2, state1, state2):
218
+ def disable_fit_button(_1, _2, state1, state2, switch_options):
203
219
  # spinner visibility
204
220
  if 'display' in state1.keys(): state1.pop('display')
205
221
  if 'display' in state2.keys(): state2.pop('display')
@@ -210,7 +226,12 @@ class PredictionModelGraph(AbstractPage):
210
226
  else:
211
227
  command = self.CommandState.redraw.value
212
228
 
213
- return state1, True, state2, True, command
229
+ # disable switch
230
+ option = switch_options[0]
231
+ option.update({'disabled': True})
232
+ switch_options[0] = option
233
+
234
+ return state1, True, state2, True, command, switch_options
214
235
 
215
236
  # ===== recreate RSM =====
216
237
  @app.callback(
@@ -256,9 +277,10 @@ class PredictionModelGraph(AbstractPage):
256
277
  State(self.axis3_obj_dropdown, 'label'),
257
278
  State(self.slider_container, 'children'), # for callback chain
258
279
  State({'type': 'prm-slider', 'index': ALL}, 'value'),
280
+ State(self.switch_3d, 'value'),
259
281
  prevent_initial_call=True,
260
282
  )
261
- def redraw_graph(command, active_tab_id, axis1_label, axis2_label, axis3_label, _2, prm_values):
283
+ def redraw_graph(command, active_tab_id, axis1_label, axis2_label, axis3_label, _2, prm_values, is_3d):
262
284
  # just in case
263
285
  if callback_context.triggered_id is None:
264
286
  raise PreventUpdate
@@ -283,6 +305,9 @@ class PredictionModelGraph(AbstractPage):
283
305
  logger.error(Msg.ERR_NO_PREDICTION_MODEL)
284
306
  return no_update, self.CommandState.ready.value # to re-enable buttons, fire callback chain
285
307
 
308
+ if not is_3d:
309
+ axis2_label = None
310
+
286
311
  # get indices to remove
287
312
  idx1 = prm_names.index(axis1_label) if axis1_label in prm_names else None
288
313
  idx2 = prm_names.index(axis2_label) if axis2_label in prm_names else None
@@ -307,23 +332,31 @@ class PredictionModelGraph(AbstractPage):
307
332
 
308
333
  return fig, self.CommandState.ready.value
309
334
 
310
- # ===== When the graph is updated, enable buttons =====
335
+ # ===== re-enable buttons when the graph is updated, =====
311
336
  @app.callback(
312
337
  Output(self.fit_rsm_button, 'disabled', allow_duplicate=True),
313
338
  Output(self.fit_rsm_button_spinner, 'spinner_style', allow_duplicate=True),
314
339
  Output(self.redraw_graph_button, 'disabled', allow_duplicate=True),
315
340
  Output(self.redraw_graph_button_spinner, 'spinner_style', allow_duplicate=True),
341
+ Output(self.switch_3d, 'options', allow_duplicate=True),
316
342
  Input(self.command_manager, self.command_manager_prop),
317
343
  State(self.fit_rsm_button_spinner, 'spinner_style'),
318
344
  State(self.redraw_graph_button_spinner, 'spinner_style'),
345
+ State(self.switch_3d, 'options'),
319
346
  prevent_initial_call=True,
320
347
  )
321
- def enable_buttons(command, state1, state2):
348
+ def enable_buttons(command, state1, state2, switch_options):
322
349
  if command != self.CommandState.ready.value:
323
350
  raise PreventUpdate
324
351
  state1.update({'display': 'none'})
325
352
  state2.update({'display': 'none'})
326
- return False, state1, False, state2
353
+
354
+ # enable switch
355
+ option = switch_options[0]
356
+ option.update({'disabled': False})
357
+ switch_options[0] = option
358
+
359
+ return False, state1, False, state2, switch_options
327
360
 
328
361
  # ===== setup dropdown and sliders from history =====
329
362
  @app.callback(
@@ -414,6 +447,7 @@ class PredictionModelGraph(AbstractPage):
414
447
  Input({'type': 'axis2-dropdown-menu-item', 'index': ALL}, 'n_clicks'),
415
448
  Input({'type': 'axis3-dropdown-menu-item', 'index': ALL}, 'n_clicks'),
416
449
  Input(self.axis1_prm_dropdown, 'children'), # for callback chain timing
450
+ Input(self.switch_3d, 'value'),
417
451
  State(self.axis1_prm_dropdown, 'label'),
418
452
  State(self.axis2_prm_dropdown, 'label'),
419
453
  State(self.axis3_obj_dropdown, 'label'),
@@ -422,10 +456,10 @@ class PredictionModelGraph(AbstractPage):
422
456
  )
423
457
  def update_controller(*args):
424
458
  # argument processing
425
- current_ax1_label = args[4]
426
- current_ax2_label = args[5]
427
- # current_ax3_label = args[6]
428
- current_styles: list[dict] = args[7]
459
+ current_ax1_label = args[5]
460
+ current_ax2_label = args[6]
461
+ current_styles: list[dict] = args[8]
462
+ is_3d = args[4]
429
463
 
430
464
  # just in case
431
465
  if callback_context.triggered_id is None:
@@ -451,6 +485,10 @@ class PredictionModelGraph(AbstractPage):
451
485
  if len(prm_names) < 2:
452
486
  ret[ax2_hidden] = True
453
487
 
488
+ # ===== hide dropdown of axis 2 if not is_3d =====
489
+ if not is_3d:
490
+ ret[ax2_hidden] = True
491
+
454
492
  # ===== update dropdown label =====
455
493
 
456
494
  # by callback chain on loaded after setup_dropdown_and_sliders()
@@ -466,29 +504,43 @@ class PredictionModelGraph(AbstractPage):
466
504
 
467
505
  # ax1
468
506
  if callback_context.triggered_id['type'] == 'axis1-dropdown-menu-item':
469
- if new_label != current_ax2_label:
470
- ret[ax1_label_key] = new_label
471
- else:
472
- logger.error(Msg.ERR_CANNOT_SELECT_SAME_PARAMETER)
507
+ ret[ax1_label_key] = new_label
508
+ if new_label == current_ax2_label:
509
+ ret[ax2_label_key] = current_ax1_label
510
+
473
511
 
474
512
  # ax2
475
513
  elif callback_context.triggered_id['type'] == 'axis2-dropdown-menu-item':
476
- if new_label != current_ax1_label:
477
- ret[ax2_label_key] = new_label
478
- else:
479
- logger.error(Msg.ERR_CANNOT_SELECT_SAME_PARAMETER)
514
+ ret[ax2_label_key] = new_label
515
+ if new_label == current_ax1_label:
516
+ ret[ax1_label_key] = current_ax2_label
517
+
480
518
 
481
519
  # ax3
482
520
  elif callback_context.triggered_id['type'] == 'axis3-dropdown-menu-item':
483
521
  ret[ax3_label_key] = new_label
484
522
 
485
523
  # ===== update visibility of sliders =====
486
- for label_key, current_label in zip((ax1_label_key, ax2_label_key), (current_ax1_label, current_ax2_label)):
487
- # get label of output
488
- label = ret[label_key] if ret[label_key] != no_update else current_label
489
- # update display style of slider
490
- idx = prm_names.index(label) if label in prm_names else None
491
- if idx is not None:
524
+
525
+ # invisible the slider correspond to the dropdown-1
526
+ label_key, current_label = ax1_label_key, current_ax1_label
527
+ # get label of output
528
+ label = ret[label_key] if ret[label_key] != no_update else current_label
529
+ # update display style of slider
530
+ idx = prm_names.index(label) if label in prm_names else None
531
+ if idx is not None:
532
+ current_styles[idx].update({'display': 'none'})
533
+ ret[slider_style_list_key][idx] = current_styles[idx]
534
+
535
+ # invisible the slider correspond to the dropdown-2
536
+ label_key, current_label = ax2_label_key, current_ax2_label
537
+ # get label of output
538
+ label = ret[label_key] if ret[label_key] != no_update else current_label
539
+ # update display style of slider
540
+ idx = prm_names.index(label) if label in prm_names else None
541
+ if idx is not None:
542
+ # if 2d, should not disable the slider correspond to dropdown-2.
543
+ if is_3d:
492
544
  current_styles[idx].update({'display': 'none'})
493
545
  ret[slider_style_list_key][idx] = current_styles[idx]
494
546
 
@@ -554,5 +606,5 @@ class PredictionModelGraph(AbstractPage):
554
606
  if isinstance(self.application, ProcessMonitorApplication):
555
607
  df = self.application.local_data
556
608
  else:
557
- df = self.application.history.local_data
609
+ df = self.application.history.get_df()
558
610
  return df
@@ -183,4 +183,11 @@ class PredictionModelCreator:
183
183
  )
184
184
  )
185
185
 
186
+ # layout
187
+ fig.update_layout(
188
+ title=Msg.GRAPH_TITLE_PREDICTION_MODEL,
189
+ xaxis_title=prm_name_1,
190
+ yaxis_title=obj_name,
191
+ )
192
+
186
193
  return fig
@@ -5,7 +5,7 @@ from threading import Thread
5
5
  import pandas as pd
6
6
 
7
7
  from pyfemtet.opt.visualization.base import PyFemtetApplicationBase, logger
8
- from pyfemtet.opt.visualization.process_monitor.pages import HomePage, WorkerPage, PredictionModelPage
8
+ from pyfemtet.opt.visualization.process_monitor.pages import HomePage, WorkerPage, PredictionModelPage, OptunaVisualizerPage
9
9
  from pyfemtet.message import Msg
10
10
 
11
11
 
@@ -67,14 +67,14 @@ class ProcessMonitorApplication(PyFemtetApplicationBase):
67
67
  if self._should_get_actor_data:
68
68
  return self._df
69
69
  else:
70
- return self.history.local_data
70
+ return self.history.get_df()
71
71
 
72
72
  @local_data.setter
73
73
  def local_data(self, value: pd.DataFrame):
74
74
  if self._should_get_actor_data:
75
75
  raise NotImplementedError('If should_get_actor_data, ProcessMonitorApplication.local_df is read_only.')
76
76
  else:
77
- self.history.local_data = value
77
+ self.history.set_df(value)
78
78
 
79
79
  def setup_callback(self, debug=False):
80
80
  if not debug:
@@ -112,7 +112,7 @@ class ProcessMonitorApplication(PyFemtetApplicationBase):
112
112
  worker_status.set(OptimizationStatus.INTERRUPTING)
113
113
 
114
114
  # status と df を actor から application に反映する
115
- self._df = self.history.actor_data.copy()
115
+ self._df = self.history.get_df().copy()
116
116
  self.local_entire_status_int = self.entire_status.get()
117
117
  self.local_worker_status_int_list = [s.get() for s in self.worker_status_list]
118
118
 
@@ -176,11 +176,13 @@ def g_debug():
176
176
 
177
177
  g_home_page = HomePage(Msg.PAGE_TITLE_PROGRESS)
178
178
  g_rsm_page = PredictionModelPage(Msg.PAGE_TITLE_PREDICTION_MODEL, '/prediction-model', g_application)
179
+ g_optuna = OptunaVisualizerPage(Msg.PAGE_TITLE_OPTUNA_VISUALIZATION, '/optuna', g_application)
179
180
  g_worker_page = WorkerPage(Msg.PAGE_TITLE_WORKERS, '/workers', g_application)
180
181
 
181
182
  g_application.add_page(g_home_page, 0)
182
183
  g_application.add_page(g_rsm_page, 1)
183
- g_application.add_page(g_worker_page, 2)
184
+ g_application.add_page(g_optuna, 2)
185
+ g_application.add_page(g_worker_page, 3)
184
186
  g_application.setup_callback(debug=False)
185
187
 
186
188
  g_application.run(debug=False)
@@ -191,11 +193,13 @@ def main(history, status, worker_addresses, worker_status_list, host=None, port=
191
193
 
192
194
  g_home_page = HomePage(Msg.PAGE_TITLE_PROGRESS)
193
195
  g_rsm_page = PredictionModelPage(Msg.PAGE_TITLE_PREDICTION_MODEL, '/prediction-model', g_application)
196
+ g_optuna = OptunaVisualizerPage(Msg.PAGE_TITLE_OPTUNA_VISUALIZATION, '/optuna', g_application)
194
197
  g_worker_page = WorkerPage(Msg.PAGE_TITLE_WORKERS, '/workers', g_application)
195
198
 
196
199
  g_application.add_page(g_home_page, 0)
197
200
  g_application.add_page(g_rsm_page, 1)
198
- g_application.add_page(g_worker_page, 2)
201
+ g_application.add_page(g_optuna, 2)
202
+ g_application.add_page(g_worker_page, 3)
199
203
  g_application.setup_callback()
200
204
 
201
205
  g_application.start_server(host, port)
@@ -1,6 +1,8 @@
1
1
  import numpy as np
2
2
  import pandas as pd
3
3
 
4
+ import optuna
5
+
4
6
  from dash import Output, Input, State, callback_context, no_update, ALL
5
7
  from dash.exceptions import PreventUpdate
6
8
 
@@ -289,3 +291,103 @@ class PredictionModelPage(AbstractPage):
289
291
 
290
292
  def setup_layout(self):
291
293
  self.layout = self.rsm_graph.layout
294
+
295
+
296
+ class OptunaVisualizerPage(AbstractPage):
297
+
298
+ def __init__(self, title, rel_url, application):
299
+ from pyfemtet.opt.visualization.process_monitor.application import ProcessMonitorApplication
300
+ self.application: ProcessMonitorApplication = None
301
+ super().__init__(title, rel_url, application)
302
+
303
+ def setup_component(self):
304
+ self.location = dcc.Location(id='optuna-page-location', refresh=True)
305
+ self._layout = html.Div(children=[Msg.DETAIL_PAGE_TEXT_BEFORE_LOADING])
306
+ self.layout = [self.location, self._layout]
307
+
308
+ def _setup_layout(self):
309
+
310
+ study = self.application.history.create_optuna_study()
311
+ prm_names = self.application.history.prm_names
312
+ obj_names = self.application.history.obj_names
313
+
314
+ layout = []
315
+
316
+ layout.append(html.H2(Msg.DETAIL_PAGE_HISTORY_HEADER))
317
+ layout.append(html.H4(Msg.DETAIL_PAGE_HISTORY_DESCRIPTION))
318
+ for i, obj_name in enumerate(obj_names):
319
+ fig = optuna.visualization.plot_optimization_history(
320
+ study,
321
+ target=lambda t: t.values[i],
322
+ target_name=obj_name
323
+ )
324
+ layout.append(dcc.Graph(figure=fig, style={'height': '70vh'}))
325
+
326
+ layout.append(html.H2(Msg.DETAIL_PAGE_PARALLEL_COOR_HEADER))
327
+ layout.append(html.H4(Msg.DETAIL_PAGE_PARALLEL_COOR_DESCRIPTION))
328
+ for i, obj_name in enumerate(obj_names):
329
+ fig = optuna.visualization.plot_parallel_coordinate(
330
+ study,
331
+ target=lambda t: t.values[i],
332
+ target_name=obj_name
333
+ )
334
+ layout.append(dcc.Graph(figure=fig, style={'height': '70vh'}))
335
+
336
+ layout.append(html.H2(Msg.DETAIL_PAGE_CONTOUR_HEADER))
337
+ layout.append(html.H4(Msg.DETAIL_PAGE_CONTOUR_DESCRIPTION))
338
+ for i, obj_name in enumerate(obj_names):
339
+ fig = optuna.visualization.plot_contour(
340
+ study,
341
+ target=lambda t: t.values[i],
342
+ target_name=obj_name
343
+ )
344
+ layout.append(dcc.Graph(figure=fig, style={'height': '90vh'}))
345
+
346
+ # import itertools
347
+ # for (i, j) in itertools.combinations(range(len(obj_names)), 2):
348
+ # fig = optuna.visualization.plot_pareto_front(
349
+ # study,
350
+ # targets=lambda t: (t.values[i], t.values[j]),
351
+ # target_names=[obj_names[i], obj_names[j]],
352
+ # )
353
+ # self.graphs.append(dcc.Graph(figure=fig, style={'height': '50vh'}))
354
+
355
+ layout.append(html.H2(Msg.DETAIL_PAGE_SLICE_HEADER))
356
+ layout.append(html.H4(Msg.DETAIL_PAGE_SLICE_DESCRIPTION))
357
+ for i, obj_name in enumerate(obj_names):
358
+ fig = optuna.visualization.plot_slice(
359
+ study,
360
+ target=lambda t: t.values[i],
361
+ target_name=obj_name
362
+ )
363
+ layout.append(dcc.Graph(figure=fig, style={'height': '70vh'}))
364
+
365
+ return layout
366
+
367
+ def setup_callback(self):
368
+ app = self.application.app
369
+
370
+ @app.callback(
371
+ Output(self._layout, 'children'),
372
+ Input(self.location, 'pathname'), # on page load
373
+ )
374
+ def update_page(_):
375
+ if self.application.history is None:
376
+ return Msg.ERR_NO_HISTORY_SELECTED
377
+
378
+ if len(self.data_accessor()) == 0:
379
+ return Msg.ERR_NO_FEM_RESULT
380
+
381
+ return self._setup_layout()
382
+
383
+
384
+ def setup_layout(self):
385
+ pass
386
+
387
+ def data_accessor(self) -> pd.DataFrame:
388
+ from pyfemtet.opt.visualization.process_monitor.application import ProcessMonitorApplication
389
+ if isinstance(self.application, ProcessMonitorApplication):
390
+ df = self.application.local_data
391
+ else:
392
+ df = self.application.history.local_data
393
+ return df
@@ -1,5 +1,7 @@
1
1
  from pyfemtet.opt.visualization.base import PyFemtetApplicationBase
2
2
  from pyfemtet.opt.visualization.result_viewer.pages import HomePage, PredictionModelPage
3
+ from pyfemtet.opt.visualization.process_monitor.pages import OptunaVisualizerPage
4
+
3
5
  from pyfemtet.message import Msg
4
6
 
5
7
 
@@ -24,9 +26,11 @@ def debug():
24
26
 
25
27
  g_home_page = HomePage(Msg.PAGE_TITLE_RESULT)
26
28
  g_rsm_page = PredictionModelPage(Msg.PAGE_TITLE_PREDICTION_MODEL, '/prediction-model', g_application)
29
+ g_optuna = OptunaVisualizerPage(Msg.PAGE_TITLE_OPTUNA_VISUALIZATION, '/optuna', g_application)
27
30
 
28
31
  g_application.add_page(g_home_page, 0)
29
32
  g_application.add_page(g_rsm_page, 1)
33
+ g_application.add_page(g_optuna, 2)
30
34
  g_application.setup_callback()
31
35
 
32
36
  g_application.run(debug=True)
@@ -37,9 +41,11 @@ def main():
37
41
 
38
42
  g_home_page = HomePage(Msg.PAGE_TITLE_RESULT)
39
43
  g_rsm_page = PredictionModelPage(Msg.PAGE_TITLE_PREDICTION_MODEL, '/prediction-model', g_application)
44
+ g_optuna = OptunaVisualizerPage(Msg.PAGE_TITLE_OPTUNA_VISUALIZATION, '/optuna', g_application)
40
45
 
41
46
  g_application.add_page(g_home_page, 0)
42
47
  g_application.add_page(g_rsm_page, 1)
48
+ g_application.add_page(g_optuna, 2)
43
49
  g_application.setup_callback()
44
50
 
45
51
  g_application.run()
@@ -281,7 +281,7 @@ class HomePage(AbstractPage):
281
281
  trial = pt['customdata'][0]
282
282
 
283
283
  # get parameter and update model
284
- df = self.application.history.local_data
284
+ df = self.application.history.get_df()
285
285
  row = df[df['trial'] == trial]
286
286
  metadata = np.array(self.application.history.metadata)
287
287
  idx = np.where(metadata == 'prm')[0]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyfemtet
3
- Version: 0.4.20
3
+ Version: 0.4.23
4
4
  Summary: Design parameter optimization using Femtet.
5
5
  Home-page: https://github.com/pyfemtet/pyfemtet
6
6
  License: BSD-3-Clause
@@ -13,8 +13,7 @@ Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Requires-Dist: babel (>=2.15.0,<3.0.0)
16
- Requires-Dist: botorch (>=0.9.5) ; python_version >= "3.12" and python_version < "3.13"
17
- Requires-Dist: botorch (>=0.9.5,<0.10.0) ; python_version < "3.12"
16
+ Requires-Dist: botorch (==0.9.5)
18
17
  Requires-Dist: colorlog (>=6.8.0,<7.0.0)
19
18
  Requires-Dist: dash (>=2.17.0,<3.0.0)
20
19
  Requires-Dist: dash-bootstrap-components (>=1.5.0,<2.0.0)
@@ -28,9 +27,9 @@ Requires-Dist: optuna-integration (>=3.6.0,<4.0.0)
28
27
  Requires-Dist: pandas (>=2.1.3,<3.0.0)
29
28
  Requires-Dist: plotly (>=5.22.0,<6.0.0)
30
29
  Requires-Dist: psutil (>=5.9.6,<6.0.0)
31
- Requires-Dist: pytest-dashboard (>=0.1.2,<0.2.0)
32
30
  Requires-Dist: pywin32 (>=306,<307)
33
31
  Requires-Dist: scipy (>=1.11.4,<2.0.0)
32
+ Requires-Dist: torch (>=2.3.0,<2.4.0)
34
33
  Requires-Dist: tqdm (>=4.66.1,<5.0.0)
35
34
  Project-URL: Repository, https://github.com/pyfemtet/pyfemtet
36
35
  Description-Content-Type: text/markdown