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.

Files changed (73) hide show
  1. pyfemtet/__init__.py +1 -1
  2. pyfemtet/opt/_femopt.py +9 -3
  3. pyfemtet/opt/femprj_sample/wat_ex14_parametric_parallel.py +66 -0
  4. pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_parallel_jp.py +64 -0
  5. pyfemtet/opt/interface/_femtet.py +32 -10
  6. pyfemtet/opt/interface/_femtet_parametric.py +5 -25
  7. pyfemtet/opt/opt/_base.py +9 -1
  8. pyfemtet/opt/opt/_optuna.py +4 -0
  9. pyfemtet/opt/visualization/__init__.py +0 -7
  10. pyfemtet/opt/visualization/_create_wrapped_components.py +93 -0
  11. pyfemtet/opt/visualization/base.py +254 -0
  12. pyfemtet/opt/visualization/complex_components/__init__.py +0 -0
  13. pyfemtet/opt/visualization/complex_components/alert_region.py +71 -0
  14. pyfemtet/opt/visualization/complex_components/control_femtet.py +195 -0
  15. pyfemtet/opt/visualization/{_graphs.py → complex_components/main_figure_creator.py} +13 -49
  16. pyfemtet/opt/visualization/complex_components/main_graph.py +263 -0
  17. pyfemtet/opt/visualization/process_monitor/__init__.py +0 -0
  18. pyfemtet/opt/visualization/process_monitor/application.py +201 -0
  19. pyfemtet/opt/visualization/process_monitor/pages.py +276 -0
  20. pyfemtet/opt/visualization/result_viewer/.gitignore +1 -0
  21. pyfemtet/opt/visualization/result_viewer/__init__.py +0 -0
  22. pyfemtet/opt/visualization/result_viewer/application.py +44 -0
  23. pyfemtet/opt/visualization/result_viewer/pages.py +692 -0
  24. pyfemtet/opt/visualization/result_viewer/tutorial/tutorial.csv +18 -0
  25. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.jpg +0 -0
  26. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.log +81 -0
  27. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.pdt +0 -0
  28. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow.csv +28 -0
  29. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow_el.csv +22 -0
  30. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
  31. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
  32. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
  33. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
  34. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
  35. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
  36. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
  37. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
  38. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
  39. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
  40. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
  41. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
  42. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
  43. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
  44. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
  45. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
  46. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
  47. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
  48. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
  49. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
  50. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
  51. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
  52. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
  53. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
  54. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
  55. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
  56. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
  57. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
  58. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
  59. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
  60. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.femprj +0 -0
  61. pyfemtet/opt/visualization/wrapped_components/__init__.py +0 -0
  62. pyfemtet/opt/visualization/wrapped_components/dbc.py +1518 -0
  63. pyfemtet/opt/visualization/wrapped_components/dcc.py +609 -0
  64. pyfemtet/opt/visualization/wrapped_components/html.py +3570 -0
  65. pyfemtet/opt/visualization/wrapped_components/str_enum.py +43 -0
  66. {pyfemtet-0.4.9.dist-info → pyfemtet-0.4.11.dist-info}/METADATA +1 -1
  67. pyfemtet-0.4.11.dist-info/RECORD +136 -0
  68. {pyfemtet-0.4.9.dist-info → pyfemtet-0.4.11.dist-info}/entry_points.txt +1 -1
  69. pyfemtet/opt/visualization/_monitor.py +0 -1227
  70. pyfemtet/opt/visualization/result_viewer.py +0 -13
  71. pyfemtet-0.4.9.dist-info/RECORD +0 -81
  72. {pyfemtet-0.4.9.dist-info → pyfemtet-0.4.11.dist-info}/LICENSE +0 -0
  73. {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)
@@ -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 update_hypervolume_plot(history, df):
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 update_default_figure(history, df):
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
- if len(obj_names) == 0:
67
- fig = go.Figure()
70
+ fig = go.Figure()
68
71
 
69
- elif len(obj_names) == 1:
70
- fig = update_single_objective_plot(history, df)
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 = update_multi_objective_pairplot(history, df)
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 update_single_objective_plot(history, df):
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 update_multi_objective_pairplot(history, df):
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()