pyfemtet 0.4.9__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/femprj_sample/wat_ex14_parametric_parallel.py +66 -0
- pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_parallel_jp.py +64 -0
- 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.9.dist-info → pyfemtet-0.4.11.dist-info}/METADATA +1 -1
- pyfemtet-0.4.11.dist-info/RECORD +136 -0
- {pyfemtet-0.4.9.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.9.dist-info/RECORD +0 -81
- {pyfemtet-0.4.9.dist-info → pyfemtet-0.4.11.dist-info}/LICENSE +0 -0
- {pyfemtet-0.4.9.dist-info → pyfemtet-0.4.11.dist-info}/WHEEL +0 -0
|
@@ -1,1227 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import base64
|
|
3
|
-
from typing import Optional, List
|
|
4
|
-
|
|
5
|
-
import json
|
|
6
|
-
import webbrowser
|
|
7
|
-
from time import sleep
|
|
8
|
-
from threading import Thread
|
|
9
|
-
|
|
10
|
-
import numpy as np
|
|
11
|
-
import pandas as pd
|
|
12
|
-
import psutil
|
|
13
|
-
from plotly.graph_objects import Figure
|
|
14
|
-
from dash import Dash, html, dcc, Output, Input, State, callback_context, no_update, dash_table
|
|
15
|
-
from dash.exceptions import PreventUpdate
|
|
16
|
-
import dash_bootstrap_components as dbc
|
|
17
|
-
|
|
18
|
-
import pyfemtet
|
|
19
|
-
from pyfemtet.opt.interface import FemtetInterface
|
|
20
|
-
from pyfemtet.opt.visualization._graphs import (
|
|
21
|
-
update_default_figure,
|
|
22
|
-
update_hypervolume_plot,
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
import logging
|
|
27
|
-
from pyfemtet.logger import get_logger
|
|
28
|
-
logger = get_logger('viz')
|
|
29
|
-
logger.setLevel(logging.INFO)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
here = os.path.dirname(__file__)
|
|
33
|
-
|
|
34
|
-
DBC_COLUMN_STYLE_CENTER = {
|
|
35
|
-
'display': 'flex',
|
|
36
|
-
'justify-content': 'center',
|
|
37
|
-
'align-items': 'center',
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
DBC_COLUMN_STYLE_RIGHT = {
|
|
41
|
-
'display': 'flex',
|
|
42
|
-
'justify-content': 'right',
|
|
43
|
-
'align-items': 'right',
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _unused_port_number(start=49152):
|
|
48
|
-
# "LISTEN" 状態のポート番号をリスト化
|
|
49
|
-
used_ports = [conn.laddr.port for conn in psutil.net_connections() if conn.status == 'LISTEN']
|
|
50
|
-
port = start
|
|
51
|
-
for port in range(start, 65535 + 1):
|
|
52
|
-
# 未使用のポート番号ならreturn
|
|
53
|
-
if port not in set(used_ports):
|
|
54
|
-
break
|
|
55
|
-
if port != start:
|
|
56
|
-
logger.warn(f'Specified port "{start}" seems to be used. Port "{port}" is used instead.')
|
|
57
|
-
return port
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class FemtetControl:
|
|
61
|
-
|
|
62
|
-
# component ID
|
|
63
|
-
ID_LAUNCH_FEMTET_BUTTON = 'femtet-launch-button'
|
|
64
|
-
ID_CONNECT_FEMTET_BUTTON = 'femtet-connect-button'
|
|
65
|
-
ID_CONNECT_FEMTET_DROPDOWN = 'femtet-connect-dropdown'
|
|
66
|
-
ID_FEMTET_STATE_DATA = 'femtet-state-data'
|
|
67
|
-
ID_TRANSFER_PARAMETER_BUTTON = 'femtet-transfer-parameter-button'
|
|
68
|
-
ID_INTERVAL = 'femtet-interval'
|
|
69
|
-
ID_ALERTS = 'femtet-control-alerts'
|
|
70
|
-
ID_CLEAR_ALERTS = 'femtet-control-clear-alerts'
|
|
71
|
-
|
|
72
|
-
# arbitrary attribute name
|
|
73
|
-
ATTR_FEMTET_DATA = 'data-femtet' # attribute of html.Data should start with data-*.
|
|
74
|
-
|
|
75
|
-
# interface
|
|
76
|
-
fem: Optional['pyfemtet.opt.interface.FemtetInterface'] = None
|
|
77
|
-
|
|
78
|
-
@classmethod
|
|
79
|
-
def create_components(cls):
|
|
80
|
-
|
|
81
|
-
interval = dcc.Interval(id=cls.ID_INTERVAL, interval=1000)
|
|
82
|
-
femtet_state = {'pid': 0}
|
|
83
|
-
femtet_state_data = html.Data(id=cls.ID_FEMTET_STATE_DATA, **{cls.ATTR_FEMTET_DATA: femtet_state})
|
|
84
|
-
|
|
85
|
-
launch_new_femtet = dbc.DropdownMenuItem(
|
|
86
|
-
'新しい Femtet を起動して接続',
|
|
87
|
-
id=cls.ID_LAUNCH_FEMTET_BUTTON,
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
connect_existing_femtet = dbc.DropdownMenuItem(
|
|
91
|
-
'既存の Femtet と接続',
|
|
92
|
-
id=cls.ID_CONNECT_FEMTET_BUTTON,
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
launch_femtet_dropdown = dbc.DropdownMenu(
|
|
96
|
-
[
|
|
97
|
-
launch_new_femtet,
|
|
98
|
-
connect_existing_femtet
|
|
99
|
-
],
|
|
100
|
-
label='Femtet と接続',
|
|
101
|
-
id=cls.ID_CONNECT_FEMTET_DROPDOWN,
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
transfer_parameter_button = dbc.Button('変数を Femtet に転送', id=cls.ID_TRANSFER_PARAMETER_BUTTON, color='primary', disabled=True)
|
|
105
|
-
clear_alert = dbc.Button('警告のクリア', id=cls.ID_CLEAR_ALERTS, color='secondary')
|
|
106
|
-
|
|
107
|
-
buttons = dbc.Row([
|
|
108
|
-
dbc.Col(
|
|
109
|
-
dbc.ButtonGroup([
|
|
110
|
-
launch_femtet_dropdown,
|
|
111
|
-
transfer_parameter_button,
|
|
112
|
-
])
|
|
113
|
-
),
|
|
114
|
-
dbc.Col(clear_alert, style=DBC_COLUMN_STYLE_RIGHT),
|
|
115
|
-
])
|
|
116
|
-
|
|
117
|
-
alerts = dbc.CardBody(children=[], id=cls.ID_ALERTS)
|
|
118
|
-
|
|
119
|
-
control = dbc.Card([
|
|
120
|
-
dbc.CardHeader(buttons),
|
|
121
|
-
alerts,
|
|
122
|
-
])
|
|
123
|
-
|
|
124
|
-
component = dbc.Container(
|
|
125
|
-
[
|
|
126
|
-
control,
|
|
127
|
-
]
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
return [interval, femtet_state_data, component]
|
|
131
|
-
|
|
132
|
-
@classmethod
|
|
133
|
-
def add_callback(cls, home: 'HomePageBase'):
|
|
134
|
-
|
|
135
|
-
def launch_femtet_rule():
|
|
136
|
-
# default は手動で開く
|
|
137
|
-
femprj_path = None
|
|
138
|
-
model_name = None
|
|
139
|
-
msg = None
|
|
140
|
-
color = None
|
|
141
|
-
|
|
142
|
-
# metadata の有無を確認
|
|
143
|
-
additional_metadata = home.monitor.history.metadata[0]
|
|
144
|
-
is_invalid = False
|
|
145
|
-
|
|
146
|
-
# json である && femprj_path と model_name が key にある
|
|
147
|
-
prj_data = None
|
|
148
|
-
try:
|
|
149
|
-
prj_data = json.loads(additional_metadata)
|
|
150
|
-
_ = prj_data['femprj_path']
|
|
151
|
-
_ = prj_data['model_name']
|
|
152
|
-
except (TypeError, json.decoder.JSONDecodeError, KeyError):
|
|
153
|
-
is_invalid = True
|
|
154
|
-
|
|
155
|
-
if is_invalid:
|
|
156
|
-
color = 'warning'
|
|
157
|
-
msg = (
|
|
158
|
-
f'{home.monitor.history.path} に'
|
|
159
|
-
'解析プロジェクトファイルを示すデータが存在しないので'
|
|
160
|
-
'解析ファイルを自動で開けません。'
|
|
161
|
-
'Femtet 起動後、最適化を行ったファイルを'
|
|
162
|
-
'手動で開いてください。'
|
|
163
|
-
)
|
|
164
|
-
logger.warn(msg)
|
|
165
|
-
|
|
166
|
-
else:
|
|
167
|
-
|
|
168
|
-
# femprj が存在しなければ警告
|
|
169
|
-
if not os.path.isfile(prj_data['femprj_path']):
|
|
170
|
-
color = 'warning'
|
|
171
|
-
msg = (
|
|
172
|
-
f'{femprj_path} が見つかりません。'
|
|
173
|
-
'Femtet 起動後、最適化を行ったファイルを'
|
|
174
|
-
'手動で開いてください。'
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
# femprj はあるがその中に model があるかないかは開かないとわからない
|
|
178
|
-
# そうでなければ自動で開く
|
|
179
|
-
else:
|
|
180
|
-
femprj_path = prj_data['femprj_path']
|
|
181
|
-
model_name = prj_data['model_name']
|
|
182
|
-
|
|
183
|
-
return femprj_path, model_name, msg, color
|
|
184
|
-
|
|
185
|
-
def add_alert(current_alerts, msg, color):
|
|
186
|
-
new_alert = dbc.Alert(
|
|
187
|
-
msg,
|
|
188
|
-
id=f'alert-{len(current_alerts) + 1}',
|
|
189
|
-
dismissable=True,
|
|
190
|
-
color=color,
|
|
191
|
-
)
|
|
192
|
-
current_alerts.append(new_alert)
|
|
193
|
-
return current_alerts
|
|
194
|
-
|
|
195
|
-
#
|
|
196
|
-
# DESCRIPTION:
|
|
197
|
-
# Femtet を起動する際の挙動に従って warning を起こす callback
|
|
198
|
-
@home.monitor.app.callback(
|
|
199
|
-
[
|
|
200
|
-
Output(cls.ID_ALERTS, 'children', allow_duplicate=True),
|
|
201
|
-
],
|
|
202
|
-
[
|
|
203
|
-
Input(cls.ID_LAUNCH_FEMTET_BUTTON, 'n_clicks'),
|
|
204
|
-
Input(cls.ID_CONNECT_FEMTET_BUTTON, 'n_clicks'),
|
|
205
|
-
],
|
|
206
|
-
[
|
|
207
|
-
State(cls.ID_ALERTS, 'children'),
|
|
208
|
-
],
|
|
209
|
-
prevent_initial_call=True,
|
|
210
|
-
)
|
|
211
|
-
def update_alert(_1, _2, current_alerts):
|
|
212
|
-
logger.debug('update-femtet-control-alert')
|
|
213
|
-
|
|
214
|
-
# ボタン経由でないなら無視
|
|
215
|
-
if callback_context.triggered_id is None:
|
|
216
|
-
raise PreventUpdate
|
|
217
|
-
|
|
218
|
-
# 戻り値
|
|
219
|
-
ret = {
|
|
220
|
-
(new_alerts_key := 1): no_update,
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
# 引数の処理
|
|
224
|
-
current_alerts = current_alerts or []
|
|
225
|
-
|
|
226
|
-
_, _, msg, color = launch_femtet_rule()
|
|
227
|
-
|
|
228
|
-
if msg is not None:
|
|
229
|
-
new_alerts = add_alert(current_alerts, msg, color)
|
|
230
|
-
ret[new_alerts_key] = new_alerts
|
|
231
|
-
|
|
232
|
-
return tuple(ret.values())
|
|
233
|
-
|
|
234
|
-
#
|
|
235
|
-
# DESCRIPTION:
|
|
236
|
-
# Femtet を起動し、pid を記憶する callback
|
|
237
|
-
@home.monitor.app.callback(
|
|
238
|
-
[
|
|
239
|
-
Output(cls.ID_FEMTET_STATE_DATA, cls.ATTR_FEMTET_DATA, allow_duplicate=True),
|
|
240
|
-
Output(cls.ID_ALERTS, 'children'),
|
|
241
|
-
],
|
|
242
|
-
[
|
|
243
|
-
Input(cls.ID_LAUNCH_FEMTET_BUTTON, 'n_clicks'),
|
|
244
|
-
Input(cls.ID_CONNECT_FEMTET_BUTTON, 'n_clicks'),
|
|
245
|
-
],
|
|
246
|
-
[
|
|
247
|
-
State(cls.ID_ALERTS, 'children'),
|
|
248
|
-
],
|
|
249
|
-
prevent_initial_call=True,
|
|
250
|
-
)
|
|
251
|
-
def launch_femtet(launch_n_clicks, connect_n_clicks, current_alerts):
|
|
252
|
-
logger.debug(f'launch_femtet')
|
|
253
|
-
|
|
254
|
-
# 戻り値の設定
|
|
255
|
-
ret = {
|
|
256
|
-
(femtet_state_data := 1): no_update,
|
|
257
|
-
(key_new_alerts := 2): no_update,
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
# 引数の処理
|
|
261
|
-
current_alerts = current_alerts or []
|
|
262
|
-
|
|
263
|
-
# ボタン経由でなければ無視
|
|
264
|
-
if callback_context.triggered_id is None:
|
|
265
|
-
raise PreventUpdate
|
|
266
|
-
|
|
267
|
-
# Femtet を起動するための引数をルールに沿って取得
|
|
268
|
-
femprj_path, model_name, _, _ = launch_femtet_rule()
|
|
269
|
-
|
|
270
|
-
# Femtet の起動方法を取得
|
|
271
|
-
method = None
|
|
272
|
-
if callback_context.triggered_id == cls.ID_LAUNCH_FEMTET_BUTTON:
|
|
273
|
-
method = 'new'
|
|
274
|
-
elif callback_context.triggered_id == cls.ID_CONNECT_FEMTET_BUTTON:
|
|
275
|
-
method = 'existing'
|
|
276
|
-
|
|
277
|
-
# Femtet を起動
|
|
278
|
-
try:
|
|
279
|
-
cls.fem = FemtetInterface(
|
|
280
|
-
femprj_path=femprj_path,
|
|
281
|
-
model_name=model_name,
|
|
282
|
-
connect_method=method,
|
|
283
|
-
allow_without_project=True
|
|
284
|
-
)
|
|
285
|
-
cls.fem.quit_when_destruct = False
|
|
286
|
-
ret[femtet_state_data] = {'pid': cls.fem.femtet_pid}
|
|
287
|
-
except Exception as e: # 広めに取る
|
|
288
|
-
msg = e.args[0]
|
|
289
|
-
color = 'danger'
|
|
290
|
-
new_alerts = add_alert(current_alerts, msg, color)
|
|
291
|
-
ret[femtet_state_data] = {'pid': 0} # button の disable を再制御するため
|
|
292
|
-
ret[key_new_alerts] = new_alerts
|
|
293
|
-
|
|
294
|
-
return tuple(ret.values())
|
|
295
|
-
|
|
296
|
-
#
|
|
297
|
-
# DESCRIPTION
|
|
298
|
-
# Femtet の情報をアップデートするなどの監視 callback
|
|
299
|
-
@home.monitor.app.callback(
|
|
300
|
-
[
|
|
301
|
-
Output(cls.ID_FEMTET_STATE_DATA, cls.ATTR_FEMTET_DATA),
|
|
302
|
-
],
|
|
303
|
-
[
|
|
304
|
-
Input(cls.ID_INTERVAL, 'n_intervals'),
|
|
305
|
-
],
|
|
306
|
-
[
|
|
307
|
-
State(cls.ID_FEMTET_STATE_DATA, cls.ATTR_FEMTET_DATA)
|
|
308
|
-
],
|
|
309
|
-
)
|
|
310
|
-
def interval_callback(n_intervals, current_femtet_data):
|
|
311
|
-
logger.debug('interval_callback')
|
|
312
|
-
|
|
313
|
-
# 戻り値
|
|
314
|
-
ret = {(femtet_data := 1): no_update}
|
|
315
|
-
|
|
316
|
-
# 引数の処理
|
|
317
|
-
n_intervals = n_intervals or 0
|
|
318
|
-
current_femtet_data = current_femtet_data or {}
|
|
319
|
-
|
|
320
|
-
# check pid
|
|
321
|
-
if 'pid' in current_femtet_data.keys():
|
|
322
|
-
pid = current_femtet_data['pid']
|
|
323
|
-
if pid > 0:
|
|
324
|
-
# 生きていると主張するなら、死んでいれば更新
|
|
325
|
-
if not psutil.pid_exists(pid):
|
|
326
|
-
ret[femtet_data] = current_femtet_data.update({'pid': 0})
|
|
327
|
-
|
|
328
|
-
return tuple(ret.values())
|
|
329
|
-
|
|
330
|
-
#
|
|
331
|
-
# DESCRIPTION
|
|
332
|
-
# Femtet の生死でボタンを disable にする callback
|
|
333
|
-
@home.monitor.app.callback(
|
|
334
|
-
[
|
|
335
|
-
Output(cls.ID_CONNECT_FEMTET_DROPDOWN, 'disabled', allow_duplicate=True),
|
|
336
|
-
Output(cls.ID_TRANSFER_PARAMETER_BUTTON, 'disabled', allow_duplicate=True),
|
|
337
|
-
],
|
|
338
|
-
[
|
|
339
|
-
Input(cls.ID_FEMTET_STATE_DATA, cls.ATTR_FEMTET_DATA),
|
|
340
|
-
],
|
|
341
|
-
prevent_initial_call=True,
|
|
342
|
-
)
|
|
343
|
-
def disable_femtet_button_on_state(femtet_data):
|
|
344
|
-
logger.debug(f'disable_femtet_button')
|
|
345
|
-
ret = {
|
|
346
|
-
(femtet_button_disabled := '1'): no_update,
|
|
347
|
-
(transfer_button_disabled := '3'): no_update,
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if callback_context.triggered_id is not None:
|
|
351
|
-
femtet_data = femtet_data or {}
|
|
352
|
-
if femtet_data['pid'] > 0:
|
|
353
|
-
ret[femtet_button_disabled] = True
|
|
354
|
-
ret[transfer_button_disabled] = False
|
|
355
|
-
else:
|
|
356
|
-
ret[femtet_button_disabled] = False
|
|
357
|
-
ret[transfer_button_disabled] = True
|
|
358
|
-
|
|
359
|
-
return tuple(ret.values())
|
|
360
|
-
|
|
361
|
-
#
|
|
362
|
-
# DESCRIPTION: Femtet 起動ボタンを押したとき、起動完了を待たずにボタンを disable にする callback
|
|
363
|
-
@home.monitor.app.callback(
|
|
364
|
-
[
|
|
365
|
-
Output(cls.ID_CONNECT_FEMTET_DROPDOWN, 'disabled', allow_duplicate=True),
|
|
366
|
-
Output(cls.ID_TRANSFER_PARAMETER_BUTTON, 'disabled', allow_duplicate=True),
|
|
367
|
-
],
|
|
368
|
-
[
|
|
369
|
-
Input(cls.ID_LAUNCH_FEMTET_BUTTON, 'n_clicks'),
|
|
370
|
-
Input(cls.ID_CONNECT_FEMTET_BUTTON, 'n_clicks'),
|
|
371
|
-
],
|
|
372
|
-
prevent_initial_call=True,
|
|
373
|
-
)
|
|
374
|
-
def disable_femtet_button_immediately(launch_n_clicks, connect_n_clicks):
|
|
375
|
-
logger.debug(f'disable_button_on_state')
|
|
376
|
-
ret = {
|
|
377
|
-
(disable_femtet_button := '1'): no_update,
|
|
378
|
-
(disable_transfer_button := '2'): no_update,
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if callback_context.triggered_id is not None:
|
|
382
|
-
ret[disable_femtet_button] = True
|
|
383
|
-
ret[disable_transfer_button] = True
|
|
384
|
-
|
|
385
|
-
return tuple(ret.values())
|
|
386
|
-
|
|
387
|
-
#
|
|
388
|
-
# DESCRIPTION: 転送ボタンを押すと Femtet にデータを転送する callback
|
|
389
|
-
@home.monitor.app.callback(
|
|
390
|
-
[
|
|
391
|
-
Output(home.ID_DUMMY, 'children', allow_duplicate=True),
|
|
392
|
-
Output(cls.ID_ALERTS, 'children', allow_duplicate=True),
|
|
393
|
-
],
|
|
394
|
-
[
|
|
395
|
-
Input(cls.ID_TRANSFER_PARAMETER_BUTTON, 'n_clicks'),
|
|
396
|
-
],
|
|
397
|
-
[
|
|
398
|
-
State(cls.ID_FEMTET_STATE_DATA, cls.ATTR_FEMTET_DATA),
|
|
399
|
-
State(home.ID_SELECTION_DATA, home.ATTR_SELECTION_DATA),
|
|
400
|
-
State(cls.ID_ALERTS, 'children'),
|
|
401
|
-
],
|
|
402
|
-
prevent_initial_call=True,
|
|
403
|
-
)
|
|
404
|
-
def transfer_parameter_to_femtet(
|
|
405
|
-
n_clicks,
|
|
406
|
-
femtet_data,
|
|
407
|
-
selection_data,
|
|
408
|
-
current_alerts,
|
|
409
|
-
):
|
|
410
|
-
logger.debug('transfer_parameter_to_femtet')
|
|
411
|
-
|
|
412
|
-
# 戻り値
|
|
413
|
-
ret = {
|
|
414
|
-
(dummy := 1): no_update,
|
|
415
|
-
(key_new_alerts := 2): no_update,
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
# 引数の処理
|
|
419
|
-
femtet_data = femtet_data or {}
|
|
420
|
-
selection_data = selection_data or {}
|
|
421
|
-
alerts = current_alerts or []
|
|
422
|
-
|
|
423
|
-
# Femtet が生きているか
|
|
424
|
-
femtet_alive = False
|
|
425
|
-
logger.debug(femtet_data.keys())
|
|
426
|
-
if 'pid' in femtet_data.keys():
|
|
427
|
-
pid = femtet_data['pid']
|
|
428
|
-
logger.debug(pid)
|
|
429
|
-
if (pid > 0) and (psutil.pid_exists(pid)): # pid_exists(0) == True
|
|
430
|
-
femtet_alive = True
|
|
431
|
-
|
|
432
|
-
additional_alerts = []
|
|
433
|
-
|
|
434
|
-
if not femtet_alive:
|
|
435
|
-
msg = '接続された Femtet が見つかりません。'
|
|
436
|
-
color = 'danger'
|
|
437
|
-
logger.warning(msg)
|
|
438
|
-
additional_alerts = add_alert(additional_alerts, msg, color)
|
|
439
|
-
|
|
440
|
-
if 'points' not in selection_data.keys():
|
|
441
|
-
msg = '何も選択されていません。'
|
|
442
|
-
color = 'danger'
|
|
443
|
-
logger.warning(msg)
|
|
444
|
-
additional_alerts = add_alert(additional_alerts, msg, color)
|
|
445
|
-
|
|
446
|
-
logger.debug(additional_alerts)
|
|
447
|
-
logger.debug(len(additional_alerts) > 0)
|
|
448
|
-
|
|
449
|
-
# この時点で警告(エラー)があれば処理せず終了
|
|
450
|
-
if len(additional_alerts) > 0:
|
|
451
|
-
alerts.extend(additional_alerts)
|
|
452
|
-
ret[key_new_alerts] = alerts
|
|
453
|
-
logger.debug(alerts)
|
|
454
|
-
return tuple(ret.values())
|
|
455
|
-
|
|
456
|
-
# もし手動で開いているなら現在開いているファイルとモデルを記憶させる
|
|
457
|
-
if cls.fem.femprj_path is None:
|
|
458
|
-
cls.fem.femprj_path = cls.fem.Femtet.Project
|
|
459
|
-
cls.fem.model_name = cls.fem.Femtet.AnalysisModelName
|
|
460
|
-
|
|
461
|
-
points_dicts = selection_data['points']
|
|
462
|
-
for points_dict in points_dicts:
|
|
463
|
-
logger.debug(points_dict)
|
|
464
|
-
trial = points_dict['customdata'][0]
|
|
465
|
-
logger.debug(trial)
|
|
466
|
-
index = trial - 1
|
|
467
|
-
names = [name for name in home.monitor.local_df.columns if name.startswith('prm_')]
|
|
468
|
-
values = home.monitor.local_df.iloc[index][names]
|
|
469
|
-
|
|
470
|
-
df = pd.DataFrame(
|
|
471
|
-
dict(
|
|
472
|
-
name=[name[4:] for name in names],
|
|
473
|
-
value=values,
|
|
474
|
-
)
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
try:
|
|
478
|
-
# femprj の保存先を設定
|
|
479
|
-
wo_ext, ext = os.path.splitext(cls.fem.femprj_path)
|
|
480
|
-
new_femprj_path = wo_ext + f'_trial{trial}' + ext
|
|
481
|
-
|
|
482
|
-
# 保存できないエラー
|
|
483
|
-
if os.path.exists(new_femprj_path):
|
|
484
|
-
msg = f'{new_femprj_path} は存在するため、保存はスキップされます。'
|
|
485
|
-
color = 'danger'
|
|
486
|
-
alerts = add_alert(alerts, msg, color)
|
|
487
|
-
ret[key_new_alerts] = alerts
|
|
488
|
-
|
|
489
|
-
else:
|
|
490
|
-
# Femtet に値を転送
|
|
491
|
-
warnings = cls.fem.update_model(df, with_warning=True) # exception の可能性
|
|
492
|
-
for msg in warnings:
|
|
493
|
-
color = 'warning'
|
|
494
|
-
logger.warning(msg)
|
|
495
|
-
alerts = add_alert(alerts, msg, color)
|
|
496
|
-
ret[key_new_alerts] = alerts
|
|
497
|
-
|
|
498
|
-
# 存在する femprj に対して bForce=False で SaveProject すると
|
|
499
|
-
# Exception が発生して except 節に飛んでしまう
|
|
500
|
-
cls.fem.Femtet.SaveProject(
|
|
501
|
-
new_femprj_path, # ProjectFile
|
|
502
|
-
False # bForce
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
except Exception as e: # 広めに取る
|
|
506
|
-
msg = ' '.join([arg for arg in e.args if type(arg) is str]) + 'このエラーが発生する主な理由は、Femtet でプロジェクトが開かれていないことです。'
|
|
507
|
-
color = 'danger'
|
|
508
|
-
alerts = add_alert(alerts, msg, color)
|
|
509
|
-
ret[key_new_alerts] = alerts
|
|
510
|
-
|
|
511
|
-
# 別のファイルを開いているならば元に戻す
|
|
512
|
-
if cls.fem.Femtet.Project != cls.fem.femprj_path:
|
|
513
|
-
try:
|
|
514
|
-
cls.fem.Femtet.LoadProjectAndAnalysisModel(
|
|
515
|
-
cls.fem.femprj_path, # ProjectFile
|
|
516
|
-
cls.fem.model_name, # AnalysisModelName
|
|
517
|
-
True # bForce
|
|
518
|
-
)
|
|
519
|
-
except Exception as e:
|
|
520
|
-
msg = ' '.join([arg for arg in e.args if type(arg) is str]) + '元のファイルを開けません。'
|
|
521
|
-
color = 'danger'
|
|
522
|
-
alerts = add_alert(alerts, msg, color)
|
|
523
|
-
ret[key_new_alerts] = alerts
|
|
524
|
-
|
|
525
|
-
return tuple(ret.values())
|
|
526
|
-
|
|
527
|
-
#
|
|
528
|
-
# DESCRIPTION
|
|
529
|
-
# Alerts を全部消す callback
|
|
530
|
-
@home.monitor.app.callback(
|
|
531
|
-
[
|
|
532
|
-
Output(cls.ID_ALERTS, 'children', allow_duplicate=True),
|
|
533
|
-
],
|
|
534
|
-
[
|
|
535
|
-
Input(cls.ID_CLEAR_ALERTS, 'n_clicks'),
|
|
536
|
-
],
|
|
537
|
-
prevent_initial_call=True
|
|
538
|
-
)
|
|
539
|
-
def clear_all_alerts(
|
|
540
|
-
clear_n_clicks
|
|
541
|
-
):
|
|
542
|
-
ret = {
|
|
543
|
-
(alerts_key := 1): no_update
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
if callback_context.triggered_id is not None:
|
|
547
|
-
ret[alerts_key] = []
|
|
548
|
-
|
|
549
|
-
return tuple(ret.values())
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
class HomePageBase:
|
|
553
|
-
|
|
554
|
-
layout = None
|
|
555
|
-
|
|
556
|
-
# component id
|
|
557
|
-
ID_DUMMY = 'home-dummy'
|
|
558
|
-
ID_INTERVAL = 'home-interval'
|
|
559
|
-
ID_GRAPH_TABS = 'home-graph-tabs'
|
|
560
|
-
ID_GRAPH_CARD_BODY = 'home-graph-card-body'
|
|
561
|
-
ID_GRAPH = 'home-graph'
|
|
562
|
-
ID_GRAPH_TOOLTIP = 'home-graph-hover-tooltip'
|
|
563
|
-
ID_SELECTION_DATA = 'home-selection-data'
|
|
564
|
-
|
|
565
|
-
# selection data attribute
|
|
566
|
-
ATTR_SELECTION_DATA = 'data-selection' # should start with data-*
|
|
567
|
-
|
|
568
|
-
# invisible components
|
|
569
|
-
dummy = html.Div(id=ID_DUMMY)
|
|
570
|
-
interval = dcc.Interval(id=ID_INTERVAL, interval=1000, n_intervals=0)
|
|
571
|
-
|
|
572
|
-
# visible components
|
|
573
|
-
header = html.H1('最適化の結果分析')
|
|
574
|
-
graph_card = dbc.Card()
|
|
575
|
-
contents = dbc.Container(fluid=True)
|
|
576
|
-
|
|
577
|
-
# card + tabs + tab + loading + graph 用
|
|
578
|
-
graphs = {} # {tab_id: {label, fig_func}}
|
|
579
|
-
|
|
580
|
-
def __init__(self, monitor):
|
|
581
|
-
self.monitor = monitor
|
|
582
|
-
self.app: Dash = monitor.app
|
|
583
|
-
self.history = monitor.history
|
|
584
|
-
self.setup_graph_card()
|
|
585
|
-
self.setup_contents()
|
|
586
|
-
self.setup_layout()
|
|
587
|
-
|
|
588
|
-
def setup_graph_card(self):
|
|
589
|
-
|
|
590
|
-
# graph の追加
|
|
591
|
-
default_tab = 'tab-id-1'
|
|
592
|
-
self.graphs[default_tab] = dict(
|
|
593
|
-
label='目的プロット',
|
|
594
|
-
fig_func=update_default_figure,
|
|
595
|
-
)
|
|
596
|
-
|
|
597
|
-
self.graphs['tab-id-2'] = dict(
|
|
598
|
-
label='ハイパーボリューム',
|
|
599
|
-
fig_func=update_hypervolume_plot,
|
|
600
|
-
)
|
|
601
|
-
|
|
602
|
-
# graphs から tab 作成
|
|
603
|
-
tabs = []
|
|
604
|
-
for tab_id, graph in self.graphs.items():
|
|
605
|
-
tabs.append(dbc.Tab(label=graph['label'], tab_id=tab_id))
|
|
606
|
-
|
|
607
|
-
# tab から tabs 作成
|
|
608
|
-
tabs_component = dbc.Tabs(
|
|
609
|
-
tabs,
|
|
610
|
-
id=self.ID_GRAPH_TABS,
|
|
611
|
-
active_tab=default_tab,
|
|
612
|
-
)
|
|
613
|
-
|
|
614
|
-
# タブ + グラフ本体のコンポーネント作成
|
|
615
|
-
self.graph_card = dbc.Card(
|
|
616
|
-
[
|
|
617
|
-
dbc.CardHeader(tabs_component),
|
|
618
|
-
dbc.CardBody(
|
|
619
|
-
children=[
|
|
620
|
-
# Loading : child が Output である callback について、
|
|
621
|
-
# それが発火してから return するまでの間 Spinner が出てくる
|
|
622
|
-
html.Div([
|
|
623
|
-
dcc.Loading(
|
|
624
|
-
dcc.Graph(id=self.ID_GRAPH, clear_on_unhover=True, figure=self.get_fig_by_tab_id(default_tab)),
|
|
625
|
-
),
|
|
626
|
-
dcc.Tooltip(id=self.ID_GRAPH_TOOLTIP),
|
|
627
|
-
]),
|
|
628
|
-
],
|
|
629
|
-
id=self.ID_GRAPH_CARD_BODY,
|
|
630
|
-
),
|
|
631
|
-
html.Data(
|
|
632
|
-
id=self.ID_SELECTION_DATA,
|
|
633
|
-
**{self.ATTR_SELECTION_DATA: {}}
|
|
634
|
-
),
|
|
635
|
-
],
|
|
636
|
-
)
|
|
637
|
-
|
|
638
|
-
# Loading 表示のためページロード時のみ発火させる callback
|
|
639
|
-
@self.app.callback(
|
|
640
|
-
[
|
|
641
|
-
Output(self.ID_GRAPH, 'figure'),
|
|
642
|
-
],
|
|
643
|
-
[
|
|
644
|
-
Input(self.ID_DUMMY, 'children'),
|
|
645
|
-
],
|
|
646
|
-
[
|
|
647
|
-
State(self.ID_GRAPH_TABS, 'active_tab'),
|
|
648
|
-
]
|
|
649
|
-
)
|
|
650
|
-
def on_load(_, active_tab):
|
|
651
|
-
# 1. initial_call または 2. card-body (即ちその中の graph) が変化した時に call される
|
|
652
|
-
# 2 で call された場合は ctx に card_body が格納されるのでそれで判定する
|
|
653
|
-
initial_call = callback_context.triggered_id is None
|
|
654
|
-
if initial_call:
|
|
655
|
-
return [self.get_fig_by_tab_id(active_tab)]
|
|
656
|
-
else:
|
|
657
|
-
raise PreventUpdate
|
|
658
|
-
|
|
659
|
-
# tab によるグラフ切替 callback
|
|
660
|
-
@self.app.callback(
|
|
661
|
-
[Output(self.ID_GRAPH, 'figure', allow_duplicate=True),],
|
|
662
|
-
[Input(self.ID_GRAPH_TABS, 'active_tab'),],
|
|
663
|
-
prevent_initial_call=True,
|
|
664
|
-
)
|
|
665
|
-
def switch_fig_by_tab(active_tab_id):
|
|
666
|
-
logger.debug(f'switch_fig_by_tab: {active_tab_id}')
|
|
667
|
-
if active_tab_id in self.graphs.keys():
|
|
668
|
-
fig_func = self.graphs[active_tab_id]['fig_func']
|
|
669
|
-
fig = fig_func(self.history, self.monitor.local_df)
|
|
670
|
-
else:
|
|
671
|
-
from plotly.graph_objects import Figure
|
|
672
|
-
fig = Figure()
|
|
673
|
-
|
|
674
|
-
return [fig]
|
|
675
|
-
|
|
676
|
-
# 選択したデータを記憶する callback
|
|
677
|
-
@self.monitor.app.callback(
|
|
678
|
-
[
|
|
679
|
-
Output(self.ID_SELECTION_DATA, self.ATTR_SELECTION_DATA),
|
|
680
|
-
],
|
|
681
|
-
[
|
|
682
|
-
Input(self.ID_GRAPH, 'selectedData')
|
|
683
|
-
],
|
|
684
|
-
)
|
|
685
|
-
def on_select(selected_data):
|
|
686
|
-
logger.debug(f'on_select: {selected_data}')
|
|
687
|
-
return [selected_data]
|
|
688
|
-
|
|
689
|
-
# ホバーに画像を表示する callback
|
|
690
|
-
@self.monitor.app.callback(
|
|
691
|
-
Output(self.ID_GRAPH_TOOLTIP, "show"),
|
|
692
|
-
Output(self.ID_GRAPH_TOOLTIP, "bbox"),
|
|
693
|
-
Output(self.ID_GRAPH_TOOLTIP, "children"),
|
|
694
|
-
Input(self.ID_GRAPH, "hoverData"),
|
|
695
|
-
)
|
|
696
|
-
def display_hover(hoverData):
|
|
697
|
-
if hoverData is None:
|
|
698
|
-
return False, no_update, no_update
|
|
699
|
-
|
|
700
|
-
pt = hoverData["points"][0]
|
|
701
|
-
bbox = pt["bbox"]
|
|
702
|
-
|
|
703
|
-
# get row of the history
|
|
704
|
-
trial = pt['customdata'][0]
|
|
705
|
-
row = self.monitor.local_df[self.monitor.local_df['trial'] == trial]
|
|
706
|
-
|
|
707
|
-
# === create hovered data ===
|
|
708
|
-
# get encoded image from history.additional_metadata
|
|
709
|
-
img_url = None
|
|
710
|
-
|
|
711
|
-
# Femtet specified processing
|
|
712
|
-
metadata = self.history.metadata
|
|
713
|
-
if metadata[0] != '':
|
|
714
|
-
# get img path
|
|
715
|
-
d = json.loads(metadata[0])
|
|
716
|
-
femprj_path = d['femprj_path']
|
|
717
|
-
model_name = d['model_name']
|
|
718
|
-
femprj_result_dir = femprj_path.replace('.femprj', '.Results')
|
|
719
|
-
img_path = os.path.join(femprj_result_dir, f'{model_name}_trial{trial}.jpg')
|
|
720
|
-
if os.path.exists(img_path):
|
|
721
|
-
# create encoded image
|
|
722
|
-
with open(img_path, 'rb') as f:
|
|
723
|
-
content = f.read()
|
|
724
|
-
encoded_image = base64.b64encode(content).decode('utf-8')
|
|
725
|
-
img_url = 'data:image/jpeg;base64, ' + encoded_image
|
|
726
|
-
html_img = html.Img(src=img_url, style={"width": "200px"}) if img_url is not None else html.Div()
|
|
727
|
-
|
|
728
|
-
# parameters
|
|
729
|
-
pd.options.display.float_format = '{:.4e}'.format
|
|
730
|
-
parameters = row.iloc[:, np.where(np.array(metadata) == 'prm')[0]]
|
|
731
|
-
names = parameters.columns
|
|
732
|
-
values = [f'{value:.3e}' for value in parameters.values.ravel()]
|
|
733
|
-
data = pd.DataFrame(dict(
|
|
734
|
-
name=names, value=values
|
|
735
|
-
))
|
|
736
|
-
|
|
737
|
-
# descript result
|
|
738
|
-
desc = html.Div([
|
|
739
|
-
html.H3(f"trial{trial}", style={"color": "darkblue"}),
|
|
740
|
-
dash_table.DataTable(
|
|
741
|
-
columns=[{'name': col, 'id': col} for col in data.columns],
|
|
742
|
-
data=data.to_dict('records')
|
|
743
|
-
),
|
|
744
|
-
])
|
|
745
|
-
|
|
746
|
-
# make output
|
|
747
|
-
children = html.Div([
|
|
748
|
-
html.Div(html_img, style={'display': 'inline-block', 'margin-right': '10px', 'vertical-align': 'top'}),
|
|
749
|
-
html.Div(desc, style={'display': 'inline-block', 'margin-right': '10px'})
|
|
750
|
-
])
|
|
751
|
-
|
|
752
|
-
return True, bbox, children
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
def get_fig_by_tab_id(self, tab_id):
|
|
756
|
-
if tab_id in self.graphs.keys():
|
|
757
|
-
fig_func = self.graphs[tab_id]['fig_func']
|
|
758
|
-
fig = fig_func(self.history, self.monitor.local_df)
|
|
759
|
-
else:
|
|
760
|
-
fig = Figure()
|
|
761
|
-
return fig
|
|
762
|
-
|
|
763
|
-
def setup_contents(self):
|
|
764
|
-
pass
|
|
765
|
-
|
|
766
|
-
def setup_layout(self):
|
|
767
|
-
# https://dash-bootstrap-components.opensource.faculty.ai/docs/components/accordion/
|
|
768
|
-
self.layout = dbc.Container([
|
|
769
|
-
dbc.Row([dbc.Col(self.dummy), dbc.Col(self.interval)]),
|
|
770
|
-
dbc.Row([dbc.Col(self.header)]),
|
|
771
|
-
dbc.Row([dbc.Col(self.graph_card)]),
|
|
772
|
-
dbc.Row([dbc.Col(self.contents)]),
|
|
773
|
-
], fluid=True)
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
class ResultViewerAppHomePage(HomePageBase):
|
|
778
|
-
|
|
779
|
-
def setup_contents(self):
|
|
780
|
-
# Femtet control
|
|
781
|
-
div = FemtetControl.create_components()
|
|
782
|
-
FemtetControl.add_callback(self)
|
|
783
|
-
|
|
784
|
-
# note
|
|
785
|
-
note = dcc.Markdown(
|
|
786
|
-
'---\n'
|
|
787
|
-
'- 最適化の結果分析画面です。\n'
|
|
788
|
-
'- 凡例をクリックすると、対応する要素の表示/非表示を切り替えます。\n'
|
|
789
|
-
'- ブラウザを使用しますが、解析結果のインターネット通信は行いません。\n'
|
|
790
|
-
'- ブラウザを閉じてもプログラムは終了しません。\n'
|
|
791
|
-
' - コマンドプロンプトを閉じるかコマンドプロンプトに `CTRL+C` を入力してプログラムを終了してください。\n'
|
|
792
|
-
)
|
|
793
|
-
self.contents.children = [
|
|
794
|
-
dbc.Row(div),
|
|
795
|
-
dbc.Row(note)
|
|
796
|
-
]
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
class ProcessMonitorAppHomePage(HomePageBase):
|
|
800
|
-
|
|
801
|
-
# component id for Monitor
|
|
802
|
-
ID_ENTIRE_PROCESS_STATUS_ALERT = 'home-entire-process-status-alert'
|
|
803
|
-
ID_ENTIRE_PROCESS_STATUS_ALERT_CHILDREN = 'home-entire-process-status-alert-children'
|
|
804
|
-
ID_TOGGLE_INTERVAL_BUTTON = 'home-toggle-interval-button'
|
|
805
|
-
ID_INTERRUPT_PROCESS_BUTTON = 'home-interrupt-process-button'
|
|
806
|
-
|
|
807
|
-
def setup_contents(self):
|
|
808
|
-
|
|
809
|
-
# header
|
|
810
|
-
self.header.children = '最適化の進捗状況'
|
|
811
|
-
|
|
812
|
-
# whole status
|
|
813
|
-
status_alert = dbc.Alert(
|
|
814
|
-
children=html.H4(
|
|
815
|
-
'Optimization status will be shown here.',
|
|
816
|
-
className='alert-heading',
|
|
817
|
-
id=self.ID_ENTIRE_PROCESS_STATUS_ALERT_CHILDREN,
|
|
818
|
-
),
|
|
819
|
-
id=self.ID_ENTIRE_PROCESS_STATUS_ALERT,
|
|
820
|
-
color='secondary',
|
|
821
|
-
)
|
|
822
|
-
|
|
823
|
-
# buttons
|
|
824
|
-
toggle_interval_button = dbc.Button('グラフ自動更新を一時停止', id=self.ID_TOGGLE_INTERVAL_BUTTON, color='primary')
|
|
825
|
-
interrupt_button = dbc.Button('最適化を中断', id=self.ID_INTERRUPT_PROCESS_BUTTON, color='danger')
|
|
826
|
-
|
|
827
|
-
note = dcc.Markdown(
|
|
828
|
-
'---\n'
|
|
829
|
-
'- 最適化の結果分析画面です。\n'
|
|
830
|
-
'- ブラウザを使用しますが、解析結果のインターネット通信は行いません。\n'
|
|
831
|
-
'- この画面を閉じても最適化は中断されません。\n'
|
|
832
|
-
f'- この画面を再び開くにはブラウザのアドレスバーに「localhost:{self.monitor.DEFAULT_PORT}」と入力して下さい。\n'
|
|
833
|
-
'- __マウスオーバーで表示されるデータを見る場合は、「自動更新を一時停止する」ボタンを押してください。__\n'
|
|
834
|
-
)
|
|
835
|
-
|
|
836
|
-
self.contents.children = [
|
|
837
|
-
dbc.Row([dbc.Col(status_alert)]),
|
|
838
|
-
dbc.Row([
|
|
839
|
-
dbc.Col(toggle_interval_button, style=DBC_COLUMN_STYLE_CENTER),
|
|
840
|
-
dbc.Col(interrupt_button, style=DBC_COLUMN_STYLE_CENTER)
|
|
841
|
-
]),
|
|
842
|
-
dbc.Row([dbc.Col(note)]),
|
|
843
|
-
]
|
|
844
|
-
|
|
845
|
-
self.add_callback()
|
|
846
|
-
|
|
847
|
-
def add_callback(self):
|
|
848
|
-
# 1. interval => figure を更新する
|
|
849
|
-
# 2. btn interrupt => x(status を interrupt にする) and (interrupt を無効)
|
|
850
|
-
# 3. btn toggle => (toggle の children を切替) and (interval を切替)
|
|
851
|
-
# a. status => status-alert を更新
|
|
852
|
-
# b. status terminated => (toggle を無効) and (interrupt を無効) and (interval を無効)
|
|
853
|
-
@self.monitor.app.callback(
|
|
854
|
-
[
|
|
855
|
-
Output(self.ID_GRAPH_CARD_BODY, 'children'), # 1
|
|
856
|
-
Output(self.ID_INTERRUPT_PROCESS_BUTTON, 'disabled'), # 2, b
|
|
857
|
-
Output(self.ID_TOGGLE_INTERVAL_BUTTON, 'children'), # 3
|
|
858
|
-
Output(self.ID_INTERVAL, 'max_intervals'), # 3, b
|
|
859
|
-
Output(self.ID_TOGGLE_INTERVAL_BUTTON, 'disabled'), # b
|
|
860
|
-
Output(self.ID_ENTIRE_PROCESS_STATUS_ALERT_CHILDREN, 'children'), # a
|
|
861
|
-
Output(self.ID_ENTIRE_PROCESS_STATUS_ALERT, 'color'), # a
|
|
862
|
-
],
|
|
863
|
-
[
|
|
864
|
-
Input(self.ID_INTERVAL, 'n_intervals'), # 1
|
|
865
|
-
Input(self.ID_INTERRUPT_PROCESS_BUTTON, 'n_clicks'), # 2
|
|
866
|
-
Input(self.ID_TOGGLE_INTERVAL_BUTTON, 'n_clicks'), # 3
|
|
867
|
-
],
|
|
868
|
-
[
|
|
869
|
-
State(self.ID_GRAPH_TABS, "active_tab"),
|
|
870
|
-
],
|
|
871
|
-
prevent_initial_call=True,
|
|
872
|
-
)
|
|
873
|
-
def monitor_feature(
|
|
874
|
-
_1,
|
|
875
|
-
_2,
|
|
876
|
-
toggle_n_clicks,
|
|
877
|
-
active_tab_id,
|
|
878
|
-
):
|
|
879
|
-
# 発火確認
|
|
880
|
-
logger.debug(f'monitor_feature: {active_tab_id}')
|
|
881
|
-
|
|
882
|
-
# 引数の処理
|
|
883
|
-
toggle_n_clicks = toggle_n_clicks or 0
|
|
884
|
-
trigger = callback_context.triggered_id or 'implicit trigger'
|
|
885
|
-
|
|
886
|
-
# cls
|
|
887
|
-
from pyfemtet.opt._femopt_core import OptimizationStatus
|
|
888
|
-
|
|
889
|
-
# return 値の default 値(Python >= 3.7 で順番を保持する仕様を利用)
|
|
890
|
-
ret = {
|
|
891
|
-
(card_body := 'card_body'): no_update,
|
|
892
|
-
(disable_interrupt := 'disable_interrupt'): no_update,
|
|
893
|
-
(toggle_btn_msg := 'toggle_btn_msg'): no_update,
|
|
894
|
-
(max_intervals := 'max_intervals'): no_update, # 0:disable, -1:enable
|
|
895
|
-
(disable_toggle := 'disable_toggle'): no_update,
|
|
896
|
-
(status_msg := 'status_children'): no_update,
|
|
897
|
-
(status_color := 'status_color'): no_update,
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
# 2. btn interrupt => x(status を interrupt にする) and (interrupt を無効)
|
|
901
|
-
if trigger == self.ID_INTERRUPT_PROCESS_BUTTON:
|
|
902
|
-
# status を更新
|
|
903
|
-
# component は下流の処理で更新する
|
|
904
|
-
self.monitor.local_entire_status_int = OptimizationStatus.INTERRUPTING
|
|
905
|
-
self.monitor.local_entire_status = OptimizationStatus.const_to_str(OptimizationStatus.INTERRUPTING)
|
|
906
|
-
|
|
907
|
-
# 1. interval => figure を更新する
|
|
908
|
-
if (len(self.monitor.local_df) > 0) and (active_tab_id is not None):
|
|
909
|
-
fig = self.get_fig_by_tab_id(active_tab_id)
|
|
910
|
-
ret[card_body] = [
|
|
911
|
-
dcc.Graph(figure=fig, id=self.ID_GRAPH, clear_on_unhover=True),
|
|
912
|
-
dcc.Tooltip(id=self.ID_GRAPH_TOOLTIP),
|
|
913
|
-
]
|
|
914
|
-
|
|
915
|
-
# 3. btn toggle => (toggle の children を切替) and (interval を切替)
|
|
916
|
-
if toggle_n_clicks % 2 == 1:
|
|
917
|
-
ret[max_intervals] = 0 # disable
|
|
918
|
-
ret[toggle_btn_msg] = '自動更新を再開'
|
|
919
|
-
else:
|
|
920
|
-
ret[max_intervals] = -1 # enable
|
|
921
|
-
ret[toggle_btn_msg] = '自動更新を一時停止'
|
|
922
|
-
|
|
923
|
-
# a. status => status-alert を更新
|
|
924
|
-
ret[status_msg] = 'Optimization status: ' + self.monitor.local_entire_status
|
|
925
|
-
if self.monitor.local_entire_status_int == OptimizationStatus.INTERRUPTING:
|
|
926
|
-
ret[status_color] = 'warning'
|
|
927
|
-
elif self.monitor.local_entire_status_int == OptimizationStatus.TERMINATED:
|
|
928
|
-
ret[status_color] = 'dark'
|
|
929
|
-
elif self.monitor.local_entire_status_int == OptimizationStatus.TERMINATE_ALL:
|
|
930
|
-
ret[status_color] = 'dark'
|
|
931
|
-
else:
|
|
932
|
-
ret[status_color] = 'primary'
|
|
933
|
-
|
|
934
|
-
# b. status terminated => (interrupt を無効) and (interval を無効)
|
|
935
|
-
# 中断以降なら中断ボタンを disable にする
|
|
936
|
-
if self.monitor.local_entire_status_int >= OptimizationStatus.INTERRUPTING:
|
|
937
|
-
ret[disable_interrupt] = True
|
|
938
|
-
# 終了以降ならさらに toggle, interval を disable にする
|
|
939
|
-
if self.monitor.local_entire_status_int >= OptimizationStatus.TERMINATED:
|
|
940
|
-
ret[max_intervals] = 0 # disable
|
|
941
|
-
ret[disable_toggle] = True
|
|
942
|
-
ret[toggle_btn_msg] = '更新されません'
|
|
943
|
-
|
|
944
|
-
return tuple(ret.values())
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
class ProcessMonitorAppWorkerPage:
|
|
948
|
-
|
|
949
|
-
# layout
|
|
950
|
-
layout = dbc.Container()
|
|
951
|
-
|
|
952
|
-
# id
|
|
953
|
-
ID_INTERVAL = 'worker-monitor-interval'
|
|
954
|
-
id_worker_alert_list = []
|
|
955
|
-
|
|
956
|
-
def __init__(self, monitor):
|
|
957
|
-
self.monitor = monitor
|
|
958
|
-
self.setup_layout()
|
|
959
|
-
self.add_callback()
|
|
960
|
-
|
|
961
|
-
def setup_layout(self):
|
|
962
|
-
# common
|
|
963
|
-
dummy = html.Div()
|
|
964
|
-
interval = dcc.Interval(id=self.ID_INTERVAL, interval=1000)
|
|
965
|
-
|
|
966
|
-
rows = [dbc.Row([dbc.Col(dummy), dbc.Col(interval)])]
|
|
967
|
-
|
|
968
|
-
# contents
|
|
969
|
-
worker_status_alerts = []
|
|
970
|
-
for i in range(len(self.monitor.worker_addresses)):
|
|
971
|
-
id_worker_alert = f'worker-status-alert-{i}'
|
|
972
|
-
alert = dbc.Alert('worker status here', id=id_worker_alert, color='dark')
|
|
973
|
-
worker_status_alerts.append(dbc.Row([dbc.Col(alert)]))
|
|
974
|
-
self.id_worker_alert_list.append(id_worker_alert)
|
|
975
|
-
|
|
976
|
-
rows.extend(worker_status_alerts)
|
|
977
|
-
|
|
978
|
-
self.layout = dbc.Container(rows, fluid=True)
|
|
979
|
-
|
|
980
|
-
def add_callback(self):
|
|
981
|
-
# worker_monitor のための callback
|
|
982
|
-
@self.monitor.app.callback(
|
|
983
|
-
[Output(f'{id_worker_alert}', 'children') for id_worker_alert in self.id_worker_alert_list],
|
|
984
|
-
[Output(f'{id_worker_alert}', 'color') for id_worker_alert in self.id_worker_alert_list],
|
|
985
|
-
[Input(self.ID_INTERVAL, 'n_intervals'),]
|
|
986
|
-
)
|
|
987
|
-
def update_worker_state(_):
|
|
988
|
-
|
|
989
|
-
from pyfemtet.opt._femopt_core import OptimizationStatus
|
|
990
|
-
|
|
991
|
-
ret = []
|
|
992
|
-
|
|
993
|
-
for worker_address, worker_status_int in zip(self.monitor.worker_addresses, self.monitor.local_worker_status_int_list):
|
|
994
|
-
worker_status_message = OptimizationStatus.const_to_str(worker_status_int)
|
|
995
|
-
ret.append(f'{worker_address} is {worker_status_message}')
|
|
996
|
-
|
|
997
|
-
colors = []
|
|
998
|
-
for status_int in self.monitor.local_worker_status_int_list:
|
|
999
|
-
if status_int == OptimizationStatus.INTERRUPTING:
|
|
1000
|
-
colors.append('warning')
|
|
1001
|
-
elif status_int == OptimizationStatus.TERMINATED:
|
|
1002
|
-
colors.append('dark')
|
|
1003
|
-
elif status_int == OptimizationStatus.CRASHED:
|
|
1004
|
-
colors.append('danger')
|
|
1005
|
-
else:
|
|
1006
|
-
colors.append('primary')
|
|
1007
|
-
|
|
1008
|
-
ret.extend(colors)
|
|
1009
|
-
|
|
1010
|
-
return tuple(ret)
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
class AppBase(object):
|
|
1014
|
-
|
|
1015
|
-
# process_monitor or not
|
|
1016
|
-
is_processing = False
|
|
1017
|
-
|
|
1018
|
-
# port
|
|
1019
|
-
DEFAULT_PORT = 49152
|
|
1020
|
-
|
|
1021
|
-
# members for sidebar application
|
|
1022
|
-
SIDEBAR_STYLE = {
|
|
1023
|
-
"position": "fixed",
|
|
1024
|
-
"top": 0,
|
|
1025
|
-
"left": 0,
|
|
1026
|
-
"bottom": 0,
|
|
1027
|
-
"width": "16rem",
|
|
1028
|
-
"padding": "2rem 1rem",
|
|
1029
|
-
"background-color": "#f8f9fa",
|
|
1030
|
-
}
|
|
1031
|
-
CONTENT_STYLE = {
|
|
1032
|
-
"margin-left": "18rem",
|
|
1033
|
-
"margin-right": "2rem",
|
|
1034
|
-
"padding": "2rem 1rem",
|
|
1035
|
-
}
|
|
1036
|
-
pages = {} # {href: layout}
|
|
1037
|
-
nav_links = {} # {order(positive float): NavLink}
|
|
1038
|
-
|
|
1039
|
-
# members updated by main threads
|
|
1040
|
-
local_df = None
|
|
1041
|
-
|
|
1042
|
-
def __init__(
|
|
1043
|
-
self,
|
|
1044
|
-
history,
|
|
1045
|
-
):
|
|
1046
|
-
# 引数の処理
|
|
1047
|
-
self.history = history
|
|
1048
|
-
|
|
1049
|
-
# df の初期化
|
|
1050
|
-
self.local_df = self.history.local_data
|
|
1051
|
-
|
|
1052
|
-
# app の立上げ
|
|
1053
|
-
self.app = Dash(
|
|
1054
|
-
__name__,
|
|
1055
|
-
external_stylesheets=[dbc.themes.BOOTSTRAP],
|
|
1056
|
-
title='PyFemtet Monitor',
|
|
1057
|
-
update_title=None,
|
|
1058
|
-
)
|
|
1059
|
-
|
|
1060
|
-
# def setup_some_page(self):
|
|
1061
|
-
# href = "/link-url"
|
|
1062
|
-
# page = SomePageClass(self)
|
|
1063
|
-
# self.pages[href] = page.layout
|
|
1064
|
-
# order: int = 1
|
|
1065
|
-
# self.nav_links[order] = dbc.NavLink('Some Page', href=href, active="exact")
|
|
1066
|
-
|
|
1067
|
-
def setup_layout(self):
|
|
1068
|
-
# setup sidebar
|
|
1069
|
-
# https://dash-bootstrap-components.opensource.faculty.ai/examples/simple-sidebar/
|
|
1070
|
-
|
|
1071
|
-
# sidebar に表示される順に並び替え
|
|
1072
|
-
ordered_items = sorted(self.nav_links.items(), key=lambda x: x[0])
|
|
1073
|
-
ordered_links = [value for key, value in ordered_items]
|
|
1074
|
-
|
|
1075
|
-
# sidebar と contents から app 全体の layout を作成
|
|
1076
|
-
sidebar = html.Div(
|
|
1077
|
-
[
|
|
1078
|
-
html.H2('PyFemtet Monitor', className='display-4'),
|
|
1079
|
-
html.Hr(),
|
|
1080
|
-
html.P('最適化結果の可視化', className='lead'),
|
|
1081
|
-
dbc.Nav(ordered_links, vertical=True, pills=True),
|
|
1082
|
-
],
|
|
1083
|
-
style=self.SIDEBAR_STYLE,
|
|
1084
|
-
)
|
|
1085
|
-
content = html.Div(id="page-content", style=self.CONTENT_STYLE)
|
|
1086
|
-
self.app.layout = html.Div([dcc.Location(id="url"), sidebar, content])
|
|
1087
|
-
|
|
1088
|
-
# sidebar によるページ遷移のための callback
|
|
1089
|
-
@self.app.callback(Output("page-content", "children"), [Input("url", "pathname")])
|
|
1090
|
-
def switch_page_content(pathname):
|
|
1091
|
-
if pathname in list(self.pages.keys()):
|
|
1092
|
-
return self.pages[pathname]
|
|
1093
|
-
|
|
1094
|
-
else:
|
|
1095
|
-
return html.Div(
|
|
1096
|
-
[
|
|
1097
|
-
html.H1("404: Not found", className="text-danger"),
|
|
1098
|
-
html.Hr(),
|
|
1099
|
-
html.P(f"The pathname {pathname} was not recognised..."),
|
|
1100
|
-
],
|
|
1101
|
-
className="p-3 bg-light rounded-3",
|
|
1102
|
-
)
|
|
1103
|
-
|
|
1104
|
-
def run(self, host='localhost', port=None):
|
|
1105
|
-
self.setup_layout()
|
|
1106
|
-
port = port or self.DEFAULT_PORT
|
|
1107
|
-
# port を検証
|
|
1108
|
-
port = _unused_port_number(port)
|
|
1109
|
-
# ブラウザを起動
|
|
1110
|
-
if host == '0.0.0.0':
|
|
1111
|
-
webbrowser.open(f'http://localhost:{str(port)}')
|
|
1112
|
-
else:
|
|
1113
|
-
webbrowser.open(f'http://{host}:{str(port)}')
|
|
1114
|
-
self.app.run(debug=False, host=host, port=port)
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
class ResultViewerApp(AppBase):
|
|
1118
|
-
|
|
1119
|
-
def __init__(
|
|
1120
|
-
self,
|
|
1121
|
-
history,
|
|
1122
|
-
):
|
|
1123
|
-
|
|
1124
|
-
# 引数の設定、app の設定
|
|
1125
|
-
super().__init__(history)
|
|
1126
|
-
|
|
1127
|
-
# page 設定
|
|
1128
|
-
self.setup_home()
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
def setup_home(self):
|
|
1132
|
-
href = "/"
|
|
1133
|
-
page = ResultViewerAppHomePage(self)
|
|
1134
|
-
self.pages[href] = page.layout
|
|
1135
|
-
order = 1
|
|
1136
|
-
self.nav_links[order] = dbc.NavLink("Home", href=href, active="exact")
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
class ProcessMonitorApp(AppBase):
|
|
1140
|
-
|
|
1141
|
-
DEFAULT_PORT = 8080
|
|
1142
|
-
|
|
1143
|
-
def __init__(
|
|
1144
|
-
self,
|
|
1145
|
-
history,
|
|
1146
|
-
status,
|
|
1147
|
-
worker_addresses: List[str],
|
|
1148
|
-
worker_status_list: List['pyfemtet.opt._core.OptimizationStatus'],
|
|
1149
|
-
):
|
|
1150
|
-
|
|
1151
|
-
self.status = status
|
|
1152
|
-
self.worker_addresses = worker_addresses
|
|
1153
|
-
self.worker_status_list = worker_status_list
|
|
1154
|
-
self.is_processing = True
|
|
1155
|
-
|
|
1156
|
-
# ログファイルの保存場所
|
|
1157
|
-
if logger.level != logging.DEBUG:
|
|
1158
|
-
log_path = history.path.replace('.csv', '.uilog')
|
|
1159
|
-
monitor_logger = logging.getLogger('werkzeug')
|
|
1160
|
-
monitor_logger.addHandler(logging.FileHandler(log_path))
|
|
1161
|
-
|
|
1162
|
-
# メインスレッドで更新してもらうメンバーを一旦初期化
|
|
1163
|
-
self.local_entire_status = self.status.get_text()
|
|
1164
|
-
self.local_entire_status_int = self.status.get()
|
|
1165
|
-
self.local_worker_status_int_list = [s.get() for s in self.worker_status_list]
|
|
1166
|
-
|
|
1167
|
-
# dash app 設定
|
|
1168
|
-
super().__init__(history)
|
|
1169
|
-
|
|
1170
|
-
# page 設定
|
|
1171
|
-
self.setup_home()
|
|
1172
|
-
self.setup_worker_monitor()
|
|
1173
|
-
|
|
1174
|
-
def setup_home(self):
|
|
1175
|
-
href = "/"
|
|
1176
|
-
page = ProcessMonitorAppHomePage(self)
|
|
1177
|
-
self.pages[href] = page.layout
|
|
1178
|
-
order = 1
|
|
1179
|
-
self.nav_links[order] = dbc.NavLink("Home", href=href, active="exact")
|
|
1180
|
-
|
|
1181
|
-
def setup_worker_monitor(self):
|
|
1182
|
-
href = "/worker-monitor/"
|
|
1183
|
-
page = ProcessMonitorAppWorkerPage(self)
|
|
1184
|
-
self.pages[href] = page.layout
|
|
1185
|
-
order = 2
|
|
1186
|
-
self.nav_links[order] = dbc.NavLink("Workers", href=href, active="exact")
|
|
1187
|
-
|
|
1188
|
-
def start_server(
|
|
1189
|
-
self,
|
|
1190
|
-
host=None,
|
|
1191
|
-
port=None,
|
|
1192
|
-
):
|
|
1193
|
-
host = host or 'localhost'
|
|
1194
|
-
port = port or self.DEFAULT_PORT
|
|
1195
|
-
|
|
1196
|
-
# dash app server を daemon thread で起動
|
|
1197
|
-
server_thread = Thread(
|
|
1198
|
-
target=self.run,
|
|
1199
|
-
args=(host, port,),
|
|
1200
|
-
daemon=True,
|
|
1201
|
-
)
|
|
1202
|
-
server_thread.start()
|
|
1203
|
-
|
|
1204
|
-
# dash app (=flask server) の callback で dask の actor にアクセスすると
|
|
1205
|
-
# おかしくなることがあるので、ここで必要な情報のみやり取りする
|
|
1206
|
-
from pyfemtet.opt._femopt_core import OptimizationStatus
|
|
1207
|
-
while True:
|
|
1208
|
-
# running 以前に monitor が current status を interrupting にしていれば actor に反映
|
|
1209
|
-
if (
|
|
1210
|
-
(self.status.get() <= OptimizationStatus.RUNNING) # メインプロセスが RUNNING 以前である
|
|
1211
|
-
and
|
|
1212
|
-
(self.local_entire_status_int == OptimizationStatus.INTERRUPTING) # monitor の status が INTERRUPT である
|
|
1213
|
-
):
|
|
1214
|
-
self.status.set(OptimizationStatus.INTERRUPTING)
|
|
1215
|
-
|
|
1216
|
-
# current status と df を actor から monitor に反映する
|
|
1217
|
-
self.local_entire_status_int = self.status.get()
|
|
1218
|
-
self.local_entire_status = self.status.get_text()
|
|
1219
|
-
self.local_df = self.history.actor_data.copy()
|
|
1220
|
-
self.local_worker_status_int_list = [s.get() for s in self.worker_status_list]
|
|
1221
|
-
|
|
1222
|
-
# terminate_all 指令があれば monitor server をホストするプロセスごと終了する
|
|
1223
|
-
if self.status.get() >= OptimizationStatus.TERMINATE_ALL:
|
|
1224
|
-
return 0 # take server down with me
|
|
1225
|
-
|
|
1226
|
-
# interval
|
|
1227
|
-
sleep(1)
|