pyfemtet 0.4.10__py3-none-any.whl → 0.4.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyfemtet might be problematic. Click here for more details.

Files changed (77) hide show
  1. pyfemtet/__init__.py +1 -1
  2. pyfemtet/_test_util.py +26 -0
  3. pyfemtet/opt/__init__.py +1 -1
  4. pyfemtet/opt/_femopt.py +41 -5
  5. pyfemtet/opt/interface/_base.py +6 -1
  6. pyfemtet/opt/interface/_femtet.py +32 -10
  7. pyfemtet/opt/interface/_femtet_parametric.py +5 -25
  8. pyfemtet/opt/opt/__init__.py +3 -1
  9. pyfemtet/opt/opt/_base.py +88 -8
  10. pyfemtet/opt/opt/_optuna.py +17 -2
  11. pyfemtet/opt/opt/_scipy.py +144 -0
  12. pyfemtet/opt/opt/_scipy_scalar.py +104 -0
  13. pyfemtet/opt/visualization/__init__.py +0 -7
  14. pyfemtet/opt/visualization/_create_wrapped_components.py +93 -0
  15. pyfemtet/opt/visualization/base.py +254 -0
  16. pyfemtet/opt/visualization/complex_components/__init__.py +0 -0
  17. pyfemtet/opt/visualization/complex_components/alert_region.py +71 -0
  18. pyfemtet/opt/visualization/complex_components/control_femtet.py +195 -0
  19. pyfemtet/opt/visualization/{_graphs.py → complex_components/main_figure_creator.py} +13 -49
  20. pyfemtet/opt/visualization/complex_components/main_graph.py +263 -0
  21. pyfemtet/opt/visualization/process_monitor/__init__.py +0 -0
  22. pyfemtet/opt/visualization/process_monitor/application.py +201 -0
  23. pyfemtet/opt/visualization/process_monitor/pages.py +276 -0
  24. pyfemtet/opt/visualization/result_viewer/.gitignore +1 -0
  25. pyfemtet/opt/visualization/result_viewer/__init__.py +0 -0
  26. pyfemtet/opt/visualization/result_viewer/application.py +44 -0
  27. pyfemtet/opt/visualization/result_viewer/pages.py +692 -0
  28. pyfemtet/opt/visualization/result_viewer/tutorial/tutorial.csv +18 -0
  29. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.jpg +0 -0
  30. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.log +81 -0
  31. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.pdt +0 -0
  32. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow.csv +28 -0
  33. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow_el.csv +22 -0
  34. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
  35. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
  36. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
  37. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
  38. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
  39. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
  40. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
  41. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
  42. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
  43. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
  44. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
  45. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
  46. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
  47. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
  48. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
  49. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
  50. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
  51. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
  52. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
  53. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
  54. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
  55. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
  56. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
  57. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
  58. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
  59. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
  60. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
  61. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
  62. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
  63. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
  64. pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.femprj +0 -0
  65. pyfemtet/opt/visualization/wrapped_components/__init__.py +0 -0
  66. pyfemtet/opt/visualization/wrapped_components/dbc.py +1518 -0
  67. pyfemtet/opt/visualization/wrapped_components/dcc.py +609 -0
  68. pyfemtet/opt/visualization/wrapped_components/html.py +3570 -0
  69. pyfemtet/opt/visualization/wrapped_components/str_enum.py +43 -0
  70. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/METADATA +1 -1
  71. pyfemtet-0.4.12.dist-info/RECORD +138 -0
  72. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/entry_points.txt +1 -1
  73. pyfemtet/opt/visualization/_monitor.py +0 -1227
  74. pyfemtet/opt/visualization/result_viewer.py +0 -13
  75. pyfemtet-0.4.10.dist-info/RECORD +0 -83
  76. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/LICENSE +0 -0
  77. {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/WHEEL +0 -0
@@ -0,0 +1,104 @@
1
+ # typing
2
+ import logging
3
+ from typing import Iterable
4
+
5
+ # built-in
6
+ import os
7
+
8
+ # 3rd-party
9
+ import numpy as np
10
+ import pandas as pd
11
+ import scipy.optimize
12
+ from scipy.optimize import minimize_scalar, OptimizeResult
13
+
14
+ # pyfemtet relative
15
+ from pyfemtet.opt._femopt_core import OptimizationStatus, generate_lhs
16
+ from pyfemtet.opt.opt import AbstractOptimizer, logger, OptimizationMethodChecker
17
+ from pyfemtet.core import MeshError, ModelError, SolveError
18
+
19
+
20
+ class ScipyScalarMethodChecker(OptimizationMethodChecker):
21
+ def check_incomplete_bounds(self, raise_error=True): return True
22
+
23
+
24
+ class ScipyOptimizer(AbstractOptimizer):
25
+
26
+ def __init__(
27
+ self,
28
+ **minimize_kwargs,
29
+ ):
30
+ """
31
+ Args:
32
+ **minimize_kwargs: Kwargs of `scipy.optimize.minimize_scalar` __except ``fun``.__.
33
+ """
34
+ super().__init__()
35
+
36
+ # define members
37
+ self.minimize_kwargs: dict = dict()
38
+ self.minimize_kwargs.update(minimize_kwargs)
39
+ self.res: OptimizeResult = None
40
+ self.method_checker: OptimizationMethodChecker = ScipyScalarMethodChecker(self)
41
+
42
+ def _objective(self, x: float): # x: candidate parameter
43
+ # update parameter
44
+ self.parameters['value'] = x
45
+ self.fem.update_parameter(self.parameters)
46
+
47
+ # strict constraints
48
+ ...
49
+
50
+ # fem
51
+ try:
52
+ _, obj_values, cns_values = self.f(x)
53
+ except (ModelError, MeshError, SolveError) as e:
54
+ logger.info(e)
55
+ logger.info('以下の変数で FEM 解析に失敗しました。')
56
+ print(self.get_parameter('dict'))
57
+
58
+ # 現状、エラーが起きたらスキップできない
59
+ raise StopIteration
60
+
61
+ # constraints
62
+ ...
63
+
64
+ # check interruption command
65
+ if self.entire_status.get() == OptimizationStatus.INTERRUPTING:
66
+ self.worker_status.set(OptimizationStatus.INTERRUPTING)
67
+ raise StopIteration
68
+
69
+ # objectives to objective
70
+
71
+ return obj_values[0]
72
+
73
+ def _setup_before_parallel(self):
74
+ pass
75
+
76
+ def run(self):
77
+
78
+ # create init
79
+ assert len(self.parameters) == 1
80
+
81
+ # create bounds
82
+ if 'bounds' not in self.minimize_kwargs.keys():
83
+ bounds = []
84
+ for i, row in self.parameters.iterrows():
85
+ lb, ub = row['lb'], row['ub']
86
+ if lb is None: lb = -np.inf
87
+ if ub is None: ub = np.inf
88
+ bounds.append([lb, ub])
89
+ self.minimize_kwargs.update(
90
+ {'bounds': bounds}
91
+ )
92
+
93
+ # run optimize
94
+ try:
95
+ res = minimize_scalar(
96
+ fun=self._objective,
97
+ **self.minimize_kwargs,
98
+ )
99
+ except StopIteration:
100
+ res = None
101
+ logger.warn('Optimization has been interrupted. '
102
+ 'Note that you cannot acquire the OptimizationResult.')
103
+
104
+ self.res = res
@@ -1,7 +0,0 @@
1
- from pyfemtet.opt.visualization._monitor import ResultViewerApp
2
- from pyfemtet.opt.visualization._monitor import ProcessMonitorApp
3
-
4
- __all__ = [
5
- 'ResultViewerApp',
6
- 'ProcessMonitorApp',
7
- ]
@@ -0,0 +1,93 @@
1
+ """Create autocompletable components"""
2
+ import os
3
+ import inspect
4
+
5
+ from dash.development.base_component import Component
6
+
7
+
8
+ # noinspection PyUnresolvedReferences
9
+ from dash import html, dcc, dash_table
10
+ # noinspection PyUnresolvedReferences
11
+ import dash_bootstrap_components as dbc
12
+
13
+
14
+ here, me = os.path.split(__file__)
15
+ COMPONENT_FILE_DIR = os.path.join(here, 'wrapped_components')
16
+ indent = ' '
17
+
18
+
19
+ def create(module_name: str) -> str:
20
+ header = '''# auto created module
21
+ from pyfemtet.opt.visualization.wrapped_components.str_enum import StrEnum
22
+ # from enum import StrEnum
23
+ import dash
24
+ import dash_bootstrap_components
25
+
26
+
27
+ '''
28
+ path = os.path.join(COMPONENT_FILE_DIR, module_name.replace('.py', '') + '.py')
29
+ with open(path, 'w', newline='\n') as f:
30
+ f.write(header)
31
+ return path
32
+
33
+
34
+ def append(html_class, module_path: str):
35
+ print('==========')
36
+ print(html_class)
37
+
38
+ # get property names
39
+ init_signature = inspect.signature(html_class.__init__)
40
+ props = [param.name for param in init_signature.parameters.values() if (param.name != 'self') and (param.name != 'args') and (param.name != 'kwargs')]
41
+
42
+ # create class definition
43
+ class_definition = f'class {html_class.__name__}({html_class.__module__}):\n' # library specific
44
+
45
+ # create id property
46
+ class_definition += indent + 'def _dummy(self):\n'
47
+ class_definition += indent*2 + '# noinspection PyAttributeOutsideInit\n'
48
+ class_definition += indent*2 + 'self.id = None\n\n'
49
+
50
+ # create Prop attribute
51
+ class_definition += indent + 'class Prop(StrEnum):\n'
52
+ for available_property in props:
53
+ property_definition = f'{available_property} = "{available_property}"'
54
+ try:
55
+ exec(property_definition)
56
+ except (SyntaxError, TypeError):
57
+ continue
58
+ class_definition += indent*2 + f'{property_definition}\n'
59
+
60
+ if class_definition.endswith(':\n'):
61
+ return
62
+
63
+ class_definition += '\n\n'
64
+
65
+ with open(module_path, 'a', newline='\n') as f:
66
+ f.write(class_definition)
67
+
68
+
69
+ def sub(module_name):
70
+ # glob component classes
71
+ module_path = create(module_name)
72
+ class_names = dir(eval(module_name))
73
+ for class_name in class_names:
74
+ cls = eval(f'{module_name}.{class_name}')
75
+ if not inspect.isclass(cls):
76
+ continue
77
+ if issubclass(cls, Component):
78
+ append(cls, module_path)
79
+
80
+
81
+ def main():
82
+ # html
83
+ sub('html')
84
+
85
+ # dcc
86
+ sub('dcc')
87
+
88
+ # dbc
89
+ sub('dbc')
90
+
91
+
92
+ if __name__ == '__main__':
93
+ main()
@@ -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