openstudio-workflow 1.0.0.pat1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +16 -68
- data/Rakefile +9 -9
- data/bin/openstudio_cli +786 -0
- data/lib/openstudio/workflow/adapters/input/local.rb +97 -0
- data/lib/openstudio/workflow/adapters/output/local.rb +90 -0
- data/lib/openstudio/workflow/adapters/output/socket.rb +70 -0
- data/lib/openstudio/workflow/{jobs/run_preflight/run_preflight.rb → adapters/output/web.rb} +37 -19
- data/lib/openstudio/workflow/{adapter.rb → adapters/output_adapter.rb} +53 -51
- data/lib/openstudio/workflow/job.rb +22 -0
- data/lib/openstudio/workflow/jobs/{run_energyplus → resources}/monthly_report.idf +0 -0
- data/lib/openstudio/workflow/jobs/run_energyplus.rb +49 -0
- data/lib/openstudio/workflow/jobs/run_ep_measures.rb +55 -0
- data/lib/openstudio/workflow/jobs/run_initialization.rb +136 -0
- data/lib/openstudio/workflow/jobs/run_os_measures.rb +59 -0
- data/lib/openstudio/workflow/jobs/run_postprocess.rb +53 -0
- data/lib/openstudio/workflow/jobs/run_preprocess.rb +81 -0
- data/lib/openstudio/workflow/jobs/run_reporting_measures.rb +86 -0
- data/lib/openstudio/workflow/jobs/run_translation.rb +49 -0
- data/lib/openstudio/workflow/multi_delegator.rb +1 -3
- data/lib/openstudio/workflow/registry.rb +137 -0
- data/lib/openstudio/workflow/run.rb +182 -221
- data/lib/openstudio/workflow/time_logger.rb +1 -1
- data/lib/openstudio/workflow/util/energyplus.rb +564 -0
- data/lib/openstudio/workflow/util/io.rb +33 -0
- data/lib/openstudio/workflow/util/measure.rb +520 -0
- data/lib/openstudio/workflow/util/model.rb +100 -0
- data/lib/openstudio/workflow/util/post_process.rb +177 -0
- data/lib/openstudio/workflow/util/weather_file.rb +108 -0
- data/lib/openstudio/workflow/util.rb +14 -0
- data/lib/openstudio/workflow/version.rb +1 -1
- data/lib/openstudio/workflow_json.rb +399 -0
- data/lib/openstudio/workflow_runner.rb +213 -0
- data/lib/openstudio-workflow.rb +13 -118
- metadata +45 -85
- data/lib/openstudio/extended_runner.rb +0 -105
- data/lib/openstudio/workflow/adapters/local.rb +0 -101
- data/lib/openstudio/workflow/adapters/mongo.rb +0 -227
- data/lib/openstudio/workflow/jobs/lib/apply_measures.rb +0 -253
- data/lib/openstudio/workflow/jobs/run_energyplus/run_energyplus.rb +0 -314
- data/lib/openstudio/workflow/jobs/run_openstudio/run_openstudio.rb +0 -230
- data/lib/openstudio/workflow/jobs/run_postprocess/run_postprocess.rb +0 -110
- data/lib/openstudio/workflow/jobs/run_reporting_measures/run_reporting_measures.rb +0 -471
- data/lib/openstudio/workflow/jobs/run_runmanager/run_runmanager.rb +0 -247
- data/lib/openstudio/workflow/jobs/run_xml/run_xml.rb +0 -279
@@ -1,314 +0,0 @@
|
|
1
|
-
######################################################################
|
2
|
-
# Copyright (c) 2008-2014, Alliance for Sustainable Energy.
|
3
|
-
# All rights reserved.
|
4
|
-
#
|
5
|
-
# This library is free software; you can redistribute it and/or
|
6
|
-
# modify it under the terms of the GNU Lesser General Public
|
7
|
-
# License as published by the Free Software Foundation; either
|
8
|
-
# version 2.1 of the License, or (at your option) any later version.
|
9
|
-
#
|
10
|
-
# This library is distributed in the hope that it will be useful,
|
11
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
-
# Lesser General Public License for more details.
|
14
|
-
#
|
15
|
-
# You should have received a copy of the GNU Lesser General Public
|
16
|
-
# License along with this library; if not, write to the Free Software
|
17
|
-
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
18
|
-
######################################################################
|
19
|
-
|
20
|
-
class RunEnergyplus
|
21
|
-
ENERGYPLUS_REGEX = /^energyplus\D{0,4}$/i
|
22
|
-
EXPAND_OBJECTS_REGEX = /^expandobjects\D{0,4}$/i
|
23
|
-
|
24
|
-
# Initialize
|
25
|
-
# param directory: base directory where the simulation files are prepared
|
26
|
-
# param logger: logger object in which to write log messages
|
27
|
-
def initialize(directory, logger, time_logger, adapter, workflow_arguments, past_results, options = {})
|
28
|
-
@logger = logger
|
29
|
-
|
30
|
-
energyplus_path = find_energyplus
|
31
|
-
defaults = {
|
32
|
-
energyplus_path: energyplus_path
|
33
|
-
}
|
34
|
-
@options = defaults.merge(options)
|
35
|
-
|
36
|
-
# TODO: use openstudio tool finder for this
|
37
|
-
@directory = directory
|
38
|
-
@run_directory = "#{@directory}/run"
|
39
|
-
@adapter = adapter
|
40
|
-
@time_logger = time_logger
|
41
|
-
@workflow_arguments = workflow_arguments
|
42
|
-
@past_results = past_results
|
43
|
-
@results = {}
|
44
|
-
|
45
|
-
# container for storing the energyplus files there were copied into the local directory. These will be
|
46
|
-
# removed at the end of the simulation.
|
47
|
-
@energyplus_files = []
|
48
|
-
@energyplus_exe = nil
|
49
|
-
@expand_objects_exe = nil
|
50
|
-
|
51
|
-
@logger.info "#{self.class} passed the following options #{@options}"
|
52
|
-
end
|
53
|
-
|
54
|
-
def perform
|
55
|
-
@logger.info "Calling #{__method__} in the #{self.class} class"
|
56
|
-
@logger.info "Current directory is #{@directory}"
|
57
|
-
|
58
|
-
# Ensure that the directory is created (but it should already be at this point)
|
59
|
-
FileUtils.mkdir_p(@run_directory)
|
60
|
-
|
61
|
-
# if the weather file is already in the directory, then just use that weather file
|
62
|
-
weather_file_name = nil
|
63
|
-
weather_files = Dir["#{@directory}/*.epw"]
|
64
|
-
if weather_files.size > 1
|
65
|
-
@logger.info 'Multiple weather files in the directory. Will rely on the weather file name in the openstudio model'
|
66
|
-
elsif weather_files.size == 1
|
67
|
-
weather_file_name = weather_files.first
|
68
|
-
end
|
69
|
-
|
70
|
-
# verify that the OSM, IDF, and the Weather files are in the run directory as the 'in.*' format
|
71
|
-
if !weather_file_name &&
|
72
|
-
@options[:run_openstudio] &&
|
73
|
-
@options[:run_openstudio][:weather_filename] &&
|
74
|
-
File.exist?(@options[:run_openstudio][:weather_filename])
|
75
|
-
weather_file_name = @options[:run_openstudio][:weather_filename]
|
76
|
-
end
|
77
|
-
|
78
|
-
if weather_file_name
|
79
|
-
# verify that it is named in.epw
|
80
|
-
@logger.info "Weather file for EnergyPlus simulation is #{weather_file_name}"
|
81
|
-
FileUtils.copy(weather_file_name, "#{@run_directory}/in.epw")
|
82
|
-
else
|
83
|
-
fail "EPW file not found or not sent to #{self.class}"
|
84
|
-
end
|
85
|
-
|
86
|
-
# check if the run folder has an IDF. If not then check if the parent folder does.
|
87
|
-
idf_file_name = nil
|
88
|
-
if File.exist?("#{@run_directory}/in.idf")
|
89
|
-
@logger.info 'IDF (in.idf) already exists in the run directory'
|
90
|
-
else
|
91
|
-
# glob for idf at the directory level
|
92
|
-
idfs = Dir["#{@directory}/*.idf"]
|
93
|
-
if idfs.size > 1
|
94
|
-
@logger.info 'Multiple IDF files in the directory. Cannot continue'
|
95
|
-
elsif idfs.size == 1
|
96
|
-
idf_file_name = idfs.first
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Need to check the in.idf and in.osm
|
101
|
-
# FileUtils.copy(options[:osm], "#{@run_directory}/in.osm")
|
102
|
-
if idf_file_name
|
103
|
-
FileUtils.copy(idf_file_name, "#{@run_directory}/in.idf")
|
104
|
-
end
|
105
|
-
|
106
|
-
# can't create symlinks because the /vagrant mount is actually a windows mount
|
107
|
-
@time_logger.start('Copying EnergyPlus files')
|
108
|
-
prepare_energyplus_dir
|
109
|
-
@time_logger.stop('Copying EnergyPlus files')
|
110
|
-
|
111
|
-
@time_logger.start('Running EnergyPlus Preprocess Script')
|
112
|
-
energyplus_preprocess("#{@run_directory}/in.idf")
|
113
|
-
@time_logger.start('Running EnergyPlus Preprocess Script')
|
114
|
-
|
115
|
-
@time_logger.start('Running EnergyPlus')
|
116
|
-
@results = call_energyplus
|
117
|
-
@time_logger.stop('Running EnergyPlus')
|
118
|
-
|
119
|
-
@results
|
120
|
-
end
|
121
|
-
|
122
|
-
private
|
123
|
-
|
124
|
-
def find_energyplus
|
125
|
-
if ENV['ENERGYPLUSDIR']
|
126
|
-
return ENV['ENERGYPLUSDIR']
|
127
|
-
# TODO: check if method exists! first
|
128
|
-
elsif OpenStudio.respond_to? :getEnergyPlusDirectory
|
129
|
-
return OpenStudio.getEnergyPlusDirectory.to_s
|
130
|
-
elsif ENV['RUBYLIB'] =~ /OpenStudio/
|
131
|
-
warn 'Finding EnergyPlus by RUBYLIB parsing will not be supported in the near future. Use either ENERGYPLUSDIR'\
|
132
|
-
'env variable or a newer OpenStudio version that has the getEnergyPlusDirectory method'
|
133
|
-
path = ENV['RUBYLIB'].split(':')
|
134
|
-
path = File.dirname(path.find { |p| p =~ /OpenStudio/ })
|
135
|
-
# Grab the version out of the openstudio path
|
136
|
-
path += '/sharedresources/EnergyPlus-8-3-0'
|
137
|
-
|
138
|
-
return path
|
139
|
-
else
|
140
|
-
if /cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
|
141
|
-
return 'C:/EnergyPlus-8-3-0'
|
142
|
-
else
|
143
|
-
return '/usr/local/EnergyPlus-8-3-0'
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def clean_directory
|
149
|
-
@logger.info 'Removing any copied EnergyPlus files'
|
150
|
-
@energyplus_files.each do |file|
|
151
|
-
if File.exist? file
|
152
|
-
FileUtils.rm_f file
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
paths_to_rm = []
|
157
|
-
paths_to_rm << "#{@run_directory}/packaged_measures"
|
158
|
-
paths_to_rm << "#{@run_directory}/Energy+.ini"
|
159
|
-
paths_to_rm.each { |p| FileUtils.rm_rf(p) if File.exist?(p) }
|
160
|
-
end
|
161
|
-
|
162
|
-
# Prepare the directory to run EnergyPlus. In EnergyPlus < 8.2, we have to copy all the files into the directory.
|
163
|
-
#
|
164
|
-
# @return [Boolean] Returns true is there is more than one file copied
|
165
|
-
def prepare_energyplus_dir
|
166
|
-
@logger.info "Copying EnergyPlus files to run directory: #{@run_directory}"
|
167
|
-
Dir["#{@options[:energyplus_path]}/*"].each do |file|
|
168
|
-
next if File.directory? file
|
169
|
-
next if File.extname(file).downcase =~ /.pdf|.app|.html|.gif|.txt|.xlsx/
|
170
|
-
|
171
|
-
dest_file = "#{@run_directory}/#{File.basename(file)}"
|
172
|
-
@energyplus_files << dest_file
|
173
|
-
|
174
|
-
@energyplus_exe = File.basename(dest_file) if File.basename(dest_file) =~ ENERGYPLUS_REGEX
|
175
|
-
@expand_objects_exe = File.basename(dest_file) if File.basename(dest_file) =~ EXPAND_OBJECTS_REGEX
|
176
|
-
FileUtils.copy file, dest_file
|
177
|
-
end
|
178
|
-
|
179
|
-
fail "Could not find EnergyPlus executable in #{@options[:energyplus_path]}" unless @energyplus_exe
|
180
|
-
fail "Could not find ExpandObjects executable in #{@options[:energyplus_path]}" unless @expand_objects_exe
|
181
|
-
|
182
|
-
@energyplus_files.size > 0
|
183
|
-
end
|
184
|
-
|
185
|
-
def call_energyplus
|
186
|
-
begin
|
187
|
-
current_dir = Dir.pwd
|
188
|
-
Dir.chdir(@run_directory)
|
189
|
-
@logger.info "Starting simulation in run directory: #{Dir.pwd}"
|
190
|
-
|
191
|
-
File.open('stdout-expandobject', 'w') do |file|
|
192
|
-
IO.popen("./#{@expand_objects_exe}") do |io|
|
193
|
-
while (line = io.gets)
|
194
|
-
file << line
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
# Check if expand objects did anythying
|
200
|
-
if File.exist? 'expanded.idf'
|
201
|
-
FileUtils.mv('in.idf', 'pre-expand.idf', force: true) if File.exist?('in.idf')
|
202
|
-
FileUtils.mv('expanded.idf', 'in.idf', force: true)
|
203
|
-
end
|
204
|
-
|
205
|
-
# create stdout
|
206
|
-
File.open('stdout-energyplus', 'w') do |file|
|
207
|
-
IO.popen("./#{@energyplus_exe} 2>&1") do |io|
|
208
|
-
while (line = io.gets)
|
209
|
-
file << line
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
r = $?
|
214
|
-
|
215
|
-
@logger.info "EnergyPlus returned '#{r}'"
|
216
|
-
unless r == 0
|
217
|
-
@logger.warn 'EnergyPlus returned a non-zero exit code. Check the stdout-energyplus log.'
|
218
|
-
end
|
219
|
-
|
220
|
-
if File.exist? 'eplusout.end'
|
221
|
-
f = File.read('eplusout.end').force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
|
222
|
-
warnings_count = f[/(\d*).Warning/, 1]
|
223
|
-
error_count = f[/(\d*).Severe.Errors/, 1]
|
224
|
-
@logger.info "EnergyPlus finished with #{warnings_count} warnings and #{error_count} severe errors"
|
225
|
-
if f =~ /EnergyPlus Terminated--Fatal Error Detected/
|
226
|
-
fail 'EnergyPlus Terminated with a Fatal Error. Check eplusout.err log.'
|
227
|
-
end
|
228
|
-
else
|
229
|
-
fail 'EnergyPlus failed and did not create an eplusout.end file. Check the stdout-energyplus log.'
|
230
|
-
end
|
231
|
-
|
232
|
-
if File.exist? 'eplusout.err'
|
233
|
-
eplus_err = File.read('eplusout.err').force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
|
234
|
-
if eplus_err =~ /EnergyPlus Terminated--Fatal Error Detected/
|
235
|
-
fail 'EnergyPlus Terminated with a Fatal Error. Check eplusout.err log.'
|
236
|
-
end
|
237
|
-
end
|
238
|
-
rescue => e
|
239
|
-
log_message = "#{__FILE__} failed with #{e.message}, #{e.backtrace.join("\n")}"
|
240
|
-
@logger.error log_message
|
241
|
-
raise log_message
|
242
|
-
ensure
|
243
|
-
@logger.info "Ensuring 'clean' directory"
|
244
|
-
clean_directory
|
245
|
-
|
246
|
-
Dir.chdir(current_dir)
|
247
|
-
@logger.info 'EnergyPlus Completed'
|
248
|
-
end
|
249
|
-
|
250
|
-
{}
|
251
|
-
end
|
252
|
-
|
253
|
-
# Run this code before running energyplus to make sure the reporting variables are setup correctly
|
254
|
-
def energyplus_preprocess(idf_filename)
|
255
|
-
@logger.info 'Running EnergyPlus Preprocess'
|
256
|
-
|
257
|
-
fail "Could not find IDF file in run directory (#{idf_filename})" unless File.exist? idf_filename
|
258
|
-
|
259
|
-
new_objects = []
|
260
|
-
needs_monthlyoutput = false
|
261
|
-
|
262
|
-
idf = OpenStudio::IdfFile.load(idf_filename).get
|
263
|
-
# save the pre-preprocess file
|
264
|
-
File.open("#{File.dirname(idf_filename)}/pre-preprocess.idf", 'w') { |f| f << idf.to_s }
|
265
|
-
|
266
|
-
needs_sqlobj = idf.getObjectsByType('Output:SQLite'.to_IddObjectType).empty?
|
267
|
-
|
268
|
-
needs_monthlyoutput = idf.getObjectsByName('Building Energy Performance - Natural Gas').empty? ||
|
269
|
-
idf.getObjectsByName('Building Energy Performance - Electricity').empty? ||
|
270
|
-
idf.getObjectsByName('Building Energy Performance - District Heating').empty? ||
|
271
|
-
idf.getObjectsByName('Building Energy Performance - District Cooling').empty?
|
272
|
-
|
273
|
-
# this is a workaround for issue #1699 -- remove when 1699 is closed.
|
274
|
-
new_objects << 'Output:Variable,*,Zone Air Temperature,Hourly;'
|
275
|
-
new_objects << 'Output:Variable,*,Zone Air Relative Humidity,Daily;'
|
276
|
-
new_objects << 'Output:Variable,*,Site Outdoor Air Drybulb Temperature,Monthly;'
|
277
|
-
new_objects << 'Output:Variable,*,Site Outdoor Air Wetbulb Temperature,Timestep;'
|
278
|
-
|
279
|
-
if needs_sqlobj
|
280
|
-
@logger.info 'Adding SQL Output to IDF'
|
281
|
-
new_objects << '
|
282
|
-
Output:SQLite,
|
283
|
-
SimpleAndTabular; ! Option Type
|
284
|
-
'
|
285
|
-
end
|
286
|
-
|
287
|
-
if needs_monthlyoutput
|
288
|
-
monthly_report_idf = File.join(File.dirname(__FILE__), 'monthly_report.idf')
|
289
|
-
idf_file = OpenStudio::IdfFile.load(File.read(monthly_report_idf), 'EnergyPlus'.to_IddFileType).get
|
290
|
-
idf.addObjects(idf_file.objects)
|
291
|
-
end
|
292
|
-
|
293
|
-
# These are supposedly needed for the calibration report
|
294
|
-
new_objects << 'Output:Meter:MeterFileOnly,Gas:Facility,Daily;'
|
295
|
-
new_objects << 'Output:Meter:MeterFileOnly,Electricity:Facility,Timestep;'
|
296
|
-
new_objects << 'Output:Meter:MeterFileOnly,Electricity:Facility,Daily;'
|
297
|
-
|
298
|
-
# Always add in the timestep facility meters
|
299
|
-
new_objects << 'Output:Meter,Electricity:Facility,Timestep;'
|
300
|
-
new_objects << 'Output:Meter,Gas:Facility,Timestep;'
|
301
|
-
new_objects << 'Output:Meter,DistrictCooling:Facility,Timestep;'
|
302
|
-
new_objects << 'Output:Meter,DistrictHeating:Facility,Timestep;'
|
303
|
-
|
304
|
-
new_objects.each do |obj|
|
305
|
-
object = OpenStudio::IdfObject.load(obj).get
|
306
|
-
idf.addObject(object)
|
307
|
-
end
|
308
|
-
|
309
|
-
# save the file
|
310
|
-
File.open(idf_filename, 'w') { |f| f << idf.to_s }
|
311
|
-
|
312
|
-
@logger.info 'Finished EnergyPlus Preprocess'
|
313
|
-
end
|
314
|
-
end
|
@@ -1,230 +0,0 @@
|
|
1
|
-
######################################################################
|
2
|
-
# Copyright (c) 2008-2014, Alliance for Sustainable Energy.
|
3
|
-
# All rights reserved.
|
4
|
-
#
|
5
|
-
# This library is free software; you can redistribute it and/or
|
6
|
-
# modify it under the terms of the GNU Lesser General Public
|
7
|
-
# License as published by the Free Software Foundation; either
|
8
|
-
# version 2.1 of the License, or (at your option) any later version.
|
9
|
-
#
|
10
|
-
# This library is distributed in the hope that it will be useful,
|
11
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
-
# Lesser General Public License for more details.
|
14
|
-
#
|
15
|
-
# You should have received a copy of the GNU Lesser General Public
|
16
|
-
# License along with this library; if not, write to the Free Software
|
17
|
-
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
18
|
-
######################################################################
|
19
|
-
|
20
|
-
class RunOpenstudio
|
21
|
-
# Mixin the MeasureApplication module to apply measures
|
22
|
-
include OpenStudio::Workflow::ApplyMeasures
|
23
|
-
|
24
|
-
# Initialize
|
25
|
-
# param directory: base directory where the simulation files are prepared
|
26
|
-
# param logger: logger object in which to write log messages
|
27
|
-
def initialize(directory, logger, time_logger, adapter, workflow_arguments, past_results, options = {})
|
28
|
-
defaults = { format: 'hash', analysis_root_path: '.' }
|
29
|
-
warn 'Option of use_monthly_reports is deprecated. Monthly reports are always generated.' if options[:use_monthly_reports]
|
30
|
-
|
31
|
-
@options = defaults.merge(options)
|
32
|
-
@directory = directory
|
33
|
-
# TODO: there is a base number of arguments that each job will need including @run_directory. abstract it out.
|
34
|
-
@run_directory = "#{@directory}/run"
|
35
|
-
@adapter = adapter
|
36
|
-
@results = {}
|
37
|
-
@logger = logger
|
38
|
-
@time_logger = time_logger
|
39
|
-
@workflow_arguments = workflow_arguments
|
40
|
-
@past_results = past_results
|
41
|
-
@logger.info "#{self.class} passed the following options #{@options}"
|
42
|
-
|
43
|
-
# initialize instance variables that are needed in the perform section
|
44
|
-
@model = nil
|
45
|
-
@model_idf = nil
|
46
|
-
@initial_weather_file = nil
|
47
|
-
@weather_file_path = nil
|
48
|
-
@analysis_json = nil
|
49
|
-
# TODO: rename datapoint_json to just datapoint
|
50
|
-
@datapoint_json = nil
|
51
|
-
@output_attributes = {}
|
52
|
-
@report_measures = []
|
53
|
-
end
|
54
|
-
|
55
|
-
def perform
|
56
|
-
@logger.info "Calling #{__method__} in the #{self.class} class"
|
57
|
-
@logger.info "Current directory is #{@directory}"
|
58
|
-
|
59
|
-
@logger.info 'Retrieving datapoint and problem'
|
60
|
-
@datapoint_json = @adapter.get_datapoint(@directory, @options)
|
61
|
-
@analysis_json = @adapter.get_problem(@directory, @options)
|
62
|
-
|
63
|
-
if @analysis_json && @analysis_json[:analysis]
|
64
|
-
@model = load_seed_model
|
65
|
-
|
66
|
-
load_weather_file
|
67
|
-
|
68
|
-
apply_measures(:openstudio_measure)
|
69
|
-
@logger.info('Finished applying OpenStudio measures.')
|
70
|
-
|
71
|
-
@time_logger.start('Translating to EnergyPlus')
|
72
|
-
translate_to_energyplus
|
73
|
-
@time_logger.stop('Translating to EnergyPlus')
|
74
|
-
|
75
|
-
apply_measures(:energyplus_measure)
|
76
|
-
@logger.info('Finished applying EnergyPlus measures.')
|
77
|
-
|
78
|
-
# check if the weather file has changed. This is cheesy for now. Should have a default measure that
|
79
|
-
# always sets the weather file so that it can be better controlled
|
80
|
-
updated_weather_file = get_weather_file_from_model
|
81
|
-
unless updated_weather_file == @initial_weather_file
|
82
|
-
# reset the result hash so the future processes know which weather file to run
|
83
|
-
@logger.info "Updating the weather file to be '#{updated_weather_file}'"
|
84
|
-
if (Pathname.new updated_weather_file).absolute? && (Pathname.new updated_weather_file).exist?
|
85
|
-
@results[:weather_filename] = updated_weather_file
|
86
|
-
else
|
87
|
-
@results[:weather_filename] = "#{@weather_file_path}/#{updated_weather_file}"
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
@logger.info 'Saving measure output attributes JSON'
|
92
|
-
File.open("#{@run_directory}/measure_attributes.json", 'w') do |f|
|
93
|
-
f << JSON.pretty_generate(@output_attributes)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
@time_logger.start('Saving OSM and IDF')
|
98
|
-
save_osm_and_idf
|
99
|
-
@time_logger.stop('Saving OSM and IDF')
|
100
|
-
|
101
|
-
@results
|
102
|
-
end
|
103
|
-
|
104
|
-
private
|
105
|
-
|
106
|
-
def save_osm_and_idf
|
107
|
-
# save the data
|
108
|
-
osm_filename = "#{@run_directory}/in.osm"
|
109
|
-
File.open(osm_filename, 'w') { |f| f << @model.to_s }
|
110
|
-
|
111
|
-
idf_filename = "#{@run_directory}/in.idf"
|
112
|
-
File.open(idf_filename, 'w') { |f| f << @model_idf.to_s }
|
113
|
-
|
114
|
-
@results[:osm] = File.expand_path(osm_filename)
|
115
|
-
@results[:idf] = File.expand_path(idf_filename)
|
116
|
-
end
|
117
|
-
|
118
|
-
def load_seed_model
|
119
|
-
model = nil
|
120
|
-
@logger.info 'Loading seed model'
|
121
|
-
|
122
|
-
baseline_model_path = nil
|
123
|
-
# Unique case to use the previous generated OSM as the seed
|
124
|
-
if @options[:run_xml] && @options[:run_xml][:osm_filename]
|
125
|
-
if File.exist? @options[:run_xml][:osm_filename]
|
126
|
-
baseline_model_path = @options[:run_xml][:osm_filename]
|
127
|
-
end
|
128
|
-
elsif @analysis_json[:analysis][:seed]
|
129
|
-
@logger.info "Seed model is #{@analysis_json[:analysis][:seed]}"
|
130
|
-
if @analysis_json[:analysis][:seed][:path]
|
131
|
-
|
132
|
-
# assume that the seed model has been placed in the directory
|
133
|
-
baseline_model_path = File.expand_path(
|
134
|
-
File.join(@options[:analysis_root_path], @analysis_json[:analysis][:seed][:path]))
|
135
|
-
else
|
136
|
-
fail 'No seed model path in JSON defined'
|
137
|
-
end
|
138
|
-
else
|
139
|
-
# TODO: create a blank model and return
|
140
|
-
fail 'No seed model block'
|
141
|
-
end
|
142
|
-
|
143
|
-
if baseline_model_path
|
144
|
-
if File.exist? baseline_model_path
|
145
|
-
@logger.info "Reading in baseline model #{baseline_model_path}"
|
146
|
-
translator = OpenStudio::OSVersion::VersionTranslator.new
|
147
|
-
model = translator.loadModel(baseline_model_path)
|
148
|
-
fail 'OpenStudio model is empty or could not be loaded' if model.empty?
|
149
|
-
model = model.get
|
150
|
-
else
|
151
|
-
fail "Seed model '#{baseline_model_path}' did not exist"
|
152
|
-
end
|
153
|
-
else
|
154
|
-
fail 'No baseline/seed model found'
|
155
|
-
end
|
156
|
-
|
157
|
-
model
|
158
|
-
end
|
159
|
-
|
160
|
-
# Save the weather file to the instance variable
|
161
|
-
def load_weather_file
|
162
|
-
@initial_weather_file = get_weather_file_from_model
|
163
|
-
|
164
|
-
weather_filename = nil
|
165
|
-
if @options[:run_xml] && @options[:run_xml][:weather_filename]
|
166
|
-
if File.exist? @options[:run_xml][:weather_filename]
|
167
|
-
weather_filename = @options[:run_xml][:weather_filename]
|
168
|
-
end
|
169
|
-
elsif @analysis_json[:analysis][:weather_file]
|
170
|
-
if @analysis_json[:analysis][:weather_file][:path]
|
171
|
-
weather_filename = File.expand_path(
|
172
|
-
File.join(@options[:analysis_root_path], @analysis_json[:analysis][:weather_file][:path])
|
173
|
-
)
|
174
|
-
@weather_file_path = File.dirname(weather_filename)
|
175
|
-
else
|
176
|
-
fail 'No weather file path defined'
|
177
|
-
end
|
178
|
-
else
|
179
|
-
fail 'No weather file block defined'
|
180
|
-
end
|
181
|
-
|
182
|
-
unless File.exist?(weather_filename)
|
183
|
-
fail "Could not find weather file for simulation #{weather_filename}"
|
184
|
-
end
|
185
|
-
|
186
|
-
@results[:weather_filename] = weather_filename
|
187
|
-
|
188
|
-
weather_filename
|
189
|
-
end
|
190
|
-
|
191
|
-
# return the weather file from the model. If the weather file is defined in the model, then
|
192
|
-
# it checks the file paths to check if the model exists. This allows for a user to upload a
|
193
|
-
# weather file in a measure and then have the measure's path be used for the weather file.
|
194
|
-
def get_weather_file_from_model
|
195
|
-
wf = nil
|
196
|
-
# grab the weather file out of the OSM if it exists
|
197
|
-
if @model.weatherFile.empty?
|
198
|
-
@logger.info 'No weather file in model'
|
199
|
-
else
|
200
|
-
p = @model.weatherFile.get.path.get.to_s.gsub('file://', '')
|
201
|
-
if File.exist? p
|
202
|
-
# use this entire path
|
203
|
-
@logger.info "Full path to weather file exists #{p}"
|
204
|
-
wf = p
|
205
|
-
else
|
206
|
-
# this is the weather file from the OSM model
|
207
|
-
wf = File.basename(@model.weatherFile.get.path.get.to_s)
|
208
|
-
end
|
209
|
-
|
210
|
-
# @logger.info "Initial model weather file is #{wf}" # unless model.weatherFile.empty?
|
211
|
-
end
|
212
|
-
|
213
|
-
wf
|
214
|
-
end
|
215
|
-
|
216
|
-
# Forward translate to energyplus
|
217
|
-
def translate_to_energyplus
|
218
|
-
if @model_idf.nil?
|
219
|
-
@logger.info 'Translate object to EnergyPlus IDF in Prep for EnergyPlus Measure'
|
220
|
-
a = Time.now
|
221
|
-
# ensure objects exist for reporting purposes
|
222
|
-
@model.getFacility
|
223
|
-
@model.getBuilding
|
224
|
-
forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
|
225
|
-
@model_idf = forward_translator.translateModel(@model)
|
226
|
-
b = Time.now
|
227
|
-
@logger.info "Translate object to EnergyPlus IDF took #{b.to_f - a.to_f}"
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
@@ -1,110 +0,0 @@
|
|
1
|
-
######################################################################
|
2
|
-
# Copyright (c) 2008-2014, Alliance for Sustainable Energy.
|
3
|
-
# All rights reserved.
|
4
|
-
#
|
5
|
-
# This library is free software; you can redistribute it and/or
|
6
|
-
# modify it under the terms of the GNU Lesser General Public
|
7
|
-
# License as published by the Free Software Foundation; either
|
8
|
-
# version 2.1 of the License, or (at your option) any later version.
|
9
|
-
#
|
10
|
-
# This library is distributed in the hope that it will be useful,
|
11
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
-
# Lesser General Public License for more details.
|
14
|
-
#
|
15
|
-
# You should have received a copy of the GNU Lesser General Public
|
16
|
-
# License along with this library; if not, write to the Free Software
|
17
|
-
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
18
|
-
######################################################################
|
19
|
-
|
20
|
-
# Run precanned post processing to extract object functions
|
21
|
-
# TODO: I hear that measures can step on each other if not run in their own directory
|
22
|
-
|
23
|
-
require 'csv'
|
24
|
-
require 'ostruct'
|
25
|
-
|
26
|
-
class RunPostprocess
|
27
|
-
# Mixin the MeasureApplication module to apply measures
|
28
|
-
include OpenStudio::Workflow::ApplyMeasures
|
29
|
-
|
30
|
-
def initialize(directory, logger, time_logger, adapter, workflow_arguments, past_results, options = {})
|
31
|
-
defaults = {}
|
32
|
-
@options = defaults.merge(options)
|
33
|
-
@directory = directory
|
34
|
-
@run_directory = "#{@directory}/run"
|
35
|
-
@adapter = adapter
|
36
|
-
@logger = logger
|
37
|
-
@time_logger = time_logger
|
38
|
-
@workflow_arguments = workflow_arguments
|
39
|
-
@past_results = past_results
|
40
|
-
@results = {}
|
41
|
-
@output_attributes = {}
|
42
|
-
|
43
|
-
@logger.info "#{self.class} passed the following options #{@options}"
|
44
|
-
end
|
45
|
-
|
46
|
-
def perform
|
47
|
-
@logger.info "Calling #{__method__} in the #{self.class} class"
|
48
|
-
@logger.info 'RunPostProcess Retrieving datapoint and problem'
|
49
|
-
|
50
|
-
begin
|
51
|
-
cleanup
|
52
|
-
rescue => e
|
53
|
-
log_message = "Runner error #{__FILE__} failed with #{e.message}, #{e.backtrace.join("\n")}"
|
54
|
-
@logger.error log_message
|
55
|
-
# raise log_message
|
56
|
-
end
|
57
|
-
|
58
|
-
@results
|
59
|
-
end
|
60
|
-
|
61
|
-
def cleanup
|
62
|
-
# move any of the reporting file to the 'reports' directory for serverside access
|
63
|
-
eplus_search_path = nil
|
64
|
-
FileUtils.mkdir_p "#{@directory}/reports"
|
65
|
-
|
66
|
-
# try to find the energyplus result file
|
67
|
-
eplus_html = "#{@run_directory}/eplustbl.htm"
|
68
|
-
unless File.exist? eplus_html
|
69
|
-
eplus_html = Dir["#{@directory}/*EnergyPlus*/eplustbl.htm"].last || nil
|
70
|
-
end
|
71
|
-
|
72
|
-
if eplus_html
|
73
|
-
if File.exist? eplus_html
|
74
|
-
# do some encoding on the html if possible
|
75
|
-
html = File.read(eplus_html)
|
76
|
-
html = html.force_encoding('ISO-8859-1').encode('utf-8', replace: nil)
|
77
|
-
File.open("#{@directory}/reports/eplustbl.html", 'w') { |f| f << html }
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# Also, find any "report*.*" files
|
82
|
-
Dir["#{@run_directory}/*/report*.*"].each do |report|
|
83
|
-
# get the parent directory of the file and snake case it
|
84
|
-
# do i need to force encoding on this as well?
|
85
|
-
measure_class_name = File.basename(File.dirname(report)).to_underscore
|
86
|
-
file_ext = File.extname(report)
|
87
|
-
append_str = File.basename(report, '.*')
|
88
|
-
new_file_name = "#{@directory}/reports/#{measure_class_name}_#{append_str}#{file_ext}"
|
89
|
-
FileUtils.copy report, new_file_name
|
90
|
-
end
|
91
|
-
|
92
|
-
# Remove empty directories in run folder
|
93
|
-
Dir["#{@run_directory}/*"].select { |d| File.directory? d }.select { |d| (Dir.entries(d) - %w(. ..)).empty? }.each do |d|
|
94
|
-
@logger.info "Removing empty directory #{d}"
|
95
|
-
Dir.rmdir d
|
96
|
-
end
|
97
|
-
|
98
|
-
paths_to_rm = []
|
99
|
-
# paths_to_rm << Pathname.glob("#{@run_directory}/*.osm")
|
100
|
-
# paths_to_rm << Pathname.glob("#{@run_directory}/*.idf") # keep the idfs
|
101
|
-
# paths_to_rm << Pathname.glob("*.audit")
|
102
|
-
# paths_to_rm << Pathname.glob("*.bnd")
|
103
|
-
# paths_to_rm << Pathname.glob("#{@run_directory}/*.eso")
|
104
|
-
paths_to_rm << Pathname.glob("#{@run_directory}/*.mtr")
|
105
|
-
paths_to_rm << Pathname.glob("#{@run_directory}/*.epw")
|
106
|
-
paths_to_rm << Pathname.glob("#{@run_directory}/*.mtd")
|
107
|
-
paths_to_rm << Pathname.glob("#{@run_directory}/*.rdd")
|
108
|
-
paths_to_rm.each { |p| FileUtils.rm_rf(p) }
|
109
|
-
end
|
110
|
-
end
|