Underlying Data Structure of eppy

As described in previous sections, eppy was built on EplusInterface

Let us open a small idf file to explore the data structure

# you would normaly install eppy by doing
# python setup.py install
# or
# pip install eppy
# or
# easy_install eppy

# if you have not done so, uncomment the following three lines
import sys
# pathnameto_eppy = 'c:/eppy'
pathnameto_eppy = '../../../'
sys.path.append(pathnameto_eppy)
from eppy import modeleditor
from eppy.modeleditor import IDF
iddfile = "../../../eppy/resources/iddfiles/Energy+V7_2_0.idd"
fname1 = "../../../eppy/resources/idffiles/V_7_2/dev1.idf"

IDF.setiddname(iddfile)
idf1 = IDF(fname1)
idf1.printidf()
VERSION,
    7.3;                      !- Version Identifier

SIMULATIONCONTROL,
    Yes,                      !- Do Zone Sizing Calculation
    Yes,                      !- Do System Sizing Calculation
    Yes,                      !- Do Plant Sizing Calculation
    No,                       !- Run Simulation for Sizing Periods
    Yes;                      !- Run Simulation for Weather File Run Periods

BUILDING,
    Empire State Building,    !- Name
    30.0,                     !- North Axis
    City,                     !- Terrain
    0.04,                     !- Loads Convergence Tolerance Value
    0.4,                      !- Temperature Convergence Tolerance Value
    FullExterior,             !- Solar Distribution
    25,                       !- Maximum Number of Warmup Days
    6;                        !- Minimum Number of Warmup Days

SITE:LOCATION,
    CHICAGO_IL_USA TMY2-94846,    !- Name
    41.78,                    !- Latitude
    -87.75,                   !- Longitude
    -6.0,                     !- Time Zone
    190.0;                    !- Elevation

MATERIAL:AIRGAP,
    F04 Wall air space resistance,    !- Name
    0.15;                     !- Thermal Resistance

MATERIAL:AIRGAP,
    F05 Ceiling air space resistance,    !- Name
    0.18;                     !- Thermal Resistance

Original Data Structure in EPlusInterface

The original data structure in EPlusInterface was stupidly simple and robust. In fact attributes stupidly simple and robust seem to go together. Eppy evolved in such a way that this data structure is still retained. The rest of eppy is simply syntactic sugar for this data structure.

from: https://www.princeton.edu/~achaney/tmve/wiki100k/docs/Syntactic_sugar.html “Syntactic sugar is a computer science term that refers to syntax within a programming language that is designed to make things easier to read or to express, while alternative ways of expressing them exist. Syntactic sugar”

Let us take a look at this data structure. If we open an idf file with eppy we can explore the original data structure that comes from EPlusInterface.

Note The variable names are not very intuitive at this level. I did not know what I was doing when I wrote this code and now we are stuck with it

There are three varaibles that hold all the data we need. They are:

  • idf1.model.dtls
  • idf1.model.dt
  • idf1.idd_info
dtls = idf1.model.dtls # names of all the idf objects
dt = idf1.model.dt # the idf model
idd_info = idf1.idd_info # all the idd data

idf1.model.dtls - Overview

dtls = idf1.model.dtls # names of all the idf objects
print type(dtls)
<type 'list'>
# dtls is a list
print dtls[:10] # print the first ten items
['LEAD INPUT', 'SIMULATION DATA', 'VERSION', 'SIMULATIONCONTROL', 'BUILDING', 'SHADOWCALCULATION', 'SURFACECONVECTIONALGORITHM:INSIDE', 'SURFACECONVECTIONALGORITHM:OUTSIDE', 'HEATBALANCEALGORITHM', 'HEATBALANCESETTINGS:CONDUCTIONFINITEDIFFERENCE']
print len(dtls) # print the numer of items in the list
683

Couple of points to note about dtls:

  • dtls is a list of all the names of the Energyplus objects.
  • This list is extracted from the the idd file
  • the list is in the same order as the objects in the idd file

idf1.model.dt - Overview

dt = idf1.model.dt # the idf model
print type(dt)
<type 'dict'>
# print 10 of the keys
print dt.keys()[:10]
['ZONEHVAC:OUTDOORAIRUNIT', 'TABLE:TWOINDEPENDENTVARIABLES', 'ENERGYMANAGEMENTSYSTEM:INTERNALVARIABLE', 'AVAILABILITYMANAGER:NIGHTCYCLE', 'GROUNDHEATTRANSFER:SLAB:BLDGPROPS', 'GENERATOR:MICROTURBINE', 'SHADING:BUILDING:DETAILED', 'EVAPORATIVECOOLER:INDIRECT:RESEARCHSPECIAL', 'ZONEHVAC:PACKAGEDTERMINALAIRCONDITIONER', 'CONSTRUCTION:WINDOWDATAFILE']
# dt is a dict
number_of_keys = len(dt.keys())
print number_of_keys
683
  • The keys of dt are names of the objects (note that they are in capitals)
  • Items in a python dict are unordered. So the keys may be in any order
  • dtls will give us these names in the same order as they are in the idd file.
  • so use dtls if you want the keys in an order

We’ll look at dt in further detail later

idf1.idd_info - Overview

idd_info = idf1.idd_info # all the idd data
print type(idd_info)
<type 'list'>
print len(idd_info) # number of items in the list
683
# print the first three items
idd_info[:3]
[[{}],
 [{}],
 [{'format': ['singleLine'], 'unique-object': ['']},
  {'default': ['7.0'],
   'field': ['Version Identifier'],
   'required-field': ['']}]]
# print the first three items in seperate lines
for i, item in enumerate(idd_info[:3]):
    print "%s. %s" % (i, item)
0. [{}]
1. [{}]
2. [{'unique-object': [''], 'format': ['singleLine']}, {'default': ['7.0'], 'field': ['Version Identifier'], 'required-field': ['']}]

That does not make much sense. Below is the first 3 items from the idd file

Lead Input;

Simulation Data;

\group Simulation Parameters

Version,
      \unique-object
      \format singleLine
  A1 ; \field Version Identifier
      \required-field
      \default 7.0
  • If you compare the text file with the sturcture of idd_info, you can start to see the similarities
  • Note that the idd_info does not have the object name.
  • This was an unfortunate design decision that we are stuck with now :-(.
  • We need to jump through some hoops to get to an item in idd_info
# the object "VERSION" is the third item in idd_info
# to get to "VERSION" we need to find it's location in the list
# we use "dtls" to do this
location_of_version = dtls.index("version".upper())
print location_of_version
2
# print idd_info of "VERSION"
idd_info[location_of_version]
[{'format': ['singleLine'], 'unique-object': ['']},
 {'default': ['7.0'], 'field': ['Version Identifier'], 'required-field': ['']}]

NOTE:

  • the idd file is very large and uses a lot of memory when pulled into idd_info
  • only one copy of idd_info is kept when eppy is running.
  • This is the reason, eppy throws an exception when you try to set the idd file when it has already been set

idf1.model.dt - in detail

Let us look at a specific object, say MATERIAL:AIRGAP in idf1.model.dt

dt = idf1.model.dt
airgaps = dt['MATERIAL:AIRGAP'.upper()]
print type(airgaps)
<type 'list'>
airgaps
[['MATERIAL:AIRGAP', 'F04 Wall air space resistance', 0.15],
 ['MATERIAL:AIRGAP', 'F05 Ceiling air space resistance', 0.18]]

A snippet of the idf text file shows this

MATERIAL:AIRGAP,
    F04 Wall air space resistance,    !- Name
    0.15;                     !- Thermal Resistance

MATERIAL:AIRGAP,
    F05 Ceiling air space resistance,    !- Name
    0.18;                     !- Thermal Resistance

Notice the following things about idf1.model.dt:

  • The idf model is held within a dict.
  • the keys in the dict are names of the IDF objects in caps, such as BUILDING, VERSION, CONSTRUCTION, MATERIAL:AIRGAP etc.
  • The values in the dict are lists
  • the list contains lists. This means that airgaps can contain more than one airgap.
  • So airgaps = [airgap1, airgap2, ... ].
  • where, airgaps1 = [Type_of_Object, field1, field2, field3, .... ]
  • In airgaps1, all types have been converted. Note that “Thermal Resistance” is a float and not a string

What about an Energyplus object that does not exist in the idf file ?

roofs = dt['ROOF']
print roofs
[]

You get an empty list, meaning there are no roof items within roofs

idf1.idd_info - in detail

Let us find the idd_info for airgaps

location_of_airgaps = dtls.index("material:airgap".upper())
print location_of_airgaps
50
idd_airgaps = idd_info[location_of_airgaps]
idd_airgaps
[{'memo': ['Air Space in Opaque Construction'], 'min-fields': ['2']},
 {'field': ['Name'],
  'reference': ['MaterialName'],
  'required-field': [''],
  'type': ['alpha']},
 {'field': ['Thermal Resistance'],
  'minimum>': ['0'],
  'type': ['real'],
  'units': ['m2-K/W']}]

Compare to text in idd file:

Material:AirGap,
       \min-fields 2
       \memo Air Space in Opaque Construction
  A1 , \field Name
       \required-field
       \type alpha
       \reference MaterialName
  N1 ; \field Thermal Resistance
       \units m2-K/W
       \type real
       \minimum> 0
  • idd_airgaps gives details about each field
  • the last field N1 says that type = real
  • This tells us that the text value coming from the the test file has to be converted to a float

Syntactic Sugar

from: https://www.princeton.edu/~achaney/tmve/wiki100k/docs/Syntactic_sugar.html “Syntactic sugar is a computer science term that refers to syntax within a programming language that is designed to make things easier to read or to express, while alternative ways of expressing them exist”

Wikipedia article on syntactic sugar

All the rest of the code in eppy is simply syntactic sugar over the data structure in model.dtls, model.dt and idd_info

Of course, the above statement is a gross exageration, but it gives you a basis for understanding the code that comes later. At the end of the day, any further code is simply a means for changing the data within model.dt. And you need to access the data within model.dtls and idd_info to do so.

Bunch

Bunch is a great library that subclasses dict. You can see it at:

Let us first take a look at a dict

adict = {'a':1, 'b':2, 'c':3}
adict
{'a': 1, 'b': 2, 'c': 3}
# one would access the values in this dict by:
print adict
print adict['a']
print adict['b']
print adict['c']
{'a': 1, 'c': 3, 'b': 2}
1
2
3

Bunch allows us to do this with a lot less typing

from bunch import Bunch
bunchdict = Bunch(adict)
print bunchdict
print bunchdict.a
print bunchdict.b
print bunchdict.c
Bunch(a=1, b=2, c=3)
1
2
3

Let us take a look at variable airgaps from the previous section.

airgaps
[['MATERIAL:AIRGAP', 'F04 Wall air space resistance', 0.15],
 ['MATERIAL:AIRGAP', 'F05 Ceiling air space resistance', 0.18]]
airgap1, airgap2 = airgaps[0], airgaps[1]
airgap1
['MATERIAL:AIRGAP', 'F04 Wall air space resistance', 0.15]

We are going to subclass bunch so that we can do the following to airgap1 from the previous section:

  • airgap1.Name
  • airgap1.Thermal_Resistance

to remind you, the text file we are reading looks like this:

MATERIAL:AIRGAP,
    F04 Wall air space resistance,    !- Name
    0.15;                             !- Thermal Resistance
  • We are using the field names that come from the idd file
  • A space and other illegal (illegal for python) characters are replaced by an underscore

It is a little tricky tring to use bunch with airgap, because:

  • airgap is a list
  • but bunch works on dicts

So we do it in the following way:

  • we make a new Bunch from the airgap list.
  • The Bunch is made by by doing airgap1 = Bunch( {“Name” : “F04 Wall air space resistance”, “Thermal_Resistance” : 0.15} )
  • This will allow us to use the dot notation we see in bunch
  • Of course if we make changes in this Bunch, the airgap list does not change
  • Ideally we would like to see the changes reflected in the airgap list
  • We subclass Bunch as EpBunch. EpBunch is designed so that changes in EpBunch will make changes to the airgap list

Note: Some simplifications were made in the explanations above. So take it with a pinch of salt :-)

EpBunch

The code of EpBunch is in eppy/bunch_subclass.py. If you look at the code you will see The subclassing happening in the following manner:

  • Bunch -> EpBunch1 -> EpBunch2 -> ..... -> EpBunch5 , where “Bunch -> EpBunch” means “EpBunch subclassed from Bunch”
  • then EpBunch = EpBunch5

Question: Are you demented ? Why don’t you just subclass Bunch -> EpBunch ?

Answer: One can get demented trying to subclass from dict. This is pretty tricky coding and testing-debugging is difficult, since we are overriding built-in functions of dict. When you make mistakes there, the subclassed dict just stops working, or does very strange things. So I built it in a carefull and incremental way, fully testing before subclassing again. Each subclass implements some functionality and the next one implements more.

EpBunch is described in more detail in the next section