Skip to content

Instantly share code, notes, and snippets.

@jmarrec
Last active August 5, 2021 12:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmarrec/d3a028b92df5a30898413205240de5d6 to your computer and use it in GitHub Desktop.
Save jmarrec/d3a028b92df5a30898413205240de5d6 to your computer and use it in GitHub Desktop.
# this script records how much time it takes to run the model_annual_occupied_unmet_cooling_hours_detailed() method on a moderately sized model
# as suggested by Scott Horowitz, I tried toggling the boolean createIndexes argument in the SQL file constructor:
# sql_file = OpenStudio::SqlFile.new(OpenStudio::Path.new(sql_path), false)
# this method takes ~23.6 seconds to run (average of 23.6, 23.8, 23.5) with createIndexes set to false,
# and ~24.5 seconds to run (average of 24.1, 25.7, 23.6) with createIndexes set to true (default)
# there is some time spent requiring ruby gems and loading the model, but the main issue is the time it takes to do SQL calls
# each SQL call is ~.3-.4 seconds, which adds up over many zones
# Gets the annual occupied unmet cooling hours from zone temperature time series in the sql file
#
# @param model [OpenStudio::Model::Model] OpenStudio model object
# @param occupied_percentage_threshold [Double] the minimum fraction (0 to 1) that counts as occupied
# @param tolerance [Double] tolerance in degrees Rankine to log an unmet hour
# @return [Hash] Hash with 'sum' of cooling unmet hours and 'zone_temperature_differences' of all zone unmet hours data
# @todo account for operative temperature thermostats
def model_annual_occupied_unmet_cooling_hours_detailed(model, std, tolerance: 1.0, occupied_percentage_threshold: 0.05)
load_sql_weather_run_t = Time.now
OpenStudio.logFree(OpenStudio::Info, 'openstudio.model.Model', "Calculating zone cooling occupied unmet hours with #{tolerance} R tolerance. This may take some time.")
sql = std.model_sql_file(model)
# convert tolerance to Kelvin
tolerance_k = OpenStudio.convert(tolerance, 'R', 'K').get
ann_env_pd = std.model_weather_run_period(model)
unless ann_env_pd
OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', 'Could not get annual run period.')
return false
end
load_sql_weather_run_t = Time.now - load_sql_weather_run_t
puts "load sql and weather run: #{load_sql_weather_run_t.round(3)} seconds"
timings = []
# for each zone calculate unmet hours and store in array
bldg_unmet_hours = []
bldg_occ_unmet_hours = []
zone_data = []
model.getThermalZones.each do |zone|
process_zone_t = Time.now
# skip zones that aren't cooled
next unless std.thermal_zone_cooled?(zone)
# get zone air temperatures
get_zone_temp_try_1 = Time.now
get_zone_temp_try_2 = Float::NAN
zone_temp_timeseries = sql.timeSeries(ann_env_pd, 'Hourly', 'Zone Air Temperature', zone.name.get)
get_zone_temp_try_1 = Time.now - get_zone_temp_try_1
if zone_temp_timeseries.empty?
# try mean air temperature instead
get_zone_temp_try_2 = Time.now
zone_temp_timeseries = sql.timeSeries(ann_env_pd, 'Hourly', 'Zone Mean Air Temperature', zone.name.get)
get_zone_temp_try_2 = Time.now - get_zone_temp_try_2
if zone_temp_timeseries.empty?
# no air temperature found
OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Could not find zone air temperature timeseries for zone '#{zone.name.get}'")
return false
end
end
convert_ruby_temp_t = Time.now
# convert to ruby array
zone_temperatures = []
zone_temp_vector = zone_temp_timeseries.get.values
for i in (0..zone_temp_vector.size - 1)
zone_temperatures << zone_temp_vector[i]
end
convert_ruby_temp_t = Time.now - convert_ruby_temp_t
# get zone thermostat heating setpoint temperatures
get_zone_tstat_t = Time.now
zone_setpoint_temp_timeseries = sql.timeSeries(ann_env_pd, 'Hourly', 'Zone Thermostat Cooling Setpoint Temperature', zone.name.get)
if zone_setpoint_temp_timeseries.empty?
# no setpoint temperature found
OpenStudio.logFree(OpenStudio::Error, 'openstudio.model.Model', "Could not find cooling setpoint temperature timeseries for zone '#{zone.name.get}'")
return false
end
get_zone_tstat_t = Time.now - get_zone_tstat_t
convert_ruby_tsat_t = Time.now
# convert to ruby array
zone_setpoint_temperatures = []
zone_setpoint_temp_vector = zone_setpoint_temp_timeseries.get.values
for i in (0..zone_setpoint_temp_vector.size - 1)
zone_setpoint_temperatures << zone_setpoint_temp_vector[i]
end
convert_ruby_tsat_t = Time.now - convert_ruby_tsat_t
rest_t = Time.now
# calculate zone occupancy by making a new ruleset schedule
occ_schedule_ruleset = std.thermal_zone_get_occupancy_schedule(zone)
occ_values = std.schedule_ruleset_annual_hourly_values(occ_schedule_ruleset)
# calculate difference accounting for unmet hours tolerance
zone_temperature_diff = zone_setpoint_temperatures.map.with_index { |x, i| (x - zone_temperatures[i]) }
zone_unmet_hours = zone_temperature_diff.map { |x| (x - tolerance_k) > 0 ? 1 : 0 }
zone_occ_unmet_hours = []
for i in (0..zone_unmet_hours.size - 1)
bldg_unmet_hours[i] = 0 if bldg_unmet_hours[i].nil?
bldg_occ_unmet_hours[i] = 0 if bldg_occ_unmet_hours[i].nil?
bldg_unmet_hours[i] += zone_unmet_hours[i]
if occ_values[i] >= occupied_percentage_threshold
zone_occ_unmet_hours[i] = zone_unmet_hours[i]
bldg_occ_unmet_hours[i] += zone_unmet_hours[i]
else
zone_occ_unmet_hours[i] = 0
end
end
# log information for zone
# could reduce the number of returned variables if this poses a storage or data transfer problem
zone_data << { 'zone_name' => zone.name,
'zone_area' => zone.floorArea,
'zone_air_temperatures' => zone_temperatures.map { |x| x.round(3) },
'zone_air_setpoint_temperatures' => zone_setpoint_temperatures.map { |x| x.round(3) },
'zone_air_temperature_differences' => zone_temperature_diff.map { |x| x.round(3) },
'zone_occupancy' => occ_values.map { |x| x.round(3) },
'zone_unmet_hours' => zone_unmet_hours,
'zone_occupied_unmet_hours' => zone_occ_unmet_hours,
'sum_zone_unmet_hours' => zone_unmet_hours.count { |x| x > 0 },
'sum_zone_occupied_unmet_hours' => zone_occ_unmet_hours.count { |x| x > 0 } }
rest_t = Time.now - rest_t
process_zone_t = Time.now - process_zone_t
puts "thermal zone #{zone.name} sql calls complete: #{process_zone_t.round(3)} seconds"
timings << {
:zone_name => zone.nameString,
:get_zone_temp_try_1 => get_zone_temp_try_1,
:get_zone_temp_try_2 => get_zone_temp_try_2,
:convert_ruby_temp_t => convert_ruby_temp_t,
:get_zone_tstat_t => get_zone_tstat_t,
:convert_ruby_tsat_t => convert_ruby_tsat_t,
:rest_t => rest_t,
:total => process_zone_t
}
end
occupied_unmet_cooling_hours_detailed = { 'sum_bldg_unmet_hours' => bldg_unmet_hours.count { |x| x > 0 },
'sum_bldg_occupied_unmet_hours' => bldg_occ_unmet_hours.count { |x| x > 0 },
'bldg_unmet_hours' => bldg_unmet_hours,
'bldg_occupied_unmet_hours' => bldg_occ_unmet_hours,
'zone_data' => zone_data }
return timings
end
load_t = Time.now
require 'openstudio'
load_t = Time.now - load_t
puts "require openstudio: #{load_t.round(3)} seconds"
load_t = Time.now
require 'openstudio-standards'
load_t = Time.now - load_t
puts "require openstudio-standards: #{load_t.round(3)} seconds"
# load model files and results
load_t = Time.now
osm_path = "#{File.dirname(__FILE__)}/in.osm"
idf_path = "#{File.dirname(__FILE__)}/in.idf"
epw_path = "#{File.dirname(__FILE__)}/in.epw"
sql_path = "#{File.dirname(__FILE__)}/eplusout.sql"
# set up runner, this will happen automatically when measure is run in PAT or OpenStudio
runner = OpenStudio::Measure::OSRunner.new(OpenStudio::WorkflowJSON.new)
runner.setLastOpenStudioModelPath(OpenStudio::Path.new(osm_path))
runner.setLastEnergyPlusWorkspacePath(OpenStudio::Path.new(idf_path))
runner.setLastEpwFilePath(OpenStudio::Path.new(epw_path))
runner.setLastEnergyPlusSqlFilePath(OpenStudio::Path.new(sql_path))
translator = OpenStudio::OSVersion::VersionTranslator.new
model = translator.loadModel(osm_path).get
load_t = Time.now - load_t
puts "model load: #{load_t.round(3)} seconds"
# set SQL file
sql_file = OpenStudio::SqlFile.new(OpenStudio::Path.new(sql_path))
model.setSqlFile(sql_file)
# build standard
build_std_t = Time.now
std = Standard.build('DEER 2003')
build_std_t = Time.now - build_std_t
puts "build std: #{build_std_t.round(3)} seconds"
tolerance = 1.0
tolerance_K = OpenStudio.convert(tolerance, 'R', 'K').get
occupied_percentage_threshold = 0.1
sql = std.model_sql_file(model)
weather_t = Time.now
ann_env_pd = std.model_weather_run_period(model)
zone = model.getThermalZones[1]
#puts "zone name #{zone.name}"
weather_t = Time.now - weather_t
puts "weather run period: #{weather_t.round(3)} seconds"
one_zone_t = Time.now
zone_temp_timeseries = sql.timeSeries(ann_env_pd, 'Hourly', 'Zone Mean Air Temperature', zone.name.get)
zone_temp_vector = zone_temp_timeseries.get.values
#puts zone_temp_vector.length
one_zone_t = Time.now - one_zone_t
puts "basic sql call to one zone: #{one_zone_t.round(3)} seconds"
puts "detailed unmet cooling hours method start"
detailed_t = Time.now
timings = model_annual_occupied_unmet_cooling_hours_detailed(model, std, tolerance: 1.0, occupied_percentage_threshold: 0.05)
detailed_t = Time.now - detailed_t
puts "detailed unmet cooling hours method: #{detailed_t.round(3)} seconds"
require 'json'
File.open("timings.json","w") do |f|
f.write(timings.to_json)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment