Skip to content

Commit 2d4b123

Browse files
Add modal issue
- add function to stop computation if any alarm is raised - add modal triggered when any alarm is raised - fix logger level and add --logging_level to app arguments - add --test_alarms as temporary test mode to force all alarms to trigger at any step
1 parent d54084d commit 2d4b123

File tree

5 files changed

+224
-79
lines changed

5 files changed

+224
-79
lines changed

grid2game/VizServer.py

Lines changed: 100 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
import dash_bootstrap_components as dbc
1515
from dash import html, dcc
1616

17-
from grid2game._utils import (add_callbacks_temporal,
18-
setupLayout_temporal,
19-
add_callbacks,
17+
from grid2game._utils import (add_callbacks_temporal,
18+
setupLayout_temporal,
19+
add_callbacks,
2020
setupLayout,
21-
add_callbacks_action_search,
22-
setupLayout_action_search,
21+
add_callbacks_action_search,
22+
setupLayout_action_search,
2323
)
2424
from grid2game.envs import Env
2525
from grid2game.plot import PlotGrids, PlotTemporalSeries
@@ -82,6 +82,20 @@ def __init__(self,
8282
os.path.join(os.path.dirname(__file__), "assets")
8383
)
8484

85+
# create the dash app
86+
self.my_app = dash.Dash(__name__,
87+
server=server if server is not None else True,
88+
meta_tags=meta_tags,
89+
assets_folder=assets_dir,
90+
external_stylesheets=external_stylesheets,
91+
external_scripts=external_scripts)
92+
93+
# Configure logging after dash initialization.
94+
# Otherwise dash is resetting logging level to INFO
95+
96+
if not logging_level and build_args.logging_level:
97+
logging_level = build_args.logging_level
98+
8599
if logger is None:
86100
import logging
87101
self.logger = logging.getLogger(__name__)
@@ -94,19 +108,14 @@ def __init__(self,
94108
if logging_level is not None:
95109
fh.setLevel(logging_level)
96110
ch.setLevel(logging_level)
111+
self.logger.setLevel(logging_level)
97112
self.logger.addHandler(fh)
98113
self.logger.addHandler(ch)
99114
else:
100115
self.logger = logger.getChild("VizServer")
101116

102-
# create the dash app
103-
self.my_app = dash.Dash(__name__,
104-
server=server if server is not None else True,
105-
meta_tags=meta_tags,
106-
assets_folder=assets_dir,
107-
external_stylesheets=external_stylesheets,
108-
external_scripts=external_scripts)
109117
self.logger.info("Dash app initialized")
118+
110119
# self.app.config.suppress_callback_exceptions = True
111120

112121
# create the grid2op related things
@@ -125,8 +134,14 @@ def __init__(self,
125134
# read the right config
126135
g2op_config = self._make_glop_env_config(build_args)
127136

137+
if build_args.test_alarms:
138+
test_alarms = True
139+
else:
140+
test_alarms = False
141+
128142
self.env = Env(build_args.env_name,
129143
test=build_args.is_test,
144+
test_alarms=test_alarms,
130145
assistant_path=self.assistant_path,
131146
assistant_seed=int(build_args.assistant_seed) if build_args.assistant_seed is not None else None,
132147
logger=self.logger,
@@ -187,37 +202,37 @@ def __init__(self,
187202
self.plot_grids.init_figs(self.env.obs, self.env.sim_obs)
188203
self.real_time = self.plot_grids.figure_rt
189204
self.forecast = self.plot_grids.figure_forecat
190-
205+
191206
# initialize the layout
192207
self._layout_temporal = html.Div(setupLayout_temporal(self),
193208
id="all_temporal")
194209
self._layout_temporal_tab = dcc.Tab(label='Temporal view',
195210
value=f'tab-temporal-view',
196211
children=self._layout_temporal)
197-
212+
198213
self._layout_action_search = html.Div(setupLayout_action_search(self),
199214
id="all_action_search")
200215
self._layout_action_search_tab = dcc.Tab(label='Explore actions',
201216
value='tab-explore-action',
202217
children=self._layout_action_search)
203-
218+
204219
tmp_ = setupLayout(self,
205220
self._layout_temporal_tab,
206221
self._layout_action_search_tab)
207-
222+
208223
self.my_app.layout = tmp_
209-
224+
210225
add_callbacks_temporal(self.my_app, self)
211226
add_callbacks_action_search(self.my_app, self)
212227
add_callbacks(self.my_app, self)
213-
228+
214229
self.logger.info("Viz server initialized")
215230

216231
# last node id (to not plot twice the same stuff to gain time)
217232
self._last_node_id = -1
218233

219234
# last action taken
220-
self._last_action = "assistant"
235+
self._last_action = "assistant"
221236
self._do_display_action = True
222237
self._dropdown_value = "assistant"
223238

@@ -254,11 +269,11 @@ def run_server(self, debug=False):
254269

255270
def change_nb_step_go_fast(self, nb_step_go_fast):
256271
if nb_step_go_fast is None:
257-
return dash.no_update,
272+
return dash.no_update,
258273

259274
nb = int(nb_step_go_fast)
260275
self.nb_step_gofast = nb
261-
return f"+ {self.nb_step_gofast}",
276+
return f"+ {self.nb_step_gofast}",
262277

263278
def unit_clicked(self, line_unit, line_side, load_unit, gen_unit, stor_unit,
264279
trigger_rt_graph, trigger_for_graph):
@@ -296,22 +311,24 @@ def unit_clicked(self, line_unit, line_side, load_unit, gen_unit, stor_unit,
296311
def _reset_action_to_assistant_if_not_prev(self):
297312
if self._last_action != "prev" :
298313
self._next_action_is_assistant()
299-
314+
300315
# handle the interaction with the grid2op environment
301-
def handle_act_on_env(self,
302-
step_butt,
303-
simulate_butt,
304-
back_butt,
305-
reset_butt,
306-
go_butt,
307-
gofast_clicks,
308-
until_game_over,
309-
untilgo_butt,
310-
self_loop,
311-
state_trigger_rt,
312-
state_trigger_for,
313-
state_trigger_self_loop,
314-
timer):
316+
def handle_act_on_env(
317+
self,
318+
step_butt,
319+
simulate_butt,
320+
back_butt,
321+
reset_butt,
322+
go_butt,
323+
gofast_clicks,
324+
until_game_over,
325+
untilgo_butt,
326+
self_loop,
327+
state_trigger_rt,
328+
state_trigger_for,
329+
state_trigger_self_loop,
330+
timer
331+
):
315332
"""
316333
dash do not make "synch" callbacks (two callbacks can be called at the same time),
317334
however, grid2op environments are not "thread safe": accessing them from different "thread"
@@ -344,18 +361,21 @@ def handle_act_on_env(self,
344361
self._go_till_go_button_shape = "btn btn-secondary"
345362
change_graph_title = dash.no_update
346363
update_progress_bar = 1
364+
check_issue = dash.no_update
347365

348366
# now register the next computation to do, based on the button triggerd
349367
if button_id == "step-button":
350368
self.env.start_computation()
351369
self.env.next_computation = "step"
352370
self.env.next_computation_kwargs = {}
353371
self.need_update_figures = False
372+
check_issue = 1
354373
elif button_id == "go_till_game_over-button":
355374
self.env.start_computation()
356375
self.env.next_computation = "step_end"
357376
self.env.next_computation_kwargs = {}
358377
self.need_update_figures = True
378+
check_issue = 1
359379
elif button_id == "reset-button":
360380
self.env.start_computation()
361381
self.env.next_computation = "reset"
@@ -378,6 +398,7 @@ def handle_act_on_env(self,
378398
self.env.start_computation()
379399
self.env.next_computation = "step_rec_fast"
380400
self.env.next_computation_kwargs = {"nb_step_gofast": self.nb_step_gofast}
401+
check_issue = 1
381402
elif button_id == "go-button":
382403
self.go_clicks += 1
383404
if self.go_clicks % 2:
@@ -390,6 +411,7 @@ def handle_act_on_env(self,
390411
self.env.start_computation()
391412
self._button_shape = "btn btn-secondary"
392413
self._gofast_button_shape = "btn btn-secondary"
414+
check_issue = 1
393415
self.env.next_computation = "step_rec"
394416
self.env.next_computation_kwargs = {}
395417
self.need_update_figures = False
@@ -434,7 +456,7 @@ def handle_act_on_env(self,
434456
self._go_till_go_button_shape = "btn btn-secondary"
435457
self._gofast_button_shape = "btn btn-secondary"
436458
self._button_shape = "btn btn-secondary"
437-
459+
438460
return [display_new_state,
439461
self._button_shape,
440462
self._button_shape,
@@ -446,7 +468,8 @@ def handle_act_on_env(self,
446468
i_am_computing_state,
447469
i_am_computing_state,
448470
change_graph_title,
449-
update_progress_bar]
471+
update_progress_bar,
472+
check_issue]
450473

451474
def _wait_for_computing_over(self):
452475
i = 0
@@ -458,6 +481,28 @@ def _wait_for_computing_over(self):
458481
# in this case the user should probably call reset another time !
459482
raise dash.exceptions.PreventUpdate
460483

484+
def check_issue(self, n1, n_clicks):
485+
# make sure the environment has nothing to compute
486+
while self.env.needs_compute():
487+
time.sleep(0.1)
488+
489+
issues = self.env._current_issues
490+
if issues:
491+
len_issues = len(issues)
492+
if len_issues == 1:
493+
issue_text = f"There is {len_issues} issue: "
494+
else:
495+
issue_text = f"There are {len_issues} issues: "
496+
for issue in issues:
497+
issue_text += f"{issue}, "
498+
# Replace last ', ' by '.' to end the sentence
499+
issue_text = '.'.join(issue_text.rsplit(', ', 1))
500+
is_open = True
501+
else:
502+
is_open = False
503+
504+
return [is_open, issue_text]
505+
461506
def change_graph_title(self, change_graph_title):
462507
# make sure that the environment has done computing
463508
self._wait_for_computing_over()
@@ -483,7 +528,7 @@ def computation_wrapper(self, display_new_state, recompute_rt_from_timeline):
483528
# simulate a "state" of the application that depends on the computation
484529
if not self.env.is_computing():
485530
self.env.heavy_compute()
486-
531+
487532
if self.env.is_computing() and display_new_state != type(self).GO_MODE:
488533
# environment is computing I do not update anything
489534
raise dash.exceptions.PreventUpdate
@@ -513,7 +558,7 @@ def update_rt_fig(self, env_act):
513558
trigger_for_graph = 1
514559
else:
515560
raise dash.exceptions.PreventUpdate
516-
561+
517562
if trigger_rt_graph == 1:
518563
self.fig_timeline = self.env.get_timeline_figure()
519564

@@ -661,7 +706,7 @@ def _next_action_is_assistant(self):
661706
self._last_action = "assistant"
662707
self._do_display_action = True
663708
self._dropdown_value = "assistant"
664-
709+
665710
def display_action_fun(self,
666711
which_action_button,
667712
do_display,
@@ -712,7 +757,7 @@ def display_action_fun(self,
712757
# i should not display the action
713758
res = [f"{self.env.current_action}", dropdown_value, update_substation_layout_clicked_from_sub]
714759
return res
715-
760+
716761
# i need to display the action
717762
# self._last_action = "manual"
718763
# dropdown_value = "manual"
@@ -748,7 +793,7 @@ def display_action_fun(self,
748793

749794
if not is_modif:
750795
raise dash.exceptions.PreventUpdate
751-
796+
752797
# TODO optim here to save that if not needed because nothing has changed
753798
res = [f"{self.env.current_action}", self._dropdown_value, update_substation_layout_clicked_from_sub]
754799
return res
@@ -821,7 +866,7 @@ def display_click_data(self,
821866
button_id == "go-button" or button_id == "gofast-button" or\
822867
button_id == "back-button":
823868
# i never clicked on simulate, step, go, gofast or back
824-
do_display_action = 0
869+
do_display_action = 0
825870
self._last_sub_id = None
826871
else:
827872
# I clicked on the graph of the grid
@@ -980,7 +1025,7 @@ def timeline_set_time(self, time_line_graph_clicked):
9801025

9811026
def tab_content_display(self, tab):
9821027
res = [self._layout_temporal]
983-
1028+
9841029
if tab == 'tab-temporal-view':
9851030
self.need_update_figures = True
9861031
return [self._layout_temporal]
@@ -991,13 +1036,13 @@ def tab_content_display(self, tab):
9911036
msg_ = f"Unknown tab {tab}"
9921037
self.logger.error(msg_)
9931038
return res
994-
1039+
9951040
def _aux_tab_as_retrieve_updated_figs(self):
9961041
progress_pct = 100. * self._last_step / self._last_max_step
9971042
progress_label = f"{self._last_step} / {self._last_max_step}"
9981043
self.fig_timeline = self.env.get_timeline_figure()
9991044
self.update_obs_fig()
1000-
1045+
10011046
pbar_value = progress_pct
10021047
pbar_label = progress_label
10031048
pbar_color = self._progress_color
@@ -1006,7 +1051,7 @@ def _aux_tab_as_retrieve_updated_figs(self):
10061051
fig_rt = self.real_time
10071052
return (pbar_value, pbar_label, pbar_color, fig_timeline,
10081053
dt_label, fig_rt)
1009-
1054+
10101055
def main_action_search(self,
10111056
refresh_button,
10121057
explore_butt_pressed,
@@ -1017,9 +1062,9 @@ def main_action_search(self,
10171062
raise dash.exceptions.PreventUpdate
10181063
else:
10191064
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
1020-
1065+
10211066
something_clicked = True
1022-
1067+
10231068
# TODO button color here too !
10241069
i_am_computing_state = {'display': 'block'}
10251070
pbar_value = dash.no_update
@@ -1029,25 +1074,25 @@ def main_action_search(self,
10291074
dt_label = dash.no_update
10301075
fig_rt = dash.no_update
10311076
start_computation = 1
1032-
1077+
10331078
if button_id == "refresh-button_as":
10341079
# (pbar_value, pbar_label, pbar_color, fig_timeline,
10351080
# dt_label, fig_rt) = self._aux_tab_as_retrieve_updated_figs()
10361081
start_computation = dash.no_update
10371082
# hack for it to resynch everything
10381083
self.need_update_figures = True
1039-
elif button_id == "explore-button_as":
1084+
elif button_id == "explore-button_as":
10401085
self.env.next_computation = "explore"
10411086
self.need_update_figures = True
10421087
self.env.start_computation()
10431088
else:
10441089
something_clicked = False
1045-
1090+
10461091
if not self.env.needs_compute():
10471092
# don't start the computation if not needed
10481093
i_am_computing_state = {'display': 'none'} # deactivate the "i am computing button"
10491094
start_computation = dash.no_update # I am NOT computing I DO update the graphs
1050-
1095+
10511096
if not self.env.needs_compute() and self.need_update_figures and not something_clicked:
10521097
# in this case, this should be the last call to this function after the "explore"
10531098
# function is finished
@@ -1058,7 +1103,7 @@ def main_action_search(self,
10581103

10591104
(pbar_value, pbar_label, pbar_color, fig_timeline,
10601105
dt_label, fig_rt) = self._aux_tab_as_retrieve_updated_figs()
1061-
1106+
10621107
return [start_computation,
10631108
pbar_value,
10641109
pbar_label,

0 commit comments

Comments
 (0)