Skip to content

Commit

Permalink
Merge pull request #140 from NREL/develop
Browse files Browse the repository at this point in the history
merge for release 2.3.0
  • Loading branch information
nllong authored Oct 21, 2021
2 parents 6f8ca20 + 3b3dd8d commit 19610d1
Show file tree
Hide file tree
Showing 10 changed files with 1,655 additions and 14 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
OpenStudio::Workflow Change Log
==================================

Version 2.3.0
-------------
* Feature [#135](https://github.com/NREL/OpenStudio-workflow-gem/issues/135) Add option to export/run epJSON

Version 2.2.1
-------------
* Fixes [#4150](https://github.com/NREL/OpenStudio/issues/4150) LoadError changes current working directory in CLI
Expand Down
95 changes: 95 additions & 0 deletions lib/openstudio/workflow/adapters/input/local.rb
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,101 @@ def verify_osw(user_options, default)
return default
end

def epjson(user_options, default, logger)
# check version for this feature
os_version = OpenStudio::VersionString.new(OpenStudio.openStudioVersion)
min_version_feature = OpenStudio::VersionString.new('3.3.0')
unless os_version >= min_version_feature
log_message = 'epJSON is only supported for versions >= 3.3.0. Falling back to using IDF'
logger.info log_message
return default
end

# user option trumps all others
return user_options[:epjson] if user_options[:epjson]

# try to read from OSW

if @run_options.is_initialized && @run_options.get.respond_to?(:epjson)
return @run_options.get.epjson
end

return default
end

# Process the `run` method `ft_options` subhash
#
# This will validate that each suboption is supported in the current
# version as well as enhance the hash with the corresponding
# ForwardTranslator method name to set the value on the FT later
# in `translate_to_energyplus`
# user option trumps all others
def ft_options(user_options, default, logger)
# check version for this feature
os_version = OpenStudio::VersionString.new(OpenStudio.openStudioVersion)

os300 = OpenStudio::VersionString.new('3.0.0')
os330 = OpenStudio::VersionString.new('3.3.0')
known_ft_opts = {
# All Versions
runcontrolspecialdays: { method_name: :setKeepRunControlSpecialDays, min_version: nil },
ip_tabular_output: { method_name: :setIPTabularOutput, min_version: nil },
no_lifecyclecosts: { method_name: :setExcludeLCCObjects, min_version: nil },
# 3.0.0
no_sqlite_output: { method_name: :setExcludeSQliteOutputReport, min_version: os300 },
no_html_output: { method_name: :setExcludeHTMLOutputReport, min_version: os300 },
no_variable_dictionary: { method_name: :setExcludeVariableDictionary, min_version: os300 },
# 3.3.0
no_space_translation: { method_name: :setExcludeSpaceTranslation, min_version: os330 }
}

# user option trumps all others
if user_options[:ft_options]
ft_opts = {}
user_options[:ft_options].each do |opt_flag_name, opt_flag|
puts "#{opt_flag_name} = #{opt_flag}"
unless known_ft_opts.key?(opt_flag_name)
log_message = "'ft_options' suboption '#{opt_flag_name}' is not recognized, ignoring it."
logger.warn log_message
next
end
min_version = known_ft_opts[opt_flag_name][:min_version]
if !min_version.nil? && os_version < min_version
log_message = "'ft_options' suboption '#{opt_flag_name}' is only supported for OpenStudio Version >= #{min_version.str}, ignoring it."
logger.warn log_message
next
end
ft_opts[opt_flag_name] = { method_name: known_ft_opts[opt_flag_name][:method_name], value: opt_flag }
end

return ft_opts
end

# try to read from OSW

if @run_options.is_initialized && @run_options.get.respond_to?(:forwardTranslateOptions)
ft_opts = {}
JSON.parse(@run_options.get.forwardTranslateOptions, symbolize_names: true).each do |opt_flag_name, opt_flag|
unless known_ft_opts.key?(opt_flag_name)
log_message = "'ft_options' suboption '#{opt_flag_name}' is not recognized, ignoring it."
logger.warn log_message
next
end
min_version = known_ft_opts[opt_flag_name.to_sym][:min_version]
if !min_version.nil? && os_version < min_version
log_message = "'ft_options' suboption '#{opt_flag_name}' is only supported for OpenStudio Version >= #{min_version.str}, ignoring it."
logger.warn log_message
next
end
ft_opts[opt_flag_name] = { method_name: known_ft_opts[opt_flag_name][:method_name], value: opt_flag }
end

return ft_opts
end

return default
end

def weather_file(user_options, default)
# user option trumps all others
return user_options[:weather_file] if user_options[:weather_file]
Expand Down
4 changes: 4 additions & 0 deletions lib/openstudio/workflow/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ def self.default_jobs
# @option user_options [Hash] :targets Log targets to write to, defaults to standard out and run.log
# @option user_options [Hash] :verify_osw Check OSW for correctness, defaults to true
# @option user_options [Hash] :weather_file Initial weather file to load, overrides OSW option if set, defaults to empty
# @option user_options [Hash] :epjson - Create, export and run using epjson format. Default is IDF
# @option user_options [Hash] :ft_options - A subhash of options to set on the ForwardTranslator
def initialize(osw_path, user_options = {})
# DLM - what is final_message?
@final_message = ''
Expand Down Expand Up @@ -199,6 +201,8 @@ def initialize(osw_path, user_options = {})
@options[:weather_file] = @input_adapter.weather_file(user_options, nil)
@options[:fast] = @input_adapter.fast(user_options, false)
@options[:skip_zip_results] = @input_adapter.skip_zip_results(user_options, false)
@options[:epjson] = @input_adapter.epjson(user_options, false, @logger)
@options[:ft_options] = @input_adapter.ft_options(user_options, {}, @logger)

openstudio_dir = 'unknown'
begin
Expand Down
58 changes: 47 additions & 11 deletions lib/openstudio/workflow/util/energyplus.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ module Util
module EnergyPlus
require 'openstudio/workflow/util/io'
include OpenStudio::Workflow::Util::IO
require 'openstudio/workflow/util/model'
include OpenStudio::Workflow::Util::Model
ENERGYPLUS_REGEX = /^energyplus\D{0,4}$/i.freeze
EXPAND_OBJECTS_REGEX = /^expandobjects\D{0,4}$/i.freeze

Expand Down Expand Up @@ -94,8 +96,8 @@ def prepare_energyplus_dir(run_directory, logger, energyplus_path = nil)
Dir["#{energyplus_path}/*"].each do |file|
next if File.directory? file

# copy idd and ini files
if File.extname(file).downcase =~ /.idd|.ini/
# copy idd, ini and epJSON schema files
if File.extname(file).downcase =~ /.idd|.ini|.epjson/
dest_file = "#{run_directory}/#{File.basename(file)}"
energyplus_files << dest_file
FileUtils.copy file, dest_file
Expand Down Expand Up @@ -130,7 +132,9 @@ def call_energyplus(run_directory, energyplus_path = nil, output_adapter = nil,
current_dir = Dir.pwd
energyplus_path ||= find_energyplus
logger.info "EnergyPlus path is #{energyplus_path}"

energyplus_files, energyplus_exe, expand_objects_exe = prepare_energyplus_dir(run_directory, logger, energyplus_path)

Dir.chdir(run_directory)
logger.info "Starting simulation in run directory: #{Dir.pwd}"

Expand All @@ -152,18 +156,50 @@ def call_energyplus(run_directory, energyplus_path = nil, output_adapter = nil,
end
end

# create stdout
command = popen_command("\"#{energyplus_exe}\" 2>&1")
logger.info "Running command '#{command}'"
File.open('stdout-energyplus', 'w') do |file|
::IO.popen(command) do |io|
while (line = io.gets)
file << line
output_adapter&.communicate_energyplus_stdout(line)
# Translate the IDF to an epJSON if @options[:epjson] is true
# Ideally, this would be done sooner in the workflow process but many processes
# manipulate the model_idf, some which are ep_measures that may not work with json
# and ExpandObjects does not currently support epjson anyway to that still needs to run
# before this can be changed.
if @options[:epjson]
@logger.info 'Beginning the translation to epJSON'
@registry[:time_logger]&.start('Translating to EnergyPlus epJSON')
idf_final = load_idf("#{run_directory}/in.idf", @logger)
model_epjson = translate_idf_to_epjson idf_final, @logger
@registry[:time_logger]&.stop('Translating to EnergyPlus')
@logger.info 'Successfully translated to epJSON'
@registry[:time_logger]&.start('Saving epJSON')
epjson_name = save_epjson(model_epjson, run_directory)
@registry[:time_logger]&.stop('Saving epJSON')
@logger.debug "Saved epJSON as #{epjson_name}"
end

# Run using epJSON if @options[:epjson] true, otherwise use IDF
if @options[:epjson]
command = popen_command("\"#{energyplus_exe}\" in.epJSON 2>&1")
logger.info "Running command '#{command}'"
File.open('stdout-energyplus', 'w') do |file|
::IO.popen(command) do |io|
while (line = io.gets)
file << line
output_adapter&.communicate_energyplus_stdout(line)
end
end
end
r = $?
else
command = popen_command("\"#{energyplus_exe}\" 2>&1")
logger.info "Running command '#{command}'"
File.open('stdout-energyplus', 'w') do |file|
::IO.popen(command) do |io|
while (line = io.gets)
file << line
output_adapter&.communicate_energyplus_stdout(line)
end
end
end
r = $?
end
r = $?

logger.info "EnergyPlus returned '#{r}'"
unless r.to_i.zero?
Expand Down
61 changes: 59 additions & 2 deletions lib/openstudio/workflow/util/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,49 @@ def translate_to_energyplus(model, logger = nil)
# ensure objects exist for reporting purposes
model.getFacility
model.getBuilding
forward_translator = OpenStudio::EnergyPlus::ForwardTranslator.new
model_idf = forward_translator.translateModel(model)
ft = OpenStudio::EnergyPlus::ForwardTranslator.new

ft_options = @options[:ft_options]
if !ft_options.empty?

msg = "Custom ForwardTranslator options passed:\n"

ft_options.each do |opt_flag_name, h|
ft_method = h[:method_name]
opt_flag = h[:value]

# Call the FT setter with the value passed in
ft.method(ft_method).call(opt_flag)

msg += "* :#{opt_flag_name}=#{opt_flag} => ft.#{ft_method}(#{opt_flag})\n"
end

logger.info msg
end

model_idf = ft.translateModel(model)
b = ::Time.now
logger.info "Translate object to EnergyPlus IDF took #{b.to_f - a.to_f}"
model_idf
end

# Translates an IDF model into an EnergyPlus epJSON object
#
# @param [Object] OpenStudio::IdfFile instance to translate into an OpenStudio epJSON object -- see
# the OpenStudio SDK for details on the process
# @return [Object] Returns and OpenStudio::epJSONobject
#
def translate_idf_to_epjson(model_idf, logger = nil)
logger ||= ::Logger.new($stdout)
logger.info 'Translate IDF to epJSON in preparation for EnergyPlus'
a = ::Time.now
model_epjson = OpenStudio::EPJSON.toJSONString(model_idf)
b = ::Time.now
logger.info "Translate IDF to EnergyPlus epJSON took #{b.to_f - a.to_f}"

model_epjson
end

# Saves an OpenStudio model object to file
#
# @param [Object] model The OpenStudio::Model instance to save to file
Expand Down Expand Up @@ -149,6 +185,27 @@ def save_idf(model_idf, save_directory, name = 'in.idf')
end
idf_filename
end

# Saves an OpenStudio EpJSON model object to file
#
# @param [Object] model The OpenStudio::Workspace instance to save to file
# @param [String] save_directory Folder to save the model in
# @param [String] name ('in.epJSON') Option to define a non-standard name
# @return [String] epJSON file name
#
def save_epjson(model_epjson, save_directory, name = 'in.epJSON')
epjson_filename = File.join(save_directory.to_s, name.to_s)
File.open(epjson_filename, 'w') do |f|
f << model_epjson.to_s
# make sure data is written to the disk one way or the other
begin
f.fsync
rescue StandardError
f.flush
end
end
epjson_filename
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/openstudio/workflow/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@

module OpenStudio
module Workflow
VERSION = '2.2.1' # Suffixes must have periods (not dashes)
VERSION = '2.3.0' # Suffixes must have periods (not dashes)
end
end
Loading

0 comments on commit 19610d1

Please sign in to comment.