Class: ShiftHoursOfOperation

Inherits:
OpenStudio::Measure::ModelMeasure
  • Object
show all
Defined in:
lib/measures/shift_hours_of_operation/measure.rb

Overview

start the measure

Instance Method Summary collapse

Instance Method Details

#arguments(model) ⇒ Object

define the arguments that the user will input



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/measures/shift_hours_of_operation/measure.rb', line 33

def arguments(model)
  args = OpenStudio::Measure::OSArgumentVector.new

  # delta hoo_start for weekdays
  hoo_start_weekday = OpenStudio::Measure::OSArgument.makeDoubleArgument('hoo_start_weekday', true)
  hoo_start_weekday.setDisplayName('Shift the weekday start of hours of operation.')
  hoo_start_weekday.setDescription('Use decimal hours so an 1 hour and 15 minute shift would be 1.25. Positive value moves the hour of operation later')
  hoo_start_weekday.setDefaultValue(0.0)
  hoo_start_weekday.setUnits('Hours')
  hoo_start_weekday.setMinValue(-24.0)
  hoo_start_weekday.setMaxValue(24.0)
  args << hoo_start_weekday

  # delta hoo_dur for weekday
  hoo_dur_weekday = OpenStudio::Measure::OSArgument.makeDoubleArgument('hoo_dur_weekday', true)
  hoo_dur_weekday.setDisplayName('Extend the weekday of hours of operation.')
  hoo_dur_weekday.setDescription('Use decimal hours so an 1 hour and 15 minute would be 1.25. Positive value makes the hour of operation longer.')
  hoo_dur_weekday.setDefaultValue(0.0)
  hoo_dur_weekday.setUnits('Hours')
  hoo_dur_weekday.setMinValue(-24.0)
  hoo_dur_weekday.setMaxValue(24.0)
  args << hoo_dur_weekday

  # TODO: - could include every day of the week

  # delta hoo_start for saturdays
  hoo_start_saturday = OpenStudio::Measure::OSArgument.makeDoubleArgument('hoo_start_saturday', true)
  hoo_start_saturday.setDisplayName('Shift the saturday start of hours of operation.')
  hoo_start_saturday.setDescription('Use decimal hours so an 1 hour and 15 minute shift would be 1.25. Positive value moves the hour of operation later')
  hoo_start_saturday.setDefaultValue(0.0)
  hoo_start_saturday.setUnits('Hours')
  hoo_start_saturday.setMinValue(-24.0)
  hoo_start_saturday.setMaxValue(24.0)
  args << hoo_start_saturday

  # delta hoo_dur for saturday
  hoo_dur_saturday = OpenStudio::Measure::OSArgument.makeDoubleArgument('hoo_dur_saturday', true)
  hoo_dur_saturday.setDisplayName('Extend the saturday of hours of operation.')
  hoo_dur_saturday.setDescription('Use decimal hours so an 1 hour and 15 minute would be 1.25. Positive value makes the hour of operation longer.')
  hoo_dur_saturday.setDefaultValue(0.0)
  hoo_dur_saturday.setUnits('Hours')
  hoo_dur_saturday.setMinValue(-24.0)
  hoo_dur_saturday.setMaxValue(24.0)
  args << hoo_dur_saturday

  # delta hoo_start for sundays
  hoo_start_sunday = OpenStudio::Measure::OSArgument.makeDoubleArgument('hoo_start_sunday', true)
  hoo_start_sunday.setDisplayName('Shift the sunday start of hours of operation.')
  hoo_start_sunday.setDescription('Use decimal hours so an 1 hour and 15 minute shift would be 1.25. Positive value moves the hour of operation later')
  hoo_start_sunday.setDefaultValue(0.0)
  hoo_start_sunday.setUnits('Hours')
  hoo_start_sunday.setMinValue(-24.0)
  hoo_start_sunday.setMaxValue(24.0)
  args << hoo_start_sunday

  # delta hoo_dur for sunday
  hoo_dur_sunday = OpenStudio::Measure::OSArgument.makeDoubleArgument('hoo_dur_sunday', true)
  hoo_dur_sunday.setDisplayName('Extend the sunday of hours of operation.')
  hoo_dur_sunday.setDescription('Use decimal hours so an 1 hour and 15 minute would be 1.25. Positive value makes the hour of operation longer.')
  hoo_dur_sunday.setDefaultValue(0.0)
  hoo_dur_sunday.setUnits('Hours')
  hoo_dur_sunday.setMinValue(-24.0)
  hoo_dur_sunday.setMaxValue(24.0)
  args << hoo_dur_sunday

  # TODO: - could include start and end days to have delta or absolute values applied to. (maybe decimal between 1.0 and 13.0 month where 3.50 would be March 15th)

  # make an argument for delta_values
  delta_values = OpenStudio::Measure::OSArgument.makeBoolArgument('delta_values', true)
  delta_values.setDisplayName('Hours of operation values treated as deltas')
  delta_values.setDescription('When this is true the hours of operation start and duration represent a delta from the original model values. When switched to false they represent absolute values.')
  delta_values.setDefaultValue(true)
  args << delta_values

  # make an argument for infer_parametric_schedules
  infer_parametric_schedules = OpenStudio::Measure::OSArgument.makeBoolArgument('infer_parametric_schedules', true)
  infer_parametric_schedules.setDisplayName('Dynamically generate parametric schedules from current ruleset schedules.')
  infer_parametric_schedules.setDescription('When this is true the parametric schedule formulas and hours of operation will be generated from the existing model schedules. When false it expects the model already has parametric formulas stored.')
  infer_parametric_schedules.setDefaultValue(true)
  args << infer_parametric_schedules

  # delta hoo_start for sundays
  fraction_of_daily_occ_range = OpenStudio::Measure::OSArgument.makeDoubleArgument('fraction_of_daily_occ_range', true)
  fraction_of_daily_occ_range.setDisplayName('Fraction of Daily Occupancy Range.')
  fraction_of_daily_occ_range.setDescription('This determine what fraction of occupancy be considered operating conditions. This fraction is normalized to expanded to range seen over the full year and does not necessary equal fraction of design occupancy. This value should be between 0 and 1.0 and is only used if dynamically generated parametric schedules are used.')
  fraction_of_daily_occ_range.setDefaultValue(0.25)
  fraction_of_daily_occ_range.setUnits('Hours')
  fraction_of_daily_occ_range.setMinValue(0.0)
  fraction_of_daily_occ_range.setMaxValue(1.0)
  args << fraction_of_daily_occ_range

  # argument to choose hour of operation variable method
  choices = OpenStudio::StringVector.new
  choices << 'fractional'
  choices << 'hours'
  hoo_var_method = OpenStudio::Measure::OSArgument.makeChoiceArgument('hoo_var_method', choices, true)
  hoo_var_method.setDisplayName('Hours of Operation Variable Method for Scheudle Profile Formula.')
  hoo_var_method.setDescription('If dynamically generate parametric schedules from current ruleset scheudles is selected ,this argument is used to determine if the schedule profile formulas define time of points in a profile as a a specific delta from the star, middle, or end of the horus of operation, or if the delta is fractaionl percentate of the horus fo operation ro non-operation/vacant time.')
  hoo_var_method.setDefaultValue('fractional')
  args << hoo_var_method

  # make an argument for target_hoo_from_model
  # Should only be true when infer_parametric_schedules is false
  target_hoo_from_model = OpenStudio::Measure::OSArgument.makeBoolArgument('target_hoo_from_model', true)
  target_hoo_from_model.setDisplayName('Use model hours of operation as target')
  target_hoo_from_model.setDescription('The default behavior is for this to be false. This can not be used unless Dynamically generate parametric schedules from current ruleset schedules is set to false and if the schedules in the model already have parametric profiles. When changed to true all of the hours of operation start and duration values will be ignored as the bool to treat those values as relative or absolute. Instead the hours of operation schedules for the model will be used.')
  target_hoo_from_model.setDefaultValue(false)
  args << target_hoo_from_model

  # TODO: - add argument for step frequency, which is hours per step (should be fractional 1 or less generally).
  # For now it defaults to simulation timestep

  return args
end

#descriptionObject

human readable description



23
24
25
# File 'lib/measures/shift_hours_of_operation/measure.rb', line 23

def description
  return 'This measure will infer the hours of operation for the building and then will shift the start of the hours of operation and change the duration of the hours of operation. In an alternate workflow you can directly pass in target start and duration rather than a shift and delta. Inputs can vary for weekday, Saturday, and Sunday. if a day does not have any hours of operation to start with increasing hours of operation may not have any impact as the auto generated data may not know what to do during operating hours. Future version may be able to borrow a profile formula but would probably require additional user arguments.'
end

#hoo_summary(model, runner, standard) ⇒ Object

get model hoo info



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/measures/shift_hours_of_operation/measure.rb', line 149

def hoo_summary(model, runner, standard)
  hoo_summary_hash = {}
  hoo_summary_hash[:zero_hoo] = []
  hoo_summary_hash[:final_hoo_start_range] = []
  hoo_summary_hash[:final_hoo_dur_range] = []
  model.getSpaces.sort.each do |space|
    default_sch_type = OpenStudio::Model::DefaultScheduleType.new('HoursofOperationSchedule')
    hours_of_operation = space.getDefaultSchedule(default_sch_type)
    if !hours_of_operation.is_initialized
      runner.registerWarning("Hours of Operation Schedule is not set for #{space.name}.")
      next
    end
    hours_of_operation_hash = OpenstudioStandards::Space.space_hours_of_operation(space)
    hours_of_operation_hash.each do |hoo_key, val|
      if val[:hoo_hours] == 0.0
        hoo_summary_hash[:zero_hoo] << val[:hoo_hours]
      else
        hoo_summary_hash[:final_hoo_dur_range] << val[:hoo_hours]
        hoo_summary_hash[:final_hoo_start_range] << val[:hoo_start]
      end
    end
  end

  return hoo_summary_hash
end

#modeler_descriptionObject

human readable description of modeling approach



28
29
30
# File 'lib/measures/shift_hours_of_operation/measure.rb', line 28

def modeler_description
  return 'This will only impact schedule rulesets. It will use methods in openstudio-standards to infer hours of operation, develop a parametric formula for all of the ruleset schedules, alter the hours of operation inputs to that formula and then re-apply the schedules. Input is expose to set ramp frequency of the resulting schedules. If inputs are such that no changes are requested, bypass the measure with NA so that it will not be parameterized. An advanced option for this measure would be bool to use hours of operation from OSM schedule ruleset hours of operation instead of inferring from standards. This should allow different parts of the building to have different hours of operation in the seed model.'
end

#nameObject

human readable name



17
18
19
20
# File 'lib/measures/shift_hours_of_operation/measure.rb', line 17

def name
  # Measure name should be the title case of the class name.
  return 'Shift Hours of Operation'
end

#process_hoo(used_hoo_sch_sets, model, runner, args, days_of_week, hoo_start_dows, hoo_dur_dows) ⇒ Object

process hoo schedules for various days of the week todo - when date range arg is used and not full year will never want to change default profile



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/measures/shift_hours_of_operation/measure.rb', line 177

def process_hoo(used_hoo_sch_sets, model, runner, args, days_of_week, hoo_start_dows, hoo_dur_dows)
  # profiles added to this will be processed
  altered_schedule_days = {} # key is profile value is original index position defined in used_hoo_sch_sets

  # loop through horus of operation schedules
  used_hoo_sch_sets.uniq.each do |hoo_sch, hours_of_operation_hash|
    if !hoo_sch.to_ScheduleRuleset.is_initialized
      runner.registerWarning("#{hoo_sch.name} is not schedule schedule ruleset, will not be altered by this method.")
      next
    end
    hoo_sch = hoo_sch.to_ScheduleRuleset.get

    year_description = hoo_sch.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)

    orig_profile_indexes_used = hoo_sch.getActiveRuleIndices(year_start_date, year_end_date)
    orig_num_days_default_used = orig_profile_indexes_used.count(-1)
    if orig_num_days_default_used > 0
      default_prof = hoo_sch.defaultDaySchedule

      # clone default profile as rule that sits above it so it can be evauluated and used if needed
      new_prof = default_prof.clone(model).to_ScheduleDay.get
      new_rule = OpenStudio::Model::ScheduleRule.new(hoo_sch, new_prof)
      hoo_sch.setScheduleRuleIndex(new_rule, hoo_sch.scheduleRules.size - 1)

      # set days of week for clone to match days of week passed into the method
      if days_of_week.include?('mon')
        new_rule.setApplyMonday(true)
      end
      if days_of_week.include?('tue')
        new_rule.setApplyTuesday(true)
      end
      if days_of_week.include?('wed')
        new_rule.setApplyWednesday(true)
      end
      if days_of_week.include?('thur')
        new_rule.setApplyThursday(true)
      end
      if days_of_week.include?('fri')
        new_rule.setApplyFriday(true)
      end
      if days_of_week.include?('sat')
        new_rule.setApplySaturday(true)
      end
      if days_of_week.include?('sun')
        new_rule.setApplySunday(true)
      end

      # check if default days are used at all
      profile_indexes_used = hoo_sch.getActiveRuleIndices(year_start_date, year_end_date)
      num_days_new_profile_used = profile_indexes_used.count(hoo_sch.scheduleRules.size - 1)
      if !profile_indexes_used.uniq.include?(-1) && num_days_new_profile_used > 0
        # don't need new profile, can use default
        new_rule.remove
        altered_schedule_days[hoo_sch.defaultDaySchedule] = -1
      elsif num_days_new_profile_used == 0.0
        # can remove cloned rule and skip the default profile (don't pass into array)
        new_rule.remove
      else
        altered_schedule_days[new_rule.daySchedule] = -1 # use hoo that was applicable to the default before it was cloned
      end
    end

    # use this to link to hoo from hours_of_operation_hash
    counter_of_orig_index = hours_of_operation_hash.size - 2 # this is not impacted by cloning that may have happened above

    hoo_sch.scheduleRules.reverse_each do |rule|
      # inspect days of the week
      actual_days_of_week_for_profile = []
      if rule.applyMonday then actual_days_of_week_for_profile << 'mon' end
      if rule.applyTuesday then actual_days_of_week_for_profile << 'tue' end
      if rule.applyWednesday then actual_days_of_week_for_profile << 'wed' end
      if rule.applyThursday then actual_days_of_week_for_profile << 'thur' end
      if rule.applyFriday then actual_days_of_week_for_profile << 'fri' end
      if rule.applySaturday then actual_days_of_week_for_profile << 'sat' end
      if rule.applySunday then actual_days_of_week_for_profile << 'sun' end

      # if an exact match for the rules passed in are met, this rule can be edited in place (update later for date range)
      day_of_week_intersect = days_of_week & actual_days_of_week_for_profile
      current_rule_index = rule.ruleIndex
      if days_of_week == actual_days_of_week_for_profile
        altered_schedule_days[rule.daySchedule] = counter_of_orig_index

      # if this rule contains the requested days of the week and another then a clone should be made above this with only the requested days of the week that are also already on for this rule
      elsif !day_of_week_intersect.empty?

        # clone default profile as rule that sits above it so it can be evaluated and used if needed
        new_rule = rule.clone(model).to_ScheduleRule.get
        hoo_sch.setScheduleRuleIndex(new_rule, current_rule_index) # the cloned rule should be just above what was cloned

        # update days of week for rule
        if day_of_week_intersect.include?('mon')
          new_rule.setApplyMonday(true)
        else
          new_rule.setApplyMonday(false)
        end
        if day_of_week_intersect.include?('tue')
          new_rule.setApplyTuesday(true)
        else
          new_rule.setApplyTuesday(false)
        end
        if day_of_week_intersect.include?('wed')
          new_rule.setApplyWednesday(true)
        else
          new_rule.setApplyWednesday(false)
        end
        if day_of_week_intersect.include?('thur')
          new_rule.setApplyThursday(true)
        else
          new_rule.setApplyThursday(false)
        end
        if day_of_week_intersect.include?('fri')
          new_rule.setApplyFriday(true)
        else
          new_rule.setApplyFriday(false)
        end
        if day_of_week_intersect.include?('sat')
          new_rule.setApplySaturday(true)
        else
          new_rule.setApplySaturday(false)
        end
        if day_of_week_intersect.include?('sun')
          new_rule.setApplySunday(true)
        else
          new_rule.setApplySunday(false)
        end

        # add to array
        altered_schedule_days[new_rule.daySchedule] = counter_of_orig_index
      end

      # adjust the count used to find hoo from hours_of_operation_hash
      counter_of_orig_index -= 1
    end
    runner.registerInfo("For #{hoo_sch.name} #{days_of_week.inspect} #{altered_schedule_days.size} profiles will be processed.")

    # convert altered_schedule_days to hash where key is profile and value is key of index in hours_of_operation_hash

    # loop through profiles to changes
    altered_schedule_days.each do |new_profile, hoo_hash_index|
      # gather info and edit selected profile
      if args['delta_values']
        orig_hoo_start = hours_of_operation_hash[hoo_hash_index][:hoo_start]
        orig_hoo_dur = hours_of_operation_hash[hoo_hash_index][:hoo_hours]

        # check for duration grater than 24 or lower than 0
        max_dur_delta = 24 - orig_hoo_dur
        min_dur_delta = orig_hoo_dur * -1.0
        if hoo_dur_dows > max_dur_delta
          target_dur = 24.0
          runner.registerWarning("For profile in #{hoo_sch.name} duration is being capped at 24 hours.")
        elsif hoo_dur_dows < min_dur_delta
          target_dur = 0.0
          runner.registerWarning("For profile in #{hoo_sch.name} duration is being limited to a low of 0 hours.")
        else
          target_dur = hoo_dur_dows + orig_hoo_dur
        end

        # setup new hoo values with delta
        if orig_hoo_start + hoo_start_dows <= 24.0
          new_hoo_start = orig_hoo_start + hoo_start_dows
        else
          new_hoo_start = orig_hoo_start + hoo_start_dows - 24.0
        end
        if new_hoo_start + hoo_dur_dows + orig_hoo_dur <= 24.0
          new_hoo_end = new_hoo_start + target_dur
        else
          new_hoo_end = new_hoo_start + target_dur - 24.0
        end
      else
        new_hoo_start = hoo_start_dows
        target_dur = hoo_dur_dows
        if new_hoo_start + target_dur < 24.0
          new_hoo_end = new_hoo_start + target_dur
        elsif new_hoo_start + target_dur == 24.0
          new_hoo_end = 0.0
        else # greater than 24
          new_hoo_end = new_hoo_start + target_dur - 24.0
        end
      end

      # setup hoo start time
      target_start_hr = new_hoo_start.truncate
      target_start_min = ((new_hoo_start - target_start_hr) * 60.0).truncate
      target_start_time = OpenStudio::Time.new(0, target_start_hr, target_start_min, 0)

      # setup hoo end time
      target_end_hr = new_hoo_end.truncate
      target_end_min = ((new_hoo_end - target_end_hr) * 60.0).truncate
      target_end_time = OpenStudio::Time.new(0, target_end_hr, target_end_min, 0)

      # adding new values
      new_profile.clearValues
      if target_dur < 24
        new_profile.addValue(target_start_time, 0)
      end
      if target_dur > 0
        new_profile.addValue(target_end_time, 1)
      end
      os_time_24 = OpenStudio::Time.new(0, 24, 0, 0)
      if target_end_time > target_start_time || target_start_time == os_time_24
        new_profile.addValue(os_time_24, 0)
      elsif target_end_time < target_start_time
        new_profile.addValue(os_time_24, 1)
      else # they are equal
        if target_dur == 24.0
          new_profile.addValue(os_time_24, 1)
        else
          new_profile.addValue(os_time_24, 0)
        end
      end
    end
  end

  return altered_schedule_days
end

#run(model, runner, user_arguments) ⇒ Object

define what happens when the measure is run



397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/measures/shift_hours_of_operation/measure.rb', line 397

def run(model, runner, user_arguments)
  super(model, runner, user_arguments)

  # assign the user inputs to variables
  args = runner.getArgumentValues(arguments(model), user_arguments)
  args = Hash[args.collect{ |k, v| [k.to_s, v] }]
  if !args then return false end

  # open channel to log messages
  reset_log

  # Turn debugging output on/off
  debug = false

  # load standards
  standard = Standard.build('90.1-2004') # selected template doesn't matter

  if args['infer_parametric_schedules']
    # infer hours of operation for the building
    # @param fraction_of_daily_occ_range [Double] fraction above/below daily min range required to start and end hours of operation
    occ_fraction = args['fraction_of_daily_occ_range']
    OpenstudioStandards::Schedules.model_infer_hours_of_operation_building(model, fraction_of_daily_occ_range: occ_fraction, gen_occ_profile: true)
    runner.registerInfo('Inferring initial hours of operation for the building and generating parametric profile formulas.')

    # report back hours of operation
    initial_hoo_range = []
    hours_of_operation_hash_test = OpenstudioStandards::Space.space_hours_of_operation(model.getSpaces.first)
    hours_of_operation_hash_test.each do |hoo_key, val|
      initial_hoo_range << val[:hoo_hours]
      runner.registerInfo("For Profile Index #{hoo_key} hours of operation run for #{val[:hoo_hours]} hours, from #{val[:hoo_start]} to #{val[:hoo_end]} and is used for #{val[:days_used].size} days of the year.")
    end

    # model_setup_parametric_schedules
    OpenstudioStandards::Schedules.model_setup_parametric_schedules(model, gather_data_only: false, hoo_var_method: args['hoo_var_method'])
  end

  # report initial condition of model
  hoo_summary_hash = hoo_summary(model, runner, standard)
  if !hoo_summary_hash[:zero_hoo].empty?
    runner.registerInitialCondition("Across the building the non-zero hours of operation range from #{hoo_summary_hash[:final_hoo_dur_range].min} hours to #{hoo_summary_hash[:final_hoo_dur_range].max} hours. Start of hours of operation range from #{hoo_summary_hash[:final_hoo_start_range].min} to #{hoo_summary_hash[:final_hoo_start_range].max}. One or more hours of operation schedules used contain a profile with 0 hours of operation.")
  else
    runner.registerInitialCondition("Across the building the hours of operation range from #{hoo_summary_hash[:final_hoo_dur_range].min} hours to #{hoo_summary_hash[:final_hoo_dur_range].max} hours. Start of hours of operation range from #{hoo_summary_hash[:final_hoo_start_range].min} to #{hoo_summary_hash[:final_hoo_start_range].max}.")
  end

  # gather hours of operation schedules used by this model
  used_hoo_sch_sets = {}
  model.getSpaces.sort.each do |space|
    default_sch_type = OpenStudio::Model::DefaultScheduleType.new('HoursofOperationSchedule')
    hours_of_operation = space.getDefaultSchedule(default_sch_type)
    if !hours_of_operation.is_initialized
      runner.registerWarning("Hours of Operation Schedule is not set for #{space.name}.")
      next
    end
    hours_of_operation_hash = OpenstudioStandards::Space.space_hours_of_operation(space)
    used_hoo_sch_sets[hours_of_operation.get] = hours_of_operation_hash
  end

  # loop through and alter hours of operation schedules
  runner.registerInfo("There are #{used_hoo_sch_sets.uniq.size} hours of operation schedules in the model to alter.")

  # process weekday profiles
  runner.registerInfo('Altering hours of operation schedules for weekday profiles')
  weekday = process_hoo(used_hoo_sch_sets, model, runner, args, ['mon', 'tue', 'wed', 'thur', 'fri'], args['hoo_start_weekday'], args['hoo_dur_weekday'])

  # process saturday profiles
  runner.registerInfo('Altering hours of operation schedules for saturday profiles')
  saturday = process_hoo(used_hoo_sch_sets, model, runner, args, ['sat'], args['hoo_start_saturday'], args['hoo_dur_saturday'])

  # process sunday profiles
  runner.registerInfo('Altering hours of operation schedules for sunday profiles')
  sunday = process_hoo(used_hoo_sch_sets, model, runner, args, ['sun'], args['hoo_start_sunday'], args['hoo_dur_sunday'])

  # TODO: - need to address this error when manipulating schedules
  # [openstudio.standards.ScheduleRuleset] <1> Pre-interpolated processed hash for Large Office Bldg Equip Default Schedule has one or more out of order conflicts: [[3.5, 0.8], [4.5, 0.6], [5.0, 0.6], [7.0, 0.5], [9.0, 0.4], [6.0, 0.4], [10.0, 0.9], [16.5, 0.9], [17.5, 0.8], [18.5, 0.9], [21.5, 0.9]]. Method will stop because Error on Out of Order was set to true.
  # model_build_parametric_schedules
  parametric_schedules = OpenstudioStandards::Schedules.model_apply_parametric_schedules(model, ramp_frequency: nil, infer_hoo_for_non_assigned_objects: true, error_on_out_of_order: false)
  runner.registerInfo("Created #{parametric_schedules.size} parametric schedules.")

  # report final condition of model
  hoo_summary_hash = hoo_summary(model, runner, standard)
  if !hoo_summary_hash[:zero_hoo].empty?
    runner.registerFinalCondition("Across the building the non-zero hours of operation range from #{hoo_summary_hash[:final_hoo_dur_range].min} hours to #{hoo_summary_hash[:final_hoo_dur_range].max} hours. Start of hours of operation range from #{hoo_summary_hash[:final_hoo_start_range].min} to #{hoo_summary_hash[:final_hoo_start_range].max}. One or more hours of operation schedules used contain a profile with 0 hours of operation.")
  else
    runner.registerFinalCondition("Across the building the hours of operation range from #{hoo_summary_hash[:final_hoo_dur_range].min} hours to #{hoo_summary_hash[:final_hoo_dur_range].max} hours. Start of hours of operation range from #{hoo_summary_hash[:final_hoo_start_range].min} to #{hoo_summary_hash[:final_hoo_start_range].max}.")
  end

  # gather log
  log_messages_to_runner(runner, debug)
  reset_log

  # TODO: - adding hours of operation to a schedule that doesn't have them to start with, like a sunday, can be problematic
  # todo - start of day may not be reliable and there may not be formula inputs to show what occupied behavior is
  # todo - in a situation like that it could be good to get formula from day that was non-zero to start with like weekday or saturday.
  # todo - maybe standards can do something like this when making the formulas in the first place.

  return true
end