-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathesoreader.py
206 lines (176 loc) · 7.88 KB
/
esoreader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# This file is licensed under the terms of the MIT license. See the file
# "LICENSE" in the project root for more information.
#
# This module was developed by Daren Thomas at the assistant chair for
# Sustainable Architecture and Building Technologies (Suat) at the Institute of
# Technology in Architecture, ETH Zuerich. See http://suat.arch.ethz.ch for
# more information.
'''
esoreader.py
A python module for reading \*.eso files generated by EnergyPlus
The eso files generated by EnergyPlus contains a data dictionary, which
describes the values reported by EnergyPlus. The list of values reported
depends on the simulation input file, specifically the Output:Variable
objects. EnergyPlus can output the same variable at different
frequencies and for different "keys", which are for instance surfaces or
equipment names.
Following the data dictionary is a list of output variable values for
each of the configured variable coordinates.
The output of the esoreader module is therefore a data dictionary object
that contains a mapping of variable "coordinates" (grouping of reporting
frequency, key and variable name) to the index used by EnergyPlus and a
data object, which essentially just maps that index to the timeseries
data.
Example
=======
New interface:
import esoreader
PATH_TO_ESO = r'/Path/To/EnergyPlus/Output/eplusout.eso'
eso = esoreader.read_from_path(PATH_TO_ESO)
df = eso.to_frame('total heat loss energy') # pandas.DataFrame
Old interface: (still works)
::
import esoreader
PATH_TO_ESO = r'/Path/To/EnergyPlus/Output/eplusout.eso'
dd, data = esoreader.read(PATH_TO_ESO)
frequency, key, variable = dd.find_variable(
'Zone Ventilation Total Heat Loss Energy')[0]
idx = dd.index[frequency, key, variable]
time_series = data[idx]
'''
def read(eso_file_path):
"""Read in an .eso file and return the data dictionary and a dictionary
representing the data.
NOTE: this function is here for backward compatibilty reasons. Use
read_from_path() instead to obtain an EsoFile object.
"""
eso = read_from_path(eso_file_path)
return eso.dd, eso.data
def read_from_path(eso_file_path):
"""
read in a .eso file and return an EsoFile object that can be used
to read in pandas DataFrame and Series objects.
"""
with open(eso_file_path, 'r') as eso_file:
eso = EsoFile(eso_file)
return eso
class DataDictionary(object):
def __init__(self, version=None, timestamp=None):
'''
variables = dict of ids, int => [reporting_frequency,
key, variable, unit]
index = dict {(key, variable, reporting_frequency) => id)}
'''
self.version = version
self.timestamp = timestamp
self.variables = {}
self.index = {}
def build_index(self):
"""builds a reverse index for finding ids.
"""
for id, value in self.variables.items():
reporting_frequency, key, variable, unit = value
self.index[reporting_frequency, key, variable] = id
def find_variable(self, search):
"""returns the coordinates (timestep, key, variable_name) in the
data dictionary that can be used to find an index. The search is case
insensitive."""
return [(timestep, key, variable_name)
for timestep, key, variable_name in self.index.keys()
if search.lower() in variable_name.lower()]
class EsoFile(object):
def __init__(self, eso_file):
self.eso_file = eso_file
self.dd = self._read_data_dictionary()
self.dd.build_index()
self.data = self._read_data()
def find_variable(self, search, key=None, frequency='TimeStep'):
"""returns the coordinates (timestep, key, variable_name) in the
data dictionary that can be used to find an index. The search is case
insensitive and need only be specified partially."""
variables = self.dd.find_variable(search)
variables = [v for v in variables
if v[0].lower() == frequency.lower()]
if key:
variables = [v for v in variables
if v[1].lower() == key.lower()]
return variables
def to_frame(self, search, key=None, frequency='TimeStep', index=None, use_key_for_columns=True):
"""
creates a pandas DataFrame objects with a column for every variable
that matches the search pattern and key. An None key matches all keys.
NOTE: The frequency *has* to be the same for all variables selected.
(uses find_variable to select the variables)
"""
from pandas import DataFrame
variables = self.find_variable(search, key=key, frequency=frequency)
if use_key_for_columns:
data = {v[1]: self.data[self.dd.index[v]] for v in variables}
else:
# use variable name as column name
data = {v[2]: self.data[self.dd.index[v]] for v in variables}
df = DataFrame(data)
if index is not None:
df.index = index
return df
def _read_reporting_frequency(self, line):
reporting_frequency = None
if '! ' in line:
line = line.split('! ')[0]
if ' !' in line:
line, reporting_frequency = line.split(' !')
# RunPeriod contains more stuff (" [Value,Min,Month,Day,Hour,
# Minute, Max,Month,Day,Hour,Minute]")split it off
reporting_frequency = reporting_frequency.split()[0]
return line, reporting_frequency
def _read_variable_unit(self, variable):
unit = None
if '[' in variable:
variable, unit = variable.split('[')
unit = unit[:-1] # remove ']' at the end
variable = variable.strip()
return variable, unit
def _read_data_dictionary(self):
"""parses the head of the eso_file, returning the data dictionary.
the file object eso_file is advanced to the position needed by
read_data.
"""
version, timestamp = [s.strip() for s
in self.eso_file.readline().split(',')[-2:]]
dd = DataDictionary(version, timestamp)
line = self.eso_file.readline().strip()
while line != 'End of Data Dictionary':
line, reporting_frequency = self._read_reporting_frequency(line)
if reporting_frequency:
fields = [f.strip() for f in line.split(',')]
if len(fields) >= 4:
id, nfields, key, variable = fields[:4]
else:
id, nfields, variable = fields[:3]
key = None
variable, unit = self._read_variable_unit(variable)
dd.variables[int(id)] = [reporting_frequency, key,
variable, unit]
else:
# ignore the lines that aren't report variables
pass
line = self.eso_file.readline().strip()
dd.ids = set(dd.variables.keys())
return dd
def _read_data(self):
'''parse the data from the .eso file returning,
NOTE: eso_file should be the same file object that was passed to
read_data_dictionary(eso_file) to obtain dd.'''
data = {} # id => [value]
for id in self.dd.variables.keys():
data[id] = []
for line in self.eso_file:
if line.startswith('End of Data'):
break
fields = [f.strip() for f in line.split(',')]
id = int(fields[0])
if id not in self.dd.ids:
# skip entries that are not output:variables
continue
data[id].append(float(fields[1]))
return data