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_complex_schedule(model, options = {}) click to toggle source

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_schedule_ruleset(model, value, name: nil, schedule_type_limit: nil) click to toggle source

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_schedule_from_rate_of_change(model, schedule_ruleset) click to toggle source

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_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) click to toggle source

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?
      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
    else
      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
    end
  end
  return schedule_type_limits
end
create_simple_schedule(model, options = {}) click to toggle source

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
create_weighted_merge_schedules(model, schedule_weights_hash, sch_name: 'Merged Schedule') click to toggle source

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
model_apply_parametric_schedules(model, ramp_frequency: nil, infer_hoo_for_non_assigned_objects: true, error_on_out_of_order: true) click to toggle source

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
model_infer_hours_of_operation_building(model, fraction_of_daily_occ_range: 0.25, invert_res: true, gen_occ_profile: false) click to toggle source

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
model_setup_parametric_schedules(model, step_ramp_logic: nil, infer_hoo_for_non_assigned_objects: true, gather_data_only: false, hoo_var_method: 'hours') click to toggle source

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
schedule_compact_get_design_day_min_max(schedule_compact, type = 'winter') click to toggle source

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.size.zero?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Could not find any value in #{schedule_compact.name} design day schedule when determining min and max.")
    result = { 'min' => nil, 'max' => nil }
    return result
  end

  result = { 'min' => vals.min, 'max' => vals.max }

  return result
end
schedule_compact_get_hourly_values(schedule_compact) click to toggle source

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
schedule_compact_get_min_max(schedule_compact) click to toggle source

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.size.zero?
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Could not find any value in #{schedule_compact.name} when determining min and max.")
    result = { 'min' => nil, 'max' => nil }
    return result
  end

  result = { 'min' => vals.min, 'max' => vals.max }

  return result
end
schedule_constant_get_design_day_min_max(schedule_constant, type) click to toggle source

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
schedule_constant_get_equivalent_full_load_hours(schedule_constant) click to toggle source

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
schedule_constant_get_hourly_values(schedule_constant) click to toggle source

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
schedule_constant_get_min_max(schedule_constant) click to toggle source

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
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) click to toggle source

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 1125
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

  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
    time = time.gsub('mid', (hoo_start + occ * 0.5).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
        # pick midpoint and put each time there. e.g. times of (2,7,9,8,11) would be changed to  (2,7,8.5,8.5,11)
        delta = last_time - time_value_pair[0]

        # 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

        # center if possible but don't exceed available buffer
        updated_time = time_value_pairs[i - 1][0] - [delta / 2.0, 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] != 24.0
    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
schedule_day_get_equivalent_full_load_hours(schedule_day) click to toggle source

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 316
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
schedule_day_get_hourly_values(schedule_day) click to toggle source

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 337
def self.schedule_day_get_hourly_values(schedule_day)
  schedule_values = []

  # determine smallest time interval
  times = schedule_day.times
  time_interval_min = 15.0
  previous_time_decimal = 0.0
  times.each_with_index do |time, i|
    time_decimal = (time.days * 24.0 * 60.0) + (time.hours * 60.0) + time.minutes + (time.seconds / 60)
    interval_min = time_decimal - previous_time_decimal
    time_interval_min = interval_min if interval_min < time_interval_min
    previous_time_decimal = time_decimal
  end
  time_interval_min = time_interval_min.round(0).to_i

  # get the hourly average by averaging the values in the hour at the smallest time interval
  (0..23).each do |j|
    values = []
    times = (time_interval_min..60).step(time_interval_min).to_a
    times.each { |t| values << schedule_day.getValue(OpenStudio::Time.new(0, j, t, 0)) }
    schedule_values << (values.sum / times.size).round(5)
  end

  return schedule_values
end
schedule_day_get_min_max(schedule_day) click to toggle source

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
  schedule_day.values.each do |value|
    if min.nil?
      min = value
    else
      if min > value then min = value end
    end
    if max.nil?
      max = value
    else
      if max < value then max = value end
    end
  end

  result = { 'min' => min, 'max' => max }
end
schedule_day_multiply_by_value(schedule_day, multiplier, lower_apply_limit: nil) click to toggle source

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
schedule_day_populate_from_array_of_values(schedule_day, value_array) click to toggle source

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
schedule_day_set_hours_of_operation(schedule_day, start_time, end_time) click to toggle source

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
schedule_get_design_day_min_max(schedule, type = 'winter') click to toggle source

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', 'schedule_get_design_day_min_max does not yet support ScheduleYear schedules.')
    result = { 'min' => nil, 'max' => nil }
  when 'OS_Schedule_Interval'
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_design_day_min_max does not yet support ScheduleInterval schedules.')
    result = { 'min' => nil, 'max' => nil }
  else
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for schedule_get_design_day_min_max.")
    result = { 'min' => nil, 'max' => nil }
  end

  return result
end
schedule_get_equivalent_full_load_hours(schedule) click to toggle source

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', 'schedule_get_equivalent_full_load_hours does not yet support ScheduleCompact schedules.')
    result = nil
  when 'OS_Schedule_Year'
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_equivalent_full_load_hours does not yet support ScheduleYear schedules.')
    result = nil
  when 'OS_Schedule_Interval'
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_equivalent_full_load_hours does not yet support ScheduleInterval schedules.')
    result = nil
  else
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for schedule_get_equivalent_full_load_hours.")
    result = nil
  end

  return result
end
schedule_get_hourly_values(schedule) click to toggle source

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', 'schedule_get_hourly_values does not yet support ScheduleYear schedules.')
    result = nil
  when 'OS_Schedule_Interval'
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_hourly_values does not yet support ScheduleInterval schedules.')
    result = nil
  else
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for schedule_get_hourly_values.")
    result = nil
  end

  return result
end
schedule_get_min_max(schedule, only_run_period_values: false) click to toggle source

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', 'schedule_get_min_max does not yet support ScheduleYear schedules.')
    result = { 'min' => nil, 'max' => nil }
  when 'OS_Schedule_Interval'
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', 'schedule_get_min_max does not yet support ScheduleInterval schedules.')
    result = { 'min' => nil, 'max' => nil }
  else
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for schedule_get_min_max.")
    result = { 'min' => nil, 'max' => nil }
  end

  return result
end
schedule_ruleset_add_rule(schedule_ruleset, values, start_date: nil, end_date: nil, day_names: nil, rule_name: nil) click to toggle source

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
schedule_ruleset_adjust_hours_of_operation(schedule_ruleset, options = {}) click to toggle source

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
schedule_ruleset_apply_parametric_inputs(schedule_ruleset, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order, parametric_inputs = nil) click to toggle source

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 928
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
schedule_ruleset_cleanup_profiles(schedule_ruleset) click to toggle source

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
schedule_ruleset_conditional_adjust_value(schedule_ruleset, test_value, pass_value, fail_value, floor_value, modification_type = 'Multiplier') click to toggle source

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
schedule_ruleset_create_rules_from_day_list(schedule_ruleset, days_used, schedule_day: nil) click to toggle source

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
schedule_ruleset_get_annual_days_used(schedule_ruleset) click to toggle source

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 869
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
schedule_ruleset_get_day_schedules(schedule_ruleset, include_design_days: false) click to toggle source

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 839
def self.schedule_ruleset_get_day_schedules(schedule_ruleset, include_design_days: false)
  profiles = []
  profiles << schedule_ruleset.defaultDaySchedule
  schedule_ruleset.scheduleRules.each do |rule|
    profiles << rule.daySchedule
  end

  if include_design_days

    if schedule_ruleset.isSummerDesignDayScheduleDefaulted
      OpenStudio.logFree(OpenStudio::Warning, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_day_schedules called for #{schedule_ruleset.name.get} with include_design_days: true, but the summer design day is defaulted. Duplicate design day will not be added.")
    else
      profiles << rule.summerDesignDaySchedule
    end

    if schedule_ruleset.isWinterDesignDayScheduleDefaulted
      OpenStudio.logFree(OpenStudio::Warning, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_day_schedules called for #{schedule_ruleset.name.get} with include_design_days: true, but the winter design day is defaulted. Duplicate design day will not be added.")
    else
      profiles << rule.winterDesignDaySchedule
    end

  end

  return profiles
end
schedule_ruleset_get_design_day_min_max(schedule_ruleset, type = 'winter') click to toggle source

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 473
def self.schedule_ruleset_get_design_day_min_max(schedule_ruleset, type = 'winter')
  # validate schedule
  unless schedule_ruleset.to_ScheduleRuleset.is_initialized
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_design_day_min_max() failed because object #{schedule_ruleset} is not a ScheduleRuleset.")
    return nil
  end

  if type == 'winter'
    schedule = schedule_ruleset.winterDesignDaySchedule
  elsif type == 'summer'
    schedule = schedule_ruleset.summerDesignDaySchedule
  end

  if !schedule
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', "#{schedule_ruleset.name} is missing #{type} design day schedule, use default day schedule to process the min max search")
    schedule = schedule_ruleset.defaultDaySchedule
  end

  min = nil
  max = nil
  schedule.values.each do |value|
    if min.nil?
      min = value
    else
      min = value if min > value
    end
    if max.nil?
      max = value
    else
      max = value if max < value
    end
  end
  result = { 'min' => min, 'max' => max }

  return result
end
schedule_ruleset_get_equivalent_full_load_hours(schedule_ruleset) click to toggle source

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 516
def self.schedule_ruleset_get_equivalent_full_load_hours(schedule_ruleset)
  # validate schedule
  unless schedule_ruleset.to_ScheduleRuleset.is_initialized
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_equivalent_full_load_hours() failed because object #{schedule_ruleset} is not a ScheduleRuleset.")
    return nil
  end

  # define the start and end date
  year_start_date = nil
  year_end_date = nil
  if schedule_ruleset.model.yearDescription.is_initialized
    year_description = schedule_ruleset.model.yearDescription.get
    year = year_description.assumedYear
    year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year)
    year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', 'Year description is not specified. Full load hours calculation will assume 2009, the default year OS uses.')
    year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009)
    year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009)
  end

  # Get the ordered list of all the day schedules
  day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date)

  # Get the array of which schedule is used on each day of the year
  day_schs_used_each_day = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date)

  # Create a map that shows how many days each schedule is used
  day_sch_freq = day_schs_used_each_day.group_by { |n| n }

  # Build a hash that maps schedule day index to schedule day
  schedule_index_to_day = {}
  day_schs.each_with_index do |day_sch, i|
    schedule_index_to_day[day_schs_used_each_day[i]] = day_sch
  end

  # Loop through each of the schedules that is used, figure out the
  # full load hours for that day, then multiply this by the number
  # of days that day schedule applies and add this to the total.
  annual_flh = 0.0
  max_daily_flh = 0.0
  default_day_sch = schedule_ruleset.defaultDaySchedule
  day_sch_freq.each do |freq|
    sch_index = freq[0]
    number_of_days_sch_used = freq[1].size

    # Get the day schedule at this index
    day_sch = nil
    if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule)
      day_sch = default_day_sch
    else
      day_sch = schedule_index_to_day[sch_index]
    end
    daily_flh = OpenstudioStandards::Schedules.schedule_day_get_equivalent_full_load_hours(day_sch)

    # Multiply the daily EFLH by the number
    # of days this schedule is used per year
    # and add this to the overall total
    annual_flh += daily_flh * number_of_days_sch_used
  end

  # Warn if the max daily EFLH is more than 24,
  # which would indicate that this isn't a fractional schedule.
  if max_daily_flh > 24
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', "#{schedule_ruleset.name} has more than 24 EFLH in one day schedule, indicating that it is not a fractional schedule.")
  end

  return annual_flh
end
schedule_ruleset_get_hourly_values(schedule_ruleset) click to toggle source

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 591
def self.schedule_ruleset_get_hourly_values(schedule_ruleset)
  # validate schedule
  unless schedule_ruleset.to_ScheduleRuleset.is_initialized
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_hourly_values() failed because object #{schedule_ruleset} is not a ScheduleRuleset.")
    return nil
  end

  # define the start and end date
  year_start_date = nil
  year_end_date = nil
  if schedule_ruleset.model.yearDescription.is_initialized
    year_description = schedule_ruleset.model.yearDescription.get
    year = year_description.assumedYear
    year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year)
    year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', 'Year description is not specified. Annual hours above value calculation will assume 2009, the default year OS uses.')
    year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009)
    year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009)
  end

  # Get the ordered list of all the day schedules
  day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date)

  # Loop through each day schedule and add its hours to total
  # @todo store the 24 hourly average values for each day schedule instead of recalculating for all days
  annual_hourly_values = []
  day_schs.each do |day_sch|
    # add daily average hourly values to annual hourly values array
    daily_hours = OpenstudioStandards::Schedules.schedule_day_get_hourly_values(day_sch)
    daily_hours.each { |h| annual_hourly_values << h }
  end

  return annual_hourly_values
end
schedule_ruleset_get_hours_above_value(schedule_ruleset, lower_limit) click to toggle source

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 633
def self.schedule_ruleset_get_hours_above_value(schedule_ruleset, lower_limit)
  # validate schedule
  unless schedule_ruleset.to_ScheduleRuleset.is_initialized
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_hours_above_value() failed because object #{schedule_ruleset} is not a ScheduleRuleset.")
    return nil
  end

  # define the start and end date
  year_start_date = nil
  year_end_date = nil
  if schedule_ruleset.model.yearDescription.is_initialized
    year_description = schedule_ruleset.model.yearDescription.get
    year = year_description.assumedYear
    year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year)
    year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year)
  else
    OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', 'Year description is not specified. Annual hours above value calculation will assume 2009, the default year OS uses.')
    year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009)
    year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009)
  end

  # Get the ordered list of all the day schedules
  day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date)

  # Get the array of which schedule is used on each day of the year
  day_schs_used_each_day = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date)

  # Create a map that shows how many days each schedule is used
  day_sch_freq = day_schs_used_each_day.group_by { |n| n }

  # Build a hash that maps schedule day index to schedule day
  schedule_index_to_day = {}
  day_schs.each_with_index do |day_sch, i|
    schedule_index_to_day[day_schs_used_each_day[i]] = day_sch
  end

  # Loop through each of the schedules that is used, figure out the
  # hours for that day, then multiply this by the number
  # of days that day schedule applies and add this to the total.
  annual_hrs = 0.0
  default_day_sch = schedule_ruleset.defaultDaySchedule
  day_sch_freq.each do |freq|
    sch_index = freq[0]
    number_of_days_sch_used = freq[1].size

    # Get the day schedule at this index
    day_sch = nil
    day_sch = if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule)
                default_day_sch
              else
                schedule_index_to_day[sch_index]
              end

    # Determine the hours for just one day
    daily_hrs = 0.0
    values = day_sch.values
    times = day_sch.times

    previous_time_decimal = 0.0
    times.each_with_index do |time, i|
      time_decimal = (time.days * 24.0) + time.hours + (time.minutes / 60.0) + (time.seconds / 3600.0)
      duration_of_value = time_decimal - previous_time_decimal
      if values[i] > lower_limit
        daily_hrs += duration_of_value
      end
      previous_time_decimal = time_decimal
    end

    # Multiply the daily hours by the number
    # of days this schedule is used per year
    # and add this to the overall total
    annual_hrs += daily_hrs * number_of_days_sch_used
  end

  return annual_hrs
end
schedule_ruleset_get_min_max(schedule_ruleset, only_run_period_values: false) click to toggle source

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 376
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 schedule_ruleset_get_min_max() failed because object #{schedule_ruleset} 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|
    day_schedule.values.each do |value|
      if min.nil?
        min = value
      else
        if min > value then min = value end
      end
      if max.nil?
        max = value
      else
        if max < value then max = value end
      end
    end
  end
  result = { 'min' => min, 'max' => max }

  return result
end
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') click to toggle source

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)
    if 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
  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
  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 indices
  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 = {}
  sch_ruleset_days_used.each do |sch_index, sch_days|
    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
  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.")
        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
  schedule_ruleset.scheduleRules[new_rule_ct..-1].each(&:remove) unless new_rule_ct == 0

  # 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 ot 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]

        if hoo_var_method == '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

        elsif hoo_var_method == '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

        elsif hoo_var_method == '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
schedule_ruleset_get_schedule_day_rule_indices(schedule_ruleset) click to toggle source

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 886
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
schedule_ruleset_get_start_and_end_times(schedule_ruleset) click to toggle source

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 745
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 schedule_ruleset_get_start_and_end_times() failed because object #{schedule_ruleset} 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
schedule_ruleset_get_timeseries(schedule_ruleset) click to toggle source

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 714
def self.schedule_ruleset_get_timeseries(schedule_ruleset)
  # validate schedule
  unless schedule_ruleset.to_ScheduleRuleset.is_initialized
    OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Method schedule_ruleset_get_timeseries() failed because object #{schedule_ruleset} is not a ScheduleRuleset.")
    return nil
  end

  yd = schedule_ruleset.model.getYearDescription
  start_date = yd.makeDate(1, 1)
  end_date = yd.makeDate(12, 31)

  values = OpenStudio::DoubleVector.new
  day = OpenStudio::Time.new(1.0)
  interval = OpenStudio::Time.new(1.0 / 48.0)
  day_schedules = schedule_ruleset.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
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) click to toggle source

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 884
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
schedule_ruleset_simple_value_adjust(schedule_ruleset, value, modification_type = 'Multiplier') click to toggle source

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
schedule_ruleset_time_conditional_adjust_value(schedule_ruleset, hhmm_before, hhmm_after, inside_value, outside_value, modification_type = 'Sum') click to toggle source

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
spaces_space_types_get_parametric_schedule_inputs(spaces_space_types, parametric_inputs, gather_data_only) click to toggle source

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.class == 'OpenStudio::Model::SpaceTypes' && 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