module OpenstudioStandards::Schedules
The Schedules
module provides methods to create, modify, and get information about Schedule objects
The Schedules
module provides methods to create, modify, and get information about Schedule objects
The Schedules
module provides methods to create, modify, and get information about Schedule objects
Public Class Methods
create a ruleset schedule with a complex profile
@param model [OpenStudio::Model::Model] OpenStudio model object @param options [Hash] Hash
of name and time value pairs @return [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object
# File lib/openstudio-standards/schedules/create.rb, line 208 def self.create_complex_schedule(model, options = {}) defaults = { 'name' => nil, 'default_day' => ['always_on', [24.0, 1.0]] } # merge user inputs with defaults options = defaults.merge(options) # ScheduleRuleset sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(model) if name sch_ruleset.setName(options['name']) end # Winter Design Day unless options['winter_design_day'].nil? sch_ruleset.setWinterDesignDaySchedule(sch_ruleset.winterDesignDaySchedule) winter_dsn_day = sch_ruleset.winterDesignDaySchedule winter_dsn_day.setName("#{sch_ruleset.name} Winter Design Day") options['winter_design_day'].each do |data_pair| hour = data_pair[0].truncate min = ((data_pair[0] - hour) * 60).to_i winter_dsn_day.addValue(OpenStudio::Time.new(0, hour, min, 0), data_pair[1]) end end # Summer Design Day unless options['summer_design_day'].nil? sch_ruleset.setSummerDesignDaySchedule(sch_ruleset.summerDesignDaySchedule) summer_dsn_day = sch_ruleset.summerDesignDaySchedule summer_dsn_day.setName("#{sch_ruleset.name} Summer Design Day") options['summer_design_day'].each do |data_pair| hour = data_pair[0].truncate min = ((data_pair[0] - hour) * 60).to_i summer_dsn_day.addValue(OpenStudio::Time.new(0, hour, min, 0), data_pair[1]) end end # Default Day default_day = sch_ruleset.defaultDaySchedule default_day.setName("#{sch_ruleset.name} #{options['default_day'][0]}") default_data_array = options['default_day'] default_data_array.delete_at(0) default_data_array.each do |data_pair| hour = data_pair[0].truncate min = ((data_pair[0] - hour) * 60).to_i default_day.addValue(OpenStudio::Time.new(0, hour, min, 0), data_pair[1]) end # Rules unless options['rules'].nil? options['rules'].each do |data_array| rule = OpenStudio::Model::ScheduleRule.new(sch_ruleset) rule.setName("#{sch_ruleset.name} #{data_array[0]} Rule") date_range = data_array[1].split('-') start_date = date_range[0].split('/') end_date = date_range[1].split('/') rule.setStartDate(model.getYearDescription.makeDate(start_date[0].to_i, start_date[1].to_i)) rule.setEndDate(model.getYearDescription.makeDate(end_date[0].to_i, end_date[1].to_i)) days = data_array[2].split('/') rule.setApplySunday(true) if days.include? 'Sun' rule.setApplyMonday(true) if days.include? 'Mon' rule.setApplyTuesday(true) if days.include? 'Tue' rule.setApplyWednesday(true) if days.include? 'Wed' rule.setApplyThursday(true) if days.include? 'Thu' rule.setApplyFriday(true) if days.include? 'Fri' rule.setApplySaturday(true) if days.include? 'Sat' day_schedule = rule.daySchedule day_schedule.setName("#{sch_ruleset.name} #{data_array[0]}") data_array.delete_at(0) data_array.delete_at(0) data_array.delete_at(0) data_array.each do |data_pair| hour = data_pair[0].truncate min = ((data_pair[0] - hour) * 60).to_i day_schedule.addValue(OpenStudio::Time.new(0, hour, min, 0), data_pair[1]) end end end return sch_ruleset end
Create constant ScheduleRuleset with a given value
@param model [OpenStudio::Model::Model] OpenStudio model object @param value [Double] the value to use, 24-7, 365 @param name [String] the name of the schedule @param schedule_type_limit [String] the name of a schedule type limit
options are Dimensionless, Temperature, Humidity Ratio, Fraction, Fractional, OnOff, and Activity
@return [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object
# File lib/openstudio-standards/schedules/create.rb, line 113 def self.create_constant_schedule_ruleset(model, value, name: nil, schedule_type_limit: nil) # check to see if schedule exists with same name and constant value and return if true unless name.nil? existing_sch = model.getScheduleRulesetByName(name) if existing_sch.is_initialized existing_sch = existing_sch.get existing_day_sch_vals = existing_sch.defaultDaySchedule.values if existing_day_sch_vals.size == 1 && (existing_day_sch_vals[0] - value).abs < 1.0e-6 return existing_sch end end end # create ScheduleRuleset schedule = OpenStudio::Model::ScheduleRuleset.new(model) schedule.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), value) # set name unless name.nil? schedule.setName(name) schedule.defaultDaySchedule.setName("#{name} Default") end # set schedule type limits if !schedule_type_limit.nil? sch_type_limits_obj = OpenstudioStandards::Schedules.create_schedule_type_limits(model, standard_schedule_type_limit: schedule_type_limit) schedule.setScheduleTypeLimits(sch_type_limits_obj) end return schedule end
Create a ScheduleDay from another ScheduleDay with inverted values
@param old_schedule_day [OpenStudio::Model::ScheduleDay] OpenStudio ScheduleDay object to invert @param new_schedule_day [OpenStudio::Model::ScheduleDay] An OpenStudio ScheduleDay object.
Default nil. If provided, will add values to this ScheduleDay object instead of creating a new one.
@param schedule_name [String] Optional name of new schedule @return [OpenStudio::Model::ScheduleDay] OpenStudio ScheduleDay object of inverted schedule
# File lib/openstudio-standards/schedules/create.rb, line 510 def self.create_inverted_schedule_day(old_schedule_day, new_schedule_day: nil, schedule_name: nil) # create new schedule object if none provided if new_schedule_day.nil? new_schedule_day = OpenStudio::Model::ScheduleDay.new(old_schedule_day.model) end # set default name if none provided if schedule_name.nil? new_schedule_day.setName("#{old_schedule_day.name} inverted") else new_schedule_day.setName(schedule_name) end # invert schedule values for index in 0..old_schedule_day.times.size - 1 old_value = old_schedule_day.values[index] if old_value == 0 new_value = 1 else new_value = 0 end new_schedule_day.addValue(old_schedule_day.times[index], new_value) end return new_schedule_day end
Create a ScheduleRuleset from another ScheduleRuleset with inverted values
@param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object to invert @param schedule_name [String] Optional name of new schedule @return [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object of inverted schedule
# File lib/openstudio-standards/schedules/create.rb, line 542 def self.create_inverted_schedule_ruleset(schedule_ruleset, schedule_name: nil) model = schedule_ruleset.model new_schedule = OpenStudio::Model::ScheduleRuleset.new(model, 0.0) # set default name if none provided if schedule_name.nil? new_schedule.setName("#{schedule_ruleset.name} inverted") else new_schedule.setName(schedule_name) end # change summer design day new_summer_dd_schedule = OpenstudioStandards::Schedules.create_inverted_schedule_day(schedule_ruleset.summerDesignDaySchedule) new_schedule.setSummerDesignDaySchedule(new_summer_dd_schedule) # change winter design day new_winter_dd_schedule = OpenstudioStandards::Schedules.create_inverted_schedule_day(schedule_ruleset.winterDesignDaySchedule) new_schedule.setWinterDesignDaySchedule(new_winter_dd_schedule) # change the default day values OpenstudioStandards::Schedules.create_inverted_schedule_day(schedule_ruleset.defaultDaySchedule, new_schedule_day: new_schedule.defaultDaySchedule) # change for schedule rules schedule_ruleset.scheduleRules.each_with_index do |rule, i| old_schedule_day = rule.daySchedule new_schedule_day = OpenstudioStandards::Schedules.create_inverted_schedule_day(old_schedule_day) new_rule = OpenStudio::Model::ScheduleRule.new(new_schedule, new_schedule_day) new_rule.setName("#{new_schedule_day.name} Rule") new_rule.setApplySunday(rule.applySunday) new_rule.setApplyMonday(rule.applyMonday) new_rule.setApplyTuesday(rule.applyTuesday) new_rule.setApplyWednesday(rule.applyWednesday) new_rule.setApplyThursday(rule.applyThursday) new_rule.setApplyFriday(rule.applyFriday) new_rule.setApplySaturday(rule.applySaturday) end return new_schedule end
create a new schedule using absolute velocity of existing schedule
@param model [OpenStudio::Model::Model] OpenStudio model object @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object @return [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object @todo fix velocity so it isn’t fraction change per step, but per hour
(I need to count hours between times and divide value by this)
# File lib/openstudio-standards/schedules/create.rb, line 299 def self.create_schedule_from_rate_of_change(model, schedule_ruleset) # clone source schedule new_schedule = schedule_ruleset.clone(model) new_schedule.setName("#{schedule_ruleset.name} - Rate of Change") new_schedule = new_schedule.to_ScheduleRuleset.get # create array of all profiles to change. This includes summer, winter, default, and rules profiles = [] profiles << new_schedule.winterDesignDaySchedule profiles << new_schedule.summerDesignDaySchedule profiles << new_schedule.defaultDaySchedule # time values may need end_profile_time = OpenStudio::Time.new(0, 24, 0, 0) hour_bump_time = OpenStudio::Time.new(0, 1, 0, 0) one_hour_left_time = OpenStudio::Time.new(0, 23, 0, 0) rules = new_schedule.scheduleRules rules.each do |rule| profiles << rule.daySchedule end profiles.uniq.each do |profile| times = profile.times values = profile.values i = 0 values_intermediate = [] times_intermediate = [] until i == values.size if i == 0 values_intermediate << 0.0 if times[i] > hour_bump_time times_intermediate << (times[i] - hour_bump_time) if times[i + 1].nil? time_step_value = end_profile_time.hours + (end_profile_time.minutes / 60) - times[i].hours - (times[i].minutes / 60) else time_step_value = times[i + 1].hours + (times[i + 1].minutes / 60) - times[i].hours - (times[i].minutes / 60) end values_intermediate << ((values[i + 1].to_f - values[i].to_f).abs / (time_step_value * 2)) end times_intermediate << times[i] elsif i == (values.size - 1) if times[times.size - 2] < one_hour_left_time times_intermediate << (times[times.size - 2] + hour_bump_time) # this should be the second to last time time_step_value = times[i - 1].hours + (times[i - 1].minutes / 60) - times[i - 2].hours - (times[i - 2].minutes / 60) values_intermediate << ((values[i - 1].to_f - values[i - 2].to_f).abs / (time_step_value * 2)) end values_intermediate << 0.0 times_intermediate << times[i] # this should be the last time else # get value multiplier based on how many hours it is spread over time_step_value = times[i].hours + (times[i].minutes / 60) - times[i - 1].hours - (times[i - 1].minutes / 60) values_intermediate << ((values[i].to_f - values[i - 1].to_f).abs / time_step_value) times_intermediate << times[i] end i += 1 end # delete all profile values profile.clearValues i = 0 until i == times_intermediate.size if i == (times_intermediate.size - 1) profile.addValue(times_intermediate[i], values_intermediate[i].to_f) else profile.addValue(times_intermediate[i], values_intermediate[i].to_f) end i += 1 end end return new_schedule end
create a ScheduleTypeLimits object for a schedule
@param model [OpenStudio::Model::Model] OpenStudio model object @param standard_schedule_type_limit [String] the name of a standard schedule type limit with predefined limits
options are Dimensionless, Temperature, Humidity Ratio, Fraction, Fractional, OnOff, and Activity
@param name [String] the name of the schedule type limits @param lower_limit_value [double] the lower limit value for the schedule type @param upper_limit_value [double] the upper limit value for the schedule type @param numeric_type [String] the numeric type, options are Continuous or Discrete @param unit_type [String] the unit type, options are defined in EnergyPlus I/O reference @return [OpenStudio::Model::ScheduleTypeLimits] OpenStudio ScheduleTypeLimits object
# File lib/openstudio-standards/schedules/create.rb, line 18 def self.create_schedule_type_limits(model, standard_schedule_type_limit: nil, name: nil, lower_limit_value: nil, upper_limit_value: nil, numeric_type: nil, unit_type: nil) if standard_schedule_type_limit.nil? if lower_limit_value.nil? || upper_limit_value.nil? || numeric_type.nil? || unit_type.nil? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Create', 'If calling create_schedule_type_limits without a standard_schedule_type_limit, you must specify all properties of ScheduleTypeLimits.') return false end schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName(name) if !name.nil? schedule_type_limits.setLowerLimitValue(lower_limit_value) schedule_type_limits.setUpperLimitValue(upper_limit_value) schedule_type_limits.setNumericType(numeric_type) schedule_type_limits.setUnitType(unit_type) else schedule_type_limits = model.getScheduleTypeLimitsByName(standard_schedule_type_limit) if schedule_type_limits.empty? case standard_schedule_type_limit.downcase when 'dimensionless' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('Dimensionless') schedule_type_limits.setLowerLimitValue(0.0) schedule_type_limits.setUpperLimitValue(1000.0) schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('Dimensionless') when 'temperature' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('Temperature') schedule_type_limits.setLowerLimitValue(0.0) schedule_type_limits.setUpperLimitValue(100.0) schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('Temperature') when 'humidity ratio' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('Humidity Ratio') schedule_type_limits.setLowerLimitValue(0.0) schedule_type_limits.setUpperLimitValue(0.3) schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('Dimensionless') when 'fraction', 'fractional' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('Fraction') schedule_type_limits.setLowerLimitValue(0.0) schedule_type_limits.setUpperLimitValue(1.0) schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('Dimensionless') when 'onoff' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('OnOff') schedule_type_limits.setLowerLimitValue(0) schedule_type_limits.setUpperLimitValue(1) schedule_type_limits.setNumericType('Discrete') schedule_type_limits.setUnitType('Availability') when 'activity' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('Activity') schedule_type_limits.setLowerLimitValue(70.0) schedule_type_limits.setUpperLimitValue(1000.0) schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('ActivityLevel') else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Create', 'Invalid standard_schedule_type_limit for method create_schedule_type_limits.') return false end else schedule_type_limits = schedule_type_limits.get if schedule_type_limits.name.to_s.downcase == 'temperature' schedule_type_limits.resetLowerLimitValue schedule_type_limits.resetUpperLimitValue schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('Temperature') end end end return schedule_type_limits end
create a ruleset schedule with a basic profile
@param model [OpenStudio::Model::Model] OpenStudio model object @param options [Hash] Hash
of name and time value pairs @return [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object
# File lib/openstudio-standards/schedules/create.rb, line 154 def self.create_simple_schedule(model, options = {}) defaults = { 'name' => nil, 'winter_time_value_pairs' => { 24.0 => 0.0 }, 'summer_time_value_pairs' => { 24.0 => 1.0 }, 'default_time_value_pairs' => { 24.0 => 1.0 } } # merge user inputs with defaults options = defaults.merge(options) # ScheduleRuleset sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(model) if name sch_ruleset.setName(options['name']) end # Winter Design Day sch_ruleset.setWinterDesignDaySchedule(sch_ruleset.winterDesignDaySchedule) winter_dsn_day = sch_ruleset.winterDesignDaySchedule winter_dsn_day.setName("#{sch_ruleset.name} Winter Design Day") options['winter_time_value_pairs'].each do |k, v| hour = k.truncate min = ((k - hour) * 60).to_i winter_dsn_day.addValue(OpenStudio::Time.new(0, hour, min, 0), v) end # Summer Design Day sch_ruleset.setSummerDesignDaySchedule(sch_ruleset.summerDesignDaySchedule) summer_dsn_day = sch_ruleset.summerDesignDaySchedule summer_dsn_day.setName("#{sch_ruleset.name} Summer Design Day") options['summer_time_value_pairs'].each do |k, v| hour = k.truncate min = ((k - hour) * 60).to_i summer_dsn_day.addValue(OpenStudio::Time.new(0, hour, min, 0), v) end # All Days default_day = sch_ruleset.defaultDaySchedule default_day.setName("#{sch_ruleset.name} Schedule Week Day") options['default_time_value_pairs'].each do |k, v| hour = k.truncate min = ((k - hour) * 60).to_i default_day.addValue(OpenStudio::Time.new(0, hour, min, 0), v) end return sch_ruleset end
merge multiple schedules into one using load or other value to weight each schedules influence on the merge
@param model [OpenStudio::Model::Model] OpenStudio model object @param schedule_weights_hash [Hash] Hash
of OpenStudio::Model::ScheduleRuleset, Double @param sch_name [String] Optional name of new schedule @return [Hash] Hash
of merged schedule and the total denominator @todo apply weights to schedule rules as well, not just winter, summer, and default profile
# File lib/openstudio-standards/schedules/create.rb, line 382 def self.create_weighted_merge_schedules(model, schedule_weights_hash, sch_name: 'Merged Schedule') # get denominator for weight denominator = 0.0 schedule_weights_hash.each do |schedule, weight| denominator += weight end # create new schedule sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(model) sch_ruleset.setName(sch_name) # create winter design day profile winter_dsn_day = OpenStudio::Model::ScheduleDay.new(model) sch_ruleset.setWinterDesignDaySchedule(winter_dsn_day) winter_dsn_day = sch_ruleset.winterDesignDaySchedule winter_dsn_day.setName("#{sch_ruleset.name} Winter Design Day") # create summer design day profile summer_dsn_day = OpenStudio::Model::ScheduleDay.new(model) sch_ruleset.setSummerDesignDaySchedule(summer_dsn_day) summer_dsn_day = sch_ruleset.summerDesignDaySchedule summer_dsn_day.setName("#{sch_ruleset.name} Summer Design Day") # create default profile default_day = sch_ruleset.defaultDaySchedule default_day.setName("#{sch_ruleset.name} Schedule Week Day") # hash of schedule rules rules_hash = {} # mon, tue, wed, thur, fri, sat, sun, startDate, endDate # to avoid stacking order issues across schedules, I may need to make a rule for each day of the week for each date range schedule_weights_hash.each do |schedule, weight| # populate winter design day profile old_winter_profile = schedule.to_ScheduleRuleset.get.winterDesignDaySchedule times_final = summer_dsn_day.times i = 0 value_updated_array = [] # loop through times already in profile and update values until i > times_final.size - 1 value = old_winter_profile.getValue(times_final[i]) * weight / denominator starting_value = winter_dsn_day.getValue(times_final[i]) winter_dsn_day.addValue(times_final[i], value + starting_value) value_updated_array << times_final[i] i += 1 end # loop through any new times unique to the current old profile to be merged j = 0 times = old_winter_profile.times values = old_winter_profile.values until j > times.size - 1 unless value_updated_array.include? times[j] value = values[j] * weight / denominator starting_value = winter_dsn_day.getValue(times[j]) winter_dsn_day.addValue(times[j], value + starting_value) end j += 1 end # populate summer design day profile old_summer_profile = schedule.to_ScheduleRuleset.get.summerDesignDaySchedule times_final = summer_dsn_day.times i = 0 value_updated_array = [] # loop through times already in profile and update values until i > times_final.size - 1 value = old_summer_profile.getValue(times_final[i]) * weight / denominator starting_value = summer_dsn_day.getValue(times_final[i]) summer_dsn_day.addValue(times_final[i], value + starting_value) value_updated_array << times_final[i] i += 1 end # loop through any new times unique to the current old profile to be merged j = 0 times = old_summer_profile.times values = old_summer_profile.values until j > times.size - 1 unless value_updated_array.include? times[j] value = values[j] * weight / denominator starting_value = summer_dsn_day.getValue(times[j]) summer_dsn_day.addValue(times[j], value + starting_value) end j += 1 end # populate default profile old_default_profile = schedule.to_ScheduleRuleset.get.defaultDaySchedule times_final = default_day.times i = 0 value_updated_array = [] # loop through times already in profile and update values until i > times_final.size - 1 value = old_default_profile.getValue(times_final[i]) * weight / denominator starting_value = default_day.getValue(times_final[i]) default_day.addValue(times_final[i], value + starting_value) value_updated_array << times_final[i] i += 1 end # loop through any new times unique to the current old profile to be merged j = 0 times = old_default_profile.times values = old_default_profile.values until j > times.size - 1 unless value_updated_array.include? times[j] value = values[j] * weight / denominator starting_value = default_day.getValue(times[j]) default_day.addValue(times[j], value + starting_value) end j += 1 end # create rules # gather data for rule profiles # populate rule profiles end result = { 'mergedSchedule' => sch_ruleset, 'denominator' => denominator } return result end
This method applies the hours of operation for a space and the load profile formulas in the overloaded ScheduleRulset objects to update time value pairs for ScheduleDay objects. Object
type specific logic will be used to generate profiles for summer and winter design days.
@note This measure will replace any prior chagnes made to ScheduleRule objects with new ScheduleRule values from profile formulas @author David Goldwasser @param model [OpenStudio::Model::Model] OpenStudio Model object @param ramp_frequency [Double] ramp frequency in minutes. If nil method will match simulation timestep @param infer_hoo_for_non_assigned_objects [Boolean] # attempt to get hoo for objects like swh with and exterior lighting @param error_on_out_of_order [Boolean] true will error if applying formula creates out of order values @return [Array] of modified ScheduleRuleset objects
# File lib/openstudio-standards/schedules/parametric.rb, line 348 def self.model_apply_parametric_schedules(model, ramp_frequency: nil, infer_hoo_for_non_assigned_objects: true, error_on_out_of_order: true) # get ramp frequency (fractional hour) from timestep if ramp_frequency.nil? steps_per_hour = if model.getSimulationControl.timestep.is_initialized model.getSimulationControl.timestep.get.numberOfTimestepsPerHour else 6 # default OpenStudio timestep if none specified end ramp_frequency = 1.0 / steps_per_hour.to_f end # Go through model and create parametric formulas for all schedules parametric_inputs = OpenstudioStandards::Schedules.model_setup_parametric_schedules(model, gather_data_only: true) parametric_schedules = [] model.getScheduleRulesets.sort.each do |sch| if !sch.hasAdditionalProperties || !sch.additionalProperties.hasFeature('param_sch_ver') # for now don't look at schedules without targets, in future can alter these by looking at building level hours of operation next if sch.directUseCount <= 0 # won't catch if used for space type load instance, but that space type isn't used # @todo address schedules that fall into this category, if they are used in the model OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.Model', "For #{sch.sources.first.name}, #{sch.name} is not setup as parametric schedule. It has #{sch.sources.size} sources.") next end # apply parametric inputs OpenstudioStandards::Schedules.schedule_ruleset_apply_parametric_inputs(sch, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order, parametric_inputs) # add schedule to array parametric_schedules << sch end return parametric_schedules end
Get the predominant air loop HVAC
schedule in the model by floor area served.
@param model [OpenStudio::Model::Model] OpenStudio model object @return [OpenStudio::Model::Schedule] OpenStudio Schedule object
# File lib/openstudio-standards/schedules/information.rb, line 921 def self.model_get_hvac_schedule(model) # lookup from model, using largest air loop # check multiple kinds of systems, including unitary systems hvac_schedule = nil largest_area = 0.0 model.getAirLoopHVACs.each do |air_loop| air_loop_area = 0.0 air_loop.thermalZones.each { |tz| air_loop_area += tz.floorArea } if air_loop_area > largest_area hvac_schedule = air_loop.availabilitySchedule largest_area = air_loop_area end end model.getAirLoopHVACUnitarySystems.each do |unitary| next unless unitary.thermalZone.is_initialized air_loop_area = unitary.thermalZone.get.floorArea if air_loop_area > largest_area if unitary.availabilitySchedule.is_initialized hvac_schedule = unitary.availabilitySchedule.get else hvac_schedule = model.alwaysOnDiscreteSchedule end largest_area = air_loop_area end end model.getAirLoopHVACUnitaryHeatPumpAirToAirs.each do |unitary| next unless unitary.controllingZone.is_initialized air_loop_area = unitary.controllingZone.get.floorArea if air_loop_area > largest_area hvac_schedule = unitary.availabilitySchedule.get largest_area = air_loop_area end end model.getAirLoopHVACUnitaryHeatPumpAirToAirMultiSpeeds.each do |unitary| next unless unitary.controllingZoneorThermostatLocation.is_initialized air_loop_area = unitary.controllingZoneorThermostatLocation.get.floorArea if air_loop_area > largest_area if unitary.availabilitySchedule.is_initialized hvac_schedule = unitary.availabilitySchedule.get else hvac_schedule = model.alwaysOnDiscreteSchedule end largest_area = air_loop_area end end model.getFanZoneExhausts.each do |fan| next unless fan.thermalZone.is_initialized air_loop_area = fan.thermalZone.get.floorArea if air_loop_area > largest_area if fan.availabilitySchedule.is_initialized hvac_schedule = fan.availabilitySchedule.get else hvac_schedule = model.alwaysOnDiscreteSchedule end largest_area = air_loop_area end end building_area = model.getBuilding.floorArea if largest_area < 0.05 * building_area OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Schedules', "The largest airloop or HVAC system serves #{largest_area.round(1)} m^2, which is less than 5% of the building area #{building_area.round(1)} m^2. Attempting to use building hours of operation schedule instead.") default_schedule_set = model.getBuilding.defaultScheduleSet if default_schedule_set.is_initialized default_schedule_set = default_schedule_set.get hoo = default_schedule_set.hoursofOperationSchedule if hoo.is_initialized hvac_schedule = hoo.get largest_area = building_area else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules', 'Unable to determine building hours of operation schedule. Treating the building as if there is no HVAC system schedule.') hvac_schedule = nil end else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules', 'Unable to determine building hours of operation schedule. Treating the building as if there is no HVAC system schedule.') hvac_schedule = nil end end unless hvac_schedule.nil? area_fraction = 100.0 * largest_area / building_area OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Schedules', "Using schedule #{hvac_schedule.name} serving area #{largest_area.round(1)} m^2, #{area_fraction.round(0)}% of building area #{building_area.round(1)} m^2 as the building HVAC operation schedule.") end return hvac_schedule end
This method looks at occupancy profiles for the building as a whole and generates an hours of operation default schedule for the building. It also clears out any higher level hours of operation schedule assignments. Spaces are organized by res and non_res. Whichever of the two groups has higher design level of people is used for building hours of operation Resulting hours of operation can have as many rules as necessary to describe the operation. Each ScheduleDay should be an on/off schedule with only values of 0 and 1. There should not be more than one on/off cycle per day. In future this could create different hours of operation for residential vs. non-residential, by building type, story, or space type. However this measure is a stop gap to convert old generic schedules to parametric schedules. Future new schedules should be designed as paramtric from the start and would not need to run through this inference process
@author David Goldwasser @param model [OpenStudio::Model::Model] OpenStudio model object @param fraction_of_daily_occ_range [Double] fraction above/below daily min range required to start and end hours of operation @param invert_res [Boolean] if true will reverse hours of operation for residential space types @param gen_occ_profile [Boolean] if true creates a merged occupancy schedule for diagnostic purposes. This schedule is added to the model but no specifically returned by this method @return [ScheduleRuleset] schedule that is assigned to the building as default hours of operation
# File lib/openstudio-standards/schedules/parametric.rb, line 23 def self.model_infer_hours_of_operation_building(model, fraction_of_daily_occ_range: 0.25, invert_res: true, gen_occ_profile: false) # create an array of non-residential and residential spaces res_spaces = [] non_res_spaces = [] res_people_design = 0 non_res_people_design = 0 model.getSpaces.sort.each do |space| if OpenstudioStandards::Space.space_residential?(space) res_spaces << space res_people_design += space.numberOfPeople * space.multiplier else non_res_spaces << space non_res_people_design += space.numberOfPeople * space.multiplier end end OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Parametric.Model', "Model has design level of #{non_res_people_design.round(2)} people in non residential spaces and #{res_people_design.round(2)} people in residential spaces.") # # create merged schedule for prevalent type (not used but can be generated for diagnostics) # if gen_occ_profile # res_prevalent = false # if res_people_design > non_res_people_design # occ_merged = OpenstudioStandards::Space.spaces_get_occupancy_schedule(res_spaces, sch_name: 'Calculated Occupancy Fraction Residential Merged') # res_prevalent = true # else # occ_merged = OpenstudioStandards::Space.spaces_get_occupancy_schedule(non_res_spaces, sch_name: 'Calculated Occupancy Fraction NonResidential Merged') # end # end # re-run spaces_get_occupancy_schedule with x above min occupancy to create on/off schedule if res_people_design > non_res_people_design hours_of_operation = OpenstudioStandards::Space.spaces_get_occupancy_schedule(res_spaces, sch_name: 'Building Hours of Operation Residential', occupied_percentage_threshold: fraction_of_daily_occ_range, threshold_calc_method: 'normalized_daily_range') res_prevalent = true else hours_of_operation = OpenstudioStandards::Space.spaces_get_occupancy_schedule(non_res_spaces, sch_name: 'Building Hours of Operation NonResidential', occupied_percentage_threshold: fraction_of_daily_occ_range, threshold_calc_method: 'normalized_daily_range') end # remove gaps resulting in multiple on off cycles for each rule in schedule so it will be valid hours of operation profiles = [] profiles << hours_of_operation.defaultDaySchedule hours_of_operation.scheduleRules.each do |rule| profiles << rule.daySchedule end profiles.sort.each do |profile| times = profile.times values = profile.values next if times.size <= 3 # length of 1-3 should produce valid hours_of_operation profiles # Find the latest time where the value == 1 latest_time = nil times.zip(values).each do |time, value| if value > 0 latest_time = time end end # Skip profiles that are zero all the time next if latest_time.nil? # Calculate the duration from this point to midnight wrap_dur_left_hr = 0 if values.first == 0 && values.last == 0 wrap_dur_left_hr = 24.0 - latest_time.totalHours end # calculate time at first start first_start_time = times[values.index(0)].totalHours occ_gap_hash = {} prev_time = 0 prev_val = nil times.each_with_index do |time, i| next if time.totalHours == 0.0 # should not see this next if values[i] == prev_val # check if two 0 until time next to each other if values[i] == 0 # only store vacant segments if time.totalHours == 24 occ_gap_hash[prev_time] = wrap_dur_left_hr + first_start_time else occ_gap_hash[prev_time] = time.totalHours - prev_time end end prev_time = time.totalHours prev_val = values[i] end profile.clearValues max_occ_gap_start = occ_gap_hash.key(occ_gap_hash.values.max) max_occ_gap_end_hr = max_occ_gap_start + occ_gap_hash[max_occ_gap_start] # can't add time and duration in hours if max_occ_gap_end_hr > 24.0 then max_occ_gap_end_hr -= 24.0 end # time for gap start target_start_hr = max_occ_gap_start.truncate target_start_min = ((max_occ_gap_start - target_start_hr) * 60.0).truncate max_occ_gap_start = OpenStudio::Time.new(0, target_start_hr, target_start_min, 0) # time for gap end target_end_hr = max_occ_gap_end_hr.truncate target_end_min = ((max_occ_gap_end_hr - target_end_hr) * 60.0).truncate max_occ_gap_end = OpenStudio::Time.new(0, target_end_hr, target_end_min, 0) profile.addValue(max_occ_gap_start, 1) profile.addValue(max_occ_gap_end, 0) os_time_24 = OpenStudio::Time.new(0, 24, 0, 0) if max_occ_gap_start > max_occ_gap_end profile.addValue(os_time_24, 0) else profile.addValue(os_time_24, 1) end end # reverse 1 and 0 values for res_prevalent building # currently spaces_get_occupancy_schedule doesn't use defaultDayProflie, so only inspecting rules for now. if invert_res && res_prevalent OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Parametric.Model', 'Per argument passed in, hours of operation are being inverted for buildings with more people in residential versus non-residential spaces.') hours_of_operation.scheduleRules.each do |rule| profile = rule.daySchedule times = profile.times values = profile.values profile.clearValues times.each_with_index do |time, i| orig_val = values[i] new_value = nil if orig_val == 0 then new_value = 1 end if orig_val == 1 then new_value = 0 end profile.addValue(time, new_value) end end end # set hours of operation for building level hours of operation model.getDefaultScheduleSets.each(&:resetHoursofOperationSchedule) if model.getBuilding.defaultScheduleSet.is_initialized default_sch_set = model.getBuilding.defaultScheduleSet.get else default_sch_set = OpenStudio::Model::DefaultScheduleSet.new(model) default_sch_set.setName('Building Default Schedule Set') model.getBuilding.setDefaultScheduleSet(default_sch_set) end default_sch_set.setHoursofOperationSchedule(hours_of_operation) return hours_of_operation end
This method users the hours of operation for a space and the existing ScheduleRuleset profiles to setup parametric schedule inputs. Inputs include one or more load profile formulas. Data is stored in model attributes for downstream application. This should impact all ScheduleRuleset objects in the model. Plant and Air loop hours of operations should be traced back to a space or spaces.
@author David Goldwasser @param model [OpenStudio::Model::Model] OpenStudio model object @param step_ramp_logic [String] type of step logic to use - @TODO: this is currently not used @param infer_hoo_for_non_assigned_objects [Boolean] attempt to get hours of operation for objects like swh with and exterior lighting @param gather_data_only [Boolean] false (stops method before changes made if true) @param hoo_var_method [String] accepts ‘hours’ or ‘fractional’. Any other value value will result in hour of operation variables not being applied
Options are 'hours', 'fractional'
@return [Hash] schedule is key, value is hash of number of objects
# File lib/openstudio-standards/schedules/parametric.rb, line 183 def self.model_setup_parametric_schedules(model, step_ramp_logic: nil, infer_hoo_for_non_assigned_objects: true, gather_data_only: false, hoo_var_method: 'hours') parametric_inputs = {} default_sch_type = OpenStudio::Model::DefaultScheduleType.new('HoursofOperationSchedule') # thermal zones, air loops, plant loops will require some logic if they refer to more than one hours of operaiton schedule. # for initial use case while have same horus of operaiton so this can be pretty simple, but will have to re-visit it sometime # possible solution A: choose hoo that contributes the largest fraction of floor area # possible solution B: expand the hours of operation for a given day to include combined range of hoo objects # whatever approach is used for gathering parametric inputs for existing ruleset schedules should also be used for model_apply_parametric_schedules # loop through spaces (trace hours of operation back to space) OpenstudioStandards::Schedules.spaces_space_types_get_parametric_schedule_inputs(model.getSpaces, parametric_inputs, gather_data_only) # loop through space types (trace hours of operation back to space type). OpenstudioStandards::Schedules.spaces_space_types_get_parametric_schedule_inputs(model.getSpaceTypes, parametric_inputs, gather_data_only) # loop through thermal zones (trace hours of operation back to spaces in thermal zone) thermal_zone_hash = {} # key is zone and hash is hours of operation model.getThermalZones.sort.each do |zone| # identify hours of operation hours_of_operation = OpenstudioStandards::Space.spaces_hours_of_operation(zone.spaces) thermal_zone_hash[zone] = hours_of_operation # get thermostat setpoint schedules if zone.thermostatSetpointDualSetpoint.is_initialized thermostat = zone.thermostatSetpointDualSetpoint.get if thermostat.heatingSetpointTemperatureSchedule.is_initialized && thermostat.heatingSetpointTemperatureSchedule.get.to_ScheduleRuleset.is_initialized schedule = thermostat.heatingSetpointTemperatureSchedule.get.to_ScheduleRuleset.get OpenstudioStandards::Schedules.schedule_ruleset_get_parametric_inputs(schedule, thermostat, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: 'tstat') end if thermostat.coolingSetpointTemperatureSchedule.is_initialized && thermostat.coolingSetpointTemperatureSchedule.get.to_ScheduleRuleset.is_initialized schedule = thermostat.coolingSetpointTemperatureSchedule.get.to_ScheduleRuleset.get OpenstudioStandards::Schedules.schedule_ruleset_get_parametric_inputs(schedule, thermostat, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: 'tstat') end end end # loop through air loops (trace hours of operation back through spaces served by air loops) air_loop_hash = {} # key is zone and hash is hours of operation model.getAirLoopHVACs.sort.each do |air_loop| # identify hours of operation air_loop_spaces = [] air_loop.thermalZones.sort.each do |zone| air_loop_spaces += zone.spaces end hours_of_operation = OpenstudioStandards::Space.spaces_hours_of_operation(air_loop_spaces) air_loop_hash[air_loop] = hours_of_operation if air_loop.availabilitySchedule.to_ScheduleRuleset.is_initialized schedule = air_loop.availabilitySchedule.to_ScheduleRuleset.get OpenstudioStandards::Schedules.schedule_ruleset_get_parametric_inputs(schedule, air_loop, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method) end avail_mgrs = air_loop.availabilityManagers avail_mgrs.sort.each do |avail_mgr| # @todo I'm finding availability mangers, but not any resources for them, even if I use OpenStudio::Model.getRecursiveChildren(avail_mgr) resources = avail_mgr.resources resources = OpenStudio::Model.getRecursiveResources(avail_mgr) resources.sort.each do |resource| if resource.to_ScheduleRuleset.is_initialized schedule = resource.to_ScheduleRuleset.get OpenstudioStandards::Schedules.schedule_ruleset_get_parametric_inputs(schedule, avail_mgr, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method) end end end end # look through all model HVAC components find scheduleRuleset objects, resources, that use them and zone or air loop for hours of operation hvac_components = model.getHVACComponents hvac_components.sort.each do |component| # identify zone, or air loop it refers to, some may refer to plant loop, OA or other component thermal_zone = nil air_loop = nil plant_loop = nil schedules = [] if component.to_ZoneHVACComponent.is_initialized && component.to_ZoneHVACComponent.get.thermalZone.is_initialized thermal_zone = component.to_ZoneHVACComponent.get.thermalZone.get end if component.airLoopHVAC.is_initialized air_loop = component.airLoopHVAC.get end if component.plantLoop.is_initialized plant_loop = component.plantLoop.get end component.resources.sort.each do |resource| if resource.to_ThermalZone.is_initialized thermal_zone = resource.to_ThermalZone.get elsif resource.to_ScheduleRuleset.is_initialized schedules << resource.to_ScheduleRuleset.get end end # inspect resources for children of objects found in thermal zone or plant loop # get objects like OA controllers and unitary object components next if thermal_zone.nil? && air_loop.nil? children = OpenStudio::Model.getRecursiveChildren(component) children.sort.each do |child| child.resources.sort.each do |sub_resource| if sub_resource.to_ScheduleRuleset.is_initialized schedules << sub_resource.to_ScheduleRuleset.get end end end # process schedules found for this component schedules.sort.each do |schedule| hours_of_operation = nil if !thermal_zone.nil? hours_of_operation = thermal_zone_hash[thermal_zone] elsif !air_loop.nil? hours_of_operation = air_loop_hash[air_loop] elsif !plant_loop.nil? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Parametric.Model', "#{schedule.name.get} is associated with plant loop, will not gather parametric inputs") next else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.Model', "Cannot identify where #{component.name.get} is in system. Will not gather parametric inputs for #{schedule.name.get}") next end OpenstudioStandards::Schedules.schedule_ruleset_get_parametric_inputs(schedule, component, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method) end end # @todo Service Water Heating supply side (may or may not be associated with a space) # @todo water use equipment definitions (temperature, sensible, latent) may be in multiple spaces, need to identify hoo, but typically constant schedules # water use equipment (flow rate fraction) # @todo address common schedules used across multiple instances model.getWaterUseEquipments.sort.each do |water_use_equipment| if water_use_equipment.flowRateFractionSchedule.is_initialized && water_use_equipment.flowRateFractionSchedule.get.to_ScheduleRuleset.is_initialized schedule = water_use_equipment.flowRateFractionSchedule.get.to_ScheduleRuleset.get next if parametric_inputs.key?(schedule) opt_space = water_use_equipment.space if opt_space.is_initialized space = space.get hours_of_operation = OpenstudioStandards::Space.space_hours_of_operation(space) OpenstudioStandards::Schedules.schedule_ruleset_get_parametric_inputs(schedule, water_use_equipment, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method) else hours_of_operation = OpenstudioStandards::Space.spaces_hours_of_operation(model.getSpaces) if !hours_of_operation.nil? OpenstudioStandards::Schedules.schedule_ruleset_get_parametric_inputs(schedule, water_use_equipment, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: hoo_var_method) end end end end # @todo Refrigeration (will be associated with thermal zone) # @todo exterior lights (will be astronomical, but like AEDG's may have reduction later at night) return parametric_inputs 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
# File lib/openstudio-standards/schedules/information.rb, line 226 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.empty? 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
# File lib/openstudio-standards/schedules/information.rb, line 269 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
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
# File lib/openstudio-standards/schedules/information.rb, line 193 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.empty? 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 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
# File lib/openstudio-standards/schedules/information.rb, line 151 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
# File lib/openstudio-standards/schedules/information.rb, line 163 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
# File lib/openstudio-standards/schedules/information.rb, line 176 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
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
# File lib/openstudio-standards/schedules/information.rb, line 140 def self.schedule_constant_get_min_max(schedule_constant) result = { 'min' => schedule_constant.value, 'max' => schedule_constant.value } return result end
adjust individual schedule profiles from parametric inputs
@author David Goldwasser @param schedule_day [OpenStudio::Model::ScheduleDay] OpenStudio ScheduleDay object @param hoo_start [Double] hours of operation start @param hoo_end [Double] hours of operation end @param val_flr [Double] value floor @param val_clg [Double] value ceiling @param ramp_frequency [Double] ramp frequency in minutes @param infer_hoo_for_non_assigned_objects [Boolean] attempt to get hoo for objects like swh with and exterior lighting @param error_on_out_of_order [Boolean] true will error if applying formula creates out of order values @return [OpenStudio::Model::ScheduleDay] OpenStudio ScheduleDay object @api private
# File lib/openstudio-standards/schedules/parametric.rb, line 1145 def self.schedule_day_adjust_from_parameters(schedule_day, hoo_start, hoo_end, val_flr, val_clg, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order) # process hoo and floor/ceiling vars to develop formulas without variables formula_string = schedule_day.additionalProperties.getFeatureAsString('param_day_profile').get formula_hash = {} formula_string.split('|').each do |time_val_valopt| a1 = time_val_valopt.to_s.split('~') time = a1[0] value_array = a1.drop(1) formula_hash[time] = value_array end # setup additional variables if hoo_end >= hoo_start occ = hoo_end - hoo_start else occ = 24.0 + hoo_end - hoo_start end vac = 24.0 - occ range = val_clg - val_flr timestep_minutes = (0..60).step(60 * ramp_frequency).to_a OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.ScheduleDay', "Schedule #{schedule_day.name} has this formula hash: #{formula_hash}") # apply variables and create updated hash with only numbers formula_hash_var_free = {} formula_hash.each do |time, val_in_out| # replace time variables with value time = time.gsub('hoo_start', hoo_start.to_s) time = time.gsub('hoo_end', hoo_end.to_s) time = time.gsub('occ', occ.to_s) # can save special variables like lunch or break using this logic mid_start = hoo_start + (occ * 0.5) mid_start_min = mid_start.modulo(1) * 60 mid_start_min_ts = timestep_minutes.min { |a, b| (a - mid_start_min).abs <=> (b - mid_start_min).abs } mid_start_adjusted = mid_start.floor + (mid_start_min_ts / 60) time = time.gsub('mid', mid_start_adjusted.to_s) time = time.gsub('vac', vac.to_s) begin time_float = eval(time) if time_float.to_i.to_s == time_float.to_s || time_float.to_f.to_s == time_float.to_s # check to see if numeric time_float = time_float.to_f else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.ScheduleDay', "Time formula #{time} for #{schedule_day.name} is invalid. It can't be converted to a float.") end rescue SyntaxError => e OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.ScheduleDay', "Time formula #{time} for #{schedule_day.name} is invalid. It can't be evaluated.") end # replace variables in array of values val_in_out_float = [] val_in_out.each do |val| # replace variables for values val = val.gsub('val_flr', val_flr.to_s) val = val.gsub('val_clg', val_clg.to_s) val = val.gsub('val_range', range.to_s) # will expect a fractional value and will scale within ceiling and floor begin val_float = eval(val) if val_float.to_i.to_s == val_float.to_s || val_float.to_f.to_s == val_float.to_s # check to see if numeric val_float = val_float.to_f else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.ScheduleDay', "Value formula #{val_float} for #{schedule_day.name} is invalid. It can't be converted to a float.") end rescue SyntaxError => e OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.ScheduleDay', "Time formula #{val_float} for #{schedule_day.name} is invalid. It can't be evaluated.") end val_in_out_float << val_float end # update hash formula_hash_var_free[time_float] = val_in_out_float end # this is old variable used in loop, just combining for now to avoid refactor, may change this later time_value_pairs = [] formula_hash_var_free.each do |time, val_in_out| val_in_out.each do |val| time_value_pairs << [time, val] end end OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.ScheduleDay', "Schedule #{schedule_day.name} will be adjusted with these time-value pairs: #{time_value_pairs}") # re-order so first value is lowest, and last is highest (need to adjust so no negative or > 24 values first) neg_time_hash = {} temp_min_time_hash = {} time_value_pairs.each_with_index do |pair, i| # if value 24 add it to 24 so it will go on tail end of profile # case when value is greater than 24 can be left alone for now, will be addressed if pair[0] < 0.0 neg_time_hash[i] = pair[0] time = pair[0] + 24.0 time_value_pairs[i][0] = time else time = pair[0] end temp_min_time_hash[i] = pair[0] end time_value_pairs.rotate!(temp_min_time_hash.key(temp_min_time_hash.values.min)) # validate order, issue warning and correct if out of order last_time = nil throw_order_warning = false pre_fix_time_value_pairs = time_value_pairs.to_s time_value_pairs.each_with_index do |time_value_pair, i| if last_time.nil? last_time = time_value_pair[0] elsif time_value_pair[0] < last_time || neg_time_hash.key?(i) # @todo it doesn't actually stop here now if error_on_out_of_order OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.ScheduleDay', "Pre-interpolated processed hash for #{schedule_day.name} has one or more out of order conflicts: #{pre_fix_time_value_pairs}. Method will stop because Error on Out of Order was set to true.") end if neg_time_hash.key?(i) orig_current_time = time_value_pair[0] updated_time = 0.0 last_buffer = 'NA' else # determine much space last item can move if i < 2 last_buffer = time_value_pairs[i - 1][0] # can move down to 0 without any issues else last_buffer = time_value_pairs[i - 1][0] - time_value_pairs[i - 2][0] end # move to previous timestep but don't exceed available buffer updated_time = time_value_pairs[i - 1][0] - [ramp_frequency, last_buffer].min end # update values in array orig_current_time = time_value_pair[0] time_value_pairs[i - 1][0] = updated_time time_value_pairs[i][0] = updated_time # reporting mostly for diagnostic purposes # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Parametric.ScheduleDay', "For #{schedule_day.name} profile item #{i} time was #{last_time} and item #{i + 1} time was #{orig_current_time}. Last buffer is #{last_buffer}. Changing both times to #{updated_time}.") last_time = updated_time throw_order_warning = true else last_time = time_value_pair[0] end end # issue warning if order was changed if throw_order_warning # OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.ScheduleDay', "Pre-interpolated processed hash for #{schedule_day.name} has one or more out of order conflicts: #{pre_fix_time_value_pairs}. Time values were adjusted as shown to crate a valid profile: #{time_value_pairs}") end # add interpolated values at ramp_frequency time_value_pairs.each_with_index do |time_value_pair, i| # store current and next time and value current_time = time_value_pair[0] current_value = time_value_pair[1] if i + 1 < time_value_pairs.size next_time = time_value_pairs[i + 1][0] next_value = time_value_pairs[i + 1][1] else # use time and value of first item next_time = time_value_pairs[0][0] + 24 # need to adjust values for beginning of array next_value = time_value_pairs[0][1] end step_delta = next_time - current_time # skip if time between values is 0 or less than ramp frequency next if step_delta <= ramp_frequency # skip if next value is same next if current_value == next_value # add interpolated value to array interpolated_time = current_time + ramp_frequency interpolated_value = (next_value * (interpolated_time - current_time) / step_delta) + (current_value * (next_time - interpolated_time) / step_delta) time_value_pairs.insert(i + 1, [interpolated_time, interpolated_value]) end # remove second instance of time when there are two time_values_used = [] items_to_remove = [] time_value_pairs.each_with_index do |time_value_pair, i| if time_values_used.include? time_value_pair[0] items_to_remove << i else time_values_used << time_value_pair[0] end end items_to_remove.reverse.each do |i| time_value_pairs.delete_at(i) end # if time is > 24 shift to front of array and adjust value rotate_steps = 0 time_value_pairs.reverse.each_with_index do |time_value_pair, i| next unless time_value_pair[0] > 24 rotate_steps -= 1 time_value_pair[0] -= 24 end time_value_pairs.rotate!(rotate_steps) # add a 24 on the end of array that matches the first value if time_value_pairs.last[0].to_i != 24 time_value_pairs << [24.0, time_value_pairs.first[1]] end # OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.ScheduleDay', "Schedule #{schedule_day.name} will be adjusted with these time-value pairs: #{time_value_pairs}") # reset scheduleDay values based on interpolated values schedule_day.clearValues time_value_pairs.each do |time_val| hour = time_val.first.floor min = ((time_val.first - hour) * 60.0).floor os_time = OpenStudio::Time.new(0, hour, min, 0) value = time_val.last schedule_day.addValue(os_time, value) end # @todo apply secondary logic # Tell EnergyPlus to interpolate schedules to timestep so that it doesn't have to be done in this code # sch_day.setInterpolatetoTimestep(true) # if model.version < OpenStudio::VersionString.new('3.8.0') # day_sch.setInterpolatetoTimestep(true) # else # day_sch.setInterpolatetoTimestep('Average') # end return schedule_day 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
# File lib/openstudio-standards/schedules/information.rb, line 317 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
# File lib/openstudio-standards/schedules/information.rb, line 338 def self.schedule_day_get_hourly_values(schedule_day, model = nil) schedule_values = [] if model.nil? model = schedule_day.model end if model.version.str < '3.8.0' # 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 else num_timesteps = model.getTimestep.numberOfTimestepsPerHour day_timeseries = schedule_day.timeSeries.values.to_a schedule_values = day_timeseries.each_slice(num_timesteps).map { |slice| (slice.sum / slice.size.to_f).round(10) } end unless schedule_values.size == 24 OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} returned illegal number of values: #{schedule_values.size}.") return false end return schedule_values end
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
# File lib/openstudio-standards/schedules/information.rb, line 293 def self.schedule_day_get_min_max(schedule_day) min = nil max = nil values = schedule_day.values 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
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
# File lib/openstudio-standards/schedules/modify.rb, line 16 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
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]
# File lib/openstudio-standards/schedules/modify.rb, line 78 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
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
# File lib/openstudio-standards/schedules/modify.rb, line 56 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
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
# File lib/openstudio-standards/schedules/information.rb, line 45 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', "#{__method__} does not yet support ScheduleYear schedules.") result = { 'min' => nil, 'max' => nil } when 'OS_Schedule_Interval' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} 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 #{__method__}.") 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
# File lib/openstudio-standards/schedules/information.rb, line 76 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', "#{__method__} does not yet support ScheduleCompact schedules.") result = nil when 'OS_Schedule_Year' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleYear schedules.") result = nil when 'OS_Schedule_Interval' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleInterval schedules.") result = nil else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for #{__method__}.") 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
# File lib/openstudio-standards/schedules/information.rb, line 106 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', "#{__method__} does not yet support ScheduleYear schedules.") result = nil when 'OS_Schedule_Interval' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleInterval schedules.") result = nil else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for #{__method__}.") result = nil end return result end
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
# File lib/openstudio-standards/schedules/information.rb, line 15 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', "#{__method__} does not yet support ScheduleYear schedules.") result = { 'min' => nil, 'max' => nil } when 'OS_Schedule_Interval' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} 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 #{__method__}.") result = { 'min' => nil, 'max' => nil } end return result end
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
# File lib/openstudio-standards/schedules/modify.rb, line 106 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
Adjust hours of operation
@param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object @param options [Hash] Hash
of argument options @return [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object
# File lib/openstudio-standards/schedules/modify.rb, line 350 def self.schedule_ruleset_adjust_hours_of_operation(schedule_ruleset, options = {}) defaults = { 'base_start_hoo' => 8.0, # may not be good idea to have default 'base_finish_hoo' => 18.0, # may not be good idea to have default 'delta_length_hoo' => 0.0, 'shift_hoo' => 0.0, 'default' => true, 'mon' => true, 'tue' => true, 'wed' => true, 'thur' => true, 'fri' => true, 'sat' => true, 'sun' => true, 'summer' => false, 'winter' => false } # merge user inputs with defaults options = defaults.merge(options) # grab schedule out of argument if schedule_ruleset.to_ScheduleRuleset.is_initialized schedule = schedule_ruleset.to_ScheduleRuleset.get else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Modify', "schedule_ruleset_adjust_hours_of_operation only applies to ScheduleRuleset objects. Skipping #{schedule.name}") return nil end # array of all profiles to change profiles = [] # push default profiles to array if options['default'] profiles << schedule.defaultDaySchedule end # push profiles to array 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 if rule.applyTuesday && rule.applyTuesday == options['tue'] then alter_rule = true end if rule.applyWednesday && rule.applyWednesday == options['wed'] then alter_rule = true end if rule.applyThursday && rule.applyThursday == options['thur'] then alter_rule = true end if rule.applyFriday && rule.applyFriday == options['fri'] then alter_rule = true end if rule.applySaturday && rule.applySaturday == options['sat'] then alter_rule = true end if rule.applySunday && rule.applySunday == options['sun'] then alter_rule = true end # @todo add in logic to warn user about conflicts where a single rule has conflicting tests if alter_rule profiles << day_sch end end # add design days to array if options['summer'] profiles << schedule.summerDesignDaySchedule end if options['winter'] profiles << schedule.winterDesignDaySchedule end # give info messages as I change specific profiles OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Schedules.Modify', "Adjusting #{schedule.name}") # rename schedule schedule.setName("#{schedule.name} - extend #{options['delta_length_hoo']} shift #{options['shift_hoo']}") # break time args into hours and minutes start_hoo_hours = (options['base_start_hoo']).to_i start_hoo_minutes = (((options['base_start_hoo']) - (options['base_start_hoo']).to_i) * 60).to_i finish_hoo_hours = (options['base_finish_hoo']).to_i finish_hoo_minutes = (((options['base_finish_hoo']) - (options['base_finish_hoo']).to_i) * 60).to_i delta_hours = (options['delta_length_hoo']).to_i delta_minutes = (((options['delta_length_hoo']) - (options['delta_length_hoo']).to_i) * 60).to_i shift_hours = (options['shift_hoo']).to_i shift_minutes = (((options['shift_hoo']) - (options['shift_hoo']).to_i) * 60).to_i # time objects to use in measure time_0 = OpenStudio::Time.new(0, 0, 0, 0) time_1_min = OpenStudio::Time.new(0, 0, 1, 0) # add this to avoid times in day profile less than this time_12 = OpenStudio::Time.new(0, 12, 0, 0) time_24 = OpenStudio::Time.new(0, 24, 0, 0) start_hoo_time = OpenStudio::Time.new(0, start_hoo_hours, start_hoo_minutes, 0) finish_hoo_time = OpenStudio::Time.new(0, finish_hoo_hours, finish_hoo_minutes, 0) delta_time = OpenStudio::Time.new(0, delta_hours, delta_minutes, 0) # not used shift_time = OpenStudio::Time.new(0, shift_hours, shift_minutes, 0) # calculations if options['base_start_hoo'] <= options['base_finish_hoo'] base_opp_day_length = options['base_finish_hoo'] - options['base_start_hoo'] mid_hoo = start_hoo_time + ((finish_hoo_time - start_hoo_time) / 2) mid_non_hoo = mid_hoo + time_12 if mid_non_hoo > time_24 then mid_non_hoo -= time_24 end else base_opp_day_length = options['base_finish_hoo'] - options['base_start_hoo'] + 24 mid_non_hoo = finish_hoo_time + ((start_hoo_time - finish_hoo_time) / 2) mid_hoo = mid_non_hoo + time_12 if mid_non_hoo > time_24 then mid_non_hoo -= time_24 end end adjusted_opp_day_length = base_opp_day_length + options['delta_length_hoo'] hoo_time_multiplier = adjusted_opp_day_length / base_opp_day_length non_hoo_time_multiplier = (24 - adjusted_opp_day_length) / (24 - base_opp_day_length) # check for invalid input if adjusted_opp_day_length < 0 OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Modify', 'Requested hours of operation adjustment results in an invalid negative hours of operation') return false end # check for invalid input if adjusted_opp_day_length > 24 OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Modify', 'Requested hours of operation adjustment results in more than 24 hours of operation') return false end # making some temp objects to avoid having to deal with wrap around for change of hoo times mid_hoo < start_hoo_time ? (adj_mid_hoo = mid_hoo + time_24) : (adj_mid_hoo = mid_hoo) finish_hoo_time < adj_mid_hoo ? (adj_finish_hoo_time = finish_hoo_time + time_24) : (adj_finish_hoo_time = finish_hoo_time) mid_non_hoo < adj_finish_hoo_time ? (adj_mid_non_hoo = mid_non_hoo + time_24) : (adj_mid_non_hoo = mid_non_hoo) adj_start = start_hoo_time + time_24 # not used # edit profiles profiles.each do |day_sch| times = day_sch.times values = day_sch.values # in this case delete all values outside of # todo - may need similar logic if exactly 0 hours if adjusted_opp_day_length == 24 start_val = day_sch.getValue(start_hoo_time) finish_val = day_sch.getValue(finish_hoo_time) # remove times out of range that should not be reference or compressed if start_hoo_time < finish_hoo_time times.each do |time| if time <= start_hoo_time || time > finish_hoo_time day_sch.removeValue(time) end end # add in values day_sch.addValue(start_hoo_time, start_val) day_sch.addValue(finish_hoo_time, finish_val) day_sch.addValue(time_24, [start_val, finish_val].max) else times.each do |time| if time > start_hoo_time && time <= finish_hoo_time day_sch.removeValue(time) end end # add in values day_sch.addValue(finish_hoo_time, finish_val) day_sch.addValue(start_hoo_time, start_val) day_sch.addValue(time_24, [values.first, values.last].max) end end times = day_sch.times values = day_sch.values # arrays for values to avoid overlap conflict of times new_times = [] new_values = [] # this is to store what datapoint will be first after midnight, and what the value at that time should be min_time_new = time_24 min_time_value = nil # flag if found time at 24 found_24_or_0 = false # push times to array times.each do |time| # create logic for four possible quadrants. Assume any quadrant can pass over 24/0 threshold time < start_hoo_time ? (temp_time = time + time_24) : (temp_time = time) # calculate change in time do to hoo delta if temp_time <= adj_finish_hoo_time expand_time = ((temp_time - adj_mid_hoo) * hoo_time_multiplier) - (temp_time - adj_mid_hoo) else expand_time = ((temp_time - adj_mid_non_hoo) * non_hoo_time_multiplier) - (temp_time - adj_mid_non_hoo) end new_time = time + shift_time + expand_time # adjust wrap around times if new_time < time_0 new_time += time_24 elsif new_time > time_24 new_time -= time_24 end new_times << new_time # see which new_time has the lowest value. Then add a value at 24 equal to that if !found_24_or_0 && new_time <= min_time_new min_time_new = new_time min_time_value = day_sch.getValue(time) elsif new_time == time_24 # this was added to address time exactly at 24 min_time_new = new_time min_time_value = day_sch.getValue(time) found_24_or_0 = true elsif new_time == time_0 min_time_new = new_time min_time_value = day_sch.getValue(time_0) found_24_or_0 = true end end # push values to array values.each do |value| new_values << value end # add value for what will be 24 new_times << time_24 new_values << min_time_value new_time_val_hash = {} new_times.each_with_index do |time, i| new_time_val_hash[time.totalHours] = { time: time, value: new_values[i] } end # clear values day_sch.clearValues new_time_val_hash = Hash[new_time_val_hash.sort] prev_time = nil new_time_val_hash.sort.each do |hours, time_val| if prev_time.nil? || time_val[:time] - prev_time > time_1_min day_sch.addValue(time_val[:time], time_val[:value]) prev_time = time_val[:time] else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Modify', "Time step in #{day_sch.name} between #{prev_time.toString} and #{time_val[:time].toString} is too small to support, not adding value.") end end end return schedule end
this will use parametric inputs contained in schedule and profiles along with inferred hours of operation to generate updated ruleset schedule profiles
@author David Goldwasser @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object @param ramp_frequency [Double] ramp frequency in minutes @param infer_hoo_for_non_assigned_objects [Boolean] attempt to get hoo for objects like swh with and exterior lighting @param error_on_out_of_order [Boolean] true will error if applying formula creates out of order values @return [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object
# File lib/openstudio-standards/schedules/parametric.rb, line 948 def self.schedule_ruleset_apply_parametric_inputs(schedule_ruleset, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order, parametric_inputs = nil) # Check if parametric inputs were supplied and generate them if not if parametric_inputs.nil? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Parametric.ScheduleRuleset', "For #{schedule_ruleset.name}, no parametric inputs were not supplied so they will be generated now.") parametric_inputs = OpenstudioStandards::Schedules.model_setup_parametric_schedules(schedule.model, gather_data_only: true) end # Check that parametric inputs exist for this schedule after generation if parametric_inputs[schedule_ruleset].nil? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.ScheduleRuleset', "For #{schedule_ruleset.name}, no parametric inputs exists so schedule will not be changed.") return schedule_ruleset end # Check that an hours of operation schedule is associated with this schedule if parametric_inputs[schedule_ruleset][:hoo_inputs].nil? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.ScheduleRuleset', "For #{schedule_ruleset.name}, no associated hours of operation schedule was found so schedule will not be changed.") return schedule_ruleset end # Get the hours of operation schedule hours_of_operation = parametric_inputs[schedule_ruleset][:hoo_inputs] # OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.ScheduleRuleset', "For #{schedule_ruleset.name} hours_of_operation = #{hours_of_operation}.") starting_aeflh = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_ruleset) # store floor and ceiling value val_flr = nil if schedule_ruleset.hasAdditionalProperties && schedule_ruleset.additionalProperties.hasFeature('param_sch_floor') val_flr = schedule_ruleset.additionalProperties.getFeatureAsDouble('param_sch_floor').get end val_clg = nil if schedule_ruleset.hasAdditionalProperties && schedule_ruleset.additionalProperties.hasFeature('param_sch_ceiling') val_clg = schedule_ruleset.additionalProperties.getFeatureAsDouble('param_sch_ceiling').get end # loop through schedule days from highest to lowest priority (with default as lowest priority) # if rule needs to be split to address hours of operation rules add new rule next to relevant existing rule profiles = {} schedule_ruleset.scheduleRules.each do |rule| # remove any use manually generated non parametric rules or any auto-generated rules from prior application of formulas and hoo sch_day = rule.daySchedule if !sch_day.hasAdditionalProperties || !sch_day.additionalProperties.hasFeature('param_day_tag') || (sch_day.additionalProperties.getFeatureAsString('param_day_tag').get == 'autogen') sch_day.remove # remove day schedule for this rule rule.remove # remove the rule elsif !sch_day.additionalProperties.hasFeature('param_day_profile') OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.ScheduleRuleset', "#{schedule.name} doesn't have a parametric formula for #{rule.name} This profile will not be altered.") next else profiles[sch_day] = rule end end profiles[schedule_ruleset.defaultDaySchedule] = nil # get indices for current schedule 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) # process profiles profiles.each do |sch_day, rule| # for current profile index identify hours of operation index that contains all days if rule.nil? current_rule_index = -1 else current_rule_index = rule.ruleIndex end # loop through indices looking of rule in hoo that contains days in the rule hoo_target_index = nil days_used = [] indices_vector.each_with_index do |profile_index, i| if profile_index == current_rule_index then days_used << (i + 1) end end # find days_used in hoo profiles that contains all days used from this profile hoo_profile_match_hash = {} best_fit_check = {} hours_of_operation.each do |profile_index, value| days_for_rule_not_in_hoo_profile = days_used - value[:days_used] hoo_profile_match_hash[profile_index] = days_for_rule_not_in_hoo_profile best_fit_check[profile_index] = days_for_rule_not_in_hoo_profile.size if days_for_rule_not_in_hoo_profile.empty? hoo_target_index = profile_index end end clone_needed = false hoo_target_index = best_fit_check.key(best_fit_check.values.min) if best_fit_check[hoo_target_index] > 0 clone_needed = true end # get hours of operation for this specific profile hoo_start = hours_of_operation[hoo_target_index][:hoo_start] # puts hoo_start hoo_end = hours_of_operation[hoo_target_index][:hoo_end] # puts hoo_end # update scheduleDay OpenstudioStandards::Schedules.schedule_day_adjust_from_parameters(sch_day, hoo_start, hoo_end, val_flr, val_clg, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order) # clone new rule if needed if clone_needed # make list of new rules needed as has or array autogen_rules = {} days_to_fill = hoo_profile_match_hash[hoo_target_index] hours_of_operation.each do |profile_index, value| remainder = days_to_fill - value[:days_used] day_for_rule = days_to_fill - remainder if remainder.size < days_to_fill.size autogen_rules[profile_index] = { days_to_fill: day_for_rule, hoo_start: hoo_start, hoo_end: hoo_end } end days_to_fill = remainder end # loop through new rules to make and process autogen_rules.each do |autogen_rule, hash| # generate new rule sch_rule_autogen = OpenStudio::Model::ScheduleRule.new(schedule_ruleset) if current_rule_index target_index = schedule_ruleset.scheduleRules.size - 1 # just above default else target_index = current_rule_index - 1 # confirm just above orig rule end current_rule_index = target_index if rule.nil? sch_rule_autogen.setName("autogen #{schedule_ruleset.name} #{target_index}") else sch_rule_autogen.setName("autogen #{rule.name} #{target_index}") end schedule_ruleset.setScheduleRuleIndex(sch_rule_autogen, target_index) # @todo confirm this is higher priority than the non-auto-generated rule hash[:days_to_fill].each do |day| date = OpenStudio::Date.fromDayOfYear(day, year) sch_rule_autogen.addSpecificDate(date) end sch_rule_autogen.setApplySunday(true) sch_rule_autogen.setApplyMonday(true) sch_rule_autogen.setApplyTuesday(true) sch_rule_autogen.setApplyWednesday(true) sch_rule_autogen.setApplyThursday(true) sch_rule_autogen.setApplyFriday(true) sch_rule_autogen.setApplySaturday(true) # match profile from source rule (don't add time/values need a formula to process) sch_day_auto_gen = sch_rule_autogen.daySchedule sch_day_auto_gen.setName("#{sch_rule_autogen.name}_day_sch") sch_day_auto_gen.additionalProperties.setFeature('param_day_tag', 'autogen') val = sch_day.additionalProperties.getFeatureAsString('param_day_profile').get sch_day_auto_gen.additionalProperties.setFeature('param_day_profile', val) val = sch_day.additionalProperties.getFeatureAsString('param_day_secondary_logic').get sch_day_auto_gen.additionalProperties.setFeature('param_day_secondary_logic', val) val = sch_day.additionalProperties.getFeatureAsString('param_day_secondary_logic_arg_val').get sch_day_auto_gen.additionalProperties.setFeature('param_day_secondary_logic_arg_val', val) # get hours of operation for this specific profile hoo_start = hash[:hoo_start] hoo_end = hash[:hoo_end] # process new rule OpenstudioStandards::Schedules.schedule_day_adjust_from_parameters(sch_day_auto_gen, hoo_start, hoo_end, val_flr, val_clg, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order) end end end # @todo create summer and winter design day profiles (make sure scheduleDay objects parametric) # @todo should they have their own formula, or should this be hard coded logic by schedule type # check orig vs. updated aeflh final_aeflh = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_ruleset) percent_change = ((starting_aeflh - final_aeflh) / starting_aeflh) * 100.0 if percent_change.abs > 0.05 OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Parametric.ScheduleRuleset', "For #{schedule_ruleset.name}, applying parametric schedules made a #{percent_change.round(1)}% change in annual equivalent full load hours. (from #{starting_aeflh.round(2)} to #{final_aeflh.round(2)})") end return schedule_ruleset 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.
# File lib/openstudio-standards/schedules/modify.rb, line 601 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
Increase/decrease by percentage or static value change value when value passes/fails test
@param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object @param test_value [Double] if less than the test_value, use the pass_value to modify, otherwise use the fail_value @param pass_value [Double] value to adjust by if less than test value @param fail_value [Double] value to adjust by if more than test value @param floor_value [Double] minimum value that the adjustment can take @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
# File lib/openstudio-standards/schedules/modify.rb, line 211 def self.schedule_ruleset_conditional_adjust_value(schedule_ruleset, test_value, pass_value, fail_value, floor_value, modification_type = 'Multiplier') # gather profiles profiles = [] default_profile = schedule_ruleset.to_ScheduleRuleset.get.defaultDaySchedule profiles << default_profile rules = schedule_ruleset.scheduleRules rules.each do |rule| profiles << rule.daySchedule end # alter profiles profiles.each do |profile| times = profile.times i = 0 profile.values.each do |sch_value| # run test on this sch_value if sch_value < test_value adjust_value = pass_value else adjust_value = fail_value end # skip if sch_value is floor or less next if sch_value <= floor_value case modification_type when 'Multiplier' # take the max of the floor or resulting value profile.addValue(times[i], [sch_value * adjust_value, floor_value].max) when 'Sum' # take the max of the floor or resulting value profile.addValue(times[i], [sch_value + adjust_value, floor_value].max) end i += 1 end 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]
# File lib/openstudio-standards/schedules/modify.rb, line 704 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
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
# File lib/openstudio-standards/schedules/information.rb, line 889 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 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
# File lib/openstudio-standards/schedules/information.rb, line 859 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__} 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__} 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
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
# File lib/openstudio-standards/schedules/information.rb, line 490 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__} failed because object #{schedule_ruleset.name.get} 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.get} 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 values = schedule.values 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
# File lib/openstudio-standards/schedules/information.rb, line 534 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__} failed because object #{schedule_ruleset.name.get} 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.get} 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
# File lib/openstudio-standards/schedules/information.rb, line 609 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__} failed because object #{schedule_ruleset.name.get} is not a ScheduleRuleset.") return nil end model = schedule_ruleset.model # define the start and end date year_start_date = nil year_end_date = nil if model.yearDescription.is_initialized year_description = 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, model) annual_hourly_values += daily_hours 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.
# File lib/openstudio-standards/schedules/information.rb, line 653 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__} failed because object #{schedule_ruleset.name.get} 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
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 @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
# File lib/openstudio-standards/schedules/information.rb, line 392 def self.schedule_ruleset_get_min_max(schedule_ruleset, only_run_period_values: false) # validate schedule unless schedule_ruleset.to_ScheduleRuleset.is_initialized OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} failed because object #{schedule_ruleset.name.get} is not a ScheduleRuleset.") return nil end # 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 # 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 else # 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| values = day_schedule.values 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
Method to process space load instance schedules for model_setup_parametric_schedules
@author David Goldwasser @param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object @param space_load_instance [OpenStudio::Model::SpaceLoadInstance] OpenStudio SpaceLoadInstance object @param parametric_inputs [Hash] @param hours_of_operation [Hash] hash, example:
{ profile_index: { hoo_start: [float] rule operation start hour, hoo_end: [float] rule operation end hour, hoo_hours: [float] rule operation duration hours, days_used: [Array] annual day indices } }
@param ramp [Boolean] flag to add intermediate values ramp between input schedule values @param min_ramp_dur_hr [Double] minimum time difference to ramp between @param gather_data_only [Boolean] if true, no changes are made to schedules @param hoo_var_method [String] accepts hours and fractional. Any other value value will result in hoo variables not being applied @return [Hash] parametric inputs hash of ScheduleRuleset, example:
{ floor: schedule floor, ceiling: schedule ceiling, target: load instance, hoo_inputs: hours_of_operation hash }
# File lib/openstudio-standards/schedules/parametric.rb, line 491 def self.schedule_ruleset_get_parametric_inputs(schedule_ruleset, space_load_instance, parametric_inputs, hours_of_operation, ramp: true, min_ramp_dur_hr: 2.0, gather_data_only: false, hoo_var_method: 'hours') if parametric_inputs.key?(schedule_ruleset) && (hours_of_operation != parametric_inputs[schedule_ruleset][:hoo_inputs]) # don't warn if the hours of operation between old and new schedule are equivalent # OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.Schedule', "#{space_load_instance.name} uses #{schedule_ruleset.name} but parametric inputs have already been setup based on hours of operation for #{parametric_inputs[schedule_ruleset][:target].name}.") return nil end # gather and store data for scheduleRuleset min_max = OpenstudioStandards::Schedules.schedule_ruleset_get_min_max(schedule_ruleset) ruleset_hash = { floor: min_max['min'], ceiling: min_max['max'], target: space_load_instance, hoo_inputs: hours_of_operation } parametric_inputs[schedule_ruleset] = ruleset_hash # stop here if only gathering information otherwise will continue and generate additional parametric properties for schedules and rules if gather_data_only then return parametric_inputs end # set scheduleRuleset properties props = schedule_ruleset.additionalProperties # don't need to gather more than once return parametric_inputs if props.getFeatureAsString('param_sch_ver') == '0.0.1' props.setFeature('param_sch_ver', '0.0.1') # this is needed to see if formulas are in sync with version of standards that processes them also used to flag schedule as parametric props.setFeature('param_sch_floor', min_max['min']) props.setFeature('param_sch_ceiling', min_max['max']) # cleanup existing profiles OpenstudioStandards::Schedules.schedule_ruleset_cleanup_profiles(schedule_ruleset) # get initial hash of schedule days => rule index values schedule_days = OpenstudioStandards::Schedules.schedule_ruleset_get_schedule_day_rule_indices(schedule_ruleset) # get all day schedule equivalent full load hours to tag daily_flhs = schedule_days.keys.map { |day_sch| OpenstudioStandards::Schedules.schedule_day_get_equivalent_full_load_hours(day_sch) } # collect initial rule index => array of days used hash sch_ruleset_days_used = OpenstudioStandards::Schedules.schedule_ruleset_get_annual_days_used(schedule_ruleset) # match up schedule rule days with hours of operation days # sch_day_map is a hash where keys are the rule index values of the schedule # and values are hashes where keys are the hours of operation rule index, and values are arrays of days that the schedule sch_day_map = {} sch_ruleset_days_used.each do |sch_index, sch_days| # first create a hash that maps each day index to the hoo index that covers that day day_map = {} sch_days.each do |day| # find the hour of operation rule that contains the day number hoo_keys = hours_of_operation.find { |_, val| val[:days_used].include?(day) } if hoo_keys.nil? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.Schedule', "In #{__method__}, cannot find schedule #{schedule_days.key(sch_index).name.get} day #{day} in hour of operation profiles. Something went wrong.") end hoo_key = hoo_keys.first day_map[day] = hoo_key end # group days with the same hour of operation index grouped_days = Hash.new { |h, k| h[k] = [] } day_map.each { |day, hoo_idx| grouped_days[hoo_idx] << day } # group by schedule rule index sch_day_map[sch_index] = grouped_days end # create new rule corresponding to the hour of operation rules new_rule_ct = 0 rule_idxs_to_keep = [] sch_day_map.each do |sch_index, hoo_group| hoo_group.each do |hoo_index, day_group| # skip common default days next if sch_index == -1 && hoo_index == -1 # skip if rules already match if (sch_ruleset_days_used[sch_index] - day_group).empty? # OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.Schedules', "in #{__method__}: #{schedule_ruleset.name} rule #{sch_index} already matches hours of operation rule #{hoo_index}; new rule won't be created.") # keep these rules index values to avoid deleting later rule_idxs_to_keep << sch_index unless sch_index == -1 next end # create new rules new_rules = OpenstudioStandards::Schedules.schedule_ruleset_create_rules_from_day_list(schedule_ruleset, day_group, schedule_day: schedule_days.key(sch_index)) new_rule_ct += new_rules.size end end # new rules are created at top of list - cleanup old rules that have been replaced if !(new_rule_ct == 0 || new_rule_ct == schedule_ruleset.scheduleRules.size) # increase index values by the number of new rules rule_idxs_adjusted = rule_idxs_to_keep.map { |v| v + new_rule_ct } rules_to_remove = [] schedule_ruleset.scheduleRules.each_with_index do |rule, i| # don't remove new rules or rules that already match if (rule.ruleIndex > new_rule_ct - 1) && !rule_idxs_adjusted.include?(rule.ruleIndex) rules_to_remove << rule end end rules_to_remove.each(&:remove) end # re-collect new schedule rules schedule_days = OpenstudioStandards::Schedules.schedule_ruleset_get_schedule_day_rule_indices(schedule_ruleset) # re-collect new rule index => days used array sch_ruleset_days_used = OpenstudioStandards::Schedules.schedule_ruleset_get_annual_days_used(schedule_ruleset) # step through profiles and add additional properties to describe profiles schedule_days.each_with_index do |(schedule_day, current_rule_index), i| hoo_target_index = nil days_used = sch_ruleset_days_used[current_rule_index] # find days_used in hoo profiles that contains all days used from this profile hoo_profile_match_hash = {} best_fit_check = {} # loop through indices looking of rule in hoo that contains all days in the rule hours_of_operation.each do |profile_index, value| if (days_used - value[:days_used]).empty? hoo_target_index = profile_index end end # if schedule day days used can't be mapped to single hours of operation then do not use hoo variables, otherwise would have to split rule and alter model if hoo_target_index.nil? hoo_start = nil hoo_end = nil occ = nil vac = nil OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.Schedules', "In #{__method__}, schedule #{schedule_day.name} has no hours_of_operation target index. Won't be modified") # OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.Schedules', "In #{__method__}, schedule #{schedule_day.name} has no hours_of_operation target index. Won't be modified") else # get hours of operation for this specific profile hoo_start = hours_of_operation[hoo_target_index][:hoo_start] hoo_end = hours_of_operation[hoo_target_index][:hoo_end] occ = hours_of_operation[hoo_target_index][:hoo_hours] vac = 24.0 - hours_of_operation[hoo_target_index][:hoo_hours] end props = schedule_day.additionalProperties par_val_time_hash = {} # time is key, value is value in and optional value out as a one or two object array times = schedule_day.times values = schedule_day.values values.each_with_index do |value, j| # don't add value until 24 if it is the same as first value for non constant profiles if values.size > 1 && j == values.size - 1 && value == values.first next end current_time = times[j].totalHours # if step height goes floor to ceiling then do not ramp. if !ramp || (values.uniq.size < 3) # this will result in steps like old profiles, update to ramp in most cases if j == values.size - 1 par_val_time_hash[current_time] = [value, values.first] else par_val_time_hash[current_time] = [value, values[j + 1]] end else if j == 0 prev_time = times.last.totalHours - 24 # e.g. 24 would show as until 0 else prev_time = times[j - 1].totalHours end if j == values.size - 1 next_time = times.first.totalHours + 24 # e.g. 6 would show as until 30 next_value = values.first # do nothing if value is same as first value if value == next_value next end else next_time = times[j + 1].totalHours next_value = values[j + 1] end # delta time is min min_ramp_dur_hr, half of previous dur, half of next dur # todo - would be nice to change to 0.25 for vally less than 2 hours multiplier = 0.5 delta = [min_ramp_dur_hr, (current_time - prev_time) * multiplier, (next_time - current_time) * multiplier].min # add value to left if not already added if !par_val_time_hash.key?(current_time - delta) time_left = current_time - delta if time_left < 0.0 then time_left += 24.0 end par_val_time_hash[time_left] = [value] end # add value to right time_right = current_time + delta if time_right > 24.0 then time_right -= 24.0 end par_val_time_hash[time_right] = [next_value] end end # sort hash by keys par_val_time_hash.sort.to_h # calculate estimated value (not including any secondary logic) est_daily_flh = 0.0 prev_time = par_val_time_hash.keys.max - 24.0 prev_value = par_val_time_hash.values.last.last # last value in last optional pair of values par_val_time_hash.sort.each do |time, value_array| segment_length = time - prev_time avg_value = (value_array.first + prev_value) * 0.5 est_daily_flh += segment_length * avg_value prev_time = time prev_value = value_array.last end # test expected value against estimated value daily_flh = OpenstudioStandards::Schedules.schedule_day_get_equivalent_full_load_hours(schedule_day) percent_change = ((daily_flh - est_daily_flh) / daily_flh) * 100.0 if percent_change.abs > 0.05 # @todo this estimation can have flaws. Fix or remove it, make sure to update for secondary logic (if we implement that here) # post application checks compares against actual instead of estimated values OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.Schedule', "For day schedule #{schedule_day.name} in #{schedule_ruleset.name} there was a #{percent_change.round(4)}% change. Expected full load hours is #{daily_flh.round(4)}, but estimated value is #{est_daily_flh.round(4)}") end # puts "#{schedule_day.name}: par_val_time_hash: #{par_val_time_hash}" raw_string = [] # flags to control variable settings for tstats start_set = false end_set = false par_val_time_hash.sort.each do |time, value_array| # add in value variables # not currently using range, only using min max for constant schedules or schedules with just two values value_array_var = [] value_array.each do |val| if val == min_max['min'] && values.uniq.size < 3 value_array_var << 'val_flr' elsif val == min_max['max'] && values.uniq.size < 3 value_array_var << 'val_clg' else value_array_var << val end end # add in hoo variables when matching profile found if !hoo_start.nil? # identify which identifier (star,mid,end) time is closest to, which will impact formula structure # includes code to identify delta for wrap around of 24 formula_identifier = {} start_delta_array = [hoo_start - time, hoo_start - time + 24, hoo_start - time - 24] start_delta_array_abs = [(hoo_start - time).abs, (hoo_start - time + 24).abs, (hoo_start - time - 24).abs] start_delta_h = start_delta_array[start_delta_array_abs.index(start_delta_array_abs.min)] formula_identifier['start'] = start_delta_h mid_calc = hoo_start + (occ * 0.5) mid_delta_array = [mid_calc - time, mid_calc - time + 24, mid_calc - time - 24] mid_delta_array_abs = [(mid_calc - time).abs, (mid_calc - time + 24).abs, (mid_calc - time - 24).abs] mid_delta_h = mid_delta_array[mid_delta_array_abs.index(mid_delta_array_abs.min)] formula_identifier['mid'] = mid_delta_h end_delta_array = [hoo_end - time, hoo_end - time + 24, hoo_end - time - 24] end_delta_array_abs = [(hoo_end - time).abs, (hoo_end - time + 24).abs, (hoo_end - time - 24).abs] end_delta_h = end_delta_array[end_delta_array_abs.index(end_delta_array_abs.min)] formula_identifier['end'] = end_delta_h # need to store min absolute value to pick the best fit formula_identifier_min_abs = {} formula_identifier.each do |k, v| formula_identifier_min_abs[k] = v.abs end # puts formula_identifier # puts formula_identifier_min_abs # pick from possible formula approaches for any datapoint where x is hour value min_key = formula_identifier_min_abs.key(formula_identifier_min_abs.values.min) min_value = formula_identifier[min_key] case hoo_var_method when 'hours' # minimize x, which should be no greater than 12, see if rounding to 2 decimal places works min_value = min_value.round(2) if min_key == 'start' if min_value == 0 time = 'hoo_start' elsif min_value < 0 time = "hoo_start + #{min_value.abs}" else # greater than 0 time = "hoo_start - #{min_value}" end # puts time elsif min_key == 'mid' if min_value == 0 time = 'mid' # converted to variable for simplicity but could also be described like this # time = "hoo_start + occ * 0.5" elsif min_value < 0 time = "mid + #{min_value.abs}" else # greater than 0 time = "mid - #{min_value}" end # puts time else # min_key == "end" if min_value == 0 time = 'hoo_end' elsif min_value < 0 time = "hoo_end + #{min_value.abs}" else # greater than 0 time = "hoo_end - #{min_value}" end # puts time end when 'fractional' # minimize x(hour before converted to fraction), which should be no greater than 0.5 as fraction, see if rounding to 3 decimal places works if occ > 0 min_value_occ_fract = min_value.abs / occ else min_value_occ_fract = 0.0 end if vac > 0 min_value_vac_fract = min_value.abs / vac else min_value_vac_fract = 0.0 end if min_key == 'start' if min_value == 0 time = 'hoo_start' elsif min_value < 0 time = "hoo_start + occ * #{min_value_occ_fract.round(3)}" else # greater than 0 time = "hoo_start - vac * #{min_value_vac_fract.round(3)}" end elsif min_key == 'mid' # @todo see what is going wrong with after mid in formula if min_value == 0 time = 'mid' # converted to variable for simplicity but could also be described like this # time = "hoo_start + occ * 0.5" elsif min_value < 0 time = "mid + occ * #{min_value_occ_fract.round(3)}" else # greater than 0 time = "mid - occ * #{min_value_occ_fract.round(3)}" end else # min_key == "end" if min_value == 0 time = 'hoo_end' elsif min_value < 0 time = "hoo_end + vac * #{min_value_vac_fract.round(3)}" else # greater than 0 time = "hoo_end - occ * #{min_value_occ_fract.round(3)}" end end when 'tstat' # puts formula_identifier if min_key == 'start' && !start_set time = 'hoo_start + 0' start_set = true else time = 'hoo_end + 0' end end end # populate string if value_array_var.size == 1 raw_string << "#{time} ~ #{value_array_var.first}" else # should only have 1 or two values (value in and optional value out) raw_string << "#{time} ~ #{value_array_var.first} ~ #{value_array_var.last}" end end # puts "#{schedule_day.name}: param_day_profile: #{raw_string.join(' | ')}" # store profile formula with hoo and value variables props.setFeature('param_day_profile', raw_string.join(' | ')) # @todo not used yet, but will add methods described below and others # @todo lower infiltration based on air loop hours of operation if air loop has outdoor air object # @todo lower lighting or plug loads based on occupancy at given time steps in a space # @todo set elevator fraction based multiple factors such as trips, occupants per trip, and elevator type to determine floor consumption when not in use. props.setFeature('param_day_secondary_logic', '') # secondary logic method such as occupancy impacting schedule values props.setFeature('param_day_secondary_logic_arg_val', '') # optional argument used for some secondary logic applied to values # tag profile type # may be useful for parametric changes to tag typical, medium, minimal, or same ones with off_peak prefix # todo - I would like to use these same tags for hours of operation and have parametric tags then ignore the days of week and date range from the rule object # tagging min/max makes sense in fractional schedules but not temperature schedules like thermostats (specifically cooling setpoints) # todo - I think these tags should come from occpancy schedule for space(s) schedule. That way all schedules in a space will refer to same profile from hours of operation # todo - add school specific logic hear or in post processing, currently default profile for school may not be most prevalent one if current_rule_index == -1 props.setFeature('param_day_tag', 'typical_operation') elsif daily_flh == daily_flhs.min props.setFeature('param_day_tag', 'minimal_operation') elsif daily_flh == daily_flhs.max props.setFeature('param_day_tag', 'maximum_operation') # normally this should not be used as typical should be the most active day else props.setFeature('param_day_tag', 'medium_operation') # not min max or typical end end return parametric_inputs 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
# File lib/openstudio-standards/schedules/information.rb, line 906 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
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.
@param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object @return [Hash<OpenStudio:Time>] returns as hash with ‘start_time’, ‘end time’]
# File lib/openstudio-standards/schedules/information.rb, line 765 def self.schedule_ruleset_get_start_and_end_times(schedule_ruleset) # validate schedule unless schedule_ruleset.to_ScheduleRuleset.is_initialized OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} failed because object #{schedule_ruleset.name.get} is not a ScheduleRuleset.") return [nil, nil] end # 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) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) else 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 that are used by this schedule ruleset day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date) # Get a 365-value 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 } day_sch_freq = day_sch_freq.sort_by { |freq| freq[1].size } common_day_freq = day_sch_freq.last # 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 # Get the most common day schedule sch_index = common_day_freq[0] number_of_days_sch_used = common_day_freq[1].size # Get the day schedule at this index day_sch = if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule) schedule_ruleset.defaultDaySchedule else schedule_index_to_day[sch_index] end # Determine the full load hours for just one day values = [] times = [] day_sch.times.each_with_index do |time, i| times << day_sch.times[i] values << day_sch.values[i] end # Get the minimum value start_val = values.first end_val = values.last # Get the start time (first time value goes above minimum) start_time = nil values.each_with_index do |val, i| break if i == values.size - 1 # Stop if we reach end of array if val == start_val && values[i + 1] > start_val start_time = times[i] break end end # Get the end time (first time value goes back down to minimum) end_time = nil values.each_with_index do |val, i| if i < values.size - 1 if val > end_val && values[i + 1] == end_val end_time = times[i] break end else if val > end_val && values[0] == start_val # Check first hour of day for schedules that end at midnight end_time = OpenStudio::Time.new(0, 24, 0, 0) break end end end return { 'start_time' => start_time, 'end_time' => end_time } 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
# File lib/openstudio-standards/schedules/information.rb, line 734 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} failed because object #{schedule_ruleset.name.get} 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.getDaySchedules(start_date, end_date) day_schedules.each do |day_schedule| time = interval while time < day values << day_schedule.getValue(time) time += interval end end timeseries = OpenStudio::TimeSeries.new(start_date, interval, OpenStudio.createVector(values), '') return timeseries end
Apply specified hours of operation values to rules in this schedule. Weekday values will be applied to the default profile. Weekday values will be applied to any rules that are used on a weekday. Saturday values will be applied to any rules that are used on a Saturday. Sunday values will be applied to any rules that are used on a Sunday. If a rule applies to Weekdays, Saturdays, and/or Sundays, values will be applied in that order of precedence. If a rule does not apply to any of these days, it is unused and will not be modified.
@param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] schedule ruleset object @param wkdy_start_time [OpenStudio::Time] Weekday start time. If nil, no change will be made to this day. @param wkdy_end_time [OpenStudio::Time] Weekday end time. If greater than 24:00, hours of operation will wrap over midnight. @param sat_start_time [OpenStudio::Time] Saturday start time. If nil, no change will be made to this day. @param sat_end_time [OpenStudio::Time] Saturday end time. If greater than 24:00, hours of operation will wrap over midnight. @param sun_start_time [OpenStudio::Time] Sunday start time. If nil, no change will be made to this day. @param sun_end_time [OpenStudio::Time] Sunday end time. If greater than 24:00, hours of operation will wrap over midnight. @return [Boolean] returns true if successful, false if not
# File lib/openstudio-standards/schedules/parametric.rb, line 904 def self.schedule_ruleset_set_hours_of_operation(schedule_ruleset, wkdy_start_time: nil, wkdy_end_time: nil, sat_start_time: nil, sat_end_time: nil, sun_start_time: nil, sun_end_time: nil) # Default day is assumed to represent weekdays if wkdy_start_time && wkdy_end_time schedule_day_set_hours_of_operation(schedule_ruleset.defaultDaySchedule, wkdy_start_time, wkdy_end_time) # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ScheduleRuleset', "For #{schedule_ruleset.name}, set default operating hours to #{wkdy_start_time}-#{wkdy_end_time}.") end # Modify each rule schedule_ruleset.scheduleRules.each do |rule| if rule.applyMonday || rule.applyTuesday || rule.applyWednesday || rule.applyThursday || rule.applyFriday if wkdy_start_time && wkdy_end_time schedule_day_set_hours_of_operation(rule.daySchedule, wkdy_start_time, wkdy_end_time) # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ScheduleRuleset', "For #{schedule_ruleset.name}, set Saturday rule operating hours to #{wkdy_start_time}-#{wkdy_end_time}.") end elsif rule.applySaturday if sat_start_time && sat_end_time schedule_day_set_hours_of_operation(rule.daySchedule, sat_start_time, sat_end_time) # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ScheduleRuleset', "For #{schedule_ruleset.name}, set Saturday rule operating hours to #{sat_start_time}-#{sat_end_time}.") end elsif rule.applySunday if sun_start_time && sun_end_time schedule_day_set_hours_of_operation(rule.daySchedule, sun_start_time, sun_end_time) # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ScheduleRuleset', "For #{schedule_ruleset.name}, set Sunday rule operating hours to #{sun_start_time}-#{sun_end_time}.") end end end return true 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
# File lib/openstudio-standards/schedules/modify.rb, line 153 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 end # alter profiles profiles.each do |profile| times = profile.times i = 0 profile.values.each do |sch_value| case modification_type when 'Multiplier', 'Percentage' # percentage was used early on but Multiplier is preferable 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 new_value = [lower_bound, [upper_bound, sch_value + value].min].max profile.addValue(times[i], new_value) end i += 1 end end return schedule_ruleset end
Increase/decrease by percentage or static value change value when time passes test
@param schedule_ruleset [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object @param hhmm_before [String] time before string in hhmm format, e.g. 1530 @param hhmm_after [String] string in hhmm format, e.g. 1530 @param inside_value [Double] @param outside_value [Double] @param modification_type [String] Options are ‘Sum’, which adds to the value,
and 'Replace' which replaces the value
@return [OpenStudio::Model::ScheduleRuleset] OpenStudio ScheduleRuleset object
# File lib/openstudio-standards/schedules/modify.rb, line 263 def self.schedule_ruleset_time_conditional_adjust_value(schedule_ruleset, hhmm_before, hhmm_after, inside_value, outside_value, modification_type = 'Sum') # setup variables array = hhmm_before.to_s.split('') before_hour = "#{array[0]}#{array[1]}".to_i before_min = "#{array[2]}#{array[3]}".to_i array = hhmm_after.to_s.split('') after_hour = "#{array[0]}#{array[1]}".to_i after_min = "#{array[2]}#{array[3]}".to_i # gather profiles profiles = [] schedule = schedule_ruleset.to_ScheduleRuleset.get default_profile = schedule_ruleset.defaultDaySchedule profiles << default_profile rules = schedule_ruleset.scheduleRules rules.each do |rule| profiles << rule.daySchedule end # alter profiles profiles.each do |day_sch| times = day_sch.times i = 0 # set times special times needed for methods below before_time = OpenStudio::Time.new(0, before_hour, before_min, 0) after_time = OpenStudio::Time.new(0, after_hour, after_min, 0) # day_end_time = OpenStudio::Time.new(0, 24, 0, 0) # add datapoint at before and after time original_value_at_before_time = day_sch.getValue(before_time) original_value_at_after_time = day_sch.getValue(after_time) day_sch.addValue(before_time, original_value_at_before_time) day_sch.addValue(after_time, original_value_at_after_time) # make arrays for original times and values times = day_sch.times sch_values = day_sch.values day_sch.clearValues # make arrays for new values new_times = [] new_values = [] # loop through original time/value pairs to populate new array for i in 0..(sch_values.length - 1) new_times << times[i] if times[i] > before_time && times[i] <= after_time # updated this so times[i] == before_time goes into the else if inside_value.nil? new_values << sch_values[i] elsif modification_type == 'Sum' new_values << (inside_value + sch_values[i]) elsif modification_type == 'Replace' new_values << inside_value else # should be Multiplier new_values << (inside_value * sch_values[i]) end else if outside_value.nil? new_values << sch_values[i] elsif modification_type == 'Sum' new_values << (outside_value + sch_values[i]) elsif modification_type == 'Replace' new_values << outside_value else # should be Multiplier new_values << (outside_value * sch_values[i]) end end end # generate new day_sch values for i in 0..(new_values.length - 1) day_sch.addValue(new_times[i], new_values[i]) end end return schedule_ruleset end
Gathers parametric inputs for all loads objects associated with spaces/space types in provided array. Parametric formulas are encoded in AdditionalProperties objects attached to the ScheduleRuleset.
@author David Goldwasser @param spaces_space_types [Array] array of OpenStudio::Model::Space or OpenStudio::Model::SpaceType objects @param parametric_inputs [Hash] parametric inputs hash of ScheduleRuleset, example:
{ floor: schedule floor, ceiling: schedule ceiling, target: load instance, hoo_inputs: hours_of_operation hash }
@param gather_data_only [Boolean] if true, no changes will be made to schedules @return [Hash] parametric inputs hash of ScheduleRuleset, example:
{ floor: schedule floor, ceiling: schedule ceiling, target: load instance, hoo_inputs: hours_of_operation hash }
# File lib/openstudio-standards/schedules/parametric.rb, line 410 def self.spaces_space_types_get_parametric_schedule_inputs(spaces_space_types, parametric_inputs, gather_data_only) spaces_space_types.each do |space_type| # get hours of operation for space type once next if space_type.instance_of?(OpenStudio::Model::SpaceType) && space_type.floorArea == 0 hours_of_operation = Space.space_hours_of_operation(space_type) if hours_of_operation.nil? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.Space', "Can't evaluate schedules for #{space_type.name}, doesn't have hours of operation.") next end # loop through internal load instances space_type.lights.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.luminaires.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.electricEquipment.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.gasEquipment.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.steamEquipment.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.otherEquipment.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.people.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) if space_load_instance.activityLevelSchedule.is_initialized && space_load_instance.activityLevelSchedule.get.to_ScheduleRuleset.is_initialized act_sch = space_load_instance.activityLevelSchedule.get.to_ScheduleRuleset.get OpenstudioStandards::Schedules.schedule_ruleset_get_parametric_inputs(act_sch, space_load_instance, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: 'hours') end end space_type.spaceInfiltrationDesignFlowRates.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.spaceInfiltrationEffectiveLeakageAreas.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end dsgn_spec_oa = space_type.designSpecificationOutdoorAir if dsgn_spec_oa.is_initialized OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(dsgn_spec_oa.get, parametric_inputs, hours_of_operation, gather_data_only) end end return parametric_inputs end