Commit 385d7698 authored by Madhur Panwar's avatar Madhur Panwar
Browse files

plot_storages() and plot_energy_balance() completed and are now a part of the codebase

Placed both plot functions in pyehub/outputter.py
plot_energy_balance provides markers, dashes for visibility in overlap. 'Demands' and 'Supplies' in legend. kwargs in both plot functions for optional arguments.
parent e8bbff96
Loading
Loading
Loading
Loading
+211 −31
Original line number Diff line number Diff line
@@ -358,37 +358,48 @@ def stream_info(results, output_file):
        writer.save()


def plot_storages(results: dict, to_plot: dict = None, size: tuple = (10, 5)) -> None:
def plot_storages(results: dict, **kwargs) -> None:
    """
    Plots various variables related to storages:
    storage state: The level of energy remaining in the storage
    gross charge(charge from stream): Energy sent to storage from stream for charging
    gross discharge(discharge to stream): Energy going into stream after discharging
    net charge: Energy actually reaching storage(after loss of gross charge due to charging efficiency)
    net discharge: Energy actually leaving storage(after loss from this due to discharging efficiency, it becomes gross discharge)
    decay loss(standing loss): Energy dissipating due to standing losses
    storage state: The level of energy remaining in the storage.
    gross charge(charge from stream): Energy sent to storage from stream for charging.
    gross discharge(discharge to stream): Energy going into stream after discharging.
    net charge: Energy actually reaching storage(after loss of gross charge due to charging efficiency).
    net discharge: Energy actually leaving storage(after loss from this due to discharging efficiency, it becomes gross
                   discharge).
    decay loss(standing loss): Energy dissipating due to standing losses.
    Note: All of the above are plotted as default. You can change this by passing respective arguments.

    Args:
        results: the 'results' dictionary returned by solve() method
        to_plot: dictionary with following keys and values as boolean indicating whether or not to plot
        the corresponding key (All variables are plotted by default)
            'pl_state': boolean value; whether to plot 'storage state' or not
            'pl_gross_ch': boolean value; whether to plot 'gross charge' or not
            'pl_gross_dch': boolean value; whether to plot 'gross discharge' or not
            'pl_net_ch': boolean value; whether to plot 'net charge' or not
            'pl_net_dch': boolean value; whether to plot 'net discharge' or not
            'pl_decay': boolean value; whether to plot 'decay loss' or not
        size: tuple defining (width, height) of the plot.
        results: dictionary; returned by solve() method.
        pl_state: boolean value; whether to plot 'storage state' or not.
        pl_gross_ch: boolean value; whether to plot 'gross charge' or not.
        pl_gross_dch: boolean value; whether to plot 'gross discharge' or not.
        pl_net_ch: boolean value; whether to plot 'net charge' or not.
        pl_net_dch: boolean value; whether to plot 'net discharge' or not.
        pl_decay: boolean value; whether to plot 'decay loss' or not.
        size: tuple; (width, height) of the plot [default is (10,5)].
        percentage: boolean; y-axis units in % or in kWh [default].
    """
    solution_section = results['solution'].copy()
    attributes = to_dataframes(solution_section)
    if to_plot is None:
        to_plot = {'pl_state': True, 'pl_gross_ch': True, 'pl_gross_dch': True,
                   'pl_net_ch': True, 'pl_net_dch': True, 'pl_decay': True}

    pl_state = kwargs.get('pl_state', True)
    pl_gross_ch = kwargs.get('pl_gross_ch', True)
    pl_gross_dch = kwargs.get('pl_gross_dch', True)
    pl_net_ch = kwargs.get('pl_net_ch', True)
    pl_net_dch = kwargs.get('pl_net_dch', True)
    pl_decay = kwargs.get('pl_decay', True)
    size = kwargs.get('size', (10, 5))
    percentage = kwargs.get('percentage', False)

    for storage in attributes['storages']:
        fig, axes = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=size)
        fig.text(0.524, 0.02, 'Timesteps (-)', ha='center', va='center')
        fig.text(0.01, 0.5, 'Storage state (in %)', ha='center', va='center', rotation='vertical')
        if percentage:
            fig.text(0.01, 0.5, 'Storage state [%]', ha='center', va='center', rotation='vertical')
        else:
            fig.text(0.01, 0.5, 'Storage state (kWh)', ha='center', va='center', rotation='vertical')
        fig.text(0.5, 0.97, storage, ha='center', va='center')
        ax = axes[0]
        ax1 = axes[1]
@@ -413,34 +424,203 @@ def plot_storages(results: dict, to_plot: dict = None, size: tuple = (10, 5)) ->
             'discharge to stream': ser_gross_discharge, 'net discharge': ser_net_discharge, 'standing loss': decay_loss}
        df = pd.DataFrame(data=d)

        if percentage:
            if capacity != 0:
                df = (df.div(capacity)).mul(100)

        if to_plot['pl_state']:
        if pl_state:
            df['storage state'].plot(kind='bar',  title=' ', ax=ax, color='deepskyblue', legend=True)
        if to_plot['pl_gross_ch']:
        if pl_gross_ch:
            df['charge from stream'].plot(drawstyle='steps-post',  title=' ', color='lightgreen',
                                          linewidth=3, ax=ax1, legend = True)
        if to_plot['pl_net_ch']:
        if pl_net_ch:
            df['net charge'].plot(drawstyle='steps-post',  title=' ', color='green',
                                  linestyle='--', linewidth=2, ax=ax1, legend=True)
        if to_plot['pl_gross_dch']:
        if pl_gross_dch:
            df['discharge to stream'].plot(drawstyle='steps-post',  title=' ', color='orange',
                                           linewidth=3, ax=ax1, legend=True)
        if to_plot['pl_net_dch']:
        if pl_net_dch:
            df['net discharge'].plot(drawstyle='steps-post',  title=' ', color='red',
                                     linestyle='--', linewidth=2, ax=ax1, legend=True)
        if to_plot['pl_decay']:
        if pl_decay:
            df['standing loss'].plot(kind='bar',  title=' ', color='saddlebrown',
                                     ax=ax, legend=True)
        ax1.set_xticks(df.index[:-1])
        ax.set_xticks(df.index)


        if to_plot['pl_state'] or to_plot['pl_gross_ch'] or to_plot['pl_net_ch'] or to_plot['pl_gross_dch'] \
                or to_plot['pl_net_dch'] or to_plot['pl_decay']:
        if pl_state or pl_gross_ch or pl_net_ch or pl_gross_dch \
                or pl_net_dch or pl_decay:
            plt.legend(loc='best')
            for ax in fig.axes:
                plt.sca(ax)
                plt.xticks(rotation=0)
            plt.show()


def plot_energy_balance(model, results: dict, **kwargs) -> None:
    """
    Visualization of energy balance for all the streams, i.e., Plots the energy interactions of all the streams with
    loads, converters, storages, imports and exports. Plots are dashed and marked with shape markers for visibility
    in case of overlap. Adjust the properties of the plot by passing arguments from below.

    Args:
        model: The object of EHubModel class (or any child class thereof); the energy hub model.
        results: dictionary; returned by solve() method.
        size: tuple; (width, height) of the plot [default is (9,5)].
        lw: float; linewidth of the plots [default is 2].
        dl: float; length of the dashes constituting the dashed plots [default is 3].
    """
    attributes = results['solution'].copy()
    streams_wo_sources = [x for x in model.streams if x not in model.sources]

    size = kwargs.get('size', (9, 5))
    lw = kwargs.get('lw', 2)
    dl = kwargs.get('dl', 3)

    for stream in streams_wo_sources:
        fig, ax = plt.subplots(nrows=1, ncols=1, figsize=size)

        dict_data_pos = {}
        dict_data_neg = {}

        for t in model.time:
            curr_neg = curr_pos = 0
            if stream in model.demands:
                load = model.LOADS[stream][t]
                curr_neg += (-load)
                place_in_dict(t, model, dict_data_neg, 'LOAD', curr_neg)

            for storage in model._get_storages_from_stream(stream):
                q_out = float(attributes['energy_to_storage'][t][storage.name])
                curr_neg += (-q_out)
                q_in = float(attributes['energy_from_storage'][t][storage.name])
                curr_pos += q_in
                place_in_dict(t, model, dict_data_neg, 'To '+storage.name+' (S)', curr_neg)
                place_in_dict(t, model, dict_data_pos, 'From '+storage.name+' (S)', curr_pos)

            for tech in model.technologies:
                conversion_rate = float(attributes['CONVERSION_EFFICIENCY'][tech][stream])
                if conversion_rate < 0:
                    energy_input = float(attributes['energy_input'][t][tech])
                    curr_neg += (-energy_input)
                    place_in_dict(t, model, dict_data_neg, 'To '+tech+' (C)', curr_neg)

                if conversion_rate > 0:
                    energy_input = float(attributes['energy_input'][t][tech])
                    energy_output = energy_input * conversion_rate
                    curr_pos += energy_output
                    place_in_dict(t, model, dict_data_pos, 'From '+tech+' (C)', curr_pos)

            if stream in model.export_streams:
                energy_exported = attributes['energy_exported'][t][stream]
                curr_neg += (-energy_exported)
                place_in_dict(t, model, dict_data_neg, 'Exported', curr_neg)

            if stream in model.import_streams:
                energy_imported = attributes['energy_imported'][t][stream]
                curr_pos += energy_imported
                place_in_dict(t, model, dict_data_pos, 'Imported', curr_pos)

        # --------------------------------Plot Demands----------------------------------
        x = 1
        y = 1

        mk_c = ['1','2','3','4']
        mk_s = ['*','P']

        mk_index_c = 0
        mk_index_s = 0

        for label, data in dict_data_neg.items():
            if label.endswith('LOAD'):
                ds = ()

                mk = 's'
                ms = 7

            if label.endswith('(C)'):
                ds = (dl, x)
                x += 1

                mk = mk_c[mk_index_c % 4]
                mk_index_c += 1
                ms = 12

            if label.endswith('(S)'):
                ds = (dl, y, 1, y)
                y += 1

                mk = mk_s[mk_index_s % 2]
                mk_index_s += 1
                ms = 7.5

            if label.endswith('Exported'):
                mk = '|'
                ds = (dl, 1, 1.5, 1, 1.5, 1, 1.5, 1, 1.5, 1, 1.5, 1)
                ms = 12

            ax.plot(data, label=label, linewidth=lw, dashes=ds, marker=mk, markersize=ms)

        handle_demand, label_demand = ax.get_legend_handles_labels()

        # --------------------------------Plot Supplies----------------------------------
        x = 1.2
        y = 1.2

        mk_c = ['1', '2', '3', '4']
        mk_s = ['+', 'x']

        mk_index_c = 0
        mk_index_s = 0

        for label, data in dict_data_pos.items():
            if label.endswith('(C)'):
                ds = (dl, x)
                x += 1

                mk = mk_c[mk_index_c % 4]
                mk_index_c += 1
                ms = 12

            if label.endswith('(S)'):
                ds = (dl, y, 1, y)
                y += 1

                mk = mk_s[mk_index_s % 2]
                mk_index_s += 1
                ms = 12

            if label.endswith('Imported'):
                ds = (dl, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
                mk = '|'
                ms = 12

            ax.plot(data, label=label, linewidth=lw, dashes=ds, marker=mk, markersize=ms)

        ax.set(title=stream, xticks=model.time, xlabel='Timesteps (-)', ylabel=stream + ' (kWh)')

        # To have 'Demands' and 'Supplies' titles, we need a blank handle
        blank_handle = [plt.plot([], marker="", ls="")[0]]

        handle_supply, label_supply = ax.get_legend_handles_labels()

        # Update handles and labels for 'Supplies' by removing 'Demands' handles and labels from them
        handle_supply = handle_supply[len(handle_demand):]
        label_supply = label_supply[len(label_demand):]

        handles = blank_handle + handle_demand + blank_handle*2 + handle_supply

        labels = ["$\\bf{Demands}$"] + label_demand + [" "] + ["$\\bf{Supplies}$"] + label_supply

        plt.legend(loc='best', bbox_to_anchor=(1, 1), labels=labels, handles=handles)
        plt.show()


def place_in_dict(t, model, dictionary: dict, key: str, value):
    """Appends an entry in the list corresponding to key if key exists in dictionary.
    Otherwise creates a single entry in the dictionary as {key: [value]}"""
    if t == model.time[0]:
        dictionary.update({key: [value]})
    else:
        dictionary[key].append(value)
+4 −109
Original line number Diff line number Diff line
from pyehub.outputter import plot_storages
from pyehub.outputter import plot_energy_balance
import pickle
import pandas as pd
import matplotlib.pyplot as plt
@@ -9,114 +10,7 @@ from pyehub.outputter import pretty_print
# sns.set()


def plot_energy_balance(model, results: dict, size: tuple = (8, 5), lw: float = 3, dl: float = 3 ) -> None:
    attributes = results['solution'].copy()
    streams_wo_sources = [x for x in model.streams if x not in model.sources]

    for stream in streams_wo_sources:
        fig, ax = plt.subplots(nrows=1, ncols=1, figsize=size)

        dict_data_pos = {}
        dict_data_neg = {}

        for t in model.time:
            curr_neg = curr_pos = 0
            if stream in model.demands:
                load = model.LOADS[stream][t]
                curr_neg += (-load)
                place_in_dict(t, model, dict_data_neg, 'LOAD', curr_neg)

            for storage in model._get_storages_from_stream(stream):
                q_out = float(attributes['energy_to_storage'][t][storage.name])
                curr_neg += (-q_out)
                q_in = float(attributes['energy_from_storage'][t][storage.name])
                curr_pos += q_in
                place_in_dict(t, model, dict_data_neg, 'To '+storage.name+' (S)', curr_neg)
                place_in_dict(t, model, dict_data_pos, 'From '+storage.name+' (S)', curr_pos)

            for tech in model.technologies:
                conversion_rate = float(attributes['CONVERSION_EFFICIENCY'][tech][stream])
                if conversion_rate < 0:
                    energy_input = float(attributes['energy_input'][t][tech])
                    curr_neg+=(-energy_input)
                    place_in_dict(t, model, dict_data_neg, 'To '+tech+' (C)', curr_neg)

                if conversion_rate > 0:
                    energy_input = float(attributes['energy_input'][t][tech])
                    energy_output = energy_input * conversion_rate
                    curr_pos+=energy_output
                    place_in_dict(t, model, dict_data_pos, 'From '+tech+' (C)', curr_pos)

            if stream in model.export_streams:
                energy_exported = attributes['energy_exported'][t][stream]
                curr_neg += (-energy_exported)
                place_in_dict(t, model, dict_data_neg, 'Exported', curr_neg)

            if stream in model.import_streams:
                energy_imported = attributes['energy_imported'][t][stream]
                curr_pos += energy_imported
                place_in_dict(t, model, dict_data_pos, 'Imported', curr_pos)

        x = 1
        y = 1
        # mk_index_c = 1
        # mk_index_s = 1
        for label, data in dict_data_neg.items():
            if label.endswith('LOAD'):
                ds = ()
                # mk = 's'
            if label.endswith('(C)'):
                ds = (dl, x)
                x += 1
                # mk = str(mk_index_c % 5)
                # mk_index_c += 1
            if label.endswith('(S)'):
                ds = (dl, y, 1, y)
                y += 1
                # mk = str(mk_index_s % 5)
                # mk_index_s += 1
            if label.endswith('Exported'):
                ds = (dl, 1, 1.5, 1, 1.5, 1, 1.5, 1, 1.5, 1, 1.5, 1)
            if label.endswith('Imported'):
                ds = (dl, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
            plt.plot(data, label=label, axes=ax, linewidth=lw, dashes=ds)
            plt.legend(loc='best')
            ax.set(title=stream, xticks=model.time, xlabel='Timesteps', ylabel='')

        x = 1.2
        y = 1.2
        for label, data in dict_data_pos.items():
            if label.endswith('LOAD'):
                ds = ()
                # mk = 's'
            if label.endswith('(C)'):
                ds = (dl, x)
                x += 1
                # mk = str(mk_index_c % 5)
                # mk_index_c += 1
            if label.endswith('(S)'):
                ds = (dl, y, 1, y)
                y += 1
                # mk = str(mk_index_s % 5)
                # mk_index_s += 1
            if label.endswith('Exported'):
                ds = (dl, 1, 1.5, 1, 1.5, 1, 1.5, 1, 1.5, 1, 1.5, 1)
            if label.endswith('Imported'):
                ds = (dl, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
            # plt.plot(data, label=label, axes=ax, linewidth=lw, marker=mk, dashes=ds)
            plt.plot(data, label=label, axes=ax, linewidth=lw, dashes=ds)
            plt.legend(loc='best',ncol=2)
            ax.set(title=stream, xticks=model.time, xlabel='Timesteps (-)', ylabel=stream+' (kWh)')

        plt.show()

def place_in_dict(t, model, dictionary: dict, key: str, value):
    """append a value in the list corresponding to key if key exists in dictionary.
    Otherwise creates a single entry in the dictionary as {key: [value]}"""
    if t == model.time[0]:
        dictionary.update({key: [value]})
    else:
        dictionary[key].append(value)



def main():
@@ -130,7 +24,8 @@ def main():
    results = model.solve()
    results1=model1.solve()
    # print(results['solution'] == results1['solution'])
    # plot_storages(results)
    plot_storages(results)

    plot_energy_balance(model1, results)
    # pretty_print(results)
    # print(results['solution'] == results1['solution'])