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
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# type hint
|
|
2
|
+
from dash.development.base_component import Component
|
|
3
|
+
|
|
4
|
+
# application
|
|
5
|
+
from dash import Dash
|
|
6
|
+
import webbrowser
|
|
7
|
+
|
|
8
|
+
# callback
|
|
9
|
+
from dash import Output, Input # , State, no_update, callback_context
|
|
10
|
+
# from dash.exceptions import PreventUpdate
|
|
11
|
+
|
|
12
|
+
# components
|
|
13
|
+
# from dash import html, dcc
|
|
14
|
+
import dash_bootstrap_components
|
|
15
|
+
|
|
16
|
+
from pyfemtet.opt._femopt_core import History
|
|
17
|
+
from pyfemtet.opt.visualization.wrapped_components import html, dcc, dbc
|
|
18
|
+
|
|
19
|
+
# the others
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
import logging
|
|
22
|
+
import psutil
|
|
23
|
+
from pyfemtet.logger import get_logger
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
dash_logger = logging.getLogger('werkzeug')
|
|
27
|
+
dash_logger.setLevel(logging.ERROR)
|
|
28
|
+
|
|
29
|
+
logger = get_logger('viewer')
|
|
30
|
+
logger.setLevel(logging.ERROR)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AbstractPage(ABC):
|
|
34
|
+
"""Define content."""
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
=================
|
|
38
|
+
|~:8080/rel_url | <---- page.rel_url
|
|
39
|
+
=================
|
|
40
|
+
page. | | -------- |
|
|
41
|
+
title -->home| | | |
|
|
42
|
+
| | | <-------- sub_page.layout
|
|
43
|
+
| | -------- |
|
|
44
|
+
| ^ | |<--- page.layout
|
|
45
|
+
==|==============
|
|
46
|
+
|
|
|
47
|
+
sidebar
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
def __init__(self, title='base-page', rel_url='/', application=None):
|
|
51
|
+
self.layout: Component = None
|
|
52
|
+
self.rel_url = rel_url
|
|
53
|
+
self.title = title
|
|
54
|
+
self.application: PyFemtetApplicationBase = application
|
|
55
|
+
self.subpages = []
|
|
56
|
+
self.setup_component()
|
|
57
|
+
self.setup_layout()
|
|
58
|
+
|
|
59
|
+
def add_subpage(self, subpage: 'AbstractPage'):
|
|
60
|
+
subpage.setup_component()
|
|
61
|
+
subpage.setup_layout()
|
|
62
|
+
self.subpages.append(subpage)
|
|
63
|
+
|
|
64
|
+
def set_application(self, app):
|
|
65
|
+
self.application = app
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def setup_component(self):
|
|
69
|
+
# noinspection PyAttributeOutsideInit
|
|
70
|
+
self._component = html.Div('This is a abstract page.')
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def setup_layout(self):
|
|
74
|
+
self.layout = self._component
|
|
75
|
+
|
|
76
|
+
def setup_callback(self):
|
|
77
|
+
# app = self.application.app
|
|
78
|
+
|
|
79
|
+
for subpage in self.subpages:
|
|
80
|
+
subpage.set_application(self.application)
|
|
81
|
+
subpage.setup_callback()
|
|
82
|
+
|
|
83
|
+
# @app.callback(...)
|
|
84
|
+
# def do_something():
|
|
85
|
+
# return ...
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _unused_port_number(start=49152):
|
|
89
|
+
# "LISTEN" 状態のポート番号をリスト化
|
|
90
|
+
used_ports = [conn.laddr.port for conn in psutil.net_connections() if conn.status == 'LISTEN']
|
|
91
|
+
port = start
|
|
92
|
+
for port in range(start, 65535 + 1):
|
|
93
|
+
# 未使用のポート番号ならreturn
|
|
94
|
+
if port not in set(used_ports):
|
|
95
|
+
break
|
|
96
|
+
if port != start:
|
|
97
|
+
logger.warning(f'Specified port "{start}" seems to be used. Port "{port}" is used instead.')
|
|
98
|
+
return port
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class SidebarApplicationBase:
|
|
102
|
+
""""""
|
|
103
|
+
""" Define entire layout and callback.
|
|
104
|
+
+------+--------+
|
|
105
|
+
| side | con- |
|
|
106
|
+
| bar | tent |
|
|
107
|
+
+------+--------+
|
|
108
|
+
│ └─ pages (dict(href: str = layout: Component))
|
|
109
|
+
└──────── nav_links (dict(order: float) = NavLink)
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
# port
|
|
113
|
+
DEFAULT_PORT = 49152
|
|
114
|
+
|
|
115
|
+
# members for sidebar application
|
|
116
|
+
SIDEBAR_STYLE = {
|
|
117
|
+
"position": "fixed",
|
|
118
|
+
"top": 0,
|
|
119
|
+
"left": 0,
|
|
120
|
+
"bottom": 0,
|
|
121
|
+
"width": "16rem",
|
|
122
|
+
"padding": "2rem 1rem",
|
|
123
|
+
"background-color": "#f8f9fa",
|
|
124
|
+
}
|
|
125
|
+
CONTENT_STYLE = {
|
|
126
|
+
"margin-left": "18rem",
|
|
127
|
+
"margin-right": "2rem",
|
|
128
|
+
"padding": "2rem 1rem",
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
def __init__(self, title=None, subtitle=None):
|
|
132
|
+
# define app
|
|
133
|
+
self.title = title if title is not None else 'App'
|
|
134
|
+
self.subtitle = subtitle if title is not None else ''
|
|
135
|
+
self.app = Dash(
|
|
136
|
+
__name__,
|
|
137
|
+
external_stylesheets=[dash_bootstrap_components.themes.BOOTSTRAP],
|
|
138
|
+
title=title,
|
|
139
|
+
update_title=None,
|
|
140
|
+
)
|
|
141
|
+
self.pages = dict()
|
|
142
|
+
self.nav_links = dict()
|
|
143
|
+
self.page_objects = []
|
|
144
|
+
|
|
145
|
+
def add_page(self, page: AbstractPage, order: int = None):
|
|
146
|
+
page.set_application(self)
|
|
147
|
+
self.page_objects.append(page)
|
|
148
|
+
self.pages[page.rel_url] = page.layout
|
|
149
|
+
if order is None:
|
|
150
|
+
order = len(self.pages)
|
|
151
|
+
self.nav_links[order] = dbc.NavLink(page.title, href=page.rel_url, active="exact")
|
|
152
|
+
|
|
153
|
+
def setup_callback(self):
|
|
154
|
+
for page in self.page_objects:
|
|
155
|
+
page.setup_callback()
|
|
156
|
+
|
|
157
|
+
def _setup_layout(self):
|
|
158
|
+
# setup sidebar
|
|
159
|
+
# https://dash-bootstrap-components.opensource.faculty.ai/examples/simple-sidebar/
|
|
160
|
+
|
|
161
|
+
# sidebar に表示される順に並び替え
|
|
162
|
+
ordered_items = sorted(self.nav_links.items(), key=lambda x: x[0])
|
|
163
|
+
ordered_links = [value for key, value in ordered_items]
|
|
164
|
+
|
|
165
|
+
# sidebar と contents から app 全体の layout を作成
|
|
166
|
+
sidebar = html.Div(
|
|
167
|
+
[
|
|
168
|
+
html.H2(self.title, className='display-4'),
|
|
169
|
+
html.Hr(),
|
|
170
|
+
html.P(self.subtitle, className='lead'),
|
|
171
|
+
dbc.Nav(ordered_links, vertical=True, pills=True),
|
|
172
|
+
],
|
|
173
|
+
style=self.SIDEBAR_STYLE,
|
|
174
|
+
)
|
|
175
|
+
content = html.Div(id="page-content", style=self.CONTENT_STYLE)
|
|
176
|
+
self.app.layout = html.Div([dcc.Location(id="url"), sidebar, content])
|
|
177
|
+
|
|
178
|
+
# sidebar によるページ遷移のための callback
|
|
179
|
+
@self.app.callback(Output(content.id, "children"), [Input("url", "pathname")])
|
|
180
|
+
def switch_page_content(pathname):
|
|
181
|
+
if pathname in list(self.pages.keys()):
|
|
182
|
+
return self.pages[pathname]
|
|
183
|
+
|
|
184
|
+
else:
|
|
185
|
+
return html.Div(
|
|
186
|
+
[
|
|
187
|
+
html.H1("404: Not found", className="text-danger"),
|
|
188
|
+
html.Hr(),
|
|
189
|
+
html.P(f"The pathname {pathname} was not recognised..."),
|
|
190
|
+
],
|
|
191
|
+
className="p-3 bg-light rounded-3",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def run(self, host='localhost', port=None, debug=False):
|
|
195
|
+
self._setup_layout()
|
|
196
|
+
port = port or self.DEFAULT_PORT
|
|
197
|
+
# port を検証
|
|
198
|
+
port = _unused_port_number(port)
|
|
199
|
+
# ブラウザを起動
|
|
200
|
+
if host == '0.0.0.0':
|
|
201
|
+
webbrowser.open(f'http://localhost:{str(port)}')
|
|
202
|
+
else:
|
|
203
|
+
webbrowser.open(f'http://{host}:{str(port)}')
|
|
204
|
+
self.app.run(debug=debug, host=host, port=port)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class PyFemtetApplicationBase(SidebarApplicationBase):
|
|
208
|
+
""""""
|
|
209
|
+
"""
|
|
210
|
+
+------+--------+
|
|
211
|
+
| side | con- |
|
|
212
|
+
| bar | tent |
|
|
213
|
+
+--^---+--^-----+
|
|
214
|
+
│ └─ pages (dict(href: str = layout: Component))
|
|
215
|
+
└──────── nav_links (dict(order: float) = NavLink)
|
|
216
|
+
|
|
217
|
+
Accessible members:
|
|
218
|
+
- history: History
|
|
219
|
+
└ local_df: pd.DataFrame
|
|
220
|
+
- app: Dash
|
|
221
|
+
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def __init__(
|
|
225
|
+
self,
|
|
226
|
+
title=None,
|
|
227
|
+
subtitle=None,
|
|
228
|
+
history: History = None,
|
|
229
|
+
):
|
|
230
|
+
# register arguments
|
|
231
|
+
self.history = history # include actor
|
|
232
|
+
super().__init__(title, subtitle)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def check_page_layout(page_cls: type):
|
|
236
|
+
home_page = page_cls() # required
|
|
237
|
+
application = PyFemtetApplicationBase(title='test-app')
|
|
238
|
+
application.add_page(home_page, 0)
|
|
239
|
+
application.run(debug=True)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == '__main__':
|
|
243
|
+
|
|
244
|
+
# template
|
|
245
|
+
g_home_page = AbstractPage('home-page') # required
|
|
246
|
+
g_page1 = AbstractPage('page-1', '/page-1')
|
|
247
|
+
|
|
248
|
+
g_application = SidebarApplicationBase(title='test-app')
|
|
249
|
+
g_application.add_page(g_home_page, 0)
|
|
250
|
+
g_application.add_page(g_page1, 1)
|
|
251
|
+
|
|
252
|
+
# g_application.setup_callback()
|
|
253
|
+
|
|
254
|
+
g_application.run(debug=True)
|
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# type hint
|
|
2
|
+
from typing import List
|
|
3
|
+
from dash.development.base_component import Component
|
|
4
|
+
|
|
5
|
+
# callback
|
|
6
|
+
from dash import Output, Input, State, no_update, callback_context
|
|
7
|
+
from dash.exceptions import PreventUpdate
|
|
8
|
+
|
|
9
|
+
# components
|
|
10
|
+
from pyfemtet.opt.visualization.wrapped_components import html, dcc, dbc
|
|
11
|
+
|
|
12
|
+
from pyfemtet.opt.visualization.base import AbstractPage, logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AlertRegion(AbstractPage):
|
|
16
|
+
|
|
17
|
+
def setup_component(self):
|
|
18
|
+
# alert
|
|
19
|
+
# noinspection PyAttributeOutsideInit
|
|
20
|
+
self.alert_region = dbc.CardBody(children=[], id='alerts-card-body')
|
|
21
|
+
|
|
22
|
+
# clear alert
|
|
23
|
+
# noinspection PyAttributeOutsideInit
|
|
24
|
+
self.clear_alert_button = dbc.Button(
|
|
25
|
+
children='Clear messages',
|
|
26
|
+
id='clear-messages-button',
|
|
27
|
+
color='secondary',
|
|
28
|
+
outline=True,
|
|
29
|
+
className="position-relative",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def setup_layout(self):
|
|
33
|
+
self.layout = dbc.Card(
|
|
34
|
+
[
|
|
35
|
+
dbc.CardHeader(
|
|
36
|
+
children=self.clear_alert_button,
|
|
37
|
+
className='d-flex', # align right
|
|
38
|
+
style={'justify-content': 'end'}, # align right
|
|
39
|
+
),
|
|
40
|
+
self.alert_region,
|
|
41
|
+
]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def setup_callback(self):
|
|
45
|
+
app = self.application.app
|
|
46
|
+
|
|
47
|
+
# ===== clear alerts ==-==
|
|
48
|
+
@app.callback(
|
|
49
|
+
Output(self.alert_region.id, 'children', allow_duplicate=True),
|
|
50
|
+
Input(self.clear_alert_button.id, self.clear_alert_button.Prop.n_clicks),
|
|
51
|
+
prevent_initial_call=True, # required if allow_duplicate=True
|
|
52
|
+
)
|
|
53
|
+
def clear_alerts(_):
|
|
54
|
+
return []
|
|
55
|
+
|
|
56
|
+
def create_alerts(self, msg, color='secondary', current_alerts=None) -> List[Component]:
|
|
57
|
+
|
|
58
|
+
if current_alerts is None:
|
|
59
|
+
current_alerts = []
|
|
60
|
+
|
|
61
|
+
new_alert = dbc.Alert(
|
|
62
|
+
msg,
|
|
63
|
+
id=f'alert-{len(current_alerts) + 1}',
|
|
64
|
+
dismissable=True,
|
|
65
|
+
color=color,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
new_alerts = [new_alert]
|
|
69
|
+
new_alerts.extend(current_alerts)
|
|
70
|
+
|
|
71
|
+
return new_alerts
|
|
@@ -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()
|