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