pyfemtet 0.4.10__py3-none-any.whl → 0.4.12__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/__init__.py +1 -1
- pyfemtet/_test_util.py +26 -0
- pyfemtet/opt/__init__.py +1 -1
- pyfemtet/opt/_femopt.py +41 -5
- pyfemtet/opt/interface/_base.py +6 -1
- pyfemtet/opt/interface/_femtet.py +32 -10
- pyfemtet/opt/interface/_femtet_parametric.py +5 -25
- pyfemtet/opt/opt/__init__.py +3 -1
- pyfemtet/opt/opt/_base.py +88 -8
- pyfemtet/opt/opt/_optuna.py +17 -2
- pyfemtet/opt/opt/_scipy.py +144 -0
- pyfemtet/opt/opt/_scipy_scalar.py +104 -0
- pyfemtet/opt/visualization/__init__.py +0 -7
- pyfemtet/opt/visualization/_create_wrapped_components.py +93 -0
- pyfemtet/opt/visualization/base.py +254 -0
- pyfemtet/opt/visualization/complex_components/__init__.py +0 -0
- pyfemtet/opt/visualization/complex_components/alert_region.py +71 -0
- pyfemtet/opt/visualization/complex_components/control_femtet.py +195 -0
- pyfemtet/opt/visualization/{_graphs.py → complex_components/main_figure_creator.py} +13 -49
- pyfemtet/opt/visualization/complex_components/main_graph.py +263 -0
- pyfemtet/opt/visualization/process_monitor/__init__.py +0 -0
- pyfemtet/opt/visualization/process_monitor/application.py +201 -0
- pyfemtet/opt/visualization/process_monitor/pages.py +276 -0
- pyfemtet/opt/visualization/result_viewer/.gitignore +1 -0
- pyfemtet/opt/visualization/result_viewer/__init__.py +0 -0
- pyfemtet/opt/visualization/result_viewer/application.py +44 -0
- pyfemtet/opt/visualization/result_viewer/pages.py +692 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/tutorial.csv +18 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.log +81 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow.csv +28 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow_el.csv +22 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.femprj +0 -0
- pyfemtet/opt/visualization/wrapped_components/__init__.py +0 -0
- pyfemtet/opt/visualization/wrapped_components/dbc.py +1518 -0
- pyfemtet/opt/visualization/wrapped_components/dcc.py +609 -0
- pyfemtet/opt/visualization/wrapped_components/html.py +3570 -0
- pyfemtet/opt/visualization/wrapped_components/str_enum.py +43 -0
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/METADATA +1 -1
- pyfemtet-0.4.12.dist-info/RECORD +138 -0
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/entry_points.txt +1 -1
- pyfemtet/opt/visualization/_monitor.py +0 -1227
- pyfemtet/opt/visualization/result_viewer.py +0 -13
- pyfemtet-0.4.10.dist-info/RECORD +0 -83
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/LICENSE +0 -0
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# type hint
|
|
2
|
+
from dash.development.base_component import Component
|
|
3
|
+
|
|
4
|
+
# callback
|
|
5
|
+
from dash import Output, Input, State, no_update, callback_context
|
|
6
|
+
from dash.exceptions import PreventUpdate
|
|
7
|
+
|
|
8
|
+
# components
|
|
9
|
+
from pyfemtet.opt.visualization.wrapped_components import html, dcc, dbc
|
|
10
|
+
|
|
11
|
+
# the others
|
|
12
|
+
import logging
|
|
13
|
+
import os
|
|
14
|
+
from enum import Enum
|
|
15
|
+
import json
|
|
16
|
+
# noinspection PyUnresolvedReferences
|
|
17
|
+
from pythoncom import com_error
|
|
18
|
+
|
|
19
|
+
from pyfemtet.opt.visualization.base import PyFemtetApplicationBase, AbstractPage, logger
|
|
20
|
+
from pyfemtet.opt.interface._femtet import FemtetInterface
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FemtetState(Enum):
|
|
24
|
+
unconnected = -1
|
|
25
|
+
missing = 0
|
|
26
|
+
connected_but_empty = 1
|
|
27
|
+
connected = 2
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FemtetControl(AbstractPage):
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
super().__init__()
|
|
34
|
+
self.fem: FemtetInterface = None
|
|
35
|
+
|
|
36
|
+
def setup_component(self):
|
|
37
|
+
|
|
38
|
+
# noinspection PyAttributeOutsideInit
|
|
39
|
+
self.dummy = html.Div(
|
|
40
|
+
id='control-femtet-dummy',
|
|
41
|
+
hidden=True,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# button
|
|
45
|
+
# noinspection PyAttributeOutsideInit
|
|
46
|
+
self.connect_femtet_button = dbc.Button(
|
|
47
|
+
children='Connect to Femtet',
|
|
48
|
+
id='connect-femtet-button',
|
|
49
|
+
outline=True,
|
|
50
|
+
color='primary',
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# noinspection PyAttributeOutsideInit
|
|
54
|
+
self.connect_femtet_button_spinner = dbc.Spinner(
|
|
55
|
+
self.connect_femtet_button,
|
|
56
|
+
color='primary',
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# store femtet connection state
|
|
60
|
+
# noinspection PyAttributeOutsideInit
|
|
61
|
+
self.femtet_state_prop = 'data-femtet-state' # must start with "data-"
|
|
62
|
+
# noinspection PyAttributeOutsideInit
|
|
63
|
+
self.femtet_state = html.Data(
|
|
64
|
+
id='femtet-state',
|
|
65
|
+
**{
|
|
66
|
+
self.femtet_state_prop: {
|
|
67
|
+
'pid': 0,
|
|
68
|
+
'alert': '',
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def setup_layout(self):
|
|
74
|
+
self.layout = [self.dummy, self.femtet_state]
|
|
75
|
+
|
|
76
|
+
def setup_callback(self):
|
|
77
|
+
# setup callback of subpages
|
|
78
|
+
super().setup_callback()
|
|
79
|
+
|
|
80
|
+
app = self.application.app
|
|
81
|
+
|
|
82
|
+
# ===== launch femtet and show spinner =====
|
|
83
|
+
@app.callback(
|
|
84
|
+
Output(self.femtet_state.id, self.femtet_state_prop),
|
|
85
|
+
Output(self.connect_femtet_button.id, self.connect_femtet_button.Prop.children), # for spinner
|
|
86
|
+
Input(self.connect_femtet_button.id, self.connect_femtet_button.Prop.n_clicks),)
|
|
87
|
+
def connect_femtet(_):
|
|
88
|
+
|
|
89
|
+
# ignore when loaded
|
|
90
|
+
if callback_context.triggered_id is None:
|
|
91
|
+
raise PreventUpdate
|
|
92
|
+
|
|
93
|
+
# create FemtetInterface arguments
|
|
94
|
+
kwargs, warning_msg = self.create_femtet_interface_args()
|
|
95
|
+
|
|
96
|
+
# try to launch femtet
|
|
97
|
+
try:
|
|
98
|
+
self.fem = FemtetInterface(**kwargs)
|
|
99
|
+
|
|
100
|
+
except com_error:
|
|
101
|
+
# model_name is not included in femprj
|
|
102
|
+
kwargs.update(dict(model_name=None))
|
|
103
|
+
self.fem = FemtetInterface(**kwargs)
|
|
104
|
+
warning_msg = 'Analysis model name described in csv does not exist in project.'
|
|
105
|
+
|
|
106
|
+
# if 'new' femtet, Interface try to terminate Femtet when the self.fem is reconstructed.
|
|
107
|
+
self.fem.quit_when_destruct = False
|
|
108
|
+
|
|
109
|
+
state_data = {
|
|
110
|
+
'pid': self.fem.femtet_pid,
|
|
111
|
+
'alert': warning_msg,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return state_data, no_update
|
|
115
|
+
|
|
116
|
+
def create_femtet_interface_args(self) -> [dict, str]:
|
|
117
|
+
"""Returns the argument of FemtetInterface and warning message
|
|
118
|
+
|
|
119
|
+
# femprj_path=None,
|
|
120
|
+
# model_name=None,
|
|
121
|
+
# connect_method='auto', # dask worker では __init__ の中で 'new' にするので super() の引数にしない。(しても意味がない)
|
|
122
|
+
# save_pdt='all', # 'all' or None
|
|
123
|
+
# strictly_pid_specify=True, # dask worker では True にしたいので super() の引数にしない。
|
|
124
|
+
# allow_without_project=False, # main でのみ True を許容したいので super() の引数にしない。
|
|
125
|
+
# open_result_with_gui=True,
|
|
126
|
+
# parametric_output_indexes_use_as_objective=None,
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
kwargs = dict(
|
|
131
|
+
connect_method='auto',
|
|
132
|
+
femprj_path=None,
|
|
133
|
+
model_name=None,
|
|
134
|
+
allow_without_project=True,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# check holding history
|
|
138
|
+
if self.application.history is None:
|
|
139
|
+
return kwargs, 'History csv is not read yet. Open your project manually.'
|
|
140
|
+
|
|
141
|
+
# get metadata
|
|
142
|
+
additional_metadata = self.application.history.metadata[0]
|
|
143
|
+
|
|
144
|
+
# check metadata exists
|
|
145
|
+
if additional_metadata == '':
|
|
146
|
+
return kwargs, 'Cannot read project data from csv. Open your project manually.'
|
|
147
|
+
|
|
148
|
+
# check the metadata is valid json
|
|
149
|
+
try:
|
|
150
|
+
d = json.loads(additional_metadata)
|
|
151
|
+
femprj_path = os.path.abspath(d['femprj_path'])
|
|
152
|
+
except (TypeError, json.decoder.JSONDecodeError, KeyError):
|
|
153
|
+
return kwargs, ('Cannot read project data from csv because of the invalid format. '
|
|
154
|
+
'Valid format is like: '
|
|
155
|
+
'"{""femprj_path"": ""c:\\path\\to\\sample.femprj"", ""model_name"": ""<model_name>""}". '
|
|
156
|
+
'Open your project manually.')
|
|
157
|
+
|
|
158
|
+
# check containing femprj
|
|
159
|
+
if femprj_path is None:
|
|
160
|
+
return kwargs, 'Cannot read project data from csv. Open your project manually.'
|
|
161
|
+
|
|
162
|
+
# check femprj exists
|
|
163
|
+
if not os.path.exists(femprj_path):
|
|
164
|
+
return kwargs, '.femprj file described in csv is not found. Open your project manually.'
|
|
165
|
+
|
|
166
|
+
# at this point, femprj is valid at least.
|
|
167
|
+
kwargs.update({'femprj_path': femprj_path})
|
|
168
|
+
|
|
169
|
+
# check model name
|
|
170
|
+
model_name = d['model_name'] if 'model_name' in d.keys() else None
|
|
171
|
+
msg = '' if model_name is not None else 'Analysis model name is not specified. Open your model in the project manually.'
|
|
172
|
+
kwargs.update({'model_name': model_name})
|
|
173
|
+
return kwargs, msg
|
|
174
|
+
|
|
175
|
+
def check_femtet_state(self) -> FemtetState:
|
|
176
|
+
|
|
177
|
+
# check fem is initialized
|
|
178
|
+
if self.fem is None:
|
|
179
|
+
return FemtetState.missing
|
|
180
|
+
|
|
181
|
+
Femtet = self.fem.Femtet
|
|
182
|
+
|
|
183
|
+
# check Femtet is constructed
|
|
184
|
+
if Femtet is None:
|
|
185
|
+
return FemtetState.missing
|
|
186
|
+
|
|
187
|
+
# check Femtet is alive
|
|
188
|
+
if not self.fem.femtet_is_alive():
|
|
189
|
+
return FemtetState.missing
|
|
190
|
+
|
|
191
|
+
# check a project is opened
|
|
192
|
+
if Femtet.Project == '':
|
|
193
|
+
return FemtetState.connected_but_empty
|
|
194
|
+
|
|
195
|
+
return FemtetState.connected
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import plotly.graph_objs as go
|
|
2
2
|
import plotly.express as px
|
|
3
3
|
|
|
4
|
+
from pyfemtet.opt._femopt_core import History
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
class _ColorSet:
|
|
6
8
|
non_domi = {True: '#007bff', False: '#6c757d'} # color
|
|
@@ -37,7 +39,7 @@ _cs = _ColorSet()
|
|
|
37
39
|
_ss = _SymbolSet()
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
def
|
|
42
|
+
def get_hypervolume_plot(_: History, df):
|
|
41
43
|
df = _ls.localize(df)
|
|
42
44
|
|
|
43
45
|
# create figure
|
|
@@ -58,26 +60,28 @@ def update_hypervolume_plot(history, df):
|
|
|
58
60
|
return fig
|
|
59
61
|
|
|
60
62
|
|
|
61
|
-
def
|
|
63
|
+
def get_default_figure(history, df):
|
|
64
|
+
# df = history.local_data # monitor process and history process is different workers, so history.local_data is not updated in monitor process.
|
|
65
|
+
# df = history.actor_data.copy() # access to actor from flask callback makes termination unstable.
|
|
62
66
|
|
|
63
67
|
# data setting
|
|
64
68
|
obj_names = history.obj_names
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
fig = go.Figure()
|
|
70
|
+
fig = go.Figure()
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
fig =
|
|
72
|
+
if len(obj_names) == 1:
|
|
73
|
+
fig = _get_single_objective_plot(history, df)
|
|
71
74
|
|
|
72
75
|
elif len(obj_names) >= 2:
|
|
73
|
-
fig =
|
|
76
|
+
fig = _get_multi_objective_pairplot(history, df)
|
|
74
77
|
|
|
75
78
|
fig.update_traces(hoverinfo="none", hovertemplate=None)
|
|
79
|
+
fig.update_layout(clickmode='event+select')
|
|
76
80
|
|
|
77
81
|
return fig
|
|
78
82
|
|
|
79
83
|
|
|
80
|
-
def
|
|
84
|
+
def _get_single_objective_plot(history, df):
|
|
81
85
|
|
|
82
86
|
df = _ls.localize(df)
|
|
83
87
|
obj_name = history.obj_names[0]
|
|
@@ -125,7 +129,7 @@ def update_single_objective_plot(history, df):
|
|
|
125
129
|
return fig
|
|
126
130
|
|
|
127
131
|
|
|
128
|
-
def
|
|
132
|
+
def _get_multi_objective_pairplot(history, df):
|
|
129
133
|
df = _ls.localize(df)
|
|
130
134
|
|
|
131
135
|
obj_names = history.obj_names
|
|
@@ -183,43 +187,3 @@ def update_multi_objective_pairplot(history, df):
|
|
|
183
187
|
)
|
|
184
188
|
|
|
185
189
|
return fig
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
def _debug():
|
|
189
|
-
import os
|
|
190
|
-
|
|
191
|
-
os.chdir(os.path.dirname(__file__))
|
|
192
|
-
csv_path = 'sample.csv'
|
|
193
|
-
|
|
194
|
-
show_static_monitor(csv_path)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def show_static_monitor(csv_path):
|
|
198
|
-
from pyfemtet.opt._femopt_core import History
|
|
199
|
-
from pyfemtet.opt.visualization._monitor import ResultViewerApp
|
|
200
|
-
_h = History(history_path=csv_path)
|
|
201
|
-
_monitor = ResultViewerApp(history=_h)
|
|
202
|
-
_monitor.run()
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def entry_point():
|
|
206
|
-
import argparse
|
|
207
|
-
parser = argparse.ArgumentParser()
|
|
208
|
-
|
|
209
|
-
parser.add_argument('csv_path', help='pyfemtet を実行した結果の csv ファイルのパスを指定してください。', type=str)
|
|
210
|
-
|
|
211
|
-
# parser.add_argument(
|
|
212
|
-
# "-c",
|
|
213
|
-
# "--csv-path",
|
|
214
|
-
# help="pyfemtet.opt による最適化結果 csv ファイルパス",
|
|
215
|
-
# type=str,
|
|
216
|
-
# )
|
|
217
|
-
|
|
218
|
-
args = parser.parse_args()
|
|
219
|
-
|
|
220
|
-
if args.csv_path:
|
|
221
|
-
show_static_monitor(args.csv_path)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if __name__ == '__main__':
|
|
225
|
-
_debug()
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# type hint
|
|
2
|
+
from dash.development.base_component import Component
|
|
3
|
+
|
|
4
|
+
# callback
|
|
5
|
+
from dash import Output, Input, State, no_update, callback_context
|
|
6
|
+
from dash.exceptions import PreventUpdate
|
|
7
|
+
|
|
8
|
+
# components
|
|
9
|
+
from dash import dash_table
|
|
10
|
+
from pyfemtet.opt.visualization.wrapped_components import html, dcc, dbc
|
|
11
|
+
|
|
12
|
+
# graph
|
|
13
|
+
import pandas as pd
|
|
14
|
+
# import plotly.express as px
|
|
15
|
+
import plotly.graph_objs as go
|
|
16
|
+
|
|
17
|
+
# the others
|
|
18
|
+
import os
|
|
19
|
+
import base64
|
|
20
|
+
import json
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from pyfemtet.opt.visualization.complex_components import main_figure_creator
|
|
24
|
+
from pyfemtet.opt.visualization.base import PyFemtetApplicationBase, AbstractPage, logger
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
FLEXBOX_STYLE_ALLOW_VERTICAL_FILL = {
|
|
28
|
+
'display': 'flex',
|
|
29
|
+
'flex-direction': 'column',
|
|
30
|
+
'flex-grow': '1',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# noinspection PyAttributeOutsideInit
|
|
35
|
+
class MainGraph(AbstractPage):
|
|
36
|
+
""""""
|
|
37
|
+
"""
|
|
38
|
+
+=================+
|
|
39
|
+
|tab1|tab2| | <- CardHeader
|
|
40
|
+
| +------------+
|
|
41
|
+
| +-------------+ |
|
|
42
|
+
| | Loading | | <- CardBody
|
|
43
|
+
| | +---------+ | |
|
|
44
|
+
| | | graph <-------- ToolTip
|
|
45
|
+
| | +---------+ | |
|
|
46
|
+
| +-------------+ |
|
|
47
|
+
+=================+
|
|
48
|
+
|
|
49
|
+
Data(data-selection)
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self):
|
|
54
|
+
self.setup_figure_creator()
|
|
55
|
+
super().__init__()
|
|
56
|
+
|
|
57
|
+
def setup_figure_creator(self):
|
|
58
|
+
# setup figure creators
|
|
59
|
+
# list[0] is the default tab
|
|
60
|
+
self.figure_creators = [
|
|
61
|
+
dict(
|
|
62
|
+
tab_id='tab-objective-plot',
|
|
63
|
+
label='objectives',
|
|
64
|
+
creator=main_figure_creator.get_default_figure,
|
|
65
|
+
),
|
|
66
|
+
dict(
|
|
67
|
+
tab_id='tab-hypervolume-plot',
|
|
68
|
+
label='hypervolume',
|
|
69
|
+
creator=main_figure_creator.get_hypervolume_plot,
|
|
70
|
+
),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
def setup_component(self):
|
|
74
|
+
self.dummy = html.Div(id='main-graph-dummy')
|
|
75
|
+
self.location = dcc.Location(id='main-graph-location', refresh=True)
|
|
76
|
+
|
|
77
|
+
# setup header
|
|
78
|
+
self.tab_list = [dbc.Tab(label=d['label'], tab_id=d['tab_id']) for d in self.figure_creators]
|
|
79
|
+
self.tabs = dbc.Tabs(self.tab_list, id='main-graph-tabs')
|
|
80
|
+
self.card_header = dbc.CardHeader(self.tabs)
|
|
81
|
+
|
|
82
|
+
# setup body
|
|
83
|
+
self.tooltip = dcc.Tooltip(id='main-graph-tooltip')
|
|
84
|
+
|
|
85
|
+
# set kwargs of Graph to reconstruct Graph in ProcessMonitor.
|
|
86
|
+
self.graph_kwargs = dict(
|
|
87
|
+
id='main-graph',
|
|
88
|
+
# animate=True, # THIS CAUSE THE UPDATED DATA / NOT-UPDATED FIGURE STATE.
|
|
89
|
+
clear_on_unhover=True,
|
|
90
|
+
style={
|
|
91
|
+
# 'flex-grow': '1', # Uncomment if the plotly's specification if fixed, and we can use dcc.Graph with FlexBox.
|
|
92
|
+
'height': '60vh',
|
|
93
|
+
},
|
|
94
|
+
figure=go.Figure()
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
self.graph: dcc.Graph = dcc.Graph(
|
|
98
|
+
**self.graph_kwargs
|
|
99
|
+
) # Graph make an element by js, so Flexbox CSS cannot apply to the graph (The element can be bigger, but cannot be smaller.)
|
|
100
|
+
|
|
101
|
+
self.loading = dcc.Loading(
|
|
102
|
+
children=self.graph,
|
|
103
|
+
id='loading-main-graph',
|
|
104
|
+
) # Loading make an element that doesn't contain a style attribute, so Flexbox CSS cannot apply to the graph
|
|
105
|
+
|
|
106
|
+
self.card_body = dbc.CardBody(
|
|
107
|
+
children=html.Div([self.loading, self.tooltip]), # If list is passed to CardBody's children, create element that doesn't contain a style attribute, so Flexbox CSS cannot apply to graph
|
|
108
|
+
id='main-graph-card-body',
|
|
109
|
+
# style=FLEXBOX_STYLE_ALLOW_VERTICAL_FILL,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# setup selection data
|
|
113
|
+
self.selection_data_property = 'data-selection' # must be starts with "data-"
|
|
114
|
+
self.selection_data = html.Data(id='selection-data', **{self.selection_data_property: {}})
|
|
115
|
+
|
|
116
|
+
# set data length
|
|
117
|
+
self.data_length_prop = 'data-df-length' # must start with "data-"
|
|
118
|
+
self.data_length = html.Data(id='df-length-data', **{self.data_length_prop: None})
|
|
119
|
+
|
|
120
|
+
def setup_layout(self):
|
|
121
|
+
# setup component
|
|
122
|
+
self.graph_card = dbc.Card(
|
|
123
|
+
[
|
|
124
|
+
self.dummy,
|
|
125
|
+
self.location,
|
|
126
|
+
self.data_length,
|
|
127
|
+
self.card_header,
|
|
128
|
+
self.card_body,
|
|
129
|
+
self.selection_data,
|
|
130
|
+
],
|
|
131
|
+
# style=FLEXBOX_STYLE_ALLOW_VERTICAL_FILL,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
self.layout = self.graph_card
|
|
135
|
+
|
|
136
|
+
def setup_callback(self):
|
|
137
|
+
# setup callback of subpages
|
|
138
|
+
super().setup_callback()
|
|
139
|
+
|
|
140
|
+
app = self.application.app
|
|
141
|
+
|
|
142
|
+
# ===== Update Graph =====
|
|
143
|
+
@app.callback(
|
|
144
|
+
Output(self.graph.id, 'figure'),
|
|
145
|
+
Output(self.data_length.id, self.data_length_prop), # To determine whether Process Monitor should update the graph, the main graph remembers the current amount of data.
|
|
146
|
+
Input(self.tabs.id, 'active_tab'),
|
|
147
|
+
Input(self.dummy, 'children'),
|
|
148
|
+
prevent_initial_call=False,)
|
|
149
|
+
def redraw_main_graph(active_tab_id, _):
|
|
150
|
+
logger.debug('====================')
|
|
151
|
+
logger.debug(f'redraw_main_graph called by {callback_context.triggered_id}')
|
|
152
|
+
figure, length = self.get_fig_by_tab_id(active_tab_id, with_length=True)
|
|
153
|
+
return figure, length
|
|
154
|
+
|
|
155
|
+
# ===== Save Selection =====
|
|
156
|
+
@app.callback(
|
|
157
|
+
Output(self.selection_data.id, self.selection_data_property),
|
|
158
|
+
Input(self.graph.id, 'selectedData'))
|
|
159
|
+
def save_selection(data):
|
|
160
|
+
return data
|
|
161
|
+
|
|
162
|
+
# ===== Show Image and Parameter on Hover =====
|
|
163
|
+
@app.callback(
|
|
164
|
+
Output(self.tooltip.id, "show"),
|
|
165
|
+
Output(self.tooltip.id, "bbox"),
|
|
166
|
+
Output(self.tooltip.id, "children"),
|
|
167
|
+
Input(self.graph.id, "hoverData"),)
|
|
168
|
+
def show_hover(hoverData):
|
|
169
|
+
if hoverData is None:
|
|
170
|
+
return False, no_update, no_update
|
|
171
|
+
|
|
172
|
+
if self.application.history is None:
|
|
173
|
+
raise PreventUpdate
|
|
174
|
+
|
|
175
|
+
# get hover location
|
|
176
|
+
pt = hoverData["points"][0]
|
|
177
|
+
bbox = pt["bbox"]
|
|
178
|
+
|
|
179
|
+
# get row of the history from customdata defined in main_figure
|
|
180
|
+
trial = pt['customdata'][0]
|
|
181
|
+
|
|
182
|
+
df = self.data_accessor()
|
|
183
|
+
row = df[df['trial'] == trial]
|
|
184
|
+
|
|
185
|
+
# create component
|
|
186
|
+
title_component = html.H3(f"trial{trial}", style={"color": "darkblue"})
|
|
187
|
+
img_component = self.create_image_content_if_femtet(trial)
|
|
188
|
+
tbl_component = self.create_formatted_parameter(row)
|
|
189
|
+
|
|
190
|
+
# create layout
|
|
191
|
+
description = html.Div([
|
|
192
|
+
title_component,
|
|
193
|
+
tbl_component,
|
|
194
|
+
])
|
|
195
|
+
tooltip_layout = html.Div([
|
|
196
|
+
html.Div(img_component, style={'display': 'inline-block', 'margin-right': '10px', 'vertical-align': 'top'}),
|
|
197
|
+
html.Div(description, style={'display': 'inline-block', 'margin-right': '10px'})
|
|
198
|
+
])
|
|
199
|
+
|
|
200
|
+
return True, bbox, tooltip_layout
|
|
201
|
+
|
|
202
|
+
def create_formatted_parameter(self, row) -> Component:
|
|
203
|
+
metadata = self.application.history.metadata
|
|
204
|
+
pd.options.display.float_format = '{:.4e}'.format
|
|
205
|
+
parameters = row.iloc[:, np.where(np.array(metadata) == 'prm')[0]]
|
|
206
|
+
names = parameters.columns
|
|
207
|
+
values = [f'{value:.3e}' for value in parameters.values.ravel()]
|
|
208
|
+
data = pd.DataFrame(dict(
|
|
209
|
+
name=names, value=values
|
|
210
|
+
))
|
|
211
|
+
table = dash_table.DataTable(
|
|
212
|
+
columns=[{'name': col, 'id': col} for col in data.columns],
|
|
213
|
+
data=data.to_dict('records')
|
|
214
|
+
)
|
|
215
|
+
return table
|
|
216
|
+
|
|
217
|
+
def create_image_content_if_femtet(self, trial) -> Component:
|
|
218
|
+
img_url = None
|
|
219
|
+
metadata = self.application.history.metadata
|
|
220
|
+
if metadata[0] != '':
|
|
221
|
+
# get img path
|
|
222
|
+
d = json.loads(metadata[0])
|
|
223
|
+
femprj_path = d['femprj_path']
|
|
224
|
+
model_name = d['model_name']
|
|
225
|
+
femprj_result_dir = femprj_path.replace('.femprj', '.Results')
|
|
226
|
+
img_path = os.path.join(femprj_result_dir, f'{model_name}_trial{trial}.jpg')
|
|
227
|
+
if os.path.exists(img_path):
|
|
228
|
+
# create encoded image
|
|
229
|
+
with open(img_path, 'rb') as f:
|
|
230
|
+
content = f.read()
|
|
231
|
+
encoded_image = base64.b64encode(content).decode('utf-8')
|
|
232
|
+
img_url = 'data:image/jpeg;base64, ' + encoded_image
|
|
233
|
+
return html.Img(src=img_url, style={"width": "200px"}) if img_url is not None else html.Div()
|
|
234
|
+
|
|
235
|
+
def get_fig_by_tab_id(self, tab_id, with_length=False):
|
|
236
|
+
# If the history is not loaded, do nothing
|
|
237
|
+
if self.application.history is None:
|
|
238
|
+
raise PreventUpdate
|
|
239
|
+
|
|
240
|
+
# else, get creator by tab_id
|
|
241
|
+
if tab_id == 'default':
|
|
242
|
+
creator = self.figure_creators[0]['creator']
|
|
243
|
+
else:
|
|
244
|
+
creators = [d['creator'] for d in self.figure_creators if d['tab_id'] == tab_id]
|
|
245
|
+
if len(creators) == 0:
|
|
246
|
+
raise PreventUpdate
|
|
247
|
+
creator = creators[0]
|
|
248
|
+
|
|
249
|
+
# create figure
|
|
250
|
+
df = self.data_accessor()
|
|
251
|
+
fig = creator(self.application.history, df)
|
|
252
|
+
if with_length:
|
|
253
|
+
return fig, len(df)
|
|
254
|
+
else:
|
|
255
|
+
return fig
|
|
256
|
+
|
|
257
|
+
def data_accessor(self) -> pd.DataFrame:
|
|
258
|
+
from pyfemtet.opt.visualization.process_monitor.application import ProcessMonitorApplication
|
|
259
|
+
if isinstance(self.application, ProcessMonitorApplication):
|
|
260
|
+
df = self.application.local_data
|
|
261
|
+
else:
|
|
262
|
+
df = self.application.history.local_data
|
|
263
|
+
return df
|
|
File without changes
|