lib/openstudio-standards/schedules/information.rb in openstudio-standards-0.5.0 vs lib/openstudio-standards/schedules/information.rb in openstudio-standards-0.6.0.rc1

- old
+ new

@@ -1,68 +1,733 @@ -# Methods to get information from Schedule objects -# Many of these methods may be moved to core OpenStudio module OpenstudioStandards + # The Schedules module provides methods to create, modify, and get information about Schedule objects module Schedules # @!group Information + # Methods to get information about Schedule objects - # add general schedule_min_max(schedule) method and merge with other methods + # Returns the Schedule minimum and maximum values encountered during the run-period. + # This method does not include summer and winter design day values. + # + # @param schedule [OpenStudio::Model::Schedule] OpenStudio Schedule object + # @param only_run_period_values [Bool] check values encountered only during the run period + # Default to false. Only applicable to ScheduleRuleset schedules. + # This will ignore ScheduleRules or the DefaultDaySchedule if never used. + # return [Hash] returns a hash with 'min' and 'max' values + def self.schedule_get_min_max(schedule, only_run_period_values: false) + case schedule.iddObjectType.valueName.to_s + when 'OS_Schedule_Ruleset' + schedule = schedule.to_ScheduleRuleset.get + result = OpenstudioStandards::Schedules.schedule_ruleset_get_min_max(schedule, only_run_period_values: only_run_period_values) + when 'OS_Schedule_Constant' + schedule = schedule.to_ScheduleConstant.get + result = OpenstudioStandards::Schedules.schedule_constant_get_min_max(schedule) + when 'OS_Schedule_Compact' + schedule = schedule.to_ScheduleCompact.get + result = OpenstudioStandards::Schedules.schedule_compact_get_min_max(schedule) + when 'OS_Schedule_Year' + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_min_max does not yet support ScheduleYear schedules.') + result = { 'min' => nil, 'max' => nil } + when 'OS_Schedule_Interval' + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_min_max does not yet support ScheduleInterval schedules.') + result = { 'min' => nil, 'max' => nil } + else + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for schedule_get_min_max.") + result = { 'min' => nil, 'max' => nil } + end - # returns the ScheduleRuleset minimum and maximum values + return result + end + + # Returns the Schedule minimum and maximum values during the winter or summer design day. # + # @param schedule [OpenStudio::Model::Schedule] OpenStudio Schedule object + # @param type [String] 'winter' for the winter design day, 'summer' for the summer design day + # return [Hash] returns a hash with 'min' and 'max' values + def self.schedule_get_design_day_min_max(schedule, type = 'winter') + case schedule.iddObjectType.valueName.to_s + when 'OS_Schedule_Ruleset' + schedule = schedule.to_ScheduleRuleset.get + result = OpenstudioStandards::Schedules.schedule_ruleset_get_design_day_min_max(schedule, type) + when 'OS_Schedule_Constant' + schedule = schedule.to_ScheduleConstant.get + result = OpenstudioStandards::Schedules.schedule_constant_get_design_day_min_max(schedule, type) + when 'OS_Schedule_Compact' + schedule = schedule.to_ScheduleCompact.get + result = OpenstudioStandards::Schedules.schedule_compact_get_design_day_min_max(schedule, type) + when 'OS_Schedule_Year' + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_design_day_min_max does not yet support ScheduleYear schedules.') + result = { 'min' => nil, 'max' => nil } + when 'OS_Schedule_Interval' + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_design_day_min_max does not yet support ScheduleInterval schedules.') + result = { 'min' => nil, 'max' => nil } + else + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for schedule_get_design_day_min_max.") + result = { 'min' => nil, 'max' => nil } + end + + return result + end + + # Returns the Schedule equivalent full load hours (EFLH). + # For example a fractional schedule of 0.5, 24/7, 365 would return a value of 4380. + # This method includes leap days on leap years. + # + # @param schedule [OpenStudio::Model::Schedule] OpenStudio Schedule object + # return [Double] The total equivalent full load hours for this schedule + def self.schedule_get_equivalent_full_load_hours(schedule) + case schedule.iddObjectType.valueName.to_s + when 'OS_Schedule_Ruleset' + schedule = schedule.to_ScheduleRuleset.get + result = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule) + when 'OS_Schedule_Constant' + schedule = schedule.to_ScheduleConstant.get + result = OpenstudioStandards::Schedules.schedule_constant_get_equivalent_full_load_hours(schedule) + when 'OS_Schedule_Compact' + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_equivalent_full_load_hours does not yet support ScheduleCompact schedules.') + result = nil + when 'OS_Schedule_Year' + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_equivalent_full_load_hours does not yet support ScheduleYear schedules.') + result = nil + when 'OS_Schedule_Interval' + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_equivalent_full_load_hours does not yet support ScheduleInterval schedules.') + result = nil + else + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for schedule_get_equivalent_full_load_hours.") + result = nil + end + + return result + end + + # Returns an array of average hourly values from a Schedule object + # Returns 8760 values, 8784 for leap years. + # + # @param schedule [OpenStudio::Model::Schedule] OpenStudio Schedule object + # @return [Array<Double>] Array of hourly values for the year + def self.schedule_get_hourly_values(schedule) + case schedule.iddObjectType.valueName.to_s + when 'OS_Schedule_Ruleset' + schedule = schedule.to_ScheduleRuleset.get + result = OpenstudioStandards::Schedules.schedule_ruleset_get_hourly_values(schedule) + when 'OS_Schedule_Constant' + schedule = schedule.to_ScheduleConstant.get + result = OpenstudioStandards::Schedules.schedule_constant_get_hourly_values(schedule) + when 'OS_Schedule_Compact' + schedule = schedule.to_ScheduleCompact.get + result = OpenstudioStandards::Schedules.schedule_compact_get_hourly_values(schedule) + when 'OS_Schedule_Year' + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_hourly_values does not yet support ScheduleYear schedules.') + result = nil + when 'OS_Schedule_Interval' + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_hourly_values does not yet support ScheduleInterval schedules.') + result = nil + else + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for schedule_get_hourly_values.") + result = nil + end + + return result + end + + # @!endgroup Information + + # @!group Information:ScheduleConstant + + # Returns the ScheduleConstant minimum and maximum values encountered during the run-period. + # This method does not include summer and winter design day values. + # + # @param schedule_constant [OpenStudio::Model::ScheduleConstant] OpenStudio ScheduleConstant object + # return [Hash] returns a hash with 'min' and 'max' values + def self.schedule_constant_get_min_max(schedule_constant) + result = { 'min' => schedule_constant.value, 'max' => schedule_constant.value } + + return result + end + + # Returns the ScheduleConstant minimum and maximum values during the winter or summer design day. + # + # @param schedule_constant [OpenStudio::Model::ScheduleConstant] OpenStudio ScheduleConstant object + # @param type [String] 'winter' for the winter design day, 'summer' for the summer design day + # return [Hash] returns a hash with 'min' and 'max' values + def self.schedule_constant_get_design_day_min_max(schedule_constant, type) + result = { 'min' => schedule_constant.value, 'max' => schedule_constant.value } + + return result + end + + # Returns SheduleConstant equivalent full load hours (EFLH). + # For example a fractional schedule of 0.5, 24/7, 365 would return a value of 4380. + # This method includes leap days on leap years. + # + # @param schedule_constant [OpenStudio::Model::ScheduleConstant] OpenStudio ScheduleConstant object + # return [Double] The total equivalent full load hours for this schedule + def self.schedule_constant_get_equivalent_full_load_hours(schedule_constant) + hours = 8760 + hours += 24 if schedule_constant.model.getYearDescription.isLeapYear + eflh = schedule_constant.value * hours + + return eflh + end + + # Returns an array of average hourly values from a ScheduleConstant object + # Returns 8760 values, 8784 for leap years. + # + # @param schedule_constant [OpenStudio::Model::ScheduleConstant] OpenStudio ScheduleConstant object + # @return [Array<Double>] Array of hourly values for the year + def self.schedule_constant_get_hourly_values(schedule_constant) + hours = 8760 + hours += 24 if schedule_constant.model.getYearDescription.isLeapYear + values = Array.new(hours) { schedule_constant.value } + + return values + end + + # @!endgroup Information:ScheduleConstant + + # @!group Information:ScheduleCompact + + # Returns the ScheduleCompact minimum and maximum values encountered during the run-period. + # This method does not include summer and winter design day values. + # + # @param schedule_compact [OpenStudio::Model::ScheduleCompact] OpenStudio ScheduleCompact object + # return [Hash] returns a hash with 'min' and 'max' values + def self.schedule_compact_get_min_max(schedule_compact) + vals = [] + prev_str = '' + schedule_compact.extensibleGroups.each do |eg| + if prev_str.include?('until') + val = eg.getDouble(0) + if val.is_initialized + vals << eg.getDouble(0).get + end + end + str = eg.getString(0) + if str.is_initialized + prev_str = str.get.downcase + end + end + + # Error if no values were found + if vals.size.zero? + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Could not find any value in #{schedule_compact.name} when determining min and max.") + result = { 'min' => nil, 'max' => nil } + return result + end + + result = { 'min' => vals.min, 'max' => vals.max } + + return result + end + + # Returns the ScheduleCompact minimum and maximum values during the winter or summer design day. + # + # @param schedule_compact [OpenStudio::Model::ScheduleCompact] OpenStudio ScheduleCompact object + # @param type [String] 'winter' for the winter design day, 'summer' for the summer design day + # return [Hash] returns a hash with 'min' and 'max' values + def self.schedule_compact_get_design_day_min_max(schedule_compact, type = 'winter') + vals = [] + design_day_flag = false + prev_str = '' + schedule_compact.extensibleGroups.each do |eg| + if design_day_flag && prev_str.include?('until') + val = eg.getDouble(0) + if val.is_initialized + vals << val.get + end + end + + str = eg.getString(0) + if str.is_initialized + prev_str = str.get.downcase + if prev_str.include?('for:') + # Process a new day schedule, turn the flag off. + design_day_flag = false + # in the same line, if there is design day label and matches the type, turn the flag back on. + if prev_str.include?(type) || prev_str.include?('alldays') + design_day_flag = true + end + end + end + end + + # Error if no values were found + if vals.size.zero? + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Could not find any value in #{schedule_compact.name} design day schedule when determining min and max.") + result = { 'min' => nil, 'max' => nil } + return result + end + + result = { 'min' => vals.min, 'max' => vals.max } + + return result + end + + # Returns an array of average hourly values from a ScheduleCompact object + # Returns 8760 values, 8784 for leap years. + # + # @param schedule_compact [OpenStudio::Model::ScheduleCompact] OpenStudio ScheduleCompact object + # @return [Array<Double>] Array of hourly values for the year + def self.schedule_compact_get_hourly_values(schedule_compact) + # set a ScheduleTypeLimits if none is present + # this is required for the ScheduleTranslator instantiation + unless schedule_compact.scheduleTypeLimits.is_initialized + schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) + schedule_compact.setScheduleTypeLimits(schedule_type_limits) + end + + # convert to a ScheduleRuleset and use its method + sch_translator = ScheduleTranslator.new(schedule_compact.model, schedule_compact) + schedule_ruleset = sch_translator.convert_schedule_compact_to_schedule_ruleset + result = OpenstudioStandards::Schedules.schedule_ruleset_get_hourly_values(schedule_ruleset) + + return result + end + + # @!endgroup Information:ScheduleCompact + + # @!group Information:ScheduleDay + + # Returns the ScheduleDay minimum and maximum values + # + # @param schedule_day [OpenStudio::Model::ScheduleDay] OpenStudio ScheduleDay object + # @return [Hash] returns a hash with 'min' and 'max' values + def self.schedule_day_get_min_max(schedule_day) + min = nil + max = nil + schedule_day.values.each do |value| + if min.nil? + min = value + else + if min > value then min = value end + end + if max.nil? + max = value + else + if max < value then max = value end + end + end + + result = { 'min' => min, 'max' => max } + end + + # Returns the ScheduleDay daily equivalent full load hours (EFLH). + # + # @param schedule_day [OpenStudio::Model::ScheduleDay] OpenStudio ScheduleDay object + # return [Double] The daily total equivalent full load hours for this schedule + def self.schedule_day_get_equivalent_full_load_hours(schedule_day) + daily_flh = 0 + values = schedule_day.values + times = schedule_day.times + + previous_time_decimal = 0 + times.each_with_index do |time, i| + time_decimal = (time.days * 24.0) + time.hours + (time.minutes / 60.0) + (time.seconds / 3600.0) + duration_of_value = time_decimal - previous_time_decimal + daily_flh += values[i] * duration_of_value + previous_time_decimal = time_decimal + end + + return daily_flh + end + + # Returns an array of average hourly values from a ScheduleDay object + # Returns 24 values + # + # @param schedule_day [OpenStudio::Model::ScheduleDay] OpenStudio ScheduleDay object + # @return [Array<Double>] Array of hourly values for the day + def self.schedule_day_get_hourly_values(schedule_day) + schedule_values = [] + + # determine smallest time interval + times = schedule_day.times + time_interval_min = 15.0 + previous_time_decimal = 0.0 + times.each_with_index do |time, i| + time_decimal = (time.days * 24.0 * 60.0) + (time.hours * 60.0) + time.minutes + (time.seconds / 60) + interval_min = time_decimal - previous_time_decimal + time_interval_min = interval_min if interval_min < time_interval_min + previous_time_decimal = time_decimal + end + time_interval_min = time_interval_min.round(0).to_i + + # get the hourly average by averaging the values in the hour at the smallest time interval + (0..23).each do |j| + values = [] + times = (time_interval_min..60).step(time_interval_min).to_a + times.each { |t| values << schedule_day.getValue(OpenStudio::Time.new(0, j, t, 0)) } + schedule_values << (values.sum / times.size).round(5) + end + + return schedule_values + end + + # @!endgroup Information:ScheduleDay + + # @!group Information:ScheduleRuleset + + # Returns the ScheduleRuleset minimum and maximum values. + # This method does not include summer and winter design day values. + # By default the method reports values from all component day schedules even if unused, + # but can optionally report values encountered only during the run period. + # # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object - # @return [Hash] returns as hash with 'min' and 'max' values - def self.schedule_ruleset_get_min_max(schedule_ruleset) + # @param only_run_period_values [Bool] check values encountered only during the run period + # Default to false. This will ignore ScheduleRules or the DefaultDaySchedule if never used. + # @return [Hash] returns a hash with 'min' and 'max' values + def self.schedule_ruleset_get_min_max(schedule_ruleset, only_run_period_values: false) # validate schedule - if schedule_ruleset.to_ScheduleRuleset.is_initialized - schedule = schedule_ruleset.to_ScheduleRuleset.get + unless schedule_ruleset.to_ScheduleRuleset.is_initialized + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_min_max() failed because object #{schedule_ruleset} is not a ScheduleRuleset.") + return nil + end - # gather profiles - profiles = [] - profiles << schedule.defaultDaySchedule - rules = schedule.scheduleRules - rules.each do |rule| - profiles << rule.daySchedule + # day schedules + day_schedules = [] + + # check only day schedules in the run period + if only_run_period_values + # get year + if schedule_ruleset.model.yearDescription.is_initialized + year_description = schedule_ruleset.model.yearDescription.get + year = year_description.assumedYear + else + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', 'Year description is not specified. Full load hours calculation will assume 2009, the default year OS uses.') + year = 2009 end - # test profiles - min = nil - max = nil - profiles.each do |profile| - profile.values.each do |value| - if min.nil? - min = value - else - if min > value then min = value end - end - if max.nil? - max = value - else - if max < value then max = value end - end + # get start and end month and day + run_period = schedule_ruleset.model.getRunPeriod + start_month = run_period.getBeginMonth + start_day = run_period.getBeginDayOfMonth + end_month = run_period.getEndMonth + end_day = run_period.getEndDayOfMonth + + # set the start and end date + start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_month), start_day, year) + end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_month), end_day, year) + + # Get the ordered list of all the day schedules + day_schs = schedule_ruleset.getDaySchedules(start_date, end_date) + + # Get the array of which schedule is used on each day of the year + day_schs_used_each_day = schedule_ruleset.getActiveRuleIndices(start_date, end_date) + + # Create a map that shows how many days each schedule is used + day_sch_freq = day_schs_used_each_day.group_by { |n| n } + + # Build a hash that maps schedule day index to schedule day + schedule_index_to_day = {} + day_schs.each_with_index do |day_sch, i| + schedule_index_to_day[day_schs_used_each_day[i]] = day_sch + end + + # Loop through each of the schedules and record which ones are used + day_sch_freq.each do |freq| + sch_index = freq[0] + number_of_days_sch_used = freq[1].size + next unless number_of_days_sch_used > 0 + + # Get the day schedule at this index + day_sch = nil + if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule) + day_sch = schedule_ruleset.defaultDaySchedule + else + day_sch = schedule_index_to_day[sch_index] end + + # add day schedule to array + day_schedules << day_sch end - result = { 'min' => min, 'max' => max } # this doesn't include summer and winter design day else - result = nil + # use all day schedules + day_schedules << schedule_ruleset.defaultDaySchedule + schedule_ruleset.scheduleRules.each { |rule| day_schedules << rule.daySchedule } end + # get min and max from day schedules array + min = nil + max = nil + day_schedules.each do |day_schedule| + day_schedule.values.each do |value| + if min.nil? + min = value + else + if min > value then min = value end + end + if max.nil? + max = value + else + if max < value then max = value end + end + end + end + result = { 'min' => min, 'max' => max } + return result end + # Returns the ScheduleRuleset minimum and maximum values during the winter or summer design day. + # + # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object + # @param type [String] 'winter' for the winter design day, 'summer' for the summer design day + # return [Hash] returns a hash with 'min' and 'max' values + def self.schedule_ruleset_get_design_day_min_max(schedule_ruleset, type = 'winter') + # validate schedule + unless schedule_ruleset.to_ScheduleRuleset.is_initialized + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_design_day_min_max() failed because object #{schedule_ruleset} is not a ScheduleRuleset.") + return nil + end + + if type == 'winter' + schedule = schedule_ruleset.winterDesignDaySchedule + elsif type == 'summer' + schedule = schedule_ruleset.summerDesignDaySchedule + end + + if !schedule + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', "#{schedule_ruleset.name} is missing #{type} design day schedule, use default day schedule to process the min max search") + schedule = schedule_ruleset.defaultDaySchedule + end + + min = nil + max = nil + schedule.values.each do |value| + if min.nil? + min = value + else + min = value if min > value + end + if max.nil? + max = value + else + max = value if max < value + end + end + result = { 'min' => min, 'max' => max } + + return result + end + + # Returns SheduleRuleset equivalent full load hours (EFLH). + # For example a fractional schedule of 0.5, 24/7, 365 would return a value of 4380. + # This method includes leap days on leap years. + # + # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object + # return [Double] The total equivalent full load hours for this schedule + def self.schedule_ruleset_get_equivalent_full_load_hours(schedule_ruleset) + # validate schedule + unless schedule_ruleset.to_ScheduleRuleset.is_initialized + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_equivalent_full_load_hours() failed because object #{schedule_ruleset} is not a ScheduleRuleset.") + return nil + end + + # define the start and end date + year_start_date = nil + year_end_date = nil + if schedule_ruleset.model.yearDescription.is_initialized + year_description = schedule_ruleset.model.yearDescription.get + year = year_description.assumedYear + year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) + year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) + else + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', 'Year description is not specified. Full load hours calculation will assume 2009, the default year OS uses.') + year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009) + year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009) + end + + # Get the ordered list of all the day schedules + day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date) + + # Get the array of which schedule is used on each day of the year + day_schs_used_each_day = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date) + + # Create a map that shows how many days each schedule is used + day_sch_freq = day_schs_used_each_day.group_by { |n| n } + + # Build a hash that maps schedule day index to schedule day + schedule_index_to_day = {} + day_schs.each_with_index do |day_sch, i| + schedule_index_to_day[day_schs_used_each_day[i]] = day_sch + end + + # Loop through each of the schedules that is used, figure out the + # full load hours for that day, then multiply this by the number + # of days that day schedule applies and add this to the total. + annual_flh = 0.0 + max_daily_flh = 0.0 + default_day_sch = schedule_ruleset.defaultDaySchedule + day_sch_freq.each do |freq| + sch_index = freq[0] + number_of_days_sch_used = freq[1].size + + # Get the day schedule at this index + day_sch = nil + if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule) + day_sch = default_day_sch + else + day_sch = schedule_index_to_day[sch_index] + end + daily_flh = OpenstudioStandards::Schedules.schedule_day_get_equivalent_full_load_hours(day_sch) + + # Multiply the daily EFLH by the number + # of days this schedule is used per year + # and add this to the overall total + annual_flh += daily_flh * number_of_days_sch_used + end + + # Warn if the max daily EFLH is more than 24, + # which would indicate that this isn't a fractional schedule. + if max_daily_flh > 24 + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', "#{schedule_ruleset.name} has more than 24 EFLH in one day schedule, indicating that it is not a fractional schedule.") + end + + return annual_flh + end + + # Returns an array of average hourly values from a ScheduleRuleset object + # Returns 8760 values, 8784 for leap years. + # + # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object + # @return [Array<Double>] Array of hourly values for the year + def self.schedule_ruleset_get_hourly_values(schedule_ruleset) + # validate schedule + unless schedule_ruleset.to_ScheduleRuleset.is_initialized + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_hourly_values() failed because object #{schedule_ruleset} is not a ScheduleRuleset.") + return nil + end + + # define the start and end date + year_start_date = nil + year_end_date = nil + if schedule_ruleset.model.yearDescription.is_initialized + year_description = schedule_ruleset.model.yearDescription.get + year = year_description.assumedYear + year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) + year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) + else + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', 'Year description is not specified. Annual hours above value calculation will assume 2009, the default year OS uses.') + year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009) + year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009) + end + + # Get the ordered list of all the day schedules + day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date) + + # Loop through each day schedule and add its hours to total + # @todo store the 24 hourly average values for each day schedule instead of recalculating for all days + annual_hourly_values = [] + day_schs.each do |day_sch| + # add daily average hourly values to annual hourly values array + daily_hours = OpenstudioStandards::Schedules.schedule_day_get_hourly_values(day_sch) + daily_hours.each { |h| annual_hourly_values << h } + end + + return annual_hourly_values + end + + # Returns the total number of hours where the schedule is greater than the specified value. + # This method includes leap days on leap years. + # + # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object + # @param lower_limit [Double] the lower limit. Values equal to the limit will not be counted. + # @return [Double] The total number of hours this schedule is above the specified value. + def self.schedule_ruleset_get_hours_above_value(schedule_ruleset, lower_limit) + # validate schedule + unless schedule_ruleset.to_ScheduleRuleset.is_initialized + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_hours_above_value() failed because object #{schedule_ruleset} is not a ScheduleRuleset.") + return nil + end + + # define the start and end date + year_start_date = nil + year_end_date = nil + if schedule_ruleset.model.yearDescription.is_initialized + year_description = schedule_ruleset.model.yearDescription.get + year = year_description.assumedYear + year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) + year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) + else + OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', 'Year description is not specified. Annual hours above value calculation will assume 2009, the default year OS uses.') + year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009) + year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009) + end + + # Get the ordered list of all the day schedules + day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date) + + # Get the array of which schedule is used on each day of the year + day_schs_used_each_day = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date) + + # Create a map that shows how many days each schedule is used + day_sch_freq = day_schs_used_each_day.group_by { |n| n } + + # Build a hash that maps schedule day index to schedule day + schedule_index_to_day = {} + day_schs.each_with_index do |day_sch, i| + schedule_index_to_day[day_schs_used_each_day[i]] = day_sch + end + + # Loop through each of the schedules that is used, figure out the + # hours for that day, then multiply this by the number + # of days that day schedule applies and add this to the total. + annual_hrs = 0.0 + default_day_sch = schedule_ruleset.defaultDaySchedule + day_sch_freq.each do |freq| + sch_index = freq[0] + number_of_days_sch_used = freq[1].size + + # Get the day schedule at this index + day_sch = nil + day_sch = if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule) + default_day_sch + else + schedule_index_to_day[sch_index] + end + + # Determine the hours for just one day + daily_hrs = 0.0 + values = day_sch.values + times = day_sch.times + + previous_time_decimal = 0.0 + times.each_with_index do |time, i| + time_decimal = (time.days * 24.0) + time.hours + (time.minutes / 60.0) + (time.seconds / 3600.0) + duration_of_value = time_decimal - previous_time_decimal + if values[i] > lower_limit + daily_hrs += duration_of_value + end + previous_time_decimal = time_decimal + end + + # Multiply the daily hours by the number + # of days this schedule is used per year + # and add this to the overall total + annual_hrs += daily_hrs * number_of_days_sch_used + end + + return annual_hrs + end + # create OpenStudio TimeSeries object from ScheduleRuleset values # # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object # @return [OpenStudio::TimeSeries] OpenStudio TimeSeries object of schedule values def self.schedule_ruleset_get_timeseries(schedule_ruleset) + # validate schedule + unless schedule_ruleset.to_ScheduleRuleset.is_initialized + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_timeseries() failed because object #{schedule_ruleset} is not a ScheduleRuleset.") + return nil + end + yd = schedule_ruleset.model.getYearDescription start_date = yd.makeDate(1, 1) end_date = yd.makeDate(12, 31) values = OpenStudio::DoubleVector.new day = OpenStudio::Time.new(1.0) interval = OpenStudio::Time.new(1.0 / 48.0) - day_schedules = schedule_ruleset.to_ScheduleRuleset.get.getDaySchedules(start_date, end_date) + day_schedules = schedule_ruleset.getDaySchedules(start_date, end_date) day_schedules.each do |day_schedule| time = interval while time < day values << day_schedule.getValue(time) time += interval @@ -72,21 +737,20 @@ return timeseries end # Determine the hour when the schedule first exceeds the starting value and when # it goes back down to the ending value at the end of the day. - # This method only works for ScheduleRuleset schedules. # # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object # @return [Hash<OpenStudio:Time>] returns as hash with 'start_time', 'end time'] def self.schedule_ruleset_get_start_and_end_times(schedule_ruleset) - # Ensure that this is a ScheduleRuleset - schedule_ruleset = schedule_ruleset.to_ScheduleRuleset - return [nil, nil] if schedule_ruleset.empty? + # validate schedule + unless schedule_ruleset.to_ScheduleRuleset.is_initialized + OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_start_and_end_times() failed because object #{schedule_ruleset} is not a ScheduleRuleset.") + return [nil, nil] + end - schedule_ruleset = schedule_ruleset.get - # Define the start and end date if schedule_ruleset.model.yearDescription.is_initialized year_description = schedule_ruleset.model.yearDescription.get year = year_description.assumedYear year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) @@ -163,7 +827,69 @@ end end return { 'start_time' => start_time, 'end_time' => end_time } end + + # Returns the day schedules associated with a schedule ruleset + # Optionally includes summer and winter design days + # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object + # @param include_design_days [Bool] include summer and winter design day profiles + # Defaults to false + # @return [Array<OpenStudio::Model::ScheduleDay>] array of day schedules + def self.schedule_ruleset_get_day_schedules(schedule_ruleset, include_design_days: false) + profiles = [] + profiles << schedule_ruleset.defaultDaySchedule + schedule_ruleset.scheduleRules.each do |rule| + profiles << rule.daySchedule + end + + if include_design_days + + if schedule_ruleset.isSummerDesignDayScheduleDefaulted + OpenStudio.logFree(OpenStudio::Warning, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_day_schedules called for #{schedule_ruleset.name.get} with include_design_days: true, but the summer design day is defaulted. Duplicate design day will not be added.") + else + profiles << rule.summerDesignDaySchedule + end + + if schedule_ruleset.isWinterDesignDayScheduleDefaulted + OpenStudio.logFree(OpenStudio::Warning, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_day_schedules called for #{schedule_ruleset.name.get} with include_design_days: true, but the winter design day is defaulted. Duplicate design day will not be added.") + else + profiles << rule.winterDesignDaySchedule + end + + end + + return profiles + end + + # Return the annual days of year that covered by each rule of a schedule ruleset + # + # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object + # @return [Hash] hash of rule_index => [days_used]. Default day has rule_index = -1 + def self.schedule_ruleset_get_annual_days_used(schedule_ruleset) + year_description = schedule_ruleset.model.getYearDescription + year = year_description.assumedYear + year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) + year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) + sch_indices_vector = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date) + days_used_hash = Hash.new { |h, k| h[k] = [] } + sch_indices_vector.uniq.sort.each do |rule_i| + sch_indices_vector.each_with_index { |rule, i| days_used_hash[rule_i] << i + 1 if rule_i == rule } + end + return days_used_hash + end + + # Returns the rule indices associated with defaultDay and Rule days for a given ScheduleRuleset + # + # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object + # @return [Hash] hash of ScheduleDay => rule index. Default day has rule index of -1 + def self.schedule_ruleset_get_schedule_day_rule_indices(schedule_ruleset) + schedule_day_hash = {} + schedule_day_hash[schedule_ruleset.defaultDaySchedule] = -1 + schedule_ruleset.scheduleRules.each { |rule| schedule_day_hash[rule.daySchedule] = rule.ruleIndex } + return schedule_day_hash + end + + # @!endgroup Information:ScheduleRuleset end end