pyfemtet 0.4.10__py3-none-any.whl → 0.4.11__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/opt/_femopt.py +9 -3
- pyfemtet/opt/interface/_femtet.py +32 -10
- pyfemtet/opt/interface/_femtet_parametric.py +5 -25
- pyfemtet/opt/opt/_base.py +9 -1
- pyfemtet/opt/opt/_optuna.py +4 -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.11.dist-info}/METADATA +1 -1
- pyfemtet-0.4.11.dist-info/RECORD +136 -0
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.11.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.11.dist-info}/LICENSE +0 -0
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.11.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
import base64
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import shutil
|
|
9
|
+
|
|
10
|
+
from dash import Output, Input, State, callback_context, no_update
|
|
11
|
+
from dash.exceptions import PreventUpdate
|
|
12
|
+
|
|
13
|
+
from pyfemtet.opt.visualization.wrapped_components import dcc, dbc, html
|
|
14
|
+
from pyfemtet.opt.visualization.base import AbstractPage # , logger
|
|
15
|
+
from pyfemtet.opt.visualization.complex_components.main_graph import MainGraph # , FLEXBOX_STYLE_ALLOW_VERTICAL_FILL
|
|
16
|
+
from pyfemtet.opt.visualization.complex_components.control_femtet import FemtetControl, FemtetState
|
|
17
|
+
from pyfemtet.opt.visualization.complex_components.alert_region import AlertRegion
|
|
18
|
+
|
|
19
|
+
from pyfemtet.opt._femopt_core import History
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HomePage(AbstractPage):
|
|
23
|
+
|
|
24
|
+
def __init__(self, title, rel_url='/'):
|
|
25
|
+
super().__init__(title, rel_url)
|
|
26
|
+
|
|
27
|
+
# noinspection PyAttributeOutsideInit
|
|
28
|
+
def setup_component(self):
|
|
29
|
+
|
|
30
|
+
self.location = dcc.Location(id='result-viewer-location')
|
|
31
|
+
|
|
32
|
+
# control femtet subpage
|
|
33
|
+
self.femtet_control: FemtetControl = FemtetControl()
|
|
34
|
+
self.add_subpage(self.femtet_control)
|
|
35
|
+
|
|
36
|
+
# main graph subpage
|
|
37
|
+
self.main_graph: MainGraph = MainGraph()
|
|
38
|
+
self.add_subpage(self.main_graph)
|
|
39
|
+
|
|
40
|
+
# alert region subpage
|
|
41
|
+
self.alert_region: AlertRegion = AlertRegion()
|
|
42
|
+
self.add_subpage(self.alert_region)
|
|
43
|
+
|
|
44
|
+
# open pdt (or transfer variable to femtet)
|
|
45
|
+
self.open_pdt_button = dbc.Button(
|
|
46
|
+
'Open Result in Femtet',
|
|
47
|
+
id='open-pdt-button',
|
|
48
|
+
color='primary',
|
|
49
|
+
className="position-relative", # need to show badge
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# update parameter
|
|
53
|
+
self.update_parameter_button = dbc.Button(
|
|
54
|
+
'Reconstruct Model',
|
|
55
|
+
id='update-parameter-button',
|
|
56
|
+
color='secondary',
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# file picker
|
|
60
|
+
self.file_picker_button = dbc.Button(
|
|
61
|
+
'drag and drop or select files',
|
|
62
|
+
id='file-picker-button',
|
|
63
|
+
color='primary',
|
|
64
|
+
)
|
|
65
|
+
self.file_picker = dcc.Upload(
|
|
66
|
+
id='history-file-picker',
|
|
67
|
+
children=[self.file_picker_button],
|
|
68
|
+
multiple=False,
|
|
69
|
+
style={'display': 'inline'},
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# tutorial subpage (after setup self components)
|
|
73
|
+
self.tutorial: Tutorial = Tutorial(self, self.main_graph, self.femtet_control)
|
|
74
|
+
self.add_subpage(self.tutorial)
|
|
75
|
+
|
|
76
|
+
# noinspection PyAttributeOutsideInit
|
|
77
|
+
def setup_layout(self):
|
|
78
|
+
""""""
|
|
79
|
+
"""
|
|
80
|
+
=======================
|
|
81
|
+
| | ---------------- |
|
|
82
|
+
| | | | |
|
|
83
|
+
| | | Main Graph | |
|
|
84
|
+
| | | | |
|
|
85
|
+
| | ---------------- |
|
|
86
|
+
| | [] ... [] <---- Buttons
|
|
87
|
+
| | ---------------- |
|
|
88
|
+
| | | Alert Region | |
|
|
89
|
+
| ^| ---------------- |
|
|
90
|
+
==|====================
|
|
91
|
+
|
|
|
92
|
+
SideBar
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
# Uncomment if the plotly's specification if fixed, and we can use dcc.Graph with FlexBox.
|
|
96
|
+
# style = {'height': '95vh'}
|
|
97
|
+
# style.update(FLEXBOX_STYLE_ALLOW_VERTICAL_FILL)
|
|
98
|
+
self.layout = dbc.Container(
|
|
99
|
+
children=[
|
|
100
|
+
dbc.Row([self.location, self.main_graph.layout, self.tutorial.graph_popover, self.tutorial.control_visibility_input_dummy]), # visible / invisible components
|
|
101
|
+
dbc.Row(self.femtet_control.layout), # invisible components
|
|
102
|
+
dbc.Row(
|
|
103
|
+
children=[
|
|
104
|
+
dbc.Col(html.Div([self.tutorial.tutorial_mode_switch], className='d-flex justify-content-center')),
|
|
105
|
+
dbc.Col(html.Div([self.file_picker, self.tutorial.load_sample_csv_div], className='d-flex justify-content-center',),),
|
|
106
|
+
dbc.Col(html.Div([self.femtet_control.connect_femtet_button_spinner, self.tutorial.connect_femtet_popover], className='d-flex justify-content-center')),
|
|
107
|
+
dbc.Col(html.Div([self.open_pdt_button, self.update_parameter_button, self.tutorial.open_pdt_popover], className='d-flex justify-content-center')),
|
|
108
|
+
],
|
|
109
|
+
justify='evenly',
|
|
110
|
+
),
|
|
111
|
+
dbc.Row(self.alert_region.layout), # visible / invisible components
|
|
112
|
+
],
|
|
113
|
+
# style=style,
|
|
114
|
+
fluid=True,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def setup_callback(self):
|
|
118
|
+
# setup callback of subpages
|
|
119
|
+
super().setup_callback()
|
|
120
|
+
|
|
121
|
+
app = self.application.app
|
|
122
|
+
|
|
123
|
+
# ===== read history csv file from file picker =====
|
|
124
|
+
@app.callback(
|
|
125
|
+
Output(self.file_picker_button.id, self.file_picker_button.Prop.children),
|
|
126
|
+
Output(self.main_graph.tabs.id, self.main_graph.tabs.Prop.active_tab, allow_duplicate=False),
|
|
127
|
+
Output(self.femtet_control.connect_femtet_button.id, self.femtet_control.connect_femtet_button.Prop.n_clicks), # automatically connect to femtet if the metadata of csv is valid
|
|
128
|
+
Input(self.file_picker.id, self.file_picker.Prop.contents),
|
|
129
|
+
State(self.file_picker.id, self.file_picker.Prop.filename),
|
|
130
|
+
State(self.main_graph.tabs.id, self.main_graph.tabs.Prop.active_tab),
|
|
131
|
+
prevent_initial_call=False,
|
|
132
|
+
)
|
|
133
|
+
def set_history(encoded_file_content: str, file_name: str, active_tab_id: str):
|
|
134
|
+
|
|
135
|
+
# if the history is specified before launch GUI (not implemented), respond it.
|
|
136
|
+
if callback_context.triggered_id is None:
|
|
137
|
+
if self.application.history is not None:
|
|
138
|
+
file_name = os.path.split(self.application.history.path)[1]
|
|
139
|
+
return f'current file: {file_name}', no_update, no_update
|
|
140
|
+
|
|
141
|
+
if encoded_file_content is None:
|
|
142
|
+
raise PreventUpdate
|
|
143
|
+
|
|
144
|
+
if not file_name.lower().endswith('.csv'):
|
|
145
|
+
raise PreventUpdate
|
|
146
|
+
|
|
147
|
+
# create temporary file from file_content(full path is hidden by browser because of the security reason)
|
|
148
|
+
content_type, content_string = encoded_file_content.split(',')
|
|
149
|
+
file_content = base64.b64decode(content_string)
|
|
150
|
+
with tempfile.TemporaryDirectory() as tmp_dir_path:
|
|
151
|
+
csv_path = os.path.join(tmp_dir_path, file_name)
|
|
152
|
+
with open(csv_path, 'wb') as f:
|
|
153
|
+
f.write(file_content)
|
|
154
|
+
self.application.history = History(csv_path)
|
|
155
|
+
|
|
156
|
+
return f'current file: {file_name}', active_tab_id, 1
|
|
157
|
+
|
|
158
|
+
# ===== open pdt =====
|
|
159
|
+
@app.callback(
|
|
160
|
+
Output(self.alert_region.alert_region.id, 'children', allow_duplicate=True),
|
|
161
|
+
Input(self.open_pdt_button.id, self.open_pdt_button.Prop.n_clicks),
|
|
162
|
+
State(self.main_graph.selection_data.id, self.main_graph.selection_data_property),
|
|
163
|
+
State(self.alert_region.alert_region.id, 'children'),
|
|
164
|
+
prevent_initial_call=True,
|
|
165
|
+
)
|
|
166
|
+
def open_pdt(_, selection_data, current_alerts):
|
|
167
|
+
# open_pdt allows to open "解析結果単体" without opening any femprj.
|
|
168
|
+
|
|
169
|
+
# check Femtet state
|
|
170
|
+
connection_state = self.femtet_control.check_femtet_state()
|
|
171
|
+
if connection_state == FemtetState.missing or connection_state == FemtetState.unconnected:
|
|
172
|
+
msg = ('Connection to Femtet is not established. '
|
|
173
|
+
'Launch Femtet and Open a project.')
|
|
174
|
+
alerts = self.alert_region.create_alerts(msg, color='danger', current_alerts=current_alerts)
|
|
175
|
+
return alerts
|
|
176
|
+
|
|
177
|
+
# check selection
|
|
178
|
+
if selection_data is None:
|
|
179
|
+
msg = 'No result plot is selected.'
|
|
180
|
+
alerts = self.alert_region.create_alerts(msg, color='danger', current_alerts=current_alerts)
|
|
181
|
+
return alerts
|
|
182
|
+
|
|
183
|
+
# get femprj in history csv
|
|
184
|
+
kwargs = self.femtet_control.create_femtet_interface_args()[0] # read metadata from history.
|
|
185
|
+
femprj_path = kwargs['femprj_path']
|
|
186
|
+
model_name = kwargs['model_name']
|
|
187
|
+
|
|
188
|
+
# check metadata is valid
|
|
189
|
+
if femprj_path is None:
|
|
190
|
+
msg = 'The femprj file path in the history csv is not found or valid. '
|
|
191
|
+
alerts = self.alert_region.create_alerts(msg, color='danger')
|
|
192
|
+
return alerts
|
|
193
|
+
|
|
194
|
+
if model_name is None:
|
|
195
|
+
msg = 'The model name in the history csv is not found.'
|
|
196
|
+
alerts = self.alert_region.create_alerts(msg, color='danger')
|
|
197
|
+
return alerts
|
|
198
|
+
|
|
199
|
+
# check pdt_path in selection_data
|
|
200
|
+
pt = selection_data['points'][0]
|
|
201
|
+
trial = pt['customdata'][0]
|
|
202
|
+
|
|
203
|
+
# get pdt path
|
|
204
|
+
pdt_path = self.femtet_control.fem.create_pdt_path(femprj_path, model_name, trial)
|
|
205
|
+
|
|
206
|
+
# check pdt exists
|
|
207
|
+
if not os.path.exists(pdt_path):
|
|
208
|
+
msg = ('.pdt file is not found. '
|
|
209
|
+
'Please check the .Results folder. '
|
|
210
|
+
'Note that .pdt file save mode depends on '
|
|
211
|
+
'the `save_pdt` argument of FemtetInterface in optimization script'
|
|
212
|
+
'(default to `all`).')
|
|
213
|
+
alerts = self.alert_region.create_alerts(msg, color='danger')
|
|
214
|
+
return alerts
|
|
215
|
+
|
|
216
|
+
# OpenPDT(PDTFile As String, bOpenWithGUI As Boolean)
|
|
217
|
+
Femtet = self.femtet_control.fem.Femtet
|
|
218
|
+
succeed = Femtet.OpenPDT(pdt_path, True)
|
|
219
|
+
|
|
220
|
+
if not succeed:
|
|
221
|
+
msg = f'Failed to open {pdt_path}.'
|
|
222
|
+
alerts = self.alert_region.create_alerts(msg, color='danger')
|
|
223
|
+
return alerts
|
|
224
|
+
|
|
225
|
+
return no_update
|
|
226
|
+
|
|
227
|
+
# ===== reconstruct model with updating parameter =====
|
|
228
|
+
@app.callback(
|
|
229
|
+
Output(self.alert_region.alert_region.id, 'children', allow_duplicate=True),
|
|
230
|
+
Input(self.update_parameter_button.id, self.update_parameter_button.Prop.n_clicks),
|
|
231
|
+
State(self.main_graph.selection_data.id, self.main_graph.selection_data_property),
|
|
232
|
+
State(self.alert_region.alert_region.id, 'children'),
|
|
233
|
+
prevent_initial_call=True,
|
|
234
|
+
)
|
|
235
|
+
def update_parameter(_, selection_data, current_alerts):
|
|
236
|
+
|
|
237
|
+
# check Femtet state
|
|
238
|
+
connection_state = self.femtet_control.check_femtet_state()
|
|
239
|
+
if connection_state != FemtetState.connected:
|
|
240
|
+
msg = ('Connection to Femtet is not established. '
|
|
241
|
+
'Launch Femtet and Open a project.')
|
|
242
|
+
alerts = self.alert_region.create_alerts(msg, color='danger', current_alerts=current_alerts)
|
|
243
|
+
return alerts
|
|
244
|
+
|
|
245
|
+
# check selection
|
|
246
|
+
if selection_data is None:
|
|
247
|
+
msg = 'No result plot is selected.'
|
|
248
|
+
alerts = self.alert_region.create_alerts(msg, color='danger', current_alerts=current_alerts)
|
|
249
|
+
return alerts
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
Femtet = self.femtet_control.fem.Femtet
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# check model to open is included in current project
|
|
256
|
+
if (self.femtet_control.fem.femprj_path != Femtet.Project) \
|
|
257
|
+
or (self.femtet_control.fem.model_name not in Femtet.GetAnalysisModelNames_py()):
|
|
258
|
+
msg = (f'{self.femtet_control.fem.model_name} is not in current project. '
|
|
259
|
+
f'Please check opened project. '
|
|
260
|
+
f'For example, not "analysis model only" but your .femprj file.')
|
|
261
|
+
alerts = self.alert_region.create_alerts(msg, color='danger', current_alerts=current_alerts)
|
|
262
|
+
return alerts
|
|
263
|
+
|
|
264
|
+
# certify opening model
|
|
265
|
+
Femtet.LoadProjectAndAnalysisModel(
|
|
266
|
+
self.femtet_control.fem.femprj_path,
|
|
267
|
+
self.femtet_control.fem.model_name,
|
|
268
|
+
False
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# copy analysis model or femprj.
|
|
272
|
+
...
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
msg = ('Unknown error has occurred in analysis model compatibility check. '
|
|
276
|
+
f'exception message is: {e}')
|
|
277
|
+
alerts = self.alert_region.create_alerts(msg, color='danger', current_alerts=current_alerts)
|
|
278
|
+
return alerts
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
# get nth trial from selection data
|
|
282
|
+
pt = selection_data['points'][0]
|
|
283
|
+
trial = pt['customdata'][0]
|
|
284
|
+
|
|
285
|
+
# get parameter and update model
|
|
286
|
+
df = self.application.history.local_data
|
|
287
|
+
row = df[df['trial'] == trial]
|
|
288
|
+
metadata = np.array(self.application.history.metadata)
|
|
289
|
+
idx = np.where(metadata == 'prm')[0]
|
|
290
|
+
|
|
291
|
+
names = np.array(row.columns)[idx]
|
|
292
|
+
values = np.array(row.iloc[:, idx]).ravel()
|
|
293
|
+
|
|
294
|
+
parameter = pd.DataFrame(
|
|
295
|
+
dict(
|
|
296
|
+
name=names,
|
|
297
|
+
value=values,
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
self.femtet_control.fem.update_model(parameter)
|
|
302
|
+
|
|
303
|
+
Femtet.SaveProject(Femtet.Project, True)
|
|
304
|
+
|
|
305
|
+
return no_update
|
|
306
|
+
|
|
307
|
+
except Exception as e:
|
|
308
|
+
msg = ('Unknown error has occurred in updating model. '
|
|
309
|
+
f'exception message is: {e}')
|
|
310
|
+
alerts = self.alert_region.create_alerts(msg, color='danger', current_alerts=current_alerts)
|
|
311
|
+
return alerts
|
|
312
|
+
|
|
313
|
+
# ===== update alert (chained from launch femtet) =====
|
|
314
|
+
@app.callback(
|
|
315
|
+
Output(self.alert_region.alert_region.id, 'children', allow_duplicate=True),
|
|
316
|
+
Input(self.femtet_control.femtet_state.id, self.femtet_control.femtet_state_prop),
|
|
317
|
+
State(self.alert_region.alert_region.id, 'children'),
|
|
318
|
+
prevent_initial_call=True,
|
|
319
|
+
)
|
|
320
|
+
def add_alert_by_connect_femtet(femtet_state_data, current_alerts):
|
|
321
|
+
if callback_context.triggered_id is None:
|
|
322
|
+
raise PreventUpdate
|
|
323
|
+
|
|
324
|
+
msg = femtet_state_data['alert']
|
|
325
|
+
if msg == '':
|
|
326
|
+
raise PreventUpdate
|
|
327
|
+
|
|
328
|
+
new_alerts = self.alert_region.create_alerts(msg, 'warning', current_alerts)
|
|
329
|
+
|
|
330
|
+
return new_alerts
|
|
331
|
+
|
|
332
|
+
# ===== update alert (chained from read history) =====
|
|
333
|
+
@app.callback(
|
|
334
|
+
Output(self.alert_region.alert_region.id, 'children', allow_duplicate=True),
|
|
335
|
+
Input(self.file_picker_button.id, self.file_picker_button.Prop.children),
|
|
336
|
+
State(self.alert_region.alert_region.id, 'children'),
|
|
337
|
+
prevent_initial_call=True,
|
|
338
|
+
)
|
|
339
|
+
def add_alert_on_history_set(_, current_alerts):
|
|
340
|
+
if callback_context.triggered_id is None:
|
|
341
|
+
raise PreventUpdate
|
|
342
|
+
|
|
343
|
+
if self.application.history is None:
|
|
344
|
+
raise PreventUpdate
|
|
345
|
+
|
|
346
|
+
if self.femtet_control.fem is None:
|
|
347
|
+
raise PreventUpdate
|
|
348
|
+
|
|
349
|
+
# check the corresponding between history and Femtet
|
|
350
|
+
# ├ history-side
|
|
351
|
+
kwargs = self.femtet_control.create_femtet_interface_args()[0] # read metadata from history.
|
|
352
|
+
femprj_path_history_on_history: str or None = kwargs['femprj_path']
|
|
353
|
+
model_name_on_history: str or None = kwargs['model_name']
|
|
354
|
+
# ├ Femtet-side
|
|
355
|
+
Femtet = self.femtet_control.fem.Femtet
|
|
356
|
+
femprj_path: str = Femtet.Project # it can be '解析結果単体.femprj'
|
|
357
|
+
model_name: str = Femtet.AnalysisModelName
|
|
358
|
+
# └ check
|
|
359
|
+
is_same_femprj = (femprj_path == femprj_path_history_on_history) if femprj_path_history_on_history is not None else True
|
|
360
|
+
is_same_model = (model_name == model_name_on_history) if model_name is not None else True
|
|
361
|
+
|
|
362
|
+
# alert
|
|
363
|
+
new_alerts = current_alerts
|
|
364
|
+
if femprj_path_history_on_history is None:
|
|
365
|
+
msg = '.femprj file path of the history csv is invalid. Please certify matching between csv and opening .femprj file.'
|
|
366
|
+
new_alerts = self.alert_region.create_alerts(msg, 'warning', new_alerts)
|
|
367
|
+
else:
|
|
368
|
+
if not is_same_femprj:
|
|
369
|
+
msg = '.femprj file path of the history csv and opened in Femtet is inconsistent. Please certify matching between csv and .femprj file.'
|
|
370
|
+
new_alerts = self.alert_region.create_alerts(msg, 'warning', new_alerts)
|
|
371
|
+
|
|
372
|
+
if model_name_on_history is None:
|
|
373
|
+
msg = 'Analysis model name of the history csv is invalid. Please certify matching between csv and opening analysis model.'
|
|
374
|
+
new_alerts = self.alert_region.create_alerts(msg, 'warning', new_alerts)
|
|
375
|
+
else:
|
|
376
|
+
if not is_same_model:
|
|
377
|
+
msg = 'Analysis model name of the history csv and opened in Femtet is inconsistent. Please certify matching between csv and opening analysis model.'
|
|
378
|
+
new_alerts = self.alert_region.create_alerts(msg, 'warning', new_alerts)
|
|
379
|
+
|
|
380
|
+
if new_alerts == current_alerts:
|
|
381
|
+
raise PreventUpdate
|
|
382
|
+
|
|
383
|
+
return new_alerts
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class Tutorial(AbstractPage):
|
|
387
|
+
|
|
388
|
+
# noinspection PyMissingConstructor
|
|
389
|
+
def __init__(self, home_page, main_graph, femtet_control):
|
|
390
|
+
self.home_page: HomePage = home_page
|
|
391
|
+
self.main_graph: MainGraph = main_graph
|
|
392
|
+
self.femtet_control: FemtetControl = femtet_control
|
|
393
|
+
|
|
394
|
+
# noinspection PyAttributeOutsideInit
|
|
395
|
+
def setup_component(self):
|
|
396
|
+
|
|
397
|
+
self.control_visibility_input_dummy = html.Div(hidden=True)
|
|
398
|
+
|
|
399
|
+
self.tutorial_state_prop = 'data-tutorial-state'
|
|
400
|
+
self.tutorial_state = html.Data(
|
|
401
|
+
id='tutorial-state',
|
|
402
|
+
**{self.tutorial_state_prop: dict(
|
|
403
|
+
is_tutorial=False,
|
|
404
|
+
has_history=None,
|
|
405
|
+
point_selected=None,
|
|
406
|
+
)}
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
# switch tutorial mode (always visible)
|
|
410
|
+
self.tutorial_mode_switch = dbc.Checklist(
|
|
411
|
+
['tutorial mode'],
|
|
412
|
+
id='tutorial-mode-switch',
|
|
413
|
+
switch=True,
|
|
414
|
+
value=False
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
# load sample csv
|
|
418
|
+
self.load_sample_csv_badge = self.create_badge('Click Me!', 'load-sample-csv-badge')
|
|
419
|
+
self.load_sample_csv_button = dbc.Button(
|
|
420
|
+
children=['Load sample csv', self.load_sample_csv_badge],
|
|
421
|
+
id='load-sample-csv-button',
|
|
422
|
+
className="position-relative", # need to show badge
|
|
423
|
+
)
|
|
424
|
+
self.load_sample_csv_popover = dbc.Popover(
|
|
425
|
+
children=[
|
|
426
|
+
dbc.PopoverHeader('Load CSV'),
|
|
427
|
+
dbc.PopoverBody(
|
|
428
|
+
'Open your optimization result. Then connecting to femtet will start automatically. '
|
|
429
|
+
'Note that in tutorial mode, this button loads the ready-made sample csv and open sample femprj.'
|
|
430
|
+
),
|
|
431
|
+
],
|
|
432
|
+
id='load-sample-csv-popover',
|
|
433
|
+
target=self.load_sample_csv_button.id,
|
|
434
|
+
is_open=False,
|
|
435
|
+
placement='top',
|
|
436
|
+
)
|
|
437
|
+
self.load_sample_csv_div = html.Span(
|
|
438
|
+
children=[self.load_sample_csv_button, self.load_sample_csv_popover],
|
|
439
|
+
id='load-sample-csv-div',
|
|
440
|
+
style={'display': 'none'},
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
# popover and badge of main graph
|
|
444
|
+
self.graph_badge = self.create_badge('Choose a Point!', 'graph-badge')
|
|
445
|
+
self.graph_popover = dbc.Popover(
|
|
446
|
+
children=[
|
|
447
|
+
dbc.PopoverHeader('Main Graph'),
|
|
448
|
+
dbc.PopoverBody(
|
|
449
|
+
children=[
|
|
450
|
+
'Here the optimization history is shown. '
|
|
451
|
+
'Each plot represents single FEM result. '
|
|
452
|
+
'You can pick a result to open the corresponding result in Femtet. ',
|
|
453
|
+
self.graph_badge
|
|
454
|
+
],
|
|
455
|
+
className="position-relative", # need to show badge
|
|
456
|
+
),
|
|
457
|
+
],
|
|
458
|
+
id='graph-popover',
|
|
459
|
+
target=self.main_graph.tabs.id,
|
|
460
|
+
is_open=False,
|
|
461
|
+
placement='bottom',
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# popover and badge of open pdt
|
|
465
|
+
self.open_pdt_badge = self.create_badge('Click Me!', 'open-pdt-badge')
|
|
466
|
+
self.open_pdt_popover = dbc.Popover(
|
|
467
|
+
children=[
|
|
468
|
+
dbc.PopoverHeader('Open Result'),
|
|
469
|
+
dbc.PopoverBody(
|
|
470
|
+
'After pick a point in the main graph, '
|
|
471
|
+
'This button shows the corresponding FEM result in Femtet.'
|
|
472
|
+
),
|
|
473
|
+
],
|
|
474
|
+
id='open-pdt-popover',
|
|
475
|
+
target=self.home_page.open_pdt_button.id,
|
|
476
|
+
is_open=False,
|
|
477
|
+
placement='top',
|
|
478
|
+
)
|
|
479
|
+
# popover of connect-femtet
|
|
480
|
+
self.connect_femtet_popover = dbc.Popover(
|
|
481
|
+
children=[
|
|
482
|
+
dbc.PopoverBody('You can re-make connection to Femtet if it misses.'),
|
|
483
|
+
],
|
|
484
|
+
id='connect-femtet-popover',
|
|
485
|
+
target=self.femtet_control.connect_femtet_button.id,
|
|
486
|
+
is_open=False,
|
|
487
|
+
placement='bottom',
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def setup_layout(self):
|
|
492
|
+
pass
|
|
493
|
+
|
|
494
|
+
def setup_callback(self):
|
|
495
|
+
app = self.application.app
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
@app.callback(
|
|
499
|
+
Output(self.home_page.open_pdt_button, 'children'),
|
|
500
|
+
Input(self.home_page.location, self.home_page.location.Prop.pathname),
|
|
501
|
+
State(self.home_page.open_pdt_button, 'children'),
|
|
502
|
+
prevent_initial_call=False,
|
|
503
|
+
)
|
|
504
|
+
def add_badge_to_button_init(_, current_children):
|
|
505
|
+
children = current_children if isinstance(current_children, list) else [current_children]
|
|
506
|
+
if self.open_pdt_badge not in children:
|
|
507
|
+
children.append(self.open_pdt_badge)
|
|
508
|
+
return children
|
|
509
|
+
|
|
510
|
+
@app.callback(
|
|
511
|
+
Output(self.load_sample_csv_badge, 'style'), # switch visibility
|
|
512
|
+
Output(self.load_sample_csv_popover, 'is_open'), # switch visibility
|
|
513
|
+
Output(self.load_sample_csv_div, 'style'), # switch visibility
|
|
514
|
+
Output(self.home_page.file_picker, 'style'), # switch visibility
|
|
515
|
+
Output(self.graph_badge, 'style'), # switch visibility
|
|
516
|
+
Output(self.graph_popover, 'is_open'), # switch visibility
|
|
517
|
+
Output(self.open_pdt_badge, 'style'), # switch visibility
|
|
518
|
+
Output(self.open_pdt_popover, 'is_open'), # switch visibility
|
|
519
|
+
Output(self.connect_femtet_popover, 'is_open'), # switch visibility
|
|
520
|
+
Input(self.tutorial_mode_switch, 'value'),
|
|
521
|
+
Input(self.load_sample_csv_button, 'n_clicks'), # load button clicked
|
|
522
|
+
Input(self.main_graph.selection_data, self.main_graph.selection_data_property), # selection changed
|
|
523
|
+
Input(self.control_visibility_input_dummy, 'children'),
|
|
524
|
+
State(self.load_sample_csv_badge, 'style'), # switch visibility
|
|
525
|
+
State(self.load_sample_csv_div, 'style'), # switch visibility
|
|
526
|
+
State(self.home_page.file_picker, 'style'), # switch visibility
|
|
527
|
+
State(self.graph_badge, 'style'), # switch visibility
|
|
528
|
+
State(self.open_pdt_badge, 'style'), # switch visibility
|
|
529
|
+
prevent_initial_call=True,
|
|
530
|
+
)
|
|
531
|
+
def control_visibility(
|
|
532
|
+
is_tutorial,
|
|
533
|
+
_1,
|
|
534
|
+
selection_data,
|
|
535
|
+
_2,
|
|
536
|
+
load_sample_csv_badge_current_style,
|
|
537
|
+
load_sample_csv_div_current_style,
|
|
538
|
+
file_picker_current_style,
|
|
539
|
+
graph_badge_current_style,
|
|
540
|
+
open_pdt_badge_current_style,
|
|
541
|
+
):
|
|
542
|
+
ret = {
|
|
543
|
+
(load_sample_csv_badge_style := 0): no_update,
|
|
544
|
+
(load_sample_csv_popover_visible := 1): no_update,
|
|
545
|
+
(load_sample_csv_div_style := 2): no_update,
|
|
546
|
+
(file_picker_style := 3): no_update,
|
|
547
|
+
(graph_badge_style := 4): no_update,
|
|
548
|
+
(graph_popover_visible := 5): no_update,
|
|
549
|
+
(open_pdt_badge_style := 6): no_update,
|
|
550
|
+
(open_pdt_popover_visible := 7): no_update,
|
|
551
|
+
(connect_femtet_popover_visible := 8): no_update,
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
# prevent unexpected update
|
|
555
|
+
if callback_context.triggered_id is None:
|
|
556
|
+
raise PreventUpdate
|
|
557
|
+
|
|
558
|
+
# ===== initialize =====
|
|
559
|
+
# non-tutorial component
|
|
560
|
+
ret[file_picker_style] = self.control_visibility_by_style(True, file_picker_current_style)
|
|
561
|
+
# tutorial component
|
|
562
|
+
ret[load_sample_csv_badge_style] = self.control_visibility_by_style(False, load_sample_csv_badge_current_style)
|
|
563
|
+
ret[load_sample_csv_popover_visible] = False
|
|
564
|
+
ret[load_sample_csv_div_style] = self.control_visibility_by_style(False, load_sample_csv_div_current_style)
|
|
565
|
+
ret[graph_badge_style] = self.control_visibility_by_style(False, graph_badge_current_style)
|
|
566
|
+
ret[graph_popover_visible] = False
|
|
567
|
+
ret[open_pdt_badge_style] = self.control_visibility_by_style(False, open_pdt_badge_current_style)
|
|
568
|
+
ret[open_pdt_popover_visible] = False
|
|
569
|
+
ret[connect_femtet_popover_visible] = False
|
|
570
|
+
|
|
571
|
+
# if not tutorial, disable all anyway
|
|
572
|
+
if not is_tutorial:
|
|
573
|
+
return tuple(ret.values())
|
|
574
|
+
|
|
575
|
+
# else, visible popover
|
|
576
|
+
else:
|
|
577
|
+
ret[file_picker_style] = self.control_visibility_by_style(False, file_picker_current_style)
|
|
578
|
+
ret[load_sample_csv_div_style] = self.control_visibility_by_style(True, load_sample_csv_div_current_style)
|
|
579
|
+
ret[load_sample_csv_popover_visible] = True
|
|
580
|
+
ret[graph_popover_visible] = True
|
|
581
|
+
ret[connect_femtet_popover_visible] = True
|
|
582
|
+
ret[open_pdt_popover_visible] = True
|
|
583
|
+
|
|
584
|
+
# if history is None, show badge to load csv
|
|
585
|
+
if self.application.history is None:
|
|
586
|
+
ret[load_sample_csv_badge_style] = self.control_visibility_by_style(
|
|
587
|
+
True,
|
|
588
|
+
load_sample_csv_badge_current_style
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
# if history is not None,
|
|
592
|
+
else:
|
|
593
|
+
# if a point is already selected, show badge to open-pdt
|
|
594
|
+
if self.check_point_selected(selection_data):
|
|
595
|
+
ret[open_pdt_badge_style] = self.control_visibility_by_style(
|
|
596
|
+
True,
|
|
597
|
+
open_pdt_badge_current_style,
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
# selection not yet, show badge to main-graph
|
|
601
|
+
else:
|
|
602
|
+
ret[graph_badge_style] = self.control_visibility_by_style(
|
|
603
|
+
True,
|
|
604
|
+
graph_badge_current_style,
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
return tuple(ret.values())
|
|
608
|
+
|
|
609
|
+
# ===== load sample csv =====
|
|
610
|
+
alert_region = self.home_page.alert_region.alert_region
|
|
611
|
+
|
|
612
|
+
@app.callback(
|
|
613
|
+
Output(self.main_graph.tabs, self.main_graph.tabs.Prop.active_tab, allow_duplicate=True),
|
|
614
|
+
Output(self.control_visibility_input_dummy, 'children'),
|
|
615
|
+
Output(self.femtet_control.connect_femtet_button, 'n_clicks', allow_duplicate=True),
|
|
616
|
+
Output(alert_region, 'children', allow_duplicate=True),
|
|
617
|
+
Input(self.load_sample_csv_button, 'n_clicks'),
|
|
618
|
+
State(self.main_graph.tabs, self.main_graph.tabs.Prop.active_tab),
|
|
619
|
+
State(alert_region, 'children'),
|
|
620
|
+
prevent_initial_call=True,
|
|
621
|
+
)
|
|
622
|
+
def load_sample_csv(_, active_tab, current_alerts):
|
|
623
|
+
"""Load sample csv"""
|
|
624
|
+
if callback_context.triggered_id is None:
|
|
625
|
+
raise PreventUpdate
|
|
626
|
+
|
|
627
|
+
path = os.path.join(os.path.dirname(__file__), 'tutorial', 'tutorial.csv')
|
|
628
|
+
|
|
629
|
+
if not os.path.exists(path):
|
|
630
|
+
msg = 'Sample csv is not found. Please consider to re-install pyfemtet by `py -m pip install pyfemtet -U --force-reinstall`'
|
|
631
|
+
alerts = self.home_page.alert_region.create_alerts(msg, color='danger', current_alerts=current_alerts)
|
|
632
|
+
return no_update, no_update, no_update, alerts
|
|
633
|
+
self.application.history = History(path)
|
|
634
|
+
|
|
635
|
+
source_file = path.replace('tutorial.csv', 'wat_ex14_parametric.femprj')
|
|
636
|
+
if not os.path.exists(source_file):
|
|
637
|
+
msg = 'Sample femprj file is not found. Please consider to re-install pyfemtet by `py -m pip install pyfemtet -U --force-reinstall`'
|
|
638
|
+
alerts = self.home_page.alert_region.create_alerts(msg, color='danger', current_alerts=current_alerts)
|
|
639
|
+
return no_update, no_update, no_update, alerts
|
|
640
|
+
destination_file = path.replace('tutorial.csv', 'tutorial.femprj')
|
|
641
|
+
shutil.copyfile(source_file, destination_file)
|
|
642
|
+
|
|
643
|
+
source_folder = path.replace('tutorial.csv', 'wat_ex14_parametric.Results')
|
|
644
|
+
if not os.path.exists(source_file):
|
|
645
|
+
msg = 'Sample femprj result folder is not found. Please consider to re-install pyfemtet by `py -m pip install pyfemtet -U --force-reinstall`'
|
|
646
|
+
alerts = self.home_page.alert_region.create_alerts(msg, color='danger', current_alerts=current_alerts)
|
|
647
|
+
return no_update, no_update, no_update, alerts
|
|
648
|
+
destination_folder = path.replace('tutorial.csv', 'tutorial.Results')
|
|
649
|
+
shutil.copytree(source_folder, destination_folder, dirs_exist_ok=True)
|
|
650
|
+
|
|
651
|
+
self.application.history.metadata[0] = json.dumps(
|
|
652
|
+
dict(
|
|
653
|
+
femprj_path=destination_file,
|
|
654
|
+
model_name='Ex14',
|
|
655
|
+
)
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
return active_tab, 1, 1, no_update
|
|
659
|
+
|
|
660
|
+
@staticmethod
|
|
661
|
+
def check_point_selected(data):
|
|
662
|
+
if data is None:
|
|
663
|
+
return False
|
|
664
|
+
if len(data['points']) == 0:
|
|
665
|
+
return False
|
|
666
|
+
return True
|
|
667
|
+
|
|
668
|
+
@staticmethod
|
|
669
|
+
def create_badge(text, id):
|
|
670
|
+
badge = dbc.Badge(
|
|
671
|
+
children=text,
|
|
672
|
+
color="danger",
|
|
673
|
+
pill=True,
|
|
674
|
+
text_color="white",
|
|
675
|
+
style={'display': 'none'},
|
|
676
|
+
className="position-absolute top-0 start-100 translate-middle",
|
|
677
|
+
id=id,
|
|
678
|
+
)
|
|
679
|
+
return badge
|
|
680
|
+
|
|
681
|
+
@staticmethod
|
|
682
|
+
def control_visibility_by_style(visible: bool, current_style: dict):
|
|
683
|
+
|
|
684
|
+
visibility = 'inline' if visible else 'none'
|
|
685
|
+
part = {'display': visibility}
|
|
686
|
+
|
|
687
|
+
if current_style is None:
|
|
688
|
+
return part
|
|
689
|
+
|
|
690
|
+
else:
|
|
691
|
+
current_style.update(part)
|
|
692
|
+
return current_style
|