pyfemtet 0.4.20__py3-none-any.whl → 0.4.23__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 +0 -2
- pyfemtet/message/locales/ja/LC_MESSAGES/messages.mo +0 -0
- pyfemtet/message/locales/ja/LC_MESSAGES/messages.po +107 -96
- pyfemtet/message/locales/messages.pot +104 -96
- pyfemtet/message/messages.py +15 -1
- pyfemtet/opt/_femopt.py +289 -230
- pyfemtet/opt/_femopt_core.py +118 -49
- pyfemtet/opt/femprj_sample/ParametricIF.py +0 -2
- pyfemtet/opt/femprj_sample/cad_ex01_NX.py +0 -8
- pyfemtet/opt/femprj_sample/cad_ex01_SW.py +0 -8
- pyfemtet/opt/femprj_sample/gal_ex58_parametric.py +0 -8
- pyfemtet/opt/femprj_sample/gau_ex08_parametric.py +0 -8
- pyfemtet/opt/femprj_sample/her_ex40_parametric.py +0 -8
- pyfemtet/opt/femprj_sample/paswat_ex1_parametric.py +0 -8
- pyfemtet/opt/femprj_sample/paswat_ex1_parametric_parallel.py +0 -8
- pyfemtet/opt/femprj_sample/wat_ex14_parametric.py +0 -8
- pyfemtet/opt/femprj_sample/wat_ex14_parametric_parallel.py +0 -8
- pyfemtet/opt/femprj_sample_jp/ParametricIF_jp.py +0 -2
- pyfemtet/opt/femprj_sample_jp/cad_ex01_NX_jp.py +0 -8
- pyfemtet/opt/femprj_sample_jp/cad_ex01_SW_jp.py +0 -8
- pyfemtet/opt/femprj_sample_jp/gal_ex58_parametric_jp.py +0 -8
- pyfemtet/opt/femprj_sample_jp/gau_ex08_parametric_jp.py +0 -8
- pyfemtet/opt/femprj_sample_jp/her_ex40_parametric_jp.py +0 -8
- pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_jp.py +0 -8
- pyfemtet/opt/femprj_sample_jp/paswat_ex1_parametric_parallel_jp.py +0 -8
- pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_jp.py +0 -8
- pyfemtet/opt/femprj_sample_jp/wat_ex14_parametric_parallel_jp.py +0 -8
- pyfemtet/opt/interface/_femtet.py +77 -24
- pyfemtet/opt/opt/_base.py +25 -18
- pyfemtet/opt/opt/_optuna.py +53 -14
- pyfemtet/opt/opt/_optuna_botorch_helper.py +209 -0
- pyfemtet/opt/opt/_scipy.py +1 -1
- pyfemtet/opt/opt/_scipy_scalar.py +1 -1
- pyfemtet/opt/parameter.py +113 -0
- pyfemtet/opt/visualization/complex_components/main_graph.py +22 -5
- pyfemtet/opt/visualization/complex_components/pm_graph.py +77 -25
- pyfemtet/opt/visualization/complex_components/pm_graph_creator.py +7 -0
- pyfemtet/opt/visualization/process_monitor/application.py +10 -6
- pyfemtet/opt/visualization/process_monitor/pages.py +102 -0
- pyfemtet/opt/visualization/result_viewer/application.py +6 -0
- pyfemtet/opt/visualization/result_viewer/pages.py +1 -1
- {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/METADATA +3 -4
- {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/RECORD +47 -59
- pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.femprj +0 -0
- pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.prt +0 -0
- pyfemtet/FemtetPJTSample/NX_ex01/NX_ex01.py +0 -118
- pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.SLDPRT +0 -0
- pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.femprj +0 -0
- pyfemtet/FemtetPJTSample/Sldworks_ex01/Sldworks_ex01.py +0 -121
- pyfemtet/FemtetPJTSample/_her_ex40_parametric.py +0 -148
- pyfemtet/FemtetPJTSample/gau_ex08_parametric.femprj +0 -0
- pyfemtet/FemtetPJTSample/gau_ex08_parametric.py +0 -58
- pyfemtet/FemtetPJTSample/her_ex40_parametric.femprj +0 -0
- pyfemtet/FemtetPJTSample/her_ex40_parametric.py +0 -148
- pyfemtet/FemtetPJTSample/wat_ex14_parallel_parametric.py +0 -65
- pyfemtet/FemtetPJTSample/wat_ex14_parametric.femprj +0 -0
- pyfemtet/FemtetPJTSample/wat_ex14_parametric.py +0 -64
- {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/LICENSE +0 -0
- {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/WHEEL +0 -0
- {pyfemtet-0.4.20.dist-info → pyfemtet-0.4.23.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from graphlib import TopologicalSorter
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
import inspect
|
|
4
|
+
from typing import Optional, Callable, Any, Tuple, Dict
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Variable:
|
|
12
|
+
name: str
|
|
13
|
+
value: float
|
|
14
|
+
pass_to_fem: Optional[bool] = True
|
|
15
|
+
properties: Optional[dict[Any]] = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Parameter(Variable):
|
|
20
|
+
lower_bound: Optional[float] = None
|
|
21
|
+
upper_bound: Optional[float] = None
|
|
22
|
+
step: Optional[float] = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Expression(Variable):
|
|
27
|
+
# fun に params を自動で代入するので positional args は実装しない
|
|
28
|
+
fun: Optional[Callable] = None
|
|
29
|
+
kwargs: Optional[Dict] = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ExpressionEvaluator:
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self.variables = {} # Parameter 又は計算された Expression が入る
|
|
35
|
+
self.parameters = {}
|
|
36
|
+
self.expressions = {}
|
|
37
|
+
self.dependencies = {}
|
|
38
|
+
self.evaluation_order = []
|
|
39
|
+
|
|
40
|
+
def add_parameter(self, prm: Parameter):
|
|
41
|
+
self.variables[prm.name] = prm
|
|
42
|
+
self.parameters[prm.name] = prm
|
|
43
|
+
self.dependencies[prm.name] = set()
|
|
44
|
+
|
|
45
|
+
def add_expression(self, exp: Expression):
|
|
46
|
+
self.expressions[exp.name] = exp
|
|
47
|
+
|
|
48
|
+
# params は Python 変数として使える文字のみからなる文字列のリスト
|
|
49
|
+
params = inspect.signature(exp.fun).parameters
|
|
50
|
+
self.dependencies[exp.name] = set(params) - set(exp.kwargs.keys())
|
|
51
|
+
|
|
52
|
+
def resolve(self):
|
|
53
|
+
ts = TopologicalSorter(self.dependencies)
|
|
54
|
+
self.evaluation_order = list(ts.static_order())
|
|
55
|
+
|
|
56
|
+
def evaluate(self):
|
|
57
|
+
# order 順に見ていき、expression なら計算して variables を更新する
|
|
58
|
+
for var_name in self.evaluation_order:
|
|
59
|
+
if var_name in self.expressions.keys():
|
|
60
|
+
# 現在の expression に関して parameter 部分の引数 kwargs を作成
|
|
61
|
+
kwargs = {param: self.variables[param].value for param in self.dependencies[var_name]}
|
|
62
|
+
|
|
63
|
+
# fun に すべての kwargs を入れて expression の value を更新
|
|
64
|
+
exp: Expression = self.expressions[var_name]
|
|
65
|
+
kwargs.update(exp.kwargs)
|
|
66
|
+
exp.value = exp.fun(**kwargs)
|
|
67
|
+
|
|
68
|
+
# 計算済み variables に追加
|
|
69
|
+
self.variables[var_name] = exp
|
|
70
|
+
|
|
71
|
+
def get_variables(self, format='dict', filter_pass_to_fem=False, filter_parameter=False):
|
|
72
|
+
"""format: dict, values, df, raw(list of Variable object)"""
|
|
73
|
+
|
|
74
|
+
# リストを作成
|
|
75
|
+
vars = [self.variables[name] for name in self.evaluation_order if name in self.variables]
|
|
76
|
+
|
|
77
|
+
# 必要なら FEM に直接使うもののみ取り出し
|
|
78
|
+
if filter_pass_to_fem:
|
|
79
|
+
vars = [var for var in vars if var.pass_to_fem]
|
|
80
|
+
|
|
81
|
+
# 必要なら parameter のみ取り出し
|
|
82
|
+
if filter_parameter:
|
|
83
|
+
vars = [var for var in vars if isinstance(var, Parameter)]
|
|
84
|
+
|
|
85
|
+
if format == 'raw':
|
|
86
|
+
return vars
|
|
87
|
+
|
|
88
|
+
elif format == 'dict':
|
|
89
|
+
return {var.name: var.value for var in vars}
|
|
90
|
+
|
|
91
|
+
elif format == 'values':
|
|
92
|
+
return np.array([var.value for var in vars]).astype(float)
|
|
93
|
+
|
|
94
|
+
elif format == 'df':
|
|
95
|
+
data = dict(
|
|
96
|
+
name=[var.name for var in vars],
|
|
97
|
+
value=[var.value for var in vars],
|
|
98
|
+
properties=[var.properties for var in vars],
|
|
99
|
+
)
|
|
100
|
+
if filter_parameter:
|
|
101
|
+
data.update(
|
|
102
|
+
dict(
|
|
103
|
+
lower_bound=[var.lower_bound for var in vars],
|
|
104
|
+
upper_bound=[var.upper_bound for var in vars],
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
return pd.DataFrame(data)
|
|
108
|
+
|
|
109
|
+
else:
|
|
110
|
+
raise NotImplementedError(f'invalid format: {format}. Valid formats are `dict`, `values`, `df` and `raw`(= list of Variables).')
|
|
111
|
+
|
|
112
|
+
def get_parameter_names(self):
|
|
113
|
+
return list(self.parameters.keys())
|
|
@@ -184,16 +184,18 @@ class MainGraph(AbstractPage):
|
|
|
184
184
|
# create component
|
|
185
185
|
title_component = html.H3(f"trial{trial}", style={"color": "darkblue"})
|
|
186
186
|
img_component = self.create_image_content_if_femtet(trial)
|
|
187
|
-
|
|
187
|
+
tbl_component_prm = self.create_formatted_parameter(row)
|
|
188
|
+
tbl_component_obj = self.create_formatted_objective(row)
|
|
188
189
|
|
|
189
190
|
# create layout
|
|
190
191
|
description = html.Div([
|
|
191
192
|
title_component,
|
|
192
|
-
|
|
193
|
+
tbl_component_prm,
|
|
193
194
|
])
|
|
194
195
|
tooltip_layout = html.Div([
|
|
195
196
|
html.Div(img_component, style={'display': 'inline-block', 'margin-right': '10px', 'vertical-align': 'top'}),
|
|
196
|
-
html.Div(description, style={'display': 'inline-block', 'margin-right': '10px'})
|
|
197
|
+
html.Div(description, style={'display': 'inline-block', 'margin-right': '10px'}),
|
|
198
|
+
html.Div(tbl_component_obj, style={'display': 'inline-block', 'margin-right': '10px'}),
|
|
197
199
|
])
|
|
198
200
|
|
|
199
201
|
return True, bbox, tooltip_layout
|
|
@@ -205,7 +207,22 @@ class MainGraph(AbstractPage):
|
|
|
205
207
|
names = parameters.columns
|
|
206
208
|
values = [f'{value:.3e}' for value in parameters.values.ravel()]
|
|
207
209
|
data = pd.DataFrame(dict(
|
|
208
|
-
|
|
210
|
+
parameter=names, value=values
|
|
211
|
+
))
|
|
212
|
+
table = dash_table.DataTable(
|
|
213
|
+
columns=[{'name': col, 'id': col} for col in data.columns],
|
|
214
|
+
data=data.to_dict('records')
|
|
215
|
+
)
|
|
216
|
+
return table
|
|
217
|
+
|
|
218
|
+
def create_formatted_objective(self, row) -> Component:
|
|
219
|
+
metadata = self.application.history.metadata
|
|
220
|
+
pd.options.display.float_format = '{:.4e}'.format
|
|
221
|
+
objectives = row.iloc[:, np.where(np.array(metadata) == 'obj')[0]]
|
|
222
|
+
names = objectives.columns
|
|
223
|
+
values = [f'{value:.3e}' for value in objectives.values.ravel()]
|
|
224
|
+
data = pd.DataFrame(dict(
|
|
225
|
+
objective=names, value=values
|
|
209
226
|
))
|
|
210
227
|
table = dash_table.DataTable(
|
|
211
228
|
columns=[{'name': col, 'id': col} for col in data.columns],
|
|
@@ -258,5 +275,5 @@ class MainGraph(AbstractPage):
|
|
|
258
275
|
if isinstance(self.application, ProcessMonitorApplication):
|
|
259
276
|
df = self.application.local_data
|
|
260
277
|
else:
|
|
261
|
-
df = self.application.history.
|
|
278
|
+
df = self.application.history.get_df()
|
|
262
279
|
return df
|
|
@@ -129,6 +129,19 @@ class PredictionModelGraph(AbstractPage):
|
|
|
129
129
|
self.slider_stack_data = html.Data(**{self.slider_stack_data_prop: {}})
|
|
130
130
|
self.slider_container = html.Div()
|
|
131
131
|
|
|
132
|
+
# 2d or 3d
|
|
133
|
+
self.switch_3d = dbc.Checklist(
|
|
134
|
+
options=[
|
|
135
|
+
dict(
|
|
136
|
+
label=Msg.LABEL_SWITCH_PREDICTION_MODEL_3D,
|
|
137
|
+
disabled=False,
|
|
138
|
+
value=False,
|
|
139
|
+
)
|
|
140
|
+
],
|
|
141
|
+
switch=True,
|
|
142
|
+
value=[],
|
|
143
|
+
)
|
|
144
|
+
|
|
132
145
|
def setup_layout(self):
|
|
133
146
|
self.card_header = dbc.CardHeader(self.tabs)
|
|
134
147
|
|
|
@@ -151,6 +164,7 @@ class PredictionModelGraph(AbstractPage):
|
|
|
151
164
|
self.command_manager
|
|
152
165
|
],
|
|
153
166
|
direction='horizontal', gap=2),
|
|
167
|
+
self.switch_3d,
|
|
154
168
|
*dropdown_rows,
|
|
155
169
|
self.slider_container,
|
|
156
170
|
self.slider_stack_data,
|
|
@@ -193,13 +207,15 @@ class PredictionModelGraph(AbstractPage):
|
|
|
193
207
|
Output(self.redraw_graph_button_spinner, 'spinner_style', allow_duplicate=True),
|
|
194
208
|
Output(self.redraw_graph_button, 'disabled', allow_duplicate=True),
|
|
195
209
|
Output(self.command_manager, self.command_manager_prop, allow_duplicate=True),
|
|
210
|
+
Output(self.switch_3d, 'options', allow_duplicate=True),
|
|
196
211
|
Input(self.fit_rsm_button, 'n_clicks'),
|
|
197
212
|
Input(self.redraw_graph_button, 'n_clicks'),
|
|
198
213
|
State(self.fit_rsm_button_spinner, 'spinner_style'),
|
|
199
214
|
State(self.redraw_graph_button_spinner, 'spinner_style'),
|
|
215
|
+
State(self.switch_3d, 'options'),
|
|
200
216
|
prevent_initial_call=True,
|
|
201
217
|
)
|
|
202
|
-
def disable_fit_button(_1, _2, state1, state2):
|
|
218
|
+
def disable_fit_button(_1, _2, state1, state2, switch_options):
|
|
203
219
|
# spinner visibility
|
|
204
220
|
if 'display' in state1.keys(): state1.pop('display')
|
|
205
221
|
if 'display' in state2.keys(): state2.pop('display')
|
|
@@ -210,7 +226,12 @@ class PredictionModelGraph(AbstractPage):
|
|
|
210
226
|
else:
|
|
211
227
|
command = self.CommandState.redraw.value
|
|
212
228
|
|
|
213
|
-
|
|
229
|
+
# disable switch
|
|
230
|
+
option = switch_options[0]
|
|
231
|
+
option.update({'disabled': True})
|
|
232
|
+
switch_options[0] = option
|
|
233
|
+
|
|
234
|
+
return state1, True, state2, True, command, switch_options
|
|
214
235
|
|
|
215
236
|
# ===== recreate RSM =====
|
|
216
237
|
@app.callback(
|
|
@@ -256,9 +277,10 @@ class PredictionModelGraph(AbstractPage):
|
|
|
256
277
|
State(self.axis3_obj_dropdown, 'label'),
|
|
257
278
|
State(self.slider_container, 'children'), # for callback chain
|
|
258
279
|
State({'type': 'prm-slider', 'index': ALL}, 'value'),
|
|
280
|
+
State(self.switch_3d, 'value'),
|
|
259
281
|
prevent_initial_call=True,
|
|
260
282
|
)
|
|
261
|
-
def redraw_graph(command, active_tab_id, axis1_label, axis2_label, axis3_label, _2, prm_values):
|
|
283
|
+
def redraw_graph(command, active_tab_id, axis1_label, axis2_label, axis3_label, _2, prm_values, is_3d):
|
|
262
284
|
# just in case
|
|
263
285
|
if callback_context.triggered_id is None:
|
|
264
286
|
raise PreventUpdate
|
|
@@ -283,6 +305,9 @@ class PredictionModelGraph(AbstractPage):
|
|
|
283
305
|
logger.error(Msg.ERR_NO_PREDICTION_MODEL)
|
|
284
306
|
return no_update, self.CommandState.ready.value # to re-enable buttons, fire callback chain
|
|
285
307
|
|
|
308
|
+
if not is_3d:
|
|
309
|
+
axis2_label = None
|
|
310
|
+
|
|
286
311
|
# get indices to remove
|
|
287
312
|
idx1 = prm_names.index(axis1_label) if axis1_label in prm_names else None
|
|
288
313
|
idx2 = prm_names.index(axis2_label) if axis2_label in prm_names else None
|
|
@@ -307,23 +332,31 @@ class PredictionModelGraph(AbstractPage):
|
|
|
307
332
|
|
|
308
333
|
return fig, self.CommandState.ready.value
|
|
309
334
|
|
|
310
|
-
# =====
|
|
335
|
+
# ===== re-enable buttons when the graph is updated, =====
|
|
311
336
|
@app.callback(
|
|
312
337
|
Output(self.fit_rsm_button, 'disabled', allow_duplicate=True),
|
|
313
338
|
Output(self.fit_rsm_button_spinner, 'spinner_style', allow_duplicate=True),
|
|
314
339
|
Output(self.redraw_graph_button, 'disabled', allow_duplicate=True),
|
|
315
340
|
Output(self.redraw_graph_button_spinner, 'spinner_style', allow_duplicate=True),
|
|
341
|
+
Output(self.switch_3d, 'options', allow_duplicate=True),
|
|
316
342
|
Input(self.command_manager, self.command_manager_prop),
|
|
317
343
|
State(self.fit_rsm_button_spinner, 'spinner_style'),
|
|
318
344
|
State(self.redraw_graph_button_spinner, 'spinner_style'),
|
|
345
|
+
State(self.switch_3d, 'options'),
|
|
319
346
|
prevent_initial_call=True,
|
|
320
347
|
)
|
|
321
|
-
def enable_buttons(command, state1, state2):
|
|
348
|
+
def enable_buttons(command, state1, state2, switch_options):
|
|
322
349
|
if command != self.CommandState.ready.value:
|
|
323
350
|
raise PreventUpdate
|
|
324
351
|
state1.update({'display': 'none'})
|
|
325
352
|
state2.update({'display': 'none'})
|
|
326
|
-
|
|
353
|
+
|
|
354
|
+
# enable switch
|
|
355
|
+
option = switch_options[0]
|
|
356
|
+
option.update({'disabled': False})
|
|
357
|
+
switch_options[0] = option
|
|
358
|
+
|
|
359
|
+
return False, state1, False, state2, switch_options
|
|
327
360
|
|
|
328
361
|
# ===== setup dropdown and sliders from history =====
|
|
329
362
|
@app.callback(
|
|
@@ -414,6 +447,7 @@ class PredictionModelGraph(AbstractPage):
|
|
|
414
447
|
Input({'type': 'axis2-dropdown-menu-item', 'index': ALL}, 'n_clicks'),
|
|
415
448
|
Input({'type': 'axis3-dropdown-menu-item', 'index': ALL}, 'n_clicks'),
|
|
416
449
|
Input(self.axis1_prm_dropdown, 'children'), # for callback chain timing
|
|
450
|
+
Input(self.switch_3d, 'value'),
|
|
417
451
|
State(self.axis1_prm_dropdown, 'label'),
|
|
418
452
|
State(self.axis2_prm_dropdown, 'label'),
|
|
419
453
|
State(self.axis3_obj_dropdown, 'label'),
|
|
@@ -422,10 +456,10 @@ class PredictionModelGraph(AbstractPage):
|
|
|
422
456
|
)
|
|
423
457
|
def update_controller(*args):
|
|
424
458
|
# argument processing
|
|
425
|
-
current_ax1_label = args[
|
|
426
|
-
current_ax2_label = args[
|
|
427
|
-
|
|
428
|
-
|
|
459
|
+
current_ax1_label = args[5]
|
|
460
|
+
current_ax2_label = args[6]
|
|
461
|
+
current_styles: list[dict] = args[8]
|
|
462
|
+
is_3d = args[4]
|
|
429
463
|
|
|
430
464
|
# just in case
|
|
431
465
|
if callback_context.triggered_id is None:
|
|
@@ -451,6 +485,10 @@ class PredictionModelGraph(AbstractPage):
|
|
|
451
485
|
if len(prm_names) < 2:
|
|
452
486
|
ret[ax2_hidden] = True
|
|
453
487
|
|
|
488
|
+
# ===== hide dropdown of axis 2 if not is_3d =====
|
|
489
|
+
if not is_3d:
|
|
490
|
+
ret[ax2_hidden] = True
|
|
491
|
+
|
|
454
492
|
# ===== update dropdown label =====
|
|
455
493
|
|
|
456
494
|
# by callback chain on loaded after setup_dropdown_and_sliders()
|
|
@@ -466,29 +504,43 @@ class PredictionModelGraph(AbstractPage):
|
|
|
466
504
|
|
|
467
505
|
# ax1
|
|
468
506
|
if callback_context.triggered_id['type'] == 'axis1-dropdown-menu-item':
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
507
|
+
ret[ax1_label_key] = new_label
|
|
508
|
+
if new_label == current_ax2_label:
|
|
509
|
+
ret[ax2_label_key] = current_ax1_label
|
|
510
|
+
|
|
473
511
|
|
|
474
512
|
# ax2
|
|
475
513
|
elif callback_context.triggered_id['type'] == 'axis2-dropdown-menu-item':
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
514
|
+
ret[ax2_label_key] = new_label
|
|
515
|
+
if new_label == current_ax1_label:
|
|
516
|
+
ret[ax1_label_key] = current_ax2_label
|
|
517
|
+
|
|
480
518
|
|
|
481
519
|
# ax3
|
|
482
520
|
elif callback_context.triggered_id['type'] == 'axis3-dropdown-menu-item':
|
|
483
521
|
ret[ax3_label_key] = new_label
|
|
484
522
|
|
|
485
523
|
# ===== update visibility of sliders =====
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
524
|
+
|
|
525
|
+
# invisible the slider correspond to the dropdown-1
|
|
526
|
+
label_key, current_label = ax1_label_key, current_ax1_label
|
|
527
|
+
# get label of output
|
|
528
|
+
label = ret[label_key] if ret[label_key] != no_update else current_label
|
|
529
|
+
# update display style of slider
|
|
530
|
+
idx = prm_names.index(label) if label in prm_names else None
|
|
531
|
+
if idx is not None:
|
|
532
|
+
current_styles[idx].update({'display': 'none'})
|
|
533
|
+
ret[slider_style_list_key][idx] = current_styles[idx]
|
|
534
|
+
|
|
535
|
+
# invisible the slider correspond to the dropdown-2
|
|
536
|
+
label_key, current_label = ax2_label_key, current_ax2_label
|
|
537
|
+
# get label of output
|
|
538
|
+
label = ret[label_key] if ret[label_key] != no_update else current_label
|
|
539
|
+
# update display style of slider
|
|
540
|
+
idx = prm_names.index(label) if label in prm_names else None
|
|
541
|
+
if idx is not None:
|
|
542
|
+
# if 2d, should not disable the slider correspond to dropdown-2.
|
|
543
|
+
if is_3d:
|
|
492
544
|
current_styles[idx].update({'display': 'none'})
|
|
493
545
|
ret[slider_style_list_key][idx] = current_styles[idx]
|
|
494
546
|
|
|
@@ -554,5 +606,5 @@ class PredictionModelGraph(AbstractPage):
|
|
|
554
606
|
if isinstance(self.application, ProcessMonitorApplication):
|
|
555
607
|
df = self.application.local_data
|
|
556
608
|
else:
|
|
557
|
-
df = self.application.history.
|
|
609
|
+
df = self.application.history.get_df()
|
|
558
610
|
return df
|
|
@@ -5,7 +5,7 @@ from threading import Thread
|
|
|
5
5
|
import pandas as pd
|
|
6
6
|
|
|
7
7
|
from pyfemtet.opt.visualization.base import PyFemtetApplicationBase, logger
|
|
8
|
-
from pyfemtet.opt.visualization.process_monitor.pages import HomePage, WorkerPage, PredictionModelPage
|
|
8
|
+
from pyfemtet.opt.visualization.process_monitor.pages import HomePage, WorkerPage, PredictionModelPage, OptunaVisualizerPage
|
|
9
9
|
from pyfemtet.message import Msg
|
|
10
10
|
|
|
11
11
|
|
|
@@ -67,14 +67,14 @@ class ProcessMonitorApplication(PyFemtetApplicationBase):
|
|
|
67
67
|
if self._should_get_actor_data:
|
|
68
68
|
return self._df
|
|
69
69
|
else:
|
|
70
|
-
return self.history.
|
|
70
|
+
return self.history.get_df()
|
|
71
71
|
|
|
72
72
|
@local_data.setter
|
|
73
73
|
def local_data(self, value: pd.DataFrame):
|
|
74
74
|
if self._should_get_actor_data:
|
|
75
75
|
raise NotImplementedError('If should_get_actor_data, ProcessMonitorApplication.local_df is read_only.')
|
|
76
76
|
else:
|
|
77
|
-
self.history.
|
|
77
|
+
self.history.set_df(value)
|
|
78
78
|
|
|
79
79
|
def setup_callback(self, debug=False):
|
|
80
80
|
if not debug:
|
|
@@ -112,7 +112,7 @@ class ProcessMonitorApplication(PyFemtetApplicationBase):
|
|
|
112
112
|
worker_status.set(OptimizationStatus.INTERRUPTING)
|
|
113
113
|
|
|
114
114
|
# status と df を actor から application に反映する
|
|
115
|
-
self._df = self.history.
|
|
115
|
+
self._df = self.history.get_df().copy()
|
|
116
116
|
self.local_entire_status_int = self.entire_status.get()
|
|
117
117
|
self.local_worker_status_int_list = [s.get() for s in self.worker_status_list]
|
|
118
118
|
|
|
@@ -176,11 +176,13 @@ def g_debug():
|
|
|
176
176
|
|
|
177
177
|
g_home_page = HomePage(Msg.PAGE_TITLE_PROGRESS)
|
|
178
178
|
g_rsm_page = PredictionModelPage(Msg.PAGE_TITLE_PREDICTION_MODEL, '/prediction-model', g_application)
|
|
179
|
+
g_optuna = OptunaVisualizerPage(Msg.PAGE_TITLE_OPTUNA_VISUALIZATION, '/optuna', g_application)
|
|
179
180
|
g_worker_page = WorkerPage(Msg.PAGE_TITLE_WORKERS, '/workers', g_application)
|
|
180
181
|
|
|
181
182
|
g_application.add_page(g_home_page, 0)
|
|
182
183
|
g_application.add_page(g_rsm_page, 1)
|
|
183
|
-
g_application.add_page(
|
|
184
|
+
g_application.add_page(g_optuna, 2)
|
|
185
|
+
g_application.add_page(g_worker_page, 3)
|
|
184
186
|
g_application.setup_callback(debug=False)
|
|
185
187
|
|
|
186
188
|
g_application.run(debug=False)
|
|
@@ -191,11 +193,13 @@ def main(history, status, worker_addresses, worker_status_list, host=None, port=
|
|
|
191
193
|
|
|
192
194
|
g_home_page = HomePage(Msg.PAGE_TITLE_PROGRESS)
|
|
193
195
|
g_rsm_page = PredictionModelPage(Msg.PAGE_TITLE_PREDICTION_MODEL, '/prediction-model', g_application)
|
|
196
|
+
g_optuna = OptunaVisualizerPage(Msg.PAGE_TITLE_OPTUNA_VISUALIZATION, '/optuna', g_application)
|
|
194
197
|
g_worker_page = WorkerPage(Msg.PAGE_TITLE_WORKERS, '/workers', g_application)
|
|
195
198
|
|
|
196
199
|
g_application.add_page(g_home_page, 0)
|
|
197
200
|
g_application.add_page(g_rsm_page, 1)
|
|
198
|
-
g_application.add_page(
|
|
201
|
+
g_application.add_page(g_optuna, 2)
|
|
202
|
+
g_application.add_page(g_worker_page, 3)
|
|
199
203
|
g_application.setup_callback()
|
|
200
204
|
|
|
201
205
|
g_application.start_server(host, port)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import pandas as pd
|
|
3
3
|
|
|
4
|
+
import optuna
|
|
5
|
+
|
|
4
6
|
from dash import Output, Input, State, callback_context, no_update, ALL
|
|
5
7
|
from dash.exceptions import PreventUpdate
|
|
6
8
|
|
|
@@ -289,3 +291,103 @@ class PredictionModelPage(AbstractPage):
|
|
|
289
291
|
|
|
290
292
|
def setup_layout(self):
|
|
291
293
|
self.layout = self.rsm_graph.layout
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class OptunaVisualizerPage(AbstractPage):
|
|
297
|
+
|
|
298
|
+
def __init__(self, title, rel_url, application):
|
|
299
|
+
from pyfemtet.opt.visualization.process_monitor.application import ProcessMonitorApplication
|
|
300
|
+
self.application: ProcessMonitorApplication = None
|
|
301
|
+
super().__init__(title, rel_url, application)
|
|
302
|
+
|
|
303
|
+
def setup_component(self):
|
|
304
|
+
self.location = dcc.Location(id='optuna-page-location', refresh=True)
|
|
305
|
+
self._layout = html.Div(children=[Msg.DETAIL_PAGE_TEXT_BEFORE_LOADING])
|
|
306
|
+
self.layout = [self.location, self._layout]
|
|
307
|
+
|
|
308
|
+
def _setup_layout(self):
|
|
309
|
+
|
|
310
|
+
study = self.application.history.create_optuna_study()
|
|
311
|
+
prm_names = self.application.history.prm_names
|
|
312
|
+
obj_names = self.application.history.obj_names
|
|
313
|
+
|
|
314
|
+
layout = []
|
|
315
|
+
|
|
316
|
+
layout.append(html.H2(Msg.DETAIL_PAGE_HISTORY_HEADER))
|
|
317
|
+
layout.append(html.H4(Msg.DETAIL_PAGE_HISTORY_DESCRIPTION))
|
|
318
|
+
for i, obj_name in enumerate(obj_names):
|
|
319
|
+
fig = optuna.visualization.plot_optimization_history(
|
|
320
|
+
study,
|
|
321
|
+
target=lambda t: t.values[i],
|
|
322
|
+
target_name=obj_name
|
|
323
|
+
)
|
|
324
|
+
layout.append(dcc.Graph(figure=fig, style={'height': '70vh'}))
|
|
325
|
+
|
|
326
|
+
layout.append(html.H2(Msg.DETAIL_PAGE_PARALLEL_COOR_HEADER))
|
|
327
|
+
layout.append(html.H4(Msg.DETAIL_PAGE_PARALLEL_COOR_DESCRIPTION))
|
|
328
|
+
for i, obj_name in enumerate(obj_names):
|
|
329
|
+
fig = optuna.visualization.plot_parallel_coordinate(
|
|
330
|
+
study,
|
|
331
|
+
target=lambda t: t.values[i],
|
|
332
|
+
target_name=obj_name
|
|
333
|
+
)
|
|
334
|
+
layout.append(dcc.Graph(figure=fig, style={'height': '70vh'}))
|
|
335
|
+
|
|
336
|
+
layout.append(html.H2(Msg.DETAIL_PAGE_CONTOUR_HEADER))
|
|
337
|
+
layout.append(html.H4(Msg.DETAIL_PAGE_CONTOUR_DESCRIPTION))
|
|
338
|
+
for i, obj_name in enumerate(obj_names):
|
|
339
|
+
fig = optuna.visualization.plot_contour(
|
|
340
|
+
study,
|
|
341
|
+
target=lambda t: t.values[i],
|
|
342
|
+
target_name=obj_name
|
|
343
|
+
)
|
|
344
|
+
layout.append(dcc.Graph(figure=fig, style={'height': '90vh'}))
|
|
345
|
+
|
|
346
|
+
# import itertools
|
|
347
|
+
# for (i, j) in itertools.combinations(range(len(obj_names)), 2):
|
|
348
|
+
# fig = optuna.visualization.plot_pareto_front(
|
|
349
|
+
# study,
|
|
350
|
+
# targets=lambda t: (t.values[i], t.values[j]),
|
|
351
|
+
# target_names=[obj_names[i], obj_names[j]],
|
|
352
|
+
# )
|
|
353
|
+
# self.graphs.append(dcc.Graph(figure=fig, style={'height': '50vh'}))
|
|
354
|
+
|
|
355
|
+
layout.append(html.H2(Msg.DETAIL_PAGE_SLICE_HEADER))
|
|
356
|
+
layout.append(html.H4(Msg.DETAIL_PAGE_SLICE_DESCRIPTION))
|
|
357
|
+
for i, obj_name in enumerate(obj_names):
|
|
358
|
+
fig = optuna.visualization.plot_slice(
|
|
359
|
+
study,
|
|
360
|
+
target=lambda t: t.values[i],
|
|
361
|
+
target_name=obj_name
|
|
362
|
+
)
|
|
363
|
+
layout.append(dcc.Graph(figure=fig, style={'height': '70vh'}))
|
|
364
|
+
|
|
365
|
+
return layout
|
|
366
|
+
|
|
367
|
+
def setup_callback(self):
|
|
368
|
+
app = self.application.app
|
|
369
|
+
|
|
370
|
+
@app.callback(
|
|
371
|
+
Output(self._layout, 'children'),
|
|
372
|
+
Input(self.location, 'pathname'), # on page load
|
|
373
|
+
)
|
|
374
|
+
def update_page(_):
|
|
375
|
+
if self.application.history is None:
|
|
376
|
+
return Msg.ERR_NO_HISTORY_SELECTED
|
|
377
|
+
|
|
378
|
+
if len(self.data_accessor()) == 0:
|
|
379
|
+
return Msg.ERR_NO_FEM_RESULT
|
|
380
|
+
|
|
381
|
+
return self._setup_layout()
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def setup_layout(self):
|
|
385
|
+
pass
|
|
386
|
+
|
|
387
|
+
def data_accessor(self) -> pd.DataFrame:
|
|
388
|
+
from pyfemtet.opt.visualization.process_monitor.application import ProcessMonitorApplication
|
|
389
|
+
if isinstance(self.application, ProcessMonitorApplication):
|
|
390
|
+
df = self.application.local_data
|
|
391
|
+
else:
|
|
392
|
+
df = self.application.history.local_data
|
|
393
|
+
return df
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from pyfemtet.opt.visualization.base import PyFemtetApplicationBase
|
|
2
2
|
from pyfemtet.opt.visualization.result_viewer.pages import HomePage, PredictionModelPage
|
|
3
|
+
from pyfemtet.opt.visualization.process_monitor.pages import OptunaVisualizerPage
|
|
4
|
+
|
|
3
5
|
from pyfemtet.message import Msg
|
|
4
6
|
|
|
5
7
|
|
|
@@ -24,9 +26,11 @@ def debug():
|
|
|
24
26
|
|
|
25
27
|
g_home_page = HomePage(Msg.PAGE_TITLE_RESULT)
|
|
26
28
|
g_rsm_page = PredictionModelPage(Msg.PAGE_TITLE_PREDICTION_MODEL, '/prediction-model', g_application)
|
|
29
|
+
g_optuna = OptunaVisualizerPage(Msg.PAGE_TITLE_OPTUNA_VISUALIZATION, '/optuna', g_application)
|
|
27
30
|
|
|
28
31
|
g_application.add_page(g_home_page, 0)
|
|
29
32
|
g_application.add_page(g_rsm_page, 1)
|
|
33
|
+
g_application.add_page(g_optuna, 2)
|
|
30
34
|
g_application.setup_callback()
|
|
31
35
|
|
|
32
36
|
g_application.run(debug=True)
|
|
@@ -37,9 +41,11 @@ def main():
|
|
|
37
41
|
|
|
38
42
|
g_home_page = HomePage(Msg.PAGE_TITLE_RESULT)
|
|
39
43
|
g_rsm_page = PredictionModelPage(Msg.PAGE_TITLE_PREDICTION_MODEL, '/prediction-model', g_application)
|
|
44
|
+
g_optuna = OptunaVisualizerPage(Msg.PAGE_TITLE_OPTUNA_VISUALIZATION, '/optuna', g_application)
|
|
40
45
|
|
|
41
46
|
g_application.add_page(g_home_page, 0)
|
|
42
47
|
g_application.add_page(g_rsm_page, 1)
|
|
48
|
+
g_application.add_page(g_optuna, 2)
|
|
43
49
|
g_application.setup_callback()
|
|
44
50
|
|
|
45
51
|
g_application.run()
|
|
@@ -281,7 +281,7 @@ class HomePage(AbstractPage):
|
|
|
281
281
|
trial = pt['customdata'][0]
|
|
282
282
|
|
|
283
283
|
# get parameter and update model
|
|
284
|
-
df = self.application.history.
|
|
284
|
+
df = self.application.history.get_df()
|
|
285
285
|
row = df[df['trial'] == trial]
|
|
286
286
|
metadata = np.array(self.application.history.metadata)
|
|
287
287
|
idx = np.where(metadata == 'prm')[0]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyfemtet
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.23
|
|
4
4
|
Summary: Design parameter optimization using Femtet.
|
|
5
5
|
Home-page: https://github.com/pyfemtet/pyfemtet
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -13,8 +13,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Requires-Dist: babel (>=2.15.0,<3.0.0)
|
|
16
|
-
Requires-Dist: botorch (
|
|
17
|
-
Requires-Dist: botorch (>=0.9.5,<0.10.0) ; python_version < "3.12"
|
|
16
|
+
Requires-Dist: botorch (==0.9.5)
|
|
18
17
|
Requires-Dist: colorlog (>=6.8.0,<7.0.0)
|
|
19
18
|
Requires-Dist: dash (>=2.17.0,<3.0.0)
|
|
20
19
|
Requires-Dist: dash-bootstrap-components (>=1.5.0,<2.0.0)
|
|
@@ -28,9 +27,9 @@ Requires-Dist: optuna-integration (>=3.6.0,<4.0.0)
|
|
|
28
27
|
Requires-Dist: pandas (>=2.1.3,<3.0.0)
|
|
29
28
|
Requires-Dist: plotly (>=5.22.0,<6.0.0)
|
|
30
29
|
Requires-Dist: psutil (>=5.9.6,<6.0.0)
|
|
31
|
-
Requires-Dist: pytest-dashboard (>=0.1.2,<0.2.0)
|
|
32
30
|
Requires-Dist: pywin32 (>=306,<307)
|
|
33
31
|
Requires-Dist: scipy (>=1.11.4,<2.0.0)
|
|
32
|
+
Requires-Dist: torch (>=2.3.0,<2.4.0)
|
|
34
33
|
Requires-Dist: tqdm (>=4.66.1,<5.0.0)
|
|
35
34
|
Project-URL: Repository, https://github.com/pyfemtet/pyfemtet
|
|
36
35
|
Description-Content-Type: text/markdown
|