Plot room heat balance

This script:

  • Asks the user to open an IES-VE result file.

  • Plots the room heat balances for each room.

This was originally published as an article here: https://www.stevenfirth.com/how-to-plot-room-heat-balance-results-in-ies-using-python/

../_images/figure_how_to_plot_room_heat_balances.png
# PlotRoomHeatBalance

# - This script creates a plot of the (annual) heat gains and losses for all rooms in the building.
# - The heat gains and losses are plotted as a horizontal stacked bars for each room.

# 1. Setup

# - import packages
import iesve
import os
import math
from tkinter import Tk, messagebox
from tkinter.filedialog import askopenfilename
import matplotlib.pyplot as plt
import matplotlib.ticker

# - instances
current_project = iesve.VEProject.get_current_project()

# - directories
dir_current_project = current_project.path.replace('\\','/')
print('dir_current_project: ', dir_current_project)
if not os.path.exists(dir_current_project):
   root = Tk()
   root.withdraw()
   messagebox.showinfo('Error', 'No IESVE project folder found.', parent = root)
   root.destroy()
   quit()
dir_vista = os.path.join(dir_current_project, 'vista')

# 2. Select results file
# - Select file
root = Tk()
root.withdraw()
fp_in = askopenfilename(title = 'Select IES results file', parent = root, initialdir = dir_vista, filetypes = [("APS files","*.aps")])
root.destroy()
print('fp_in: ', fp_in)
# - Exit if filepath is empty string
if fp_in == '':
   root = Tk()
   root.withdraw()
   messagebox.showinfo('User input needed', 'Please select a .aps results file.', parent = root)
   root.destroy()
   quit()
# - Exit if filepath is not in project filepath
elif not dir_current_project in fp_in:
   root = Tk()
   root.withdraw()
   messagebox.showinfo('User input needed', 'Please select a .aps results file in the current IES project.', parent = root)
   root.destroy()
   quit()

# 3. Get room data
# - a dictionary {room_id: room_general_data_dict}
realmodel = current_project.models[0]
bodies = realmodel.get_bodies(False)  # False means get all bodies
room_data_dict = {body.id: body.get_room_data().get_general() for body in bodies}

# 4. Load gain data
# - a dictionary {variable_name: {room_id: annual_total_heat_gain}}
# - this selects only room variables which have 'units_type' as "Gain"
data = {}
with iesve.ResultsReader.open(fp_in) as f:
   vars = f.get_variables()
   for var in vars:
      d = {}
      if var['model_level'] == 'z' and var['units_type'] == 'Gain':
            for room_id in list(room_data_dict):
               x = f.get_room_results(room_id, var['aps_varname'], var['display_name'], 'z')
               x = sum(x) if x is not None else 0
               d[room_id] = x/1000000  # convert to MWh/year
            data[var['display_name']] = d

#for k,v in data.items(): print(k, v)


# 5. Plot figure
#
height = (0.4 * len(room_data_dict))
if height < 2: height = 2
if height > 4: height = 4
#
#height = 6 / 1920 * 1080  # fixed aspect ratio for publishing figure
#
fig, ax = plt.subplots(
   figsize = (
      6,
      height
      ),
   dpi = 200
   )
positive_lefts = [0] * len(room_data_dict)
negative_lefts = [0] * len(room_data_dict)
#
def plot_row(name, data, **kwargs):
   ""
   for i, (room_id, height) in enumerate(data.items()):
      if height > 0:
            left = positive_lefts[i]
            positive_lefts[i] += height
      else:
            left = negative_lefts[i]
            negative_lefts[i] += height
      line = ax.barh(
            i,
            height,
            left = left,
            label = name if i == 0 else None,
            edgecolor = 'black',
            linewidth = 0.5,
            **kwargs)
   return line
#
color_i = 0
for i, (k,v) in enumerate(data.items()):
   for x in v.values():
      if not math.isclose(x,0):  # plot variable provided at least one annual total is non-zero
            plot_row(
               k,
               v,
               color = f'C{color_i}',
               alpha = 0.5
               )
            color_i += 1
            break

ax.legend(
   loc='center left',
   bbox_to_anchor=(1, 0.5),
   fontsize=6
   )
ax.set_xlabel('Annual heat gain (MWh/year)')
ax.get_xaxis().set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, p: format(int(x), ',')))  # thousands separator
ax.set_xlim(min(negative_lefts) - (max(positive_lefts) - min(negative_lefts)) * 0.05, max(positive_lefts) + (max(positive_lefts) - min(negative_lefts)) * 0.05)  # 5% margin on either side
ax.set_yticks(range(len(room_data_dict)))
ax.set_yticklabels([x['name'] for x in room_data_dict.values()])
ax.set_ylabel('Room')
ax.yaxis.set_tick_params(labelsize=10 - len(room_data_dict) * 3 / 30)    # reduces room name font size based on nmber of rooms
fig.tight_layout()
plt.show()