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.
- pyfemtet/__init__.py +1 -1
- pyfemtet/_test_util.py +26 -0
- pyfemtet/opt/__init__.py +1 -1
- pyfemtet/opt/_femopt.py +41 -5
- pyfemtet/opt/interface/_base.py +6 -1
- pyfemtet/opt/interface/_femtet.py +32 -10
- pyfemtet/opt/interface/_femtet_parametric.py +5 -25
- pyfemtet/opt/opt/__init__.py +3 -1
- pyfemtet/opt/opt/_base.py +88 -8
- pyfemtet/opt/opt/_optuna.py +17 -2
- pyfemtet/opt/opt/_scipy.py +144 -0
- pyfemtet/opt/opt/_scipy_scalar.py +104 -0
- pyfemtet/opt/visualization/__init__.py +0 -7
- pyfemtet/opt/visualization/_create_wrapped_components.py +93 -0
- pyfemtet/opt/visualization/base.py +254 -0
- pyfemtet/opt/visualization/complex_components/__init__.py +0 -0
- pyfemtet/opt/visualization/complex_components/alert_region.py +71 -0
- pyfemtet/opt/visualization/complex_components/control_femtet.py +195 -0
- pyfemtet/opt/visualization/{_graphs.py → complex_components/main_figure_creator.py} +13 -49
- pyfemtet/opt/visualization/complex_components/main_graph.py +263 -0
- pyfemtet/opt/visualization/process_monitor/__init__.py +0 -0
- pyfemtet/opt/visualization/process_monitor/application.py +201 -0
- pyfemtet/opt/visualization/process_monitor/pages.py +276 -0
- pyfemtet/opt/visualization/result_viewer/.gitignore +1 -0
- pyfemtet/opt/visualization/result_viewer/__init__.py +0 -0
- pyfemtet/opt/visualization/result_viewer/application.py +44 -0
- pyfemtet/opt/visualization/result_viewer/pages.py +692 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/tutorial.csv +18 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.log +81 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow.csv +28 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_heatflow_el.csv +22 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial1.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial10.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial11.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial12.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial13.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial14.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial15.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial2.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial3.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial4.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial5.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial6.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial7.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial8.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.jpg +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.Results/Ex14_trial9.pdt +0 -0
- pyfemtet/opt/visualization/result_viewer/tutorial/wat_ex14_parametric.femprj +0 -0
- pyfemtet/opt/visualization/wrapped_components/__init__.py +0 -0
- pyfemtet/opt/visualization/wrapped_components/dbc.py +1518 -0
- pyfemtet/opt/visualization/wrapped_components/dcc.py +609 -0
- pyfemtet/opt/visualization/wrapped_components/html.py +3570 -0
- pyfemtet/opt/visualization/wrapped_components/str_enum.py +43 -0
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/METADATA +1 -1
- pyfemtet-0.4.12.dist-info/RECORD +138 -0
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/entry_points.txt +1 -1
- pyfemtet/opt/visualization/_monitor.py +0 -1227
- pyfemtet/opt/visualization/result_viewer.py +0 -13
- pyfemtet-0.4.10.dist-info/RECORD +0 -83
- {pyfemtet-0.4.10.dist-info → pyfemtet-0.4.12.dist-info}/LICENSE +0 -0
- {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
|
|
@@ -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)
|
|
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
|