diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 1ce6d353..1248ae82 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -276,8 +276,23 @@ The :func:`urbs.report` function creates a spreadsheet from the main results. Summaries of costs, emissions, capacities (process, transmissions, storage) are saved to one sheet each. For timeseries, each combination of the given ``sites`` and ``commodities`` are summarised both in sum (in sheet "Energy -sums") and as individual timeseries (in sheet "... timeseries"). See also -:ref:`report-function` for a detailed explanation of this function's implementation. +sums") and as individual timeseries (in sheet "... timeseries"). +:: + + # detailed reporting commodity/sites + report_tuples = [ + ('North', 'Elec'), ('Mid', 'Elec'), ('South', 'Elec'), + ('North', 'CO2'), ('Mid', 'CO2'), ('South', 'CO2')] + + # optional: define names for sites in report_tuples + report_sites_name = { + ('North') : 'Greenland', + ('North', 'South') : 'Not Mid'} + +Optional it is possible to define ``report_tuples`` to control what shell be reported. And +with ``report_sites_name`` you can define, if the sites inside the report tuples should +be named differently. If they are empty, the default value will be taken. +See also :ref:`report-function` for a detailed explanation of this function's implementation. Plotting ^^^^^^^^ @@ -297,30 +312,50 @@ colors is demonstrated. All plot colors are user-defineable by adding color :func:`tuple` ``(r, g, b)`` or modifying existing tuples for commodities and plot decoration elements. Here, new colors for displaying import/export are added. Without these, pseudo-random colors are generated in -:func:`to_color`.:: +:func:`to_color`. +:: # create timeseries plot for each demand (site, commodity) timeseries for sit, com in prob.demand.columns: - # create figure - fig = urbs.plot(prob, com, sit) - - # change the figure title - ax0 = fig.get_axes()[0] - nice_sce_name = sce.replace('_', ' ').title() - new_figure_title = ax0.get_title().replace( - 'Energy balance of ', '{}: '.format(nice_sce_name)) - ax0.set_title(new_figure_title) - - # save plot to files - for ext in ['png', 'pdf']: - fig_filename = os.path.join( - result_dir, '{}-{}-{}-{}.{}').format(sce, com, sit, now, ext) - fig.savefig(fig_filename, bbox_inches='tight') - + Finally, for each demand commodity (only ``Elec`` in this case), a plot over the whole optimisation period is created. If ``timesteps`` were longer, a shorter plotting period could be defined and given as an optional argument to :func:`plot`. +:: + + # plotting commodities/sites + plot_tuples = [ + ('North', 'Elec'), + ('Mid', 'Elec'), + ('South', 'Elec'), + (['North', 'Mid', 'South'], 'Elec')] + + # optional: define names for plot_tuples + plot_sites_name = { + ('North', 'Mid', 'South') : 'NMS plot'} + +As in the reporting section mentioned, also for plotting the output of plots +can optionally changed with ``plot_tuples``. The sites in title and name of +each plot can be adapted with ``plot_sites_name``. +:: + + # change the figure title + # if no custom title prefix is specified, use the figure_basename + ax0 = fig.get_axes()[0] + if not plot_title_prefix: + plot_title_prefix = os.path.basename(figure_basename) + new_figure_title = '{}: {} in {}'.format( + plot_title_prefix, com, plot_sites_name[sit]) + ax0.set_title(new_figure_title) + + # save plot to files + for ext in extensions: + fig_filename = '{}-{}-{}-{}.{}'.format( + figure_basename, com, ''.join( + plot_sites_name[sit]), period, ext) + fig.savefig(fig_filename, bbox_inches='tight') + plt.close(fig) The paragraph "change figure title" shows exemplarily how to use matplotlib manually to modify some aspects of a plot without having to recreate the diff --git a/runme.py b/runme.py index 5c42edd2..9e699eb3 100644 --- a/runme.py +++ b/runme.py @@ -91,7 +91,8 @@ def setup_solver(optim, logfile='solver.log'): def run_scenario(input_file, timesteps, scenario, result_dir, - plot_tuples=None, plot_periods=None, report_tuples=None): + plot_tuples=None, plot_sites_name=None, plot_periods=None, + report_tuples=None, report_sites_name=None): """ run an urbs model for given input, time steps and scenario Args: @@ -100,8 +101,10 @@ def run_scenario(input_file, timesteps, scenario, result_dir, scenario: a scenario function that modifies the input data dict result_dir: directory name for result spreadsheet and plots plot_tuples: (optional) list of plot tuples (c.f. urbs.result_figures) - plot_periods: (optional) dict of plot periods (c.f. urbs.result_figures) + plot_sites_name: (optional) dict of names for sites in plot_tuples + plot_periods: (optional) dict of plot periods(c.f. urbs.result_figures) report_tuples: (optional) list of (sit, com) tuples (c.f. urbs.report) + report_sites_name: (optional) dict of names for sites in report_tuples Returns: the urbs model instance @@ -132,7 +135,8 @@ def run_scenario(input_file, timesteps, scenario, result_dir, urbs.report( prob, os.path.join(result_dir, '{}.xlsx').format(sce), - report_tuples=report_tuples) + report_tuples=report_tuples, + report_sites_name=report_sites_name) # result plots urbs.result_figures( @@ -140,6 +144,7 @@ def run_scenario(input_file, timesteps, scenario, result_dir, os.path.join(result_dir, '{}'.format(sce)), plot_title_prefix=sce.replace('_', ' '), plot_tuples=plot_tuples, + plot_sites_name=plot_sites_name, periods=plot_periods, figure_size=(24, 9)) return prob @@ -165,11 +170,17 @@ def run_scenario(input_file, timesteps, scenario, result_dir, ('South', 'Elec'), (['North', 'Mid', 'South'], 'Elec')] + # optional: define names for sites in plot_tuples + plot_sites_name = {('North', 'Mid', 'South'):'All'} + # detailed reporting commodity/sites report_tuples = [ ('North', 'Elec'), ('Mid', 'Elec'), ('South', 'Elec'), ('North', 'CO2'), ('Mid', 'CO2'), ('South', 'CO2')] + # optional: define names for sites in report_tuples + report_sites_name = {'North': 'Greenland'} + # plotting timesteps plot_periods = { 'all': timesteps[1:] @@ -196,5 +207,7 @@ def run_scenario(input_file, timesteps, scenario, result_dir, for scenario in scenarios: prob = run_scenario(input_file, timesteps, scenario, result_dir, plot_tuples=plot_tuples, + plot_sites_name=plot_sites_name, plot_periods=plot_periods, - report_tuples=report_tuples) + report_tuples=report_tuples, + report_sites_name=report_sites_name) diff --git a/urbs/plot.py b/urbs/plot.py index 51ed84aa..63694b2e 100644 --- a/urbs/plot.py +++ b/urbs/plot.py @@ -295,7 +295,8 @@ def plot(prob, com, sit, timesteps=None, def result_figures(prob, figure_basename, plot_title_prefix=None, - plot_tuples=None, periods=None, extensions=None, **kwds): + plot_tuples=None, plot_sites_name=None, + periods=None, extensions=None, **kwds): """Create plots for multiple periods and sites and save them to files. Args: @@ -305,6 +306,7 @@ def result_figures(prob, figure_basename, plot_title_prefix=None, plot_tuples: (optional) list of (sit, com) tuples to plot sit may be individual site names or lists of sites default: all demand (sit, com) tuples are plotted + plot_sites_name: (optional) dict of names for created plots periods: (optional) dict of 'period name': timesteps_list items default: one period 'all' with all timesteps is assumed extensions: (optional) list of file extensions for plot images @@ -327,25 +329,35 @@ def result_figures(prob, figure_basename, plot_title_prefix=None, for sit, com in plot_tuples: # wrap single site name in 1-element list for consistent behaviour if is_string(sit): - sit = [sit] + help_sit = [sit] + else: + help_sit = sit + sit = tuple(sit) + + try: + plot_sites_name[sit] + except: + plot_sites_name[sit] = str(sit) for period, timesteps in periods.items(): # do the plotting - fig = plot(prob, com, sit, timesteps=timesteps, **kwds) + fig = plot(prob, com, help_sit, timesteps=timesteps, **kwds) # change the figure title ax0 = fig.get_axes()[0] - # if no custom title prefix is specified, use the figure basenmae + # if no custom title prefix is specified, use the figure_basename if not plot_title_prefix: plot_title_prefix = os.path.basename(figure_basename) - new_figure_title = ax0.get_title().replace( - 'Commodity balance of ', '{}: '.format(plot_title_prefix)) + + new_figure_title = '{}: {} in {}'.format( + plot_title_prefix, com, plot_sites_name[sit]) ax0.set_title(new_figure_title) # save plot to files for ext in extensions: fig_filename = '{}-{}-{}-{}.{}'.format( - figure_basename, com, '-'.join(sit), period, ext) + figure_basename, com, ''.join( + plot_sites_name[sit]), period, ext) fig.savefig(fig_filename, bbox_inches='tight') plt.close(fig) diff --git a/urbs/report.py b/urbs/report.py index 0ec057de..7fcc1e0c 100644 --- a/urbs/report.py +++ b/urbs/report.py @@ -1,9 +1,10 @@ import pandas as pd from .input import get_input from .output import get_constants, get_timeseries +from .util import is_string -def report(instance, filename, report_tuples=None): +def report(instance, filename, report_tuples=None, report_sites_name=None): """Write result summary to a spreadsheet file Args: @@ -11,7 +12,8 @@ def report(instance, filename, report_tuples=None): filename: Excel spreadsheet filename, will be overwritten if exists report_tuples: (optional) list of (sit, com) tuples for which to create detailed timeseries sheets - + report_sites_name: (optional) dict of names for created timeseries + sheets Returns: Nothing """ @@ -33,35 +35,63 @@ def report(instance, filename, report_tuples=None): # initialize timeseries tableaus energies = [] timeseries = {} + help_ts = {} # collect timeseries data for sit, com in report_tuples: - (created, consumed, stored, imported, exported, - dsm) = get_timeseries(instance, com, sit) - - overprod = pd.DataFrame( - columns=['Overproduction'], - data=created.sum(axis=1) - consumed.sum(axis=1) + - imported.sum(axis=1) - exported.sum(axis=1) + - stored['Retrieved'] - stored['Stored']) - - tableau = pd.concat( - [created, consumed, stored, imported, exported, overprod, - dsm], - axis=1, - keys=['Created', 'Consumed', 'Storage', 'Import from', - 'Export to', 'Balance', 'DSM']) - timeseries[(sit, com)] = tableau.copy() - - # timeseries sums - sums = pd.concat([created.sum(), consumed.sum(), - stored.sum().drop('Level'), - imported.sum(), exported.sum(), overprod.sum(), - dsm.sum()], - axis=0, - keys=['Created', 'Consumed', 'Storage', 'Import', - 'Export', 'Balance', 'DSM']) - energies.append(sums.to_frame("{}.{}".format(sit, com))) + + # wrap single site name in 1-element list for consistent behavior + if is_string(sit): + help_sit = [sit] + else: + help_sit = sit + sit = tuple(sit) + + # check existence of predefined names, else define them + try: + report_sites_name[sit] + except: + report_sites_name[sit] = str(sit) + + for lv in help_sit: + (created, consumed, stored, imported, exported, + dsm) = get_timeseries(instance, com, lv) + + overprod = pd.DataFrame( + columns=['Overproduction'], + data=created.sum(axis=1) - consumed.sum(axis=1) + + imported.sum(axis=1) - exported.sum(axis=1) + + stored['Retrieved'] - stored['Stored']) + + tableau = pd.concat( + [created, consumed, stored, imported, exported, overprod, + dsm], + axis=1, + keys=['Created', 'Consumed', 'Storage', 'Import from', + 'Export to', 'Balance', 'DSM']) + help_ts[(lv, com)] = tableau.copy() + + # timeseries sums + help_sums = pd.concat([created.sum(), consumed.sum(), + stored.sum().drop('Level'), + imported.sum(), exported.sum(), + overprod.sum(), dsm.sum()], + axis=0, + keys=['Created', 'Consumed', 'Storage', + 'Import', 'Export', 'Balance', + 'DSM']) + try: + timeseries[(report_sites_name[sit], com)] = timeseries[ + (report_sites_name[sit], com)].add( + help_ts[(lv, com)], axis=1, fill_value=0) + sums = sums.add(help_sums, fill_value=0) + except: + timeseries[(report_sites_name[sit], com)] = help_ts[ + (lv, com)] + sums = help_sums + + energies.append(sums.to_frame("{}.{}".format( + report_sites_name[sit], com))) # write timeseries data (if any) if timeseries: @@ -71,6 +101,10 @@ def report(instance, filename, report_tuples=None): # write timeseries to individual sheets for sit, com in report_tuples: + if isinstance(sit, list): + sit = tuple(sit) # sheet names cannot be longer than 31 characters... - sheet_name = "{}.{} timeseries".format(sit, com)[:31] - timeseries[(sit, com)].to_excel(writer, sheet_name) + sheet_name = "{}.{} timeseries".format( + report_sites_name[sit], com)[:31] + timeseries[(report_sites_name[sit], com)].to_excel( + writer, sheet_name)