Пример #1
0
    def _show_graph(self, data_frame):

        #create the plot panels for the Agent_Tasks
        panels = self._create_task_panels(data_frame)

        top_left_x_range = panels[list(panels.keys())[0]]['panel1'].x_range

        #Altitude over ground
        pAltitude = figure(plot_width=800,
                           plot_height=300,
                           x_range=top_left_x_range)
        alti_legend = []
        # Setting the second y axis range name and range
        pAltitude.extra_y_ranges = {"speed": Range1d(50, 120)}
        # Adding the second axis to the plot.
        pAltitude.add_layout(
            LinearAxis(y_range_name="speed", axis_label="IAS, TAS [Knots]"),
            'right')

        altitudeLine = pAltitude.line(data_frame.index * self.step_time,
                                      data_frame['position_h_sl_ft'],
                                      line_width=2,
                                      color=Viridis4[2])
        alti_legend.append(("Altitude [ftsl]", [altitudeLine]))
        kiasLine = pAltitude.line(data_frame.index * self.step_time,
                                  data_frame['velocities_vc_kts'],
                                  line_width=2,
                                  y_range_name="speed",
                                  color=Viridis4[1])
        alti_legend.append(("Indicated Airspeed [KIAS]", [kiasLine]))
        tasLine = pAltitude.line(data_frame.index * self.step_time,
                                 data_frame['velocities_vtrue_kts'],
                                 line_width=2,
                                 y_range_name="speed",
                                 color=Viridis4[0])
        alti_legend.append(("True Airspeed [KAS]", [tasLine]))
        pAltitude.extra_y_ranges.renderers = [
            kiasLine, tasLine
        ]  #this does not quite work: https://stackoverflow.com/questions/48631530/bokeh-twin-axes-with-datarange1d-not-well-scaling
        pAltitude.y_range.renderers = [altitudeLine]

        lg_alti = Legend(items=alti_legend,
                         location=(0, 10),
                         glyph_width=25,
                         label_width=190)
        lg_alti.click_policy = "hide"
        pAltitude.add_layout(lg_alti, 'right')

        tAlti = Title()
        tAlti.text = 'Altitude and Speed [IAS, TAS] over Timesteps'
        pAltitude.title = tAlti
        pAltitude.xaxis.axis_label = 'timestep [s]'
        pAltitude.yaxis[0].axis_label = 'Altitude [ftsl]'
        pAltitude.legend.location = "center_right"

        pSideslip = figure(plot_width=800,
                           plot_height=300,
                           x_range=top_left_x_range)
        slip_legend = []

        slip_skid_line = pSideslip.line(data_frame.index * self.step_time,
                                        data_frame['aero_beta_deg'],
                                        line_width=2,
                                        color=Viridis4[2])
        slip_legend.append(("Sideslip", [slip_skid_line]))
        pSideslip.y_range.renderers = [slip_skid_line]

        lg_slip = Legend(items=slip_legend,
                         location=(0, 10),
                         glyph_width=25,
                         label_width=190)
        lg_slip.click_policy = "hide"
        pSideslip.add_layout(lg_slip, 'right')

        tSlip = Title()
        tSlip.text = 'Sideslip'
        pSideslip.title = tSlip
        pSideslip.xaxis.axis_label = 'timestep [s]'
        pSideslip.yaxis[0].axis_label = 'Sideslip [deg]'
        pSideslip.legend.location = "center_right"

        #activate the zooming on all plots
        #this is not nice, but this not either: https://stackoverflow.com/questions/49282688/how-do-i-set-default-active-tools-for-a-bokeh-gridplot
        pAltitude.toolbar.active_scroll = pAltitude.toolbar.tools[
            1]  #this selects the WheelZoomTool instance
        pSideslip.toolbar.active_scroll = pSideslip.toolbar.tools[
            1]  #this selects the WheelZoomTool instance

        reset_output()

        # if self.env.meta_dict['model_type'] == 'trained':
        #     discriminator = self.env.meta_dict['model_base_name']+"_%+.2f" % (data_frame['reward'].sum())
        #     self.env.meta_dict['model_discriminator'] = discriminator
        # else:
        #     discriminator = self.env.meta_dict['model_discriminator']

        ts = time.time()
        overshoot_frames_per_task = self._analyze_overshoot(data_frame)
        overshoot_divs = [
            Div(text=ovs_fr.round(3).to_html(), width=600)
            for ovs_fr in overshoot_frames_per_task
        ]
        print("Overshoot analysis done in %.2f sec" % (time.time() - ts))

        ts = time.time()
        settlement_times_per_task = self._analyze_settle_times(data_frame)
        settlement_divs = [
            Div(text=settle_fr.round(3).to_html(), width=600)
            for settle_fr in settlement_times_per_task
        ]
        print("Settlement analysis done in %.2f sec" % (time.time() - ts))

        panel_grid = []
        panel_grid.append([
            Div(text='<h3>' + t.name + '</h3>', id='div_' + t.name)
            for t in self.env.task_list
        ])

        # to switch on and off the statistics panels, this is unfortuntely the best, I could achive
        # https://stackoverflow.com/a/52416676/2682209
        cols = []
        checkbox = CheckboxGroup(
            labels=["show stats"], active=[],
            width=100)  #checkbox is added to header_col later on

        for i, t in enumerate(self.env.task_list):
            # overshoot_stat = overshoot_divs[i]
            c = column(Div())  #empty for the beginning
            cols.append(c)

        callback = CustomJS(args=dict(overshoot_divs=overshoot_divs,
                                      settlement_divs=settlement_divs,
                                      cols=cols,
                                      checkbox=checkbox),
                            code="""
                    for (var j = 0; j < cols.length; j++) {
                        console.log('col', j)
                        const children = []
                        for (const i of checkbox.active) {
                            console.log('active', i)
                            children.push(overshoot_divs[j])
                            children.push(settlement_divs[j])
                        } 
                        console.log('children', children)
                        cols[j].children = children
                    }
                    """)
        checkbox.js_on_change('active', callback)

        # show_stats_btn = [Div(text="""
        # <button onclick="display_event(%s)">Try it</button>
        # """ %t.name for t in self.env.task_list]
        # for t, b in zip(self.env.task_list, show_stats_btn):
        #     b.tags = ['id', 'btn_'+t.name]
        # panel_grid.append(show_stats_btn)
        # [b.js_on_event(events.ButtonClick, display_event(b, t.name)) for t, b in zip(self.env.task_list, show_stats_btn)]

        # panel_grid.append(chkbxs)
        panel_grid.append(cols)

        panel_grid_t = [[
            panels[name]['panel1'], panels[name]['panel2'],
            panels[name]['panel3']
        ] for name in self.task_names]
        [panel_grid.append(fig) for fig in list(zip(*panel_grid_t))]

        # add the additional plots
        panel_grid.append([pAltitude, pSideslip])

        panel_grid_plot = gridplot(panel_grid,
                                   toolbar_location='right',
                                   sizing_mode='stretch_width')

        #for string formatting look here: https://pyformat.info/
        titleString = ''
        if 'experiment_name' in self.env.meta_dict:
            titleString += "{}: ".format(self.env.meta_dict['experiment_name'])
        titleString += "Run Date: {}; ".format(
            datetime.datetime.now().strftime("%c"))

        if 'train_step' in self.env.meta_dict:
            titleString += "Training Step: {}; ".format(
                self.env.meta_dict['train_step'])
        if 'episode_number' in self.env.meta_dict:
            titleString += "Episode: {}; ".format(
                self.env.meta_dict['episode_number'])
        if 'csv_line_nr' in self.env.meta_dict:
            titleString += "Env in CSV line: {}; ".format(
                self.env.meta_dict['csv_line_nr'])
        # titleString += "Total Reward: {:.2f}; ".format(data_frame['reward'].sum())
        # titleString += "Model Discriminator: {};".format(self.env.meta_dict['model_discriminator'])
        header_col = column(
            Div(text="<h1>" + self.env.unwrapped.spec.id +
                (" - " + self.env.meta_dict['env_info']) if 'env_info' in
                self.meta_dict else "" + "</h1>"),
            row(Div(text="<h2>" + titleString + "</h2>", width=1200),
                checkbox))

        webpage = gridplot([[header_col], [panel_grid_plot]],
                           toolbar_location=None,
                           sizing_mode='stretch_width')

        base_filename = 'Run_' + '_'.join([tsk.name for tsk in self.task_list])
        html_output_name = os.path.join(self.save_path, 'plots',
                                        base_filename + '_latest.html')
        os.makedirs(os.path.dirname(html_output_name), exist_ok=True)

        if self.showNextPlotFlag:
            output_file(
                html_output_name, mode='inline'
            )  #mode='absolute') #use mode='absolute' to make it work offline with the js and css installed in the bokeh package locally
            if self.firstRun:
                show(webpage)  #opens up a new browser window
                self.firstRun = False
            else:
                save(
                    webpage
                )  #just updates the HTML; Manual F5 in browser required :-(, (There must be a way to push...)

        if self.exportNextPlotFlag and self.save_path:
            #build the filename including the individual rewards
            task_rewards = [
                self.reward_variables[i].get_legal_name()
                for i in range(len(self.task_list))
            ]
            task_names_with_rewards = [
                t.name + '_' + f'{data_frame[task_rewards[i]].sum():.2f}'
                for i, t in enumerate(self.task_list)
            ]
            name_with_rewards = 'Run_' + '_'.join(
                task_names_with_rewards) + 'time_{}'.format(
                    datetime.datetime.now().strftime("%H-%M-%S"))
            base_filename = os.path.join(
                self.save_path, 'plots', name_with_rewards
            )  # 'glideAngle_Elevator_Reward_{:.2f}_time_{}'.format(data_frame['reward'].sum(), datetime.datetime.now().strftime("%H-%M-%S")))
            if self.showNextPlotFlag:
                #we keep the html as well for easy exploration
                shutil.copyfile(html_output_name, base_filename + '.html')

            def export(webpage):
                png_filename = base_filename + '.png'
                webpage.width = 1800  #set the width of the page instead of passing a width parameter to the export; https://stackoverflow.com/a/61563173/2682209
                export_png(
                    webpage, filename=png_filename
                )  #TODO: the width parameter is ignored in bokeh/io/export.py get_layout_html() as webpage isn't a Plot

            export(
                gridplot([[header_col], [panel_grid_plot]],
                         toolbar_location=None))

        self.showNextPlotFlag = False  #only show the plot once and then reset
        self.exportNextPlotFlag = False
        print("Output Plot generated: " + titleString)
Пример #2
0
    def _create_task_panels(self, data_frame):
        panels = {}
        top_left_x_range = None
        for i, t in enumerate(self.env.task_list):
            panels[t.name] = {}
            # Panel 1: Setpoint and command panel
            pCtrl = figure(plot_width=800, plot_height=300)
            ctrl_legend = []
            # Setting the second y axis range name and range
            pCtrl.extra_y_ranges = {
                t.name + '_cmd': Range1d(start=-1, end=1)
            }  # this should query the action space
            # Adding the second axis to the plot.
            pCtrl.add_layout(
                LinearAxis(y_range_name=t.name + '_cmd',
                           axis_label=t.name + '_cmd [norm.]'), 'right')
            try:
                name = self.panel_contents[
                    t.name]['panel1']['action_prop'].get_legal_name(
                    )  #maybe there are more entries in the future
                action_Line = pCtrl.line(data_frame.index * self.step_time,
                                         data_frame[name],
                                         line_width=1,
                                         y_range_name=t.name + '_cmd',
                                         color=Viridis4[1])
                ctrl_legend.append((t.name + ' Cmd.', [action_Line]))
            except KeyError:
                #we have no action prop, only setpoints to be evaluated
                pass
            try:
                name = self.panel_contents[
                    t.name]['panel1']['current_value_prop'].get_legal_name()
                current_value_line = pCtrl.line(data_frame.index *
                                                self.step_time,
                                                data_frame[name],
                                                line_width=2,
                                                color=Viridis4[0])
                ctrl_legend.append((name, [current_value_line]))
                name = self.panel_contents[
                    t.name]['panel1']['setpoint_value_prop'].get_legal_name()
                setpoint_value_line = pCtrl.line(data_frame.index *
                                                 self.step_time,
                                                 data_frame[name],
                                                 line_width=2,
                                                 color=Viridis4[3])
                ctrl_legend.append((name, [setpoint_value_line]))
            except KeyError:
                #there is no setpoint to be displayed, only actuations
                pass

            ctrl_lg = Legend(items=ctrl_legend,
                             location=(0, 10),
                             glyph_width=25,
                             label_width=190)
            ctrl_lg.click_policy = "hide"
            pCtrl.add_layout(ctrl_lg, 'right')
            pCtrl.toolbar.active_scroll = pCtrl.toolbar.tools[
                1]  #this selects the WheelZoomTool instance
            # Add the title...
            tCtrl = Title()
            tCtrl.text = 'Controlled Value over Time'
            pCtrl.title = tCtrl
            pCtrl.xaxis.axis_label = 'timestep [s]'
            pCtrl.yaxis[0].axis_label = 'Controlled Value'

            if not top_left_x_range:
                top_left_x_range = pCtrl.x_range
            else:
                pCtrl.x_range = top_left_x_range
            panels[t.name].update({'panel1': pCtrl})

            #Panel 2: Rewards and reward components
            pRwd = figure(plot_width=1057,
                          plot_height=300,
                          x_range=top_left_x_range)
            rwd_cmp_lines = []
            reward_legend = []

            cmp_names = self.panel_contents[
                t.name]['panel2']['reward_component_names']
            for idx, rwd_component in enumerate(cmp_names):
                rwd_cmp_lines.append(
                    pRwd.line(data_frame.index * self.step_time,
                              data_frame[rwd_component],
                              line_width=2,
                              color=Viridis7[idx % 6]))
                reward_legend.append((rwd_component, [rwd_cmp_lines[-1]]))

            name = self.panel_contents[
                t.name]['panel2']['reward_prop'].get_legal_name()
            reward_line = pRwd.line(data_frame.index * self.step_time,
                                    data_frame[name],
                                    line_width=2,
                                    color=Viridis7[6])
            reward_legend.append((name, [reward_line]))

            reward_lg = Legend(items=reward_legend,
                               location=(48, 10),
                               glyph_width=25,
                               label_width=190)
            reward_lg.click_policy = "hide"
            pRwd.add_layout(reward_lg, 'right')
            pRwd.toolbar.active_scroll = pRwd.toolbar.tools[
                1]  #this selects the WheelZoomTool instance
            #Add the title
            tReward = Title()
            tReward.text = f'{t.name}: last Reward over {data_frame[name].size-1} Timesteps (∑ = {data_frame[name].sum():.2f})'
            pRwd.title = tReward
            pRwd.xaxis.axis_label = 'timestep [s]'
            pRwd.yaxis[0].axis_label = 'actual Reward [norm.]'
            panels[t.name].update({'panel2': pRwd})

            #Panel 3: Presented Observations
            pState = figure(plot_width=1057,
                            plot_height=300,
                            x_range=top_left_x_range)
            # Setting the second y axis range name and range
            norm_state_extents = 10
            pState.extra_y_ranges = {
                "normalized_data":
                Range1d(start=-norm_state_extents, end=norm_state_extents)
            }
            # Adding the second axis to the plot.
            pState.add_layout(
                LinearAxis(y_range_name="normalized_data",
                           axis_label="normalized data"), 'right')
            state_lines = []
            state_legend = []
            normalized_state_lines = []
            obs_props_names = [
                o_prp.get_legal_name()
                for o_prp in self.panel_contents[t.name]['panel3']['obs_props']
            ]
            for idx, state_name in enumerate(obs_props_names):
                if (data_frame[state_name].max() <= norm_state_extents and
                        data_frame[state_name].min() >= -norm_state_extents):
                    normalized_state_lines.append(
                        pState.line(data_frame.index * self.step_time,
                                    data_frame[state_name],
                                    line_width=2,
                                    y_range_name="normalized_data",
                                    color=Inferno7[idx % 7],
                                    visible=False))
                    state_legend.append(
                        ("norm_" + state_name, [normalized_state_lines[-1]]))
                else:
                    state_lines.append(
                        pState.line(data_frame.index * self.step_time,
                                    data_frame[state_name],
                                    line_width=2,
                                    color=Inferno7[idx % 7],
                                    visible=False))
                    state_legend.append((state_name, [state_lines[-1]]))
            pState.y_range.renderers = state_lines
            pState.extra_y_ranges.renderers = normalized_state_lines  #this does not quite work: https://stackoverflow.com/questions/48631530/bokeh-twin-axes-with-datarange1d-not-well-scaling

            lg_state = Legend(items=state_legend,
                              location=(0, 10),
                              glyph_width=25,
                              label_width=190)
            lg_state.click_policy = "hide"
            pState.add_layout(lg_state, 'right')
            pState.toolbar.active_scroll = pState.toolbar.tools[
                1]  #this selects the WheelZoomTool instance
            #Add the title
            tState = Title()
            tState.text = 'Actual Observation presented to Agent'
            pState.title = tState
            pState.xaxis.axis_label = 'timestep [s]'
            pState.yaxis[0].axis_label = 'state data'

            panels[t.name].update({'panel3': pState})

        return panels