lib/openstudio-standards/schedules/modify.rb in openstudio-standards-0.5.0 vs lib/openstudio-standards/schedules/modify.rb in openstudio-standards-0.6.0.rc1
- old
+ new
@@ -1,22 +1,173 @@
-# Methods to modify existing Schedule objects
module OpenstudioStandards
+ # The Schedules module provides methods to create, modify, and get information about Schedule objects
module Schedules
- # @!group Modify
+ # Methods to modify existing Schedule objects
- # Increase/decrease by percentage or static value
+ # @!group Modify:ScheduleDay
+
+ # Method to multiply the values in a day schedule by a specified value
+ # The method can optionally apply the multiplier to only values above a lower limit.
+ # This limit prevents multipliers for things like occupancy sensors from affecting unoccupied hours.
#
+ # @param schedule_day [OpenStudio::Model::ScheduleDay] OpenStudio ScheduleDay object
+ # @param multiplier [Double] value to multiply schedule values by
+ # @param lower_apply_limit [Double] apply the multiplier to only values above this value
+ # @return [OpenStudio::Model::ScheduleDay] OpenStudio ScheduleDay object
+ def self.schedule_day_multiply_by_value(schedule_day, multiplier, lower_apply_limit: nil)
+ # Record the original times and values
+ times = schedule_day.times
+ values = schedule_day.values
+
+ # Remove the original times and values
+ schedule_day.clearValues
+
+ # Create new values by using the multiplier on the original values
+ new_values = []
+ values.each do |value|
+ if lower_apply_limit.nil?
+ new_values << value * multiplier
+ else
+ if value > lower_apply_limit
+ new_values << value * multiplier
+ else
+ new_values << value
+ end
+ end
+ end
+
+ # Add the revised time/value pairs to the schedule
+ new_values.each_with_index do |new_value, i|
+ schedule_day.addValue(times[i], new_value)
+ end
+
+ return schedule_day
+ end
+
+ # Set the hours of operation (0 or 1) for a ScheduleDay.
+ # Clears out existing time/value pairs and sets to supplied values.
+ #
+ # @author Andrew Parker
+ # @param schedule_day [OpenStudio::Model::ScheduleDay] The day schedule to set.
+ # @param start_time [OpenStudio::Time] Start time.
+ # @param end_time [OpenStudio::Time] End time. If greater than 24:00, hours of operation will wrap over midnight.
+ #
+ # @return [Void]
+ # @api private
+ def self.schedule_day_set_hours_of_operation(schedule_day, start_time, end_time)
+ schedule_day.clearValues
+ twenty_four_hours = OpenStudio::Time.new(0, 24, 0, 0)
+ if end_time < twenty_four_hours
+ # Operating hours don't wrap over midnight
+ schedule_day.addValue(start_time, 0) # 0 until start time
+ schedule_day.addValue(end_time, 1) # 1 from start time until end time
+ schedule_day.addValue(twenty_four_hours, 0) # 0 after end time
+ else
+ # Operating hours start on previous day
+ schedule_day.addValue(end_time - twenty_four_hours, 1) # 1 for hours started on the previous day
+ schedule_day.addValue(start_time, 0) # 0 from end of previous days hours until start of today's
+ schedule_day.addValue(twenty_four_hours, 1) # 1 from start of today's hours until midnight
+ end
+ end
+
+ # Sets the values of a day schedule from an array of values
+ # Clears out existing time value pairs and sets to supplied values
+ #
+ # @param schedule_day [OpenStudio::Model::ScheduleDay] The day schedule to set.
+ # @param value_array [Array] Array of 24 values. Schedule times set based on value index. Identical values will be skipped.
+ # @return [OpenStudio::Model::ScheduleDay]
+ def self.schedule_day_populate_from_array_of_values(schedule_day, value_array)
+ schedule_day.clearValues
+ if value_array.size != 24
+ OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Modify', "#{__method__} expects value_array to contain 24 values, instead #{value_array.size} values were given. Resulting schedule will use first #{[24, value_array.size].min} values")
+ end
+
+ value_array[0..23].each_with_index do |value, h|
+ next if value == value_array[h + 1]
+
+ time = OpenStudio::Time.new(0, h + 1, 0, 0)
+ schedule_day.addValue(time, value)
+ end
+ return schedule_day
+ end
+
+ # @!endgroup Modify:ScheduleDay
+
+ # @!group Modify:ScheduleRuleset
+
+ # Add a ScheduleRule to a ScheduleRuleset object from an array of hourly values
+ #
# @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object
+ # @param start_date [OpenStudio::Date] start date of week period
+ # @param end_date [OpenStudio::Date] end date of week period
+ # @param day_names [Array<String>] list of days of week for which this day type is applicable
+ # @param values [Array<Double>] array of 24 hourly values for a day
+ # @param rule_name [String] rule ScheduleDay object name
+ # @return [OpenStudio::Model::ScheduleRule] OpenStudio ScheduleRule object
+ def self.schedule_ruleset_add_rule(schedule_ruleset, values,
+ start_date: nil,
+ end_date: nil,
+ day_names: nil,
+ rule_name: nil)
+ # create new schedule rule
+ sch_rule = OpenStudio::Model::ScheduleRule.new(schedule_ruleset)
+ day_sch = sch_rule.daySchedule
+ day_sch.setName(rule_name) unless rule_name.nil?
+
+ # set the dates when the rule applies
+ sch_rule.setStartDate(start_date) unless start_date.nil?
+ sch_rule.setEndDate(end_date) unless end_date.nil?
+
+ # set the days for which the rule applies
+ unless day_names.nil?
+ day_names.each do |day_of_week|
+ sch_rule.setApplySunday(true) if day_of_week == 'Sunday'
+ sch_rule.setApplyMonday(true) if day_of_week == 'Monday'
+ sch_rule.setApplyTuesday(true) if day_of_week == 'Tuesday'
+ sch_rule.setApplyWednesday(true) if day_of_week == 'Wednesday'
+ sch_rule.setApplyThursday(true) if day_of_week == 'Thursday'
+ sch_rule.setApplyFriday(true) if day_of_week == 'Friday'
+ sch_rule.setApplySaturday(true) if day_of_week == 'Saturday'
+ end
+ end
+
+ # Create the day schedule and add hourly values
+ (0..23).each do |ihr|
+ next if values[ihr] == values[ihr + 1]
+
+ day_sch.addValue(OpenStudio::Time.new(0, ihr + 1, 0, 0), values[ihr])
+ end
+
+ return sch_rule
+ end
+
+ # Increase/decrease by percentage or static value.
+ # If the schedule has a scheduleTypeLimits object, the adjusted values will subject to the lower and upper bounds of the schedule type limits object.
+ #
+ # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object
# @param value [Double] Hash of name and time value pairs
# @param modification_type [String] Options are 'Multiplier', which multiples by the value,
# and 'Sum' which adds by the value
# @return [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object
# @todo add in design day adjustments, maybe as an optional argument
# @todo provide option to clone existing schedule
def self.schedule_ruleset_simple_value_adjust(schedule_ruleset, value, modification_type = 'Multiplier')
# gather profiles
profiles = []
+ # positive infinity
+ upper_bound = Float::INFINITY
+ # negative infinity
+ lower_bound = -upper_bound
+ if schedule_ruleset.scheduleTypeLimits.is_initialized
+ schedule_type_limits = schedule_ruleset.scheduleTypeLimits.get
+ if schedule_type_limits.lowerLimitValue.is_initialized
+ lower_bound = schedule_type_limits.lowerLimitValue.get
+ end
+ if schedule_type_limits.upperLimitValue.is_initialized
+ upper_bound = schedule_type_limits.upperLimitValue.get
+ end
+ end
default_profile = schedule_ruleset.to_ScheduleRuleset.get.defaultDaySchedule
profiles << default_profile
rules = schedule_ruleset.scheduleRules
rules.each do |rule|
profiles << rule.daySchedule
@@ -28,14 +179,16 @@
i = 0
profile.values.each do |sch_value|
case modification_type
when 'Multiplier', 'Percentage'
# percentage was used early on but Multiplier is preferable
- profile.addValue(times[i], sch_value * value)
+ new_value = [lower_bound, [upper_bound, sch_value * value].min].max
+ profile.addValue(times[i], new_value)
when 'Sum', 'Value'
# value was used early on but Sum is preferable
- profile.addValue(times[i], sch_value + value)
+ new_value = [lower_bound, [upper_bound, sch_value + value].min].max
+ profile.addValue(times[i], new_value)
end
i += 1
end
end
@@ -226,17 +379,15 @@
# array of all profiles to change
profiles = []
# push default profiles to array
if options['default']
- default_rule = schedule.defaultDaySchedule
- profiles << default_rule
+ profiles << schedule.defaultDaySchedule
end
# push profiles to array
- rules = schedule.scheduleRules
- rules.each do |rule|
+ schedule.scheduleRules.each do |rule|
day_sch = rule.daySchedule
# if any day requested also exists in the rule, then it will be altered
alter_rule = false
if rule.applyMonday && rule.applyMonday == options['mon'] then alter_rule = true end
@@ -254,16 +405,14 @@
end
end
# add design days to array
if options['summer']
- summer_design = schedule.summerDesignDaySchedule
- profiles << summer_design
+ profiles << schedule.summerDesignDaySchedule
end
if options['winter']
- winter_design = schedule.winterDesignDaySchedule
- profiles << winter_design
+ profiles << schedule.winterDesignDaySchedule
end
# give info messages as I change specific profiles
OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Schedules.Modify', "Adjusting #{schedule.name}")
@@ -439,7 +588,153 @@
end
end
return schedule
end
+
+ # Remove unused profiles and set most prevalent profile as default.
+ # This method expands on the functionality of the RemoveUnusedDefaultProfiles measure.
+ #
+ # @author David Goldwasser
+ # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object
+ # @return [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object
+ # @todo There are potential issues with overlapping rule dates or days of week when setting a profile that isn't the lowest priority as the default day.
+ def self.schedule_ruleset_cleanup_profiles(schedule_ruleset)
+ # set start and end dates
+ 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)
+
+ indices_vector = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date)
+ most_frequent_item = indices_vector.uniq.max_by { |i| indices_vector.count(i) }
+ rule_vector = schedule_ruleset.scheduleRules
+
+ replace_existing_default = false
+ if indices_vector.include?(-1) && (most_frequent_item != -1)
+ # clean up if default isn't most common (e.g. sunday vs. weekday)
+ # if no existing rules cover specific days of week, make new rule from default covering those days of week
+ possible_days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
+ used_days_of_week = []
+ rule_vector.each do |rule|
+ if rule.applyMonday then used_days_of_week << 'Monday' end
+ if rule.applyTuesday then used_days_of_week << 'Tuesday' end
+ if rule.applyWednesday then used_days_of_week << 'Wednesday' end
+ if rule.applyThursday then used_days_of_week << 'Thursday' end
+ if rule.applyFriday then used_days_of_week << 'Friday' end
+ if rule.applySaturday then used_days_of_week << 'Saturday' end
+ if rule.applySunday then used_days_of_week << 'Sunday' end
+ end
+ if used_days_of_week.uniq.size < possible_days_of_week.size
+ replace_existing_default = true
+ schedule_rule_new = OpenStudio::Model::ScheduleRule.new(schedule_ruleset, schedule_ruleset.defaultDaySchedule)
+ if !used_days_of_week.include?('Monday') then schedule_rule_new.setApplyMonday(true) end
+ if !used_days_of_week.include?('Tuesday') then schedule_rule_new.setApplyTuesday(true) end
+ if !used_days_of_week.include?('Wednesday') then schedule_rule_new.setApplyWednesday(true) end
+ if !used_days_of_week.include?('Thursday') then schedule_rule_new.setApplyThursday(true) end
+ if !used_days_of_week.include?('Friday') then schedule_rule_new.setApplyFriday(true) end
+ if !used_days_of_week.include?('Saturday') then schedule_rule_new.setApplySaturday(true) end
+ if !used_days_of_week.include?('Sunday') then schedule_rule_new.setApplySunday(true) end
+ end
+ end
+
+ if !indices_vector.include?(-1) || replace_existing_default
+ OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Schedules.Modify', "#{schedule_ruleset.name} does not use the default profile, it will be replaced.")
+
+ # reset values in default ScheduleDay
+ old_default_schedule_day = schedule_ruleset.defaultDaySchedule
+ old_default_schedule_day.clearValues
+
+ # update selection to the most commonly used profile vs. the lowest priority, if it can be done without any conflicts
+ # safe test is to see if any other rules use same days of week as most common,
+ # if doesn't pass then make highest rule the new default to avoid any problems. School may not pass this test, woudl use last rule
+ days_of_week_most_frequent_item = []
+ schedule_rule_most_frequent = rule_vector[most_frequent_item]
+ if schedule_rule_most_frequent.applyMonday then days_of_week_most_frequent_item << 'Monday' end
+ if schedule_rule_most_frequent.applyTuesday then days_of_week_most_frequent_item << 'Tuesday' end
+ if schedule_rule_most_frequent.applyWednesday then days_of_week_most_frequent_item << 'Wednesday' end
+ if schedule_rule_most_frequent.applyThursday then days_of_week_most_frequent_item << 'Thursday' end
+ if schedule_rule_most_frequent.applyFriday then days_of_week_most_frequent_item << 'Friday' end
+ if schedule_rule_most_frequent.applySaturday then days_of_week_most_frequent_item << 'Saturday' end
+ if schedule_rule_most_frequent.applySunday then days_of_week_most_frequent_item << 'Sunday' end
+
+ # loop through rules
+ conflict_found = false
+ rule_vector.each do |rule|
+ next if rule == schedule_rule_most_frequent
+
+ days_of_week_most_frequent_item.each do |day_of_week|
+ if (day_of_week == 'Monday') && rule.applyMonday then conflict_found == true end
+ if (day_of_week == 'Tuesday') && rule.applyTuesday then conflict_found == true end
+ if (day_of_week == 'Wednesday') && rule.applyWednesday then conflict_found == true end
+ if (day_of_week == 'Thursday') && rule.applyThursday then conflict_found == true end
+ if (day_of_week == 'Friday') && rule.applyFriday then conflict_found == true end
+ if (day_of_week == 'Saturday') && rule.applySaturday then conflict_found == true end
+ if (day_of_week == 'Sunday') && rule.applySunday then conflict_found == true end
+ end
+ end
+ if conflict_found
+ new_default_index = indices_vector.max
+ else
+ new_default_index = most_frequent_item
+ end
+
+ # get values for new default profile
+ new_default_day_schedule = rule_vector[new_default_index].daySchedule
+ new_default_day_schedule_values = new_default_day_schedule.values
+ new_default_day_schedule_times = new_default_day_schedule.times
+
+ # update values and times for default profile
+ for i in 0..(new_default_day_schedule_values.size - 1)
+ old_default_schedule_day.addValue(new_default_day_schedule_times[i], new_default_day_schedule_values[i])
+ end
+
+ # remove rule object that has become the default. Also try to remove the ScheduleDay
+ rule_vector[new_default_index].remove # this seems to also remove the ScheduleDay associated with the rule
+ end
+
+ return schedule_ruleset
+ end
+
+ # creates a minimal set of ScheduleRules that applies to all days in a given array of day of year indices
+ #
+ # @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset]
+ # @param days_used [Array] array of day of year integers
+ # @param schedule_day [OpenStudio::Model::ScheduleDay] optional day schedule to apply to new rule. A new default schedule will be created for each rule if nil
+ # @return [Array]
+ def self.schedule_ruleset_create_rules_from_day_list(schedule_ruleset, days_used, schedule_day: nil)
+ # get year from schedule_ruleset
+ year = schedule_ruleset.model.getYearDescription.assumedYear
+
+ # split day_used into sub arrays of consecutive days
+ consec_days = days_used.chunk_while { |i, j| i + 1 == j }.to_a
+
+ # split consec_days into sub arrays of consecutive weeks by checking that any value in next array differs by seven from a value in this array
+ consec_weeks = consec_days.chunk_while { |i, j| i.product(j).any? { |x, y| (x - y).abs == 7 } }.to_a
+
+ # make new rule for blocks of consectutive weeks
+ rules = []
+ consec_weeks.each do |week_group|
+ if schedule_day.nil?
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.ScheduleRuleset', 'Creating new Rule Schedule from days_used vector with new Day Schedule')
+ rule = OpenStudio::Model::ScheduleRule.new(schedule_ruleset)
+ else
+ OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.ScheduleRuleset', "Creating new Rule Schedule from days_used vector with clone of Day Schedule: #{schedule_day.name.get}")
+ rule = OpenStudio::Model::ScheduleRule.new(schedule_ruleset, schedule_day)
+ end
+
+ # set day types and dates
+ dates = week_group.flatten.map { |d| OpenStudio::Date.fromDayOfYear(d, year) }
+ day_types = dates.map { |date| date.dayOfWeek.valueName }.uniq
+ day_types.each { |type| rule.send("setApply#{type}", true) }
+ rule.setStartDate(dates.min)
+ rule.setEndDate(dates.max)
+
+ rules << rule
+ end
+
+ return rules
+ end
+
+ # @!endgroup Modify:ScheduleRuleset
end
end