This notebook shows the basic functionality of loading an IDF file and operating on it in XML.
I'm using Python 2.7.5 (default, Sep 16 2013, 23:11:01) [MSC v.1500 64 bit (AMD64)] However, I always use the "from future import division" and "from future import print_function" to make transition easy
In [1]:
print('Python ' + sys.version)
Extensive use of logging to track what's happening each step, if this is not clear read up on logging
In [2]:
import logging
logging.basicConfig(format='%(funcName)-20s %(levelno)-3s: %(message)s', level=logging.DEBUG, datefmt='%I:%M:%S')
logging.debug('Test logging')
Here's how I'm loading the module into IPython notebook
In [3]:
sys.path.append(r'C:\EclipseWorkspace\EnergyParser')
Load the module containing the IDF class
In [4]:
import idf.idf_parser as idf
#idf.IDF?
Here's what's happening below:
Load an .idf file from a path on disk
the from_IDF_file is the common way to instantiate
from_IDF_file calls:
In [5]:
path_test_idf = r"C:\EclipseWorkspace\EnergyParser\SampleIDFs\5ZoneElectricBaseboard.idf"
new_idf = idf.IDF.from_IDF_file(path_test_idf)
In [6]:
print(new_idf)
Here is some raw IDF text in the IDF_string attribute
In [7]:
for line in new_idf.IDF_string.split('\n')[987:1010]:
print(line)
And here is the raw XML tree:
In [8]:
for cnt,line in enumerate(new_idf.XML):
print(line)
if cnt > 8: break
With the core IDF class loaded, it's time to inspect and manipulate it
In [9]:
import idf.utilities_xml as util_xml
Convenience functions exist for listing objects, below zone names
In [10]:
util_xml.get_zone_name_list(new_idf)
Out[10]:
Using 'PrettyTable 0.5' module, nicely formatted summaries are possible
Below, a full listing of all classes and their names, and then a table showing the idf class and how many instances of each class exist
The print_table utility function has an argument for the number of rows, remove it to show all
In [11]:
table = util_xml.get_table_all_names(new_idf)
util_xml.print_table(table,5)
In [12]:
table = util_xml.get_table_object_count(new_idf)
util_xml.print_table(table, 10)
So we see that there are 3 'AirTerminal:SingleDuct:VAV:Reheat' objects. A selection can be made around all instances matching a class name.
In [13]:
util_xml.tree_get_class(new_idf, 'AirTerminal:SingleDuct:VAV:Reheat')
Out[13]:
Regular expressions are fully supported in the code (note the '^' and '$' sigils!)<br> By default, an exact '^$' match
In [14]:
selection = util_xml.tree_get_class(new_idf, 'AirTerminal', flgExact = False)
print(selection)
So we found the 2 VAV:NoReheat plus the 3 VAV:Reheat. What are they? Each selection in the list is an XML node. XML nodes can be operated on, printed, etc., according to the lxml module.
In [15]:
util_xml.printXML(selection[0])
This is the general structure of the EnergyPlus XML schema: Each OBJECT represents an IDF object. It has a class name, and 'n' attributes. The first attribute is sometimes, but not always, the name. The parser also captures the comments in the IDF string.
Classes can be deleted. Note below that 2 objects are deleted, reducing XML Object count from 348 to 346. However, the IDF lines remain at 3679. The ASCII text representation is not reflected to XML representation until convert_XML_to_IDF is called.
In [16]:
print(new_idf)
util_xml.delete_classes(new_idf, ['AirTerminal:SingleDuct:VAV:NoReheat'])
print(new_idf)
convert_XML_to_IDF() uses an XLST transfrom to reproduce the IDF. Currently, comments are not written back to ASCII.
In [17]:
new_idf.convert_XML_to_IDF()
print(new_idf)
Finally, this new object can be written back to disk. Note that write_IDF() calls convert_XML_to_IDF() first, so manual calls to convert_XML_to_IDF() are usually never necessary.
In [18]:
new_idf.write_IDF('d:\\testing EnergyParser.idf')
It might also be interesting to write the XML to disk directly.
In [19]:
new_idf.write_XML('d:\\testing EnergyParser.xml')
The definition of a valid IDF file is described by the Input Data Dictionary IDD file. This concept of validation is also important in XML, with the concepts of a schema (XSD) and Document Type Definition (DTD). Both describe the structure of an XML document. It could be possible to create an XSD from the IDD, and have a powerful definition tool within the XML paradigm. However this project does not support this. Instead, because the IDD has the exact same syntax as IDF, it is read directly as follows.
In [20]:
path_idd = r"D:\Apps\EnergyPlusV8-1-0\Energy+.idd"
idd_definition = idf.IDF.from_IDD_file(path_idd)
Note that this is the from_IDD_file() method, NOT from_IDF_file()
The IDD file has a different syntax compared to IDF which describes all aspects of each object
The speed of loading can be increased by writing this back to XML and using from_XML_file
Below is an example of an IDD object converted into XML. There is signifantly more information describing each attribute of each class, all of which is captured by the parser. This is a fairly flat representation where the information is captured in XML attributes. A clearer representation would be more hierarchical, but this suffices.
In [21]:
target_class = util_xml.tree_get_class(idd_definition, 'Site:WeatherStation')[0]
util_xml.printXML(target_class)
An advanced manipulation consists of defining
1) Which class
2) The specific instance name
3) The attribute to change (Retreived from IDD)
4) The new value of this attribute for all matched items
All selection criteria are full regex supported, so '.' matches to 'any' matched string
This definition is contained in a dictionary
For example, let's change the cieling height of all spaces to be 3 m. First, let's look at one of the spaces in detail;
In [22]:
selection = util_xml.tree_get_class(new_idf, 'Zone')
util_xml.printXML(selection[3])
Next, get this class from the IDD (Not this IDF!)
In [23]:
target_class = util_xml.tree_get_class(idd_definition, 'Zone')[0]
print(target_class)
#util_xml.printXML(target_class)
And get the integer position of our desired field. This is done again on the IDD object, since the IDF objects may not have this information (no comments in IDF file!).
In [24]:
util_xml.get_IDD_matched_position(target_class,'field','Ceiling Height')
Out[24]:
This concept can also be used to select objects with a
Now we have all information required to make a precise selection of this attribute in the IDF file. Let's select all Zone objects with the name starting with SPACE, so we don't select any PLENUM's. A utility function is provided which handles all of the above steps. It is called with a dictionary defining all aspects of the change; the class name, the object instance name (first ATTR), the attribute aka field name (From the IDD definition), and what value you want matching attributes to have.
In [40]:
this_change = {'class' :'^Zone$',
'objName' :'^SPACE',
'attr' :'Ceiling Height',
'newVal' :'3.0',
}
In [41]:
util_xml.apply_change(new_idf, idd_definition, this_change)
Out[41]:
In [44]:
selection = util_xml.tree_get_class(new_idf, 'Zone')
for obj in selection:
break
util_xml.printXML(obj)
This concept is flexible through Regular expressions.
In [45]:
this_change = {'class' :'^Zone$',
'objName' :'^SPACE4-1$',
'attr' :'Ceiling Height',
'newVal' :'3.5',
}
util_xml.apply_change(new_idf, idd_definition, this_change)
Out[45]:
In [48]:
this_change = {'class' :'^Zone$',
'objName' :'.',
'attr' :'Ceiling Height',
'newVal' :'2.8',
}
util_xml.apply_change(new_idf, idd_definition, this_change)
Out[48]:
In general my use case is as follows;
Using Excel or a text file etc., the above dictionary structures can be listed in tables to define a workflow.
In [ ]: