Last active
August 5, 2021 12:39
-
-
Save jmarrec/d3a028b92df5a30898413205240de5d6 to your computer and use it in GitHub Desktop.
NREL/OpenStudio#4386 : SqlSpeed
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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