pyfemtet 1.0.5__py3-none-any.whl → 1.0.7__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.

@@ -0,0 +1,556 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, TypeAlias
4
+
5
+ import pandas as pd
6
+ import optuna
7
+
8
+ # dash components
9
+ from pyfemtet.opt.visualization.history_viewer._wrapped_components import dcc, dbc, html
10
+
11
+ # dash callback
12
+ from dash import Output, Input, callback_context, no_update
13
+ from dash.exceptions import PreventUpdate
14
+
15
+ from pyfemtet.logger import get_module_logger
16
+ from pyfemtet._i18n import _
17
+
18
+ from pyfemtet.opt.history import History, MAIN_FILTER
19
+ from pyfemtet.opt.visualization.history_viewer._base_application import AbstractPage
20
+ from pyfemtet.opt.visualization.plotter.parallel_plot_creator import parallel_plot
21
+ from pyfemtet.opt.visualization.plotter.contour_creator import contour_creator
22
+
23
+
24
+ ItemKind: TypeAlias = Literal['prm', 'all_output', 'obj', 'cns', 'other_output']
25
+
26
+
27
+ class SelectablePlot(AbstractPage):
28
+ location: dcc.Location
29
+ graph: dcc.Graph
30
+ input_items: dcc.Checklist | dcc.RadioItems | html.Div
31
+ output_items: dcc.Checklist | dcc.RadioItems | html.Div
32
+ InputItemsClass = dcc.Checklist
33
+ OutputItemsClass = dcc.Checklist
34
+ alerts: html.Div
35
+ input_item_kind: set[ItemKind] = {'prm'}
36
+ output_item_kind: set[ItemKind] = {'all_output'}
37
+ description_markdown: str = ''
38
+
39
+ def __init__(self, title='base-page', rel_url='/', application=None,
40
+ location=None):
41
+ self.location = location
42
+ super().__init__(title, rel_url, application)
43
+
44
+ @property
45
+ def plot_title(self) -> str:
46
+ raise NotImplementedError
47
+
48
+ def setup_layout(self):
49
+
50
+ self.layout = dbc.Container([
51
+ # ----- hidden -----
52
+ dbc.Row([self.location]),
53
+
54
+ # ----- visible -----
55
+ dbc.Row([html.H2(self.plot_title)]),
56
+ dbc.Row([dbc.Col(dcc.Markdown(self.description_markdown))]),
57
+ dbc.Row(
58
+ [
59
+ dbc.Col(dbc.Spinner(self.graph)),
60
+ dbc.Col(
61
+ [
62
+ dbc.Row(html.H3(_('Choices:', '選択肢:'))),
63
+ dbc.Row(html.Hr()),
64
+ dbc.Row(self.input_items),
65
+ dbc.Row(self.output_items),
66
+ ],
67
+ md=2
68
+ ),
69
+ ],
70
+ ),
71
+ dbc.Row([self.alerts]),
72
+ dbc.Row(html.Hr()),
73
+ ])
74
+
75
+ def setup_component(self):
76
+
77
+ if self.location is None:
78
+ self.location = dcc.Location(id='selectable-plot-location', refresh=True)
79
+
80
+ # graph
81
+ self.graph = dcc.Graph(style={'height': '85vh'})
82
+
83
+ # checklist
84
+ self.input_items = self.InputItemsClass(options=[]) if self.InputItemsClass is not None else html.Div()
85
+ self.output_items = self.OutputItemsClass(options=[]) if self.OutputItemsClass is not None else html.Div()
86
+
87
+ # alert
88
+ self.alerts = html.Div()
89
+
90
+ def _check_precondition(self, logger) -> tuple[History, pd.DataFrame, pd.DataFrame]:
91
+
92
+ if callback_context.triggered_id is None:
93
+ logger.debug('PreventUpdate. No trigger.')
94
+ raise PreventUpdate
95
+
96
+ if self.application is None:
97
+ logger.debug('PreventUpdate. No application.')
98
+ raise PreventUpdate
99
+
100
+ if self.application.history is None:
101
+ logger.debug('PreventUpdate. No history.')
102
+ raise PreventUpdate
103
+
104
+ history = self.application.history
105
+
106
+ df = self.application.get_df()
107
+ main_df = self.application.get_df(MAIN_FILTER)
108
+ if len(df) == 0:
109
+ logger.debug('PreventUpdate. No df.')
110
+ raise PreventUpdate
111
+
112
+ return history, df, main_df
113
+
114
+ @staticmethod
115
+ def _return_checklist_options_and_value(history, types: set[Literal]) -> tuple[list[dict], list[str]]:
116
+
117
+ keys = []
118
+
119
+ if 'prm' in types:
120
+ keys.extend(history.prm_names)
121
+
122
+ if 'all_output' in types:
123
+ keys.extend(history.all_output_names)
124
+
125
+ else:
126
+ if 'obj' in types:
127
+ keys.extend(history.obj_names)
128
+ if 'cns' in types:
129
+ keys.extend(history.cns_names)
130
+ if 'other_output' in types:
131
+ keys.extend(history.other_output_names)
132
+
133
+ return [dict(label=key, value=key) for key in keys], keys
134
+
135
+ def _return_input_checklist_options_and_value(self, history):
136
+ return self._return_checklist_options_and_value(history, self.input_item_kind)
137
+
138
+ def _return_output_checklist_options_and_value(self, history):
139
+ return self._return_checklist_options_and_value(history, self.output_item_kind)
140
+
141
+ def setup_update_plot_input_checklist_callback(self):
142
+
143
+ @self.application.app.callback(
144
+ Output(self.input_items, 'options'),
145
+ Output(self.input_items, 'value'),
146
+ Input(self.location, 'pathname'), # on page load
147
+ )
148
+ def update_plot_input_checklist(_):
149
+
150
+ logger_name = f'opt.{type(self).__name__}.update_plot_input_checklist()'
151
+
152
+ logger = get_module_logger(
153
+ logger_name,
154
+ debug=False,
155
+ )
156
+
157
+ logger.debug('callback fired!')
158
+
159
+ # ----- preconditions -----
160
+
161
+ history, _, _ = self._check_precondition(logger)
162
+
163
+ # ----- main -----
164
+ options, value = self._return_input_checklist_options_and_value(history)
165
+
166
+ if isinstance(self.input_items, dcc.RadioItems):
167
+ value = value[0]
168
+
169
+ return options, value
170
+
171
+ def setup_update_plot_output_checklist_callback(self):
172
+
173
+ @self.application.app.callback(
174
+ Output(self.output_items, 'options'),
175
+ Output(self.output_items, 'value'),
176
+ Input(self.location, 'pathname'), # on page load
177
+ )
178
+ def update_plot_output_checklist(_):
179
+
180
+ logger_name = f'opt.{type(self).__name__}.update_plot_output_checklist()'
181
+
182
+ logger = get_module_logger(
183
+ logger_name,
184
+ debug=False,
185
+ )
186
+
187
+ logger.debug('callback fired!')
188
+
189
+ # ----- preconditions -----
190
+
191
+ history, _, _ = self._check_precondition(logger)
192
+
193
+ # ----- main -----
194
+ options, value = self._return_output_checklist_options_and_value(history)
195
+
196
+ logger.debug(value)
197
+ if isinstance(self.output_items, dcc.RadioItems):
198
+ value = value[0]
199
+ logger.debug(value)
200
+
201
+ return options, value
202
+
203
+ def setup_update_plot_graph_callback(self):
204
+
205
+ @self.application.app.callback(
206
+ # graph output
207
+ Output(self.graph, 'figure'),
208
+ Output(self.alerts, 'children'),
209
+ # checklist input
210
+ inputs=dict(
211
+ selected_input_values=Input(self.input_items, 'value'),
212
+ selected_output_values=Input(self.output_items, 'value'),
213
+ ),
214
+ )
215
+ def update_plot_graph(
216
+ selected_input_values: list[str] | str,
217
+ selected_output_values: list[str] | str,
218
+ ):
219
+
220
+ logger_name = f'opt.{type(self).__name__}.update_plot_graph()'
221
+
222
+ logger = get_module_logger(
223
+ logger_name,
224
+ debug=False,
225
+ )
226
+
227
+ logger.debug('callback fired!')
228
+
229
+ # ----- preconditions -----
230
+
231
+ history, df, main_df = self._check_precondition(logger)
232
+
233
+ # null selected values
234
+ if selected_input_values is None:
235
+ logger.debug('No input items.')
236
+ return no_update, [dbc.Alert('No input items.', color='danger')]
237
+
238
+ if selected_output_values is None:
239
+ logger.debug('No output items.')
240
+ return no_update, [dbc.Alert('No output items.', color='danger')]
241
+
242
+ # type correction
243
+ if isinstance(selected_input_values, str):
244
+ selected_input_values = [selected_input_values]
245
+ if isinstance(selected_output_values, str):
246
+ selected_output_values = [selected_output_values]
247
+
248
+ # nothing selected
249
+ # selected_values = selected_input_values + selected_output_values
250
+ # if len(selected_values) == 0:
251
+ # logger.debug('No items are selected.')
252
+ # return no_update, [dbc.Alert('No items are selected.', color='danger')]
253
+ if len(selected_input_values) == 0:
254
+ logger.debug('No input items are selected.')
255
+ return no_update, [dbc.Alert('No input items are selected.', color='danger')]
256
+ if len(selected_output_values) == 0:
257
+ logger.debug('No output items are selected.')
258
+ return no_update, [dbc.Alert('No output items are selected.', color='danger')]
259
+
260
+ # ----- main -----
261
+ used_df = self.make_used_df(history, df, main_df, selected_input_values, selected_output_values)
262
+ assert len(used_df) > 0
263
+ assert len(used_df.columns) > 0
264
+
265
+ fig_or_err = self.create_plot(used_df)
266
+
267
+ if isinstance(fig_or_err, str):
268
+ return no_update, [dbc.Alert(fig_or_err, color='danger')]
269
+
270
+ return fig_or_err, []
271
+
272
+ def setup_callback(self):
273
+ self.setup_update_plot_input_checklist_callback()
274
+ self.setup_update_plot_output_checklist_callback()
275
+ self.setup_update_plot_graph_callback()
276
+
277
+ # noinspection PyUnusedLocal
278
+ @staticmethod
279
+ def make_used_df(history, df, main_df, selected_input_values, selected_output_values):
280
+ # NotImplementedError でもいいが、汎用的なので
281
+
282
+ columns = [
283
+ col for col in history.prm_names + history.all_output_names
284
+ if col in selected_input_values + selected_output_values
285
+ ]
286
+
287
+ use_df = main_df[columns]
288
+
289
+ return use_df
290
+
291
+ @staticmethod
292
+ def create_plot(used_df):
293
+ raise NotImplementedError
294
+
295
+
296
+ class ParallelPlot(SelectablePlot):
297
+
298
+ plot_title = _('parallel coordinate plot', '平行座標プロット')
299
+ description_markdown: str = _(
300
+ en_message='Visualize the relationships between input and output values in multiple dimensions. '
301
+ 'You can intuitively grasp trends and the magnitude of influence between variables for specific output values.\n\n'
302
+ '**Tips: You can rearrange the axes and select ranges.**',
303
+ jp_message='各入力値と出力値の関係を多次元で可視化。'
304
+ '特定の出力値に対する変数間の傾向や影響の大きさを'
305
+ '直観的に把握できます。\n\n'
306
+ '**Tips: 軸は順番を入れ替えることができ、範囲選択することができます。**'
307
+ )
308
+
309
+ @staticmethod
310
+ def create_plot(used_df):
311
+ return parallel_plot(used_df)
312
+
313
+
314
+ class ContourPlot(SelectablePlot):
315
+
316
+ plot_title = _('contour plot', 'コンタープロット')
317
+ OutputItemsClass = dcc.RadioItems
318
+ description_markdown: str = _(
319
+ en_message='Visualize the correlation between input variables and changes in output using contour plots. '
320
+ 'You can identify combinations of variables that have a strong influence.\n\n'
321
+ '**Tips: You can hide the scatter plot.**',
322
+ jp_message='入力変数間の相関と、出力の変化をコンターで可視化。'
323
+ '影響の強い変数の組合せを確認できます。\n\n'
324
+ '**Tips: 点プロットは非表示にできます。**'
325
+ )
326
+
327
+ @staticmethod
328
+ def create_plot(used_df):
329
+ return contour_creator(used_df)
330
+
331
+
332
+ class SelectableOptunaPlot(SelectablePlot):
333
+
334
+ def setup_update_plot_graph_callback(self):
335
+
336
+ @self.application.app.callback(
337
+ # graph output
338
+ Output(self.graph, 'figure'),
339
+ Output(self.alerts, 'children'),
340
+ # checklist input
341
+ inputs=dict(
342
+ selected_input_values=Input(self.input_items, 'value'),
343
+ selected_output_values=Input(self.output_items, 'value'),
344
+ ),
345
+ )
346
+ def update_plot_graph(
347
+ selected_input_values: list[str] | str,
348
+ selected_output_values: list[str] | str,
349
+ ):
350
+
351
+ logger_name = f'opt.{type(self).__name__}.update_plot_graph()'
352
+
353
+ logger = get_module_logger(
354
+ logger_name,
355
+ debug=False,
356
+ )
357
+
358
+ logger.debug('callback fired!')
359
+
360
+ # ----- preconditions -----
361
+
362
+ history, df, main_df = self._check_precondition(logger)
363
+
364
+ # null selected values
365
+ if selected_input_values is None:
366
+ logger.debug('No input items.')
367
+ return no_update, [dbc.Alert('No input items.', color='danger')]
368
+
369
+ if selected_output_values is None:
370
+ logger.debug('No output items.')
371
+ return no_update, [dbc.Alert('No output items.', color='danger')]
372
+
373
+ # type correction
374
+ if isinstance(selected_input_values, str):
375
+ selected_input_values = [selected_input_values]
376
+ if isinstance(selected_output_values, str):
377
+ selected_output_values = [selected_output_values]
378
+
379
+ # nothing selected
380
+ # selected_values = selected_input_values + selected_output_values
381
+ # if len(selected_values) == 0:
382
+ # logger.debug('No items are selected.')
383
+ # return no_update, [dbc.Alert('No items are selected.', color='danger')]
384
+ if len(selected_input_values) == 0:
385
+ logger.debug('No input items are selected.')
386
+ return no_update, [dbc.Alert('No input items are selected.', color='danger')]
387
+ if len(selected_output_values) == 0:
388
+ logger.debug('No output items are selected.')
389
+ return no_update, [dbc.Alert('No output items are selected.', color='danger')]
390
+
391
+ # ----- main -----
392
+ fig = self.create_optuna_plot(
393
+ history._create_optuna_study_for_visualization(),
394
+ selected_input_values,
395
+ selected_output_values,
396
+ [history.all_output_names.index(v) for v in selected_output_values],
397
+ )
398
+
399
+ return fig, []
400
+
401
+ @staticmethod
402
+ def create_optuna_plot(
403
+ study,
404
+ prm_names: list[str],
405
+ obj_name: list[str],
406
+ obj_indices: list[int],
407
+ ):
408
+
409
+ raise NotImplementedError
410
+
411
+
412
+ class SelectableOptunaPlotAllInput(SelectablePlot):
413
+
414
+ InputItemsClass = None
415
+ OutputItemsClass = dcc.RadioItems
416
+
417
+ def setup_update_plot_graph_callback(self):
418
+
419
+ @self.application.app.callback(
420
+ # graph output
421
+ Output(self.graph, 'figure'),
422
+ Output(self.alerts, 'children'),
423
+ # checklist input
424
+ inputs=dict(
425
+ selected_output_value=Input(self.output_items, 'value'),
426
+ ),
427
+ )
428
+ def update_plot_graph(
429
+ selected_output_value: str,
430
+ ):
431
+
432
+ logger_name = f'opt.{type(self).__name__}.update_plot_graph()'
433
+
434
+ logger = get_module_logger(
435
+ logger_name,
436
+ debug=False,
437
+ )
438
+
439
+ logger.debug('callback fired!')
440
+
441
+ # ----- preconditions -----
442
+
443
+ history, df, main_df = self._check_precondition(logger)
444
+
445
+ # null selected values
446
+ if selected_output_value is None:
447
+ logger.debug('No output items.')
448
+ return no_update, [dbc.Alert('No output items.', color='danger')]
449
+
450
+ # ----- main -----
451
+ fig = self.create_optuna_plot(
452
+ history._create_optuna_study_for_visualization(),
453
+ selected_output_value,
454
+ history.all_output_names.index(selected_output_value)
455
+ )
456
+
457
+ return fig, []
458
+
459
+ def setup_callback(self):
460
+ self.setup_update_plot_output_checklist_callback()
461
+ self.setup_update_plot_graph_callback()
462
+
463
+ @staticmethod
464
+ def create_optuna_plot(
465
+ study, obj_name, obj_index,
466
+ ):
467
+
468
+ raise NotImplementedError
469
+
470
+
471
+ class ImportancePlot(SelectableOptunaPlotAllInput):
472
+
473
+ plot_title = _('importance plot', '重要度プロット')
474
+ description_markdown: str = _(
475
+ en_message='Evaluate the importance of each input variable for the output using fANOVA. '
476
+ 'You can quantitatively understand which inputs are important.',
477
+ jp_message='出力に対する各入力変数の重要度を fANOVA で評価。'
478
+ '重要な入力を定量的に把握できます。'
479
+ )
480
+
481
+ @staticmethod
482
+ def create_optuna_plot(
483
+ study, obj_name, obj_index,
484
+ ):
485
+
486
+ # create plot using optuna
487
+ fig = optuna.visualization.plot_param_importances(
488
+ study,
489
+ target=lambda trial: trial.values[obj_index],
490
+ target_name=obj_name
491
+ )
492
+ fig.update_layout(
493
+ title=f'Normalized importance of {obj_name}'
494
+ )
495
+
496
+ return fig
497
+
498
+
499
+ class HistoryPlot(SelectableOptunaPlotAllInput):
500
+
501
+ plot_title = _('optimization history plot', '最適化履歴プロット')
502
+ description_markdown: str = _(
503
+ en_message='Display the history of outputs generated during optimization. '
504
+ 'You can check the progress of improvements and the variability of the search.',
505
+ jp_message='最適化中に生成された出力の履歴を表示。'
506
+ '改善の進行や探索のばらつきを確認できます。'
507
+ )
508
+
509
+ @staticmethod
510
+ def create_optuna_plot(
511
+ study, obj_name, obj_index,
512
+ ):
513
+
514
+ # create plot using optuna
515
+ fig = optuna.visualization.plot_optimization_history(
516
+ study,
517
+ target=lambda trial: trial.values[obj_index],
518
+ target_name=obj_name
519
+ )
520
+ fig.update_layout(
521
+ title=f'Optimization history of {obj_name}'
522
+ )
523
+
524
+ return fig
525
+
526
+
527
+ class SlicePlot(SelectableOptunaPlot):
528
+
529
+ plot_title = _('slice plot', 'スライスプロット')
530
+ OutputItemsClass = dcc.RadioItems
531
+ description_markdown: str = _(
532
+ en_message='Displays the output response to a specific input. '
533
+ 'You can intuitively see the univariate effect, ignoring other variables.',
534
+ jp_message='特定の入力に対する出力の応答を表示。'
535
+ '他変数を無視した単変量の影響を'
536
+ '直観的に確認できます。'
537
+ )
538
+
539
+ @staticmethod
540
+ def create_optuna_plot(
541
+ study,
542
+ prm_names: list[str],
543
+ obj_names: list[str],
544
+ obj_indices: list[int],
545
+ ):
546
+
547
+ assert len(obj_names) == len(obj_indices) == 1
548
+
549
+ fig = optuna.visualization.plot_slice(
550
+ study,
551
+ params=prm_names,
552
+ target=lambda trial: trial.values[obj_indices[0]],
553
+ target_name=obj_names[0],
554
+ )
555
+
556
+ return fig
@@ -0,0 +1,106 @@
1
+ from __future__ import annotations
2
+
3
+ # dash components
4
+ from pyfemtet.opt.visualization.history_viewer._wrapped_components import dcc, dbc, html
5
+
6
+ # dash callback
7
+ from dash import Output, Input, callback_context
8
+ from dash.exceptions import PreventUpdate
9
+
10
+ from pyfemtet.logger import get_module_logger
11
+
12
+ from pyfemtet.opt.history import MAIN_FILTER
13
+ from pyfemtet.opt.visualization.history_viewer._base_application import AbstractPage
14
+ from pyfemtet.opt.visualization.history_viewer._complex_components.detail_graphs import (
15
+ ParallelPlot,
16
+ ContourPlot,
17
+ ImportancePlot,
18
+ SlicePlot,
19
+ HistoryPlot,
20
+ )
21
+
22
+
23
+ class DetailPage(AbstractPage):
24
+ location: dcc.Location
25
+ alerts: html.Div
26
+ parallel_plot: ParallelPlot
27
+ contour_plot: ContourPlot
28
+ importance_plot: ImportancePlot
29
+ slice_plot: SlicePlot
30
+ history_plot: HistoryPlot
31
+
32
+ def setup_component(self):
33
+
34
+ self.location = dcc.Location(id='new-detail-location', refresh=True)
35
+
36
+ # alerts
37
+ self.alerts = html.Div(id='new-detail-alerts')
38
+
39
+ # graphs
40
+ self.parallel_plot = ParallelPlot(location=self.location)
41
+ self.add_subpage(self.parallel_plot)
42
+ self.contour_plot = ContourPlot(location=self.location)
43
+ self.add_subpage(self.contour_plot)
44
+ self.importance_plot = ImportancePlot(location=self.location)
45
+ self.add_subpage(self.importance_plot)
46
+ self.slice_plot = SlicePlot(location=self.location)
47
+ self.add_subpage(self.slice_plot)
48
+ self.history_plot = HistoryPlot(location=self.location)
49
+ self.add_subpage(self.history_plot)
50
+
51
+ def setup_layout(self):
52
+
53
+ # title
54
+ title = html.H1('Detail Plot Graphs')
55
+
56
+ # layout
57
+ self.layout = dbc.Container([
58
+ dbc.Row([self.location]),
59
+ dbc.Row([title]),
60
+ dbc.Row([html.Hr()]),
61
+ dbc.Row([self.alerts]),
62
+ dbc.Row([self.importance_plot.layout]),
63
+ dbc.Row([self.history_plot.layout]),
64
+ dbc.Row([self.slice_plot.layout]),
65
+ dbc.Row([self.contour_plot.layout]),
66
+ dbc.Row([self.parallel_plot.layout]),
67
+ ])
68
+
69
+ def setup_callback(self):
70
+ super().setup_callback() # setup callback of subpages
71
+
72
+ app = self.application.app
73
+
74
+ # ===== update alert =====
75
+ @app.callback(
76
+ Output(self.alerts.id, 'children'),
77
+ Input(self.location.id, 'pathname'), # on page load
78
+ )
79
+ def update_alerts_new_detail(_):
80
+
81
+ logger = get_module_logger(
82
+ 'opt.update_alerts_new_detail',
83
+ debug=False,
84
+ )
85
+
86
+ # ----- preconditions -----
87
+
88
+ if callback_context.triggered_id is None:
89
+ logger.debug('PreventUpdate. No trigger.')
90
+ raise PreventUpdate
91
+
92
+ if self.application is None:
93
+ logger.debug('PreventUpdate. No application.')
94
+ raise PreventUpdate
95
+
96
+ if self.application.history is None:
97
+ logger.debug('PreventUpdate. No history.')
98
+ return [dbc.Alert('No history.', color='danger')]
99
+
100
+ # df = self.application.get_df()
101
+ main_df = self.application.get_df(MAIN_FILTER)
102
+ if len(main_df) == 0:
103
+ logger.debug('PreventUpdate. No df.')
104
+ return [dbc.Alert('No data.', color='danger')]
105
+
106
+ return []
@@ -11,6 +11,7 @@ from pyfemtet.opt.worker_status import *
11
11
  from pyfemtet.opt.visualization.history_viewer._base_application import *
12
12
  from pyfemtet.opt.visualization.history_viewer._common_pages import *
13
13
  from pyfemtet.opt.visualization.history_viewer._process_monitor._pages import *
14
+ from pyfemtet.opt.visualization.history_viewer._detail_page import DetailPage
14
15
 
15
16
  from pyfemtet._i18n import Msg
16
17
 
@@ -159,12 +160,12 @@ def process_monitor_main(history, status, worker_addresses, worker_names, worker
159
160
 
160
161
  g_home_page = HomePage(Msg.PAGE_TITLE_PROGRESS, '/', g_application)
161
162
  g_rsm_page = PredictionModelPage(Msg.PAGE_TITLE_PREDICTION_MODEL, '/prediction-model', g_application)
162
- g_optuna = OptunaVisualizerPage(Msg.PAGE_TITLE_OPTUNA_VISUALIZATION, '/optuna', g_application)
163
163
  g_worker_page = WorkerPage(Msg.PAGE_TITLE_WORKERS, '/workers', g_application)
164
+ g_detail = DetailPage(Msg.PAGE_TITLE_OPTUNA_VISUALIZATION, '/detail', g_application)
164
165
 
165
166
  g_application.add_page(g_home_page, 0)
166
167
  g_application.add_page(g_rsm_page, 1)
167
- g_application.add_page(g_optuna, 2)
168
+ g_application.add_page(g_detail, 2)
168
169
  g_application.add_page(g_worker_page, 3)
169
170
  g_application.setup_callback()
170
171
 
@@ -1,6 +1,7 @@
1
1
  from pyfemtet.opt.visualization.history_viewer._base_application import *
2
2
  from pyfemtet.opt.visualization.history_viewer._common_pages import *
3
3
  from pyfemtet.opt.visualization.history_viewer.result_viewer._pages import *
4
+ from pyfemtet.opt.visualization.history_viewer._detail_page import DetailPage
4
5
 
5
6
  from pyfemtet._i18n import Msg
6
7
 
@@ -41,11 +42,11 @@ def result_viewer_main():
41
42
 
42
43
  g_home_page = HomePage(Msg.PAGE_TITLE_RESULT)
43
44
  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)
45
+ g_detail = DetailPage(Msg.PAGE_TITLE_OPTUNA_VISUALIZATION, '/detail', g_application)
45
46
 
46
47
  g_application.add_page(g_home_page, 0)
47
48
  g_application.add_page(g_rsm_page, 1)
48
- g_application.add_page(g_optuna, 2)
49
+ g_application.add_page(g_detail, 2)
49
50
  g_application.setup_callback()
50
51
 
51
52
  g_application.run()