Module: OpenstudioStandards::Schedules
- Defined in:
- lib/openstudio-standards/schedules/create.rb,
lib/openstudio-standards/schedules/modify.rb,
lib/openstudio-standards/schedules/parametric.rb,
lib/openstudio-standards/schedules/information.rb
Overview
The Schedules module provides methods to create, modify, and get information about Schedule objects
Create collapse
-
.create_complex_schedule(model, options = {}) ⇒ OpenStudio::Model::ScheduleRuleset
create a ruleset schedule with a complex profile.
-
.create_constant_schedule_ruleset(model, value, name: nil, schedule_type_limit: nil) ⇒ OpenStudio::Model::ScheduleRuleset
Create constant ScheduleRuleset with a given value.
-
.create_inverted_schedule_day(old_schedule_day, new_schedule_day: nil, schedule_name: nil) ⇒ OpenStudio::Model::ScheduleDay
Create a ScheduleDay from another ScheduleDay with inverted values.
-
.create_inverted_schedule_ruleset(schedule_ruleset, schedule_name: nil) ⇒ OpenStudio::Model::ScheduleRuleset
Create a ScheduleRuleset from another ScheduleRuleset with inverted values.
-
.create_schedule_from_rate_of_change(model, schedule_ruleset) ⇒ OpenStudio::Model::ScheduleRuleset
create a new schedule using absolute velocity of existing schedule.
-
.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) ⇒ OpenStudio::Model::ScheduleTypeLimits
create a ScheduleTypeLimits object for a schedule.
-
.create_simple_schedule(model, options = {}) ⇒ OpenStudio::Model::ScheduleRuleset
create a ruleset schedule with a basic profile.
-
.create_weighted_merge_schedules(model, schedule_weights_hash, sch_name: 'Merged Schedule') ⇒ Hash
merge multiple schedules into one using load or other value to weight each schedules influence on the merge.
Modify:ScheduleDay collapse
-
.schedule_day_multiply_by_value(schedule_day, multiplier, lower_apply_limit: nil) ⇒ OpenStudio::Model::ScheduleDay
Method to multiply the values in a day schedule by a specified value The method can optionally apply the multiplier to only values above a lower limit.
-
.schedule_day_populate_from_array_of_values(schedule_day, value_array) ⇒ OpenStudio::Model::ScheduleDay
Sets the values of a day schedule from an array of values Clears out existing time value pairs and sets to supplied values.
-
.schedule_day_set_hours_of_operation(schedule_day, start_time, end_time) ⇒ Void
private
Set the hours of operation (0 or 1) for a ScheduleDay.
Modify:ScheduleRuleset collapse
-
.schedule_ruleset_add_rule(schedule_ruleset, values, start_date: nil, end_date: nil, day_names: nil, rule_name: nil) ⇒ OpenStudio::Model::ScheduleRule
Add a ScheduleRule to a ScheduleRuleset object from an array of hourly values.
-
.schedule_ruleset_adjust_hours_of_operation(schedule_ruleset, options = {}) ⇒ OpenStudio::Model::ScheduleRuleset
Adjust hours of operation.
-
.schedule_ruleset_cleanup_profiles(schedule_ruleset) ⇒ OpenStudio::Model::ScheduleRuleset
Remove unused profiles and set most prevalent profile as default.
-
.schedule_ruleset_conditional_adjust_value(schedule_ruleset, test_value, pass_value, fail_value, floor_value, modification_type = 'Multiplier') ⇒ OpenStudio::Model::ScheduleRuleset
Increase/decrease by percentage or static value change value when value passes/fails test.
-
.schedule_ruleset_create_rules_from_day_list(schedule_ruleset, days_used, schedule_day: nil) ⇒ Array
creates a minimal set of ScheduleRules that applies to all days in a given array of day of year indices.
-
.schedule_ruleset_simple_value_adjust(schedule_ruleset, value, modification_type = 'Multiplier') ⇒ OpenStudio::Model::ScheduleRuleset
Increase/decrease by percentage or static value.
-
.schedule_ruleset_time_conditional_adjust_value(schedule_ruleset, hhmm_before, hhmm_after, inside_value, outside_value, modification_type = 'Sum') ⇒ OpenStudio::Model::ScheduleRuleset
Increase/decrease by percentage or static value change value when time passes test.
Parametric:Model collapse
-
.model_apply_parametric_schedules(model, ramp_frequency: nil, infer_hoo_for_non_assigned_objects: true, error_on_out_of_order: true) ⇒ Array
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.
-
.model_infer_hours_of_operation_building(model, fraction_of_daily_occ_range: 0.25, invert_res: true, gen_occ_profile: false) ⇒ ScheduleRuleset
This method looks at occupancy profiles for the building as a whole and generates an hours of operation default schedule for the building.
-
.model_setup_parametric_schedules(model, step_ramp_logic: nil, infer_hoo_for_non_assigned_objects: true, gather_data_only: false, hoo_var_method: 'hours') ⇒ Hash
This method users the hours of operation for a space and the existing ScheduleRuleset profiles to setup parametric schedule inputs.
Parametric:Spaces collapse
-
.spaces_space_types_get_parametric_schedule_inputs(spaces_space_types, parametric_inputs, gather_data_only) ⇒ Hash
Gathers parametric inputs for all loads objects associated with spaces/space types in provided array.
Parametric:Schedule collapse
-
.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') ⇒ Hash
Method to process space load instance schedules for model_setup_parametric_schedules.
Parametric:ScheduleRuleset collapse
-
.schedule_ruleset_apply_parametric_inputs(schedule_ruleset, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order, parametric_inputs = nil) ⇒ OpenStudio::Model::ScheduleRuleset
this will use parametric inputs contained in schedule and profiles along with inferred hours of operation to generate updated ruleset schedule profiles.
-
.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) ⇒ Boolean
Apply specified hours of operation values to rules in this schedule.
Parametric:ScheduleDay collapse
-
.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) ⇒ OpenStudio::Model::ScheduleDay
private
adjust individual schedule profiles from parametric inputs.
Information collapse
-
.schedule_get_design_day_min_max(schedule, type = 'winter') ⇒ Object
Returns the Schedule minimum and maximum values during the winter or summer design day.
-
.schedule_get_equivalent_full_load_hours(schedule) ⇒ Object
Returns the Schedule equivalent full load hours (EFLH).
-
.schedule_get_hourly_values(schedule) ⇒ Array<Double>
Returns an array of average hourly values from a Schedule object Returns 8760 values, 8784 for leap years.
-
.schedule_get_min_max(schedule, only_run_period_values: false) ⇒ Object
Returns the Schedule minimum and maximum values encountered during the run-period.
Information:ScheduleConstant collapse
-
.schedule_constant_get_design_day_min_max(schedule_constant, type) ⇒ Object
Returns the ScheduleConstant minimum and maximum values during the winter or summer design day.
-
.schedule_constant_get_equivalent_full_load_hours(schedule_constant) ⇒ Object
Returns SheduleConstant equivalent full load hours (EFLH).
-
.schedule_constant_get_hourly_values(schedule_constant) ⇒ Array<Double>
Returns an array of average hourly values from a ScheduleConstant object Returns 8760 values, 8784 for leap years.
-
.schedule_constant_get_min_max(schedule_constant) ⇒ Object
Returns the ScheduleConstant minimum and maximum values encountered during the run-period.
Information:ScheduleCompact collapse
-
.schedule_compact_get_design_day_min_max(schedule_compact, type = 'winter') ⇒ Object
Returns the ScheduleCompact minimum and maximum values during the winter or summer design day.
-
.schedule_compact_get_hourly_values(schedule_compact) ⇒ Array<Double>
Returns an array of average hourly values from a ScheduleCompact object Returns 8760 values, 8784 for leap years.
-
.schedule_compact_get_min_max(schedule_compact) ⇒ Object
Returns the ScheduleCompact minimum and maximum values encountered during the run-period.
Information:ScheduleDay collapse
-
.schedule_day_get_equivalent_full_load_hours(schedule_day) ⇒ Object
Returns the ScheduleDay daily equivalent full load hours (EFLH).
-
.schedule_day_get_hourly_values(schedule_day, model = nil) ⇒ Array<Double>
Returns an array of average hourly values from a ScheduleDay object Returns 24 values.
-
.schedule_day_get_min_max(schedule_day) ⇒ Hash
Returns the ScheduleDay minimum and maximum values.
Information:ScheduleRuleset collapse
-
.schedule_ruleset_get_annual_days_used(schedule_ruleset) ⇒ Hash
Return the annual days of year that covered by each rule of a schedule ruleset.
-
.schedule_ruleset_get_day_schedules(schedule_ruleset, include_design_days: false) ⇒ Array<OpenStudio::Model::ScheduleDay>
Returns the day schedules associated with a schedule ruleset Optionally includes summer and winter design days.
-
.schedule_ruleset_get_design_day_min_max(schedule_ruleset, type = 'winter') ⇒ Object
Returns the ScheduleRuleset minimum and maximum values during the winter or summer design day.
-
.schedule_ruleset_get_equivalent_full_load_hours(schedule_ruleset) ⇒ Object
Returns SheduleRuleset equivalent full load hours (EFLH).
-
.schedule_ruleset_get_hourly_values(schedule_ruleset) ⇒ Array<Double>
Returns an array of average hourly values from a ScheduleRuleset object Returns 8760 values, 8784 for leap years.
-
.schedule_ruleset_get_hours_above_value(schedule_ruleset, lower_limit) ⇒ Double
Returns the total number of hours where the schedule is greater than the specified value.
-
.schedule_ruleset_get_min_max(schedule_ruleset, only_run_period_values: false) ⇒ Hash
Returns the ScheduleRuleset minimum and maximum values.
-
.schedule_ruleset_get_schedule_day_rule_indices(schedule_ruleset) ⇒ Hash
Returns the rule indices associated with defaultDay and Rule days for a given ScheduleRuleset.
-
.schedule_ruleset_get_start_and_end_times(schedule_ruleset) ⇒ Hash<OpenStudio:Time>
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.
-
.schedule_ruleset_get_timeseries(schedule_ruleset) ⇒ OpenStudio::TimeSeries
create OpenStudio TimeSeries object from ScheduleRuleset values.
Information:Model collapse
-
.model_get_hvac_schedule(model) ⇒ OpenStudio::Model::Schedule
Get the predominant air loop HVAC schedule in the model by floor area served.
Class Method Details
.create_complex_schedule(model, options = {}) ⇒ OpenStudio::Model::ScheduleRuleset
create a ruleset schedule with a complex profile
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 |
# File 'lib/openstudio-standards/schedules/create.rb', line 208 def self.create_complex_schedule(model, = {}) defaults = { 'name' => nil, 'default_day' => ['always_on', [24.0, 1.0]] } # merge user inputs with defaults = defaults.merge() # ScheduleRuleset sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(model) if name sch_ruleset.setName(['name']) end # Winter Design Day unless ['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") ['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 ['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") ['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} #{['default_day'][0]}") default_data_array = ['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 ['rules'].nil? ['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) ⇒ OpenStudio::Model::ScheduleRuleset
Create constant ScheduleRuleset with a given value
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 147 |
# 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_inverted_schedule_day(old_schedule_day, new_schedule_day: nil, schedule_name: nil) ⇒ OpenStudio::Model::ScheduleDay
Create a ScheduleDay from another ScheduleDay with inverted values
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 |
# File 'lib/openstudio-standards/schedules/create.rb', line 510 def self.create_inverted_schedule_day(old_schedule_day, new_schedule_day: nil, schedule_name: nil) # create new schedule object if none provided if new_schedule_day.nil? new_schedule_day = OpenStudio::Model::ScheduleDay.new(old_schedule_day.model) end # set default name if none provided if schedule_name.nil? new_schedule_day.setName("#{old_schedule_day.name} inverted") else new_schedule_day.setName(schedule_name) end # invert schedule values for index in 0..old_schedule_day.times.size - 1 old_value = old_schedule_day.values[index] if old_value == 0 new_value = 1 else new_value = 0 end new_schedule_day.addValue(old_schedule_day.times[index], new_value) end return new_schedule_day end |
.create_inverted_schedule_ruleset(schedule_ruleset, schedule_name: nil) ⇒ OpenStudio::Model::ScheduleRuleset
Create a ScheduleRuleset from another ScheduleRuleset with inverted values
542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 |
# File 'lib/openstudio-standards/schedules/create.rb', line 542 def self.create_inverted_schedule_ruleset(schedule_ruleset, schedule_name: nil) model = schedule_ruleset.model new_schedule = OpenStudio::Model::ScheduleRuleset.new(model, 0.0) # set default name if none provided if schedule_name.nil? new_schedule.setName("#{schedule_ruleset.name} inverted") else new_schedule.setName(schedule_name) end # change summer design day new_summer_dd_schedule = OpenstudioStandards::Schedules.create_inverted_schedule_day(schedule_ruleset.summerDesignDaySchedule) new_schedule.setSummerDesignDaySchedule(new_summer_dd_schedule) # change winter design day new_winter_dd_schedule = OpenstudioStandards::Schedules.create_inverted_schedule_day(schedule_ruleset.winterDesignDaySchedule) new_schedule.setWinterDesignDaySchedule(new_winter_dd_schedule) # change the default day values OpenstudioStandards::Schedules.create_inverted_schedule_day(schedule_ruleset.defaultDaySchedule, new_schedule_day: new_schedule.defaultDaySchedule) # change for schedule rules schedule_ruleset.scheduleRules.each_with_index do |rule, i| old_schedule_day = rule.daySchedule new_schedule_day = OpenstudioStandards::Schedules.create_inverted_schedule_day(old_schedule_day) new_rule = OpenStudio::Model::ScheduleRule.new(new_schedule, new_schedule_day) new_rule.setName("#{new_schedule_day.name} Rule") new_rule.setApplySunday(rule.applySunday) new_rule.setApplyMonday(rule.applyMonday) new_rule.setApplyTuesday(rule.applyTuesday) new_rule.setApplyWednesday(rule.applyWednesday) new_rule.setApplyThursday(rule.applyThursday) new_rule.setApplyFriday(rule.applyFriday) new_rule.setApplySaturday(rule.applySaturday) end return new_schedule end |
.create_schedule_from_rate_of_change(model, schedule_ruleset) ⇒ OpenStudio::Model::ScheduleRuleset
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)
create a new schedule using absolute velocity of existing schedule
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 |
# 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) ⇒ OpenStudio::Model::ScheduleTypeLimits
create a ScheduleTypeLimits object for a schedule
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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 |
# File 'lib/openstudio-standards/schedules/create.rb', line 18 def self.create_schedule_type_limits(model, standard_schedule_type_limit: nil, name: nil, lower_limit_value: nil, upper_limit_value: nil, numeric_type: nil, unit_type: nil) if standard_schedule_type_limit.nil? if lower_limit_value.nil? || upper_limit_value.nil? || numeric_type.nil? || unit_type.nil? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Create', 'If calling create_schedule_type_limits without a standard_schedule_type_limit, you must specify all properties of ScheduleTypeLimits.') return false end schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName(name) if !name.nil? schedule_type_limits.setLowerLimitValue(lower_limit_value) schedule_type_limits.setUpperLimitValue(upper_limit_value) schedule_type_limits.setNumericType(numeric_type) schedule_type_limits.setUnitType(unit_type) else schedule_type_limits = model.getScheduleTypeLimitsByName(standard_schedule_type_limit) if schedule_type_limits.empty? case standard_schedule_type_limit.downcase when 'dimensionless' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('Dimensionless') schedule_type_limits.setLowerLimitValue(0.0) schedule_type_limits.setUpperLimitValue(1000.0) schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('Dimensionless') when 'temperature' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('Temperature') schedule_type_limits.setLowerLimitValue(0.0) schedule_type_limits.setUpperLimitValue(100.0) schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('Temperature') when 'humidity ratio' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('Humidity Ratio') schedule_type_limits.setLowerLimitValue(0.0) schedule_type_limits.setUpperLimitValue(0.3) schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('Dimensionless') when 'fraction', 'fractional' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('Fraction') schedule_type_limits.setLowerLimitValue(0.0) schedule_type_limits.setUpperLimitValue(1.0) schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('Dimensionless') when 'onoff' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('OnOff') schedule_type_limits.setLowerLimitValue(0) schedule_type_limits.setUpperLimitValue(1) schedule_type_limits.setNumericType('Discrete') schedule_type_limits.setUnitType('Availability') when 'activity' schedule_type_limits = OpenStudio::Model::ScheduleTypeLimits.new(model) schedule_type_limits.setName('Activity') schedule_type_limits.setLowerLimitValue(70.0) schedule_type_limits.setUpperLimitValue(1000.0) schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('ActivityLevel') else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Create', 'Invalid standard_schedule_type_limit for method create_schedule_type_limits.') return false end else schedule_type_limits = schedule_type_limits.get if schedule_type_limits.name.to_s.downcase == 'temperature' schedule_type_limits.resetLowerLimitValue schedule_type_limits.resetUpperLimitValue schedule_type_limits.setNumericType('Continuous') schedule_type_limits.setUnitType('Temperature') end end end return schedule_type_limits end |
.create_simple_schedule(model, options = {}) ⇒ OpenStudio::Model::ScheduleRuleset
create a ruleset schedule with a basic profile
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 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 |
# File 'lib/openstudio-standards/schedules/create.rb', line 154 def self.create_simple_schedule(model, = {}) 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 = defaults.merge() # ScheduleRuleset sch_ruleset = OpenStudio::Model::ScheduleRuleset.new(model) if name sch_ruleset.setName(['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") ['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") ['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") ['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') ⇒ Hash
apply weights to schedule rules as well, not just winter, summer, and default profile
merge multiple schedules into one using load or other value to weight each schedules influence on the merge
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 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 494 495 496 497 498 499 500 501 |
# 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) ⇒ Array
This measure will replace any prior chagnes made to ScheduleRule objects with new ScheduleRule values from
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.
profile formulas
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 |
# 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_get_hvac_schedule(model) ⇒ OpenStudio::Model::Schedule
Get the predominant air loop HVAC schedule in the model by floor area served.
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 |
# File 'lib/openstudio-standards/schedules/information.rb', line 921 def self.model_get_hvac_schedule(model) # lookup from model, using largest air loop # check multiple kinds of systems, including unitary systems hvac_schedule = nil largest_area = 0.0 model.getAirLoopHVACs.each do |air_loop| air_loop_area = 0.0 air_loop.thermalZones.each { |tz| air_loop_area += tz.floorArea } if air_loop_area > largest_area hvac_schedule = air_loop.availabilitySchedule largest_area = air_loop_area end end model.getAirLoopHVACUnitarySystems.each do |unitary| next unless unitary.thermalZone.is_initialized air_loop_area = unitary.thermalZone.get.floorArea if air_loop_area > largest_area if unitary.availabilitySchedule.is_initialized hvac_schedule = unitary.availabilitySchedule.get else hvac_schedule = model.alwaysOnDiscreteSchedule end largest_area = air_loop_area end end model.getAirLoopHVACUnitaryHeatPumpAirToAirs.each do |unitary| next unless unitary.controllingZone.is_initialized air_loop_area = unitary.controllingZone.get.floorArea if air_loop_area > largest_area hvac_schedule = unitary.availabilitySchedule.get largest_area = air_loop_area end end model.getAirLoopHVACUnitaryHeatPumpAirToAirMultiSpeeds.each do |unitary| next unless unitary.controllingZoneorThermostatLocation.is_initialized air_loop_area = unitary.controllingZoneorThermostatLocation.get.floorArea if air_loop_area > largest_area if unitary.availabilitySchedule.is_initialized hvac_schedule = unitary.availabilitySchedule.get else hvac_schedule = model.alwaysOnDiscreteSchedule end largest_area = air_loop_area end end model.getFanZoneExhausts.each do |fan| next unless fan.thermalZone.is_initialized air_loop_area = fan.thermalZone.get.floorArea if air_loop_area > largest_area if fan.availabilitySchedule.is_initialized hvac_schedule = fan.availabilitySchedule.get else hvac_schedule = model.alwaysOnDiscreteSchedule end largest_area = air_loop_area end end building_area = model.getBuilding.floorArea if largest_area < 0.05 * building_area OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Schedules', "The largest airloop or HVAC system serves #{largest_area.round(1)} m^2, which is less than 5% of the building area #{building_area.round(1)} m^2. Attempting to use building hours of operation schedule instead.") default_schedule_set = model.getBuilding.defaultScheduleSet if default_schedule_set.is_initialized default_schedule_set = default_schedule_set.get hoo = default_schedule_set.hoursofOperationSchedule if hoo.is_initialized hvac_schedule = hoo.get largest_area = building_area else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules', 'Unable to determine building hours of operation schedule. Treating the building as if there is no HVAC system schedule.') hvac_schedule = nil end else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules', 'Unable to determine building hours of operation schedule. Treating the building as if there is no HVAC system schedule.') hvac_schedule = nil end end unless hvac_schedule.nil? area_fraction = 100.0 * largest_area / building_area OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Schedules', "Using schedule #{hvac_schedule.name} serving area #{largest_area.round(1)} m^2, #{area_fraction.round(0)}% of building area #{building_area.round(1)} m^2 as the building HVAC operation schedule.") end return hvac_schedule end |
.model_infer_hours_of_operation_building(model, fraction_of_daily_occ_range: 0.25, invert_res: true, gen_occ_profile: false) ⇒ ScheduleRuleset
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
23 24 25 26 27 28 29 30 31 32 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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# 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') ⇒ Hash
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.
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 |
# 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') ⇒ Object
Returns the ScheduleCompact minimum and maximum values during the winter or summer design day.
return [Hash] returns a hash with ‘min’ and ‘max’ values
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 |
# File 'lib/openstudio-standards/schedules/information.rb', line 226 def self.schedule_compact_get_design_day_min_max(schedule_compact, type = 'winter') vals = [] design_day_flag = false prev_str = '' schedule_compact.extensibleGroups.each do |eg| if design_day_flag && prev_str.include?('until') val = eg.getDouble(0) if val.is_initialized vals << val.get end end str = eg.getString(0) if str.is_initialized prev_str = str.get.downcase if prev_str.include?('for:') # Process a new day schedule, turn the flag off. design_day_flag = false # in the same line, if there is design day label and matches the type, turn the flag back on. if prev_str.include?(type) || prev_str.include?('alldays') design_day_flag = true end end end end # Error if no values were found if vals.empty? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Could not find any value in #{schedule_compact.name} design day schedule when determining min and max.") result = { 'min' => nil, 'max' => nil } return result end result = { 'min' => vals.min, 'max' => vals.max } return result end |
.schedule_compact_get_hourly_values(schedule_compact) ⇒ Array<Double>
Returns an array of average hourly values from a ScheduleCompact object Returns 8760 values, 8784 for leap years.
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# 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) ⇒ Object
Returns the ScheduleCompact minimum and maximum values encountered during the run-period. This method does not include summer and winter design day values.
return [Hash] returns a hash with ‘min’ and ‘max’ values
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 |
# File 'lib/openstudio-standards/schedules/information.rb', line 193 def self.schedule_compact_get_min_max(schedule_compact) vals = [] prev_str = '' schedule_compact.extensibleGroups.each do |eg| if prev_str.include?('until') val = eg.getDouble(0) if val.is_initialized vals << eg.getDouble(0).get end end str = eg.getString(0) if str.is_initialized prev_str = str.get.downcase end end # Error if no values were found if vals.empty? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "Could not find any value in #{schedule_compact.name} when determining min and max.") result = { 'min' => nil, 'max' => nil } return result end result = { 'min' => vals.min, 'max' => vals.max } return result end |
.schedule_constant_get_design_day_min_max(schedule_constant, type) ⇒ Object
Returns the ScheduleConstant minimum and maximum values during the winter or summer design day.
return [Hash] returns a hash with ‘min’ and ‘max’ values
151 152 153 154 155 |
# 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) ⇒ Object
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.
return [Double] The total equivalent full load hours for this schedule
163 164 165 166 167 168 169 |
# 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) ⇒ Array<Double>
Returns an array of average hourly values from a ScheduleConstant object Returns 8760 values, 8784 for leap years.
176 177 178 179 180 181 182 |
# 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) ⇒ Object
Returns the ScheduleConstant minimum and maximum values encountered during the run-period. This method does not include summer and winter design day values.
return [Hash] returns a hash with ‘min’ and ‘max’ values
140 141 142 143 144 |
# 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) ⇒ OpenStudio::Model::ScheduleDay
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
adjust individual schedule profiles from parametric inputs
1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 |
# File 'lib/openstudio-standards/schedules/parametric.rb', line 1145 def self.schedule_day_adjust_from_parameters(schedule_day, hoo_start, hoo_end, val_flr, val_clg, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order) # process hoo and floor/ceiling vars to develop formulas without variables formula_string = schedule_day.additionalProperties.getFeatureAsString('param_day_profile').get formula_hash = {} formula_string.split('|').each do |time_val_valopt| a1 = time_val_valopt.to_s.split('~') time = a1[0] value_array = a1.drop(1) formula_hash[time] = value_array end # setup additional variables if hoo_end >= hoo_start occ = hoo_end - hoo_start else occ = 24.0 + hoo_end - hoo_start end vac = 24.0 - occ range = val_clg - val_flr timestep_minutes = (0..60).step(60 * ramp_frequency).to_a OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.ScheduleDay', "Schedule #{schedule_day.name} has this formula hash: #{formula_hash}") # apply variables and create updated hash with only numbers formula_hash_var_free = {} formula_hash.each do |time, val_in_out| # replace time variables with value time = time.gsub('hoo_start', hoo_start.to_s) time = time.gsub('hoo_end', hoo_end.to_s) time = time.gsub('occ', occ.to_s) # can save special variables like lunch or break using this logic mid_start = hoo_start + (occ * 0.5) mid_start_min = mid_start.modulo(1) * 60 mid_start_min_ts = timestep_minutes.min { |a, b| (a - mid_start_min).abs <=> (b - mid_start_min).abs } mid_start_adjusted = mid_start.floor + (mid_start_min_ts / 60) time = time.gsub('mid', mid_start_adjusted.to_s) time = time.gsub('vac', vac.to_s) begin time_float = eval(time) if time_float.to_i.to_s == time_float.to_s || time_float.to_f.to_s == time_float.to_s # check to see if numeric time_float = time_float.to_f else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.ScheduleDay', "Time formula #{time} for #{schedule_day.name} is invalid. It can't be converted to a float.") end rescue SyntaxError => e OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.ScheduleDay', "Time formula #{time} for #{schedule_day.name} is invalid. It can't be evaluated.") end # replace variables in array of values val_in_out_float = [] val_in_out.each do |val| # replace variables for values val = val.gsub('val_flr', val_flr.to_s) val = val.gsub('val_clg', val_clg.to_s) val = val.gsub('val_range', range.to_s) # will expect a fractional value and will scale within ceiling and floor begin val_float = eval(val) if val_float.to_i.to_s == val_float.to_s || val_float.to_f.to_s == val_float.to_s # check to see if numeric val_float = val_float.to_f else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.ScheduleDay', "Value formula #{val_float} for #{schedule_day.name} is invalid. It can't be converted to a float.") end rescue SyntaxError => e OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.ScheduleDay', "Time formula #{val_float} for #{schedule_day.name} is invalid. It can't be evaluated.") end val_in_out_float << val_float end # update hash formula_hash_var_free[time_float] = val_in_out_float end # this is old variable used in loop, just combining for now to avoid refactor, may change this later time_value_pairs = [] formula_hash_var_free.each do |time, val_in_out| val_in_out.each do |val| time_value_pairs << [time, val] end end OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.ScheduleDay', "Schedule #{schedule_day.name} will be adjusted with these time-value pairs: #{time_value_pairs}") # re-order so first value is lowest, and last is highest (need to adjust so no negative or > 24 values first) neg_time_hash = {} temp_min_time_hash = {} time_value_pairs.each_with_index do |pair, i| # if value 24 add it to 24 so it will go on tail end of profile # case when value is greater than 24 can be left alone for now, will be addressed if pair[0] < 0.0 neg_time_hash[i] = pair[0] time = pair[0] + 24.0 time_value_pairs[i][0] = time else time = pair[0] end temp_min_time_hash[i] = pair[0] end time_value_pairs.rotate!(temp_min_time_hash.key(temp_min_time_hash.values.min)) # validate order, issue warning and correct if out of order last_time = nil throw_order_warning = false pre_fix_time_value_pairs = time_value_pairs.to_s time_value_pairs.each_with_index do |time_value_pair, i| if last_time.nil? last_time = time_value_pair[0] elsif time_value_pair[0] < last_time || neg_time_hash.key?(i) # @todo it doesn't actually stop here now if error_on_out_of_order OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.ScheduleDay', "Pre-interpolated processed hash for #{schedule_day.name} has one or more out of order conflicts: #{pre_fix_time_value_pairs}. Method will stop because Error on Out of Order was set to true.") end if neg_time_hash.key?(i) orig_current_time = time_value_pair[0] updated_time = 0.0 last_buffer = 'NA' else # determine much space last item can move if i < 2 last_buffer = time_value_pairs[i - 1][0] # can move down to 0 without any issues else last_buffer = time_value_pairs[i - 1][0] - time_value_pairs[i - 2][0] end # move to previous timestep but don't exceed available buffer updated_time = time_value_pairs[i - 1][0] - [ramp_frequency, last_buffer].min end # update values in array orig_current_time = time_value_pair[0] time_value_pairs[i - 1][0] = updated_time time_value_pairs[i][0] = updated_time # reporting mostly for diagnostic purposes # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Parametric.ScheduleDay', "For #{schedule_day.name} profile item #{i} time was #{last_time} and item #{i + 1} time was #{orig_current_time}. Last buffer is #{last_buffer}. Changing both times to #{updated_time}.") last_time = updated_time throw_order_warning = true else last_time = time_value_pair[0] end end # issue warning if order was changed if throw_order_warning # OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.ScheduleDay', "Pre-interpolated processed hash for #{schedule_day.name} has one or more out of order conflicts: #{pre_fix_time_value_pairs}. Time values were adjusted as shown to crate a valid profile: #{time_value_pairs}") end # add interpolated values at ramp_frequency time_value_pairs.each_with_index do |time_value_pair, i| # store current and next time and value current_time = time_value_pair[0] current_value = time_value_pair[1] if i + 1 < time_value_pairs.size next_time = time_value_pairs[i + 1][0] next_value = time_value_pairs[i + 1][1] else # use time and value of first item next_time = time_value_pairs[0][0] + 24 # need to adjust values for beginning of array next_value = time_value_pairs[0][1] end step_delta = next_time - current_time # skip if time between values is 0 or less than ramp frequency next if step_delta <= ramp_frequency # skip if next value is same next if current_value == next_value # add interpolated value to array interpolated_time = current_time + ramp_frequency interpolated_value = (next_value * (interpolated_time - current_time) / step_delta) + (current_value * (next_time - interpolated_time) / step_delta) time_value_pairs.insert(i + 1, [interpolated_time, interpolated_value]) end # remove second instance of time when there are two time_values_used = [] items_to_remove = [] time_value_pairs.each_with_index do |time_value_pair, i| if time_values_used.include? time_value_pair[0] items_to_remove << i else time_values_used << time_value_pair[0] end end items_to_remove.reverse.each do |i| time_value_pairs.delete_at(i) end # if time is > 24 shift to front of array and adjust value rotate_steps = 0 time_value_pairs.reverse.each_with_index do |time_value_pair, i| next unless time_value_pair[0] > 24 rotate_steps -= 1 time_value_pair[0] -= 24 end time_value_pairs.rotate!(rotate_steps) # add a 24 on the end of array that matches the first value if time_value_pairs.last[0].to_i != 24 time_value_pairs << [24.0, time_value_pairs.first[1]] end # OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.ScheduleDay', "Schedule #{schedule_day.name} will be adjusted with these time-value pairs: #{time_value_pairs}") # reset scheduleDay values based on interpolated values schedule_day.clearValues time_value_pairs.each do |time_val| hour = time_val.first.floor min = ((time_val.first - hour) * 60.0).floor os_time = OpenStudio::Time.new(0, hour, min, 0) value = time_val.last schedule_day.addValue(os_time, value) end # @todo apply secondary logic # Tell EnergyPlus to interpolate schedules to timestep so that it doesn't have to be done in this code # sch_day.setInterpolatetoTimestep(true) # if model.version < OpenStudio::VersionString.new('3.8.0') # day_sch.setInterpolatetoTimestep(true) # else # day_sch.setInterpolatetoTimestep('Average') # end return schedule_day end |
.schedule_day_get_equivalent_full_load_hours(schedule_day) ⇒ Object
Returns the ScheduleDay daily equivalent full load hours (EFLH).
return [Double] The daily total equivalent full load hours for this schedule
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/openstudio-standards/schedules/information.rb', line 317 def self.schedule_day_get_equivalent_full_load_hours(schedule_day) daily_flh = 0 values = schedule_day.values times = schedule_day.times previous_time_decimal = 0 times.each_with_index do |time, i| time_decimal = (time.days * 24.0) + time.hours + (time.minutes / 60.0) + (time.seconds / 3600.0) duration_of_value = time_decimal - previous_time_decimal daily_flh += values[i] * duration_of_value previous_time_decimal = time_decimal end return daily_flh end |
.schedule_day_get_hourly_values(schedule_day, model = nil) ⇒ Array<Double>
Returns an array of average hourly values from a ScheduleDay object Returns 24 values
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 |
# File 'lib/openstudio-standards/schedules/information.rb', line 338 def self.schedule_day_get_hourly_values(schedule_day, model = nil) schedule_values = [] if model.nil? model = schedule_day.model end if model.version.str < '3.8.0' # determine smallest time interval times = schedule_day.times time_interval_min = 15.0 previous_time_decimal = 0.0 times.each_with_index do |time, i| time_decimal = (time.days * 24.0 * 60.0) + (time.hours * 60.0) + time.minutes + (time.seconds / 60) interval_min = time_decimal - previous_time_decimal time_interval_min = interval_min if interval_min < time_interval_min previous_time_decimal = time_decimal end time_interval_min = time_interval_min.round(0).to_i # get the hourly average by averaging the values in the hour at the smallest time interval (0..23).each do |j| values = [] times = (time_interval_min..60).step(time_interval_min).to_a times.each { |t| values << schedule_day.getValue(OpenStudio::Time.new(0, j, t, 0)) } schedule_values << (values.sum / times.size).round(5) end else num_timesteps = model.getTimestep.numberOfTimestepsPerHour day_timeseries = schedule_day.timeSeries.values.to_a schedule_values = day_timeseries.each_slice(num_timesteps).map { |slice| slice.sum / slice.size.to_f } end unless schedule_values.size == 24 OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} returned illegal number of values: #{schedule_values.size}.") return false end return schedule_values end |
.schedule_day_get_min_max(schedule_day) ⇒ Hash
Returns the ScheduleDay minimum and maximum values
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/openstudio-standards/schedules/information.rb', line 293 def self.schedule_day_get_min_max(schedule_day) min = nil max = nil values = schedule_day.values values.each do |value| if min.nil? min = value else if min > value then min = value end end if max.nil? max = value else if max < value then max = value end end end result = { 'min' => min, 'max' => max } end |
.schedule_day_multiply_by_value(schedule_day, multiplier, lower_apply_limit: nil) ⇒ OpenStudio::Model::ScheduleDay
Method to multiply the values in a day schedule by a specified value The method can optionally apply the multiplier to only values above a lower limit. This limit prevents multipliers for things like occupancy sensors from affecting unoccupied hours.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# 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) ⇒ OpenStudio::Model::ScheduleDay
Sets the values of a day schedule from an array of values Clears out existing time value pairs and sets to supplied values
78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# 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) ⇒ Void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Set the hours of operation (0 or 1) for a ScheduleDay. Clears out existing time/value pairs and sets to supplied values.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# 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') ⇒ Object
Returns the Schedule minimum and maximum values during the winter or summer design day.
return [Hash] returns a hash with ‘min’ and ‘max’ values
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/openstudio-standards/schedules/information.rb', line 45 def self.schedule_get_design_day_min_max(schedule, type = 'winter') case schedule.iddObjectType.valueName.to_s when 'OS_Schedule_Ruleset' schedule = schedule.to_ScheduleRuleset.get result = OpenstudioStandards::Schedules.schedule_ruleset_get_design_day_min_max(schedule, type) when 'OS_Schedule_Constant' schedule = schedule.to_ScheduleConstant.get result = OpenstudioStandards::Schedules.schedule_constant_get_design_day_min_max(schedule, type) when 'OS_Schedule_Compact' schedule = schedule.to_ScheduleCompact.get result = OpenstudioStandards::Schedules.schedule_compact_get_design_day_min_max(schedule, type) when 'OS_Schedule_Year' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleYear schedules.") result = { 'min' => nil, 'max' => nil } when 'OS_Schedule_Interval' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleInterval schedules.") result = { 'min' => nil, 'max' => nil } else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for #{__method__}.") result = { 'min' => nil, 'max' => nil } end return result end |
.schedule_get_equivalent_full_load_hours(schedule) ⇒ Object
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.
return [Double] The total equivalent full load hours for this schedule
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/openstudio-standards/schedules/information.rb', line 76 def self.schedule_get_equivalent_full_load_hours(schedule) case schedule.iddObjectType.valueName.to_s when 'OS_Schedule_Ruleset' schedule = schedule.to_ScheduleRuleset.get result = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule) when 'OS_Schedule_Constant' schedule = schedule.to_ScheduleConstant.get result = OpenstudioStandards::Schedules.schedule_constant_get_equivalent_full_load_hours(schedule) when 'OS_Schedule_Compact' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleCompact schedules.") result = nil when 'OS_Schedule_Year' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleYear schedules.") result = nil when 'OS_Schedule_Interval' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleInterval schedules.") result = nil else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for #{__method__}.") result = nil end return result end |
.schedule_get_hourly_values(schedule) ⇒ Array<Double>
Returns an array of average hourly values from a Schedule object Returns 8760 values, 8784 for leap years.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/openstudio-standards/schedules/information.rb', line 106 def self.schedule_get_hourly_values(schedule) case schedule.iddObjectType.valueName.to_s when 'OS_Schedule_Ruleset' schedule = schedule.to_ScheduleRuleset.get result = OpenstudioStandards::Schedules.schedule_ruleset_get_hourly_values(schedule) when 'OS_Schedule_Constant' schedule = schedule.to_ScheduleConstant.get result = OpenstudioStandards::Schedules.schedule_constant_get_hourly_values(schedule) when 'OS_Schedule_Compact' schedule = schedule.to_ScheduleCompact.get result = OpenstudioStandards::Schedules.schedule_compact_get_hourly_values(schedule) when 'OS_Schedule_Year' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleYear schedules.") result = nil when 'OS_Schedule_Interval' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleInterval schedules.") result = nil else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for #{__method__}.") result = nil end return result end |
.schedule_get_min_max(schedule, only_run_period_values: false) ⇒ Object
Returns the Schedule minimum and maximum values encountered during the run-period. This method does not include summer and winter design day values.
return [Hash] returns a hash with ‘min’ and ‘max’ values
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/openstudio-standards/schedules/information.rb', line 15 def self.schedule_get_min_max(schedule, only_run_period_values: false) case schedule.iddObjectType.valueName.to_s when 'OS_Schedule_Ruleset' schedule = schedule.to_ScheduleRuleset.get result = OpenstudioStandards::Schedules.schedule_ruleset_get_min_max(schedule, only_run_period_values: only_run_period_values) when 'OS_Schedule_Constant' schedule = schedule.to_ScheduleConstant.get result = OpenstudioStandards::Schedules.schedule_constant_get_min_max(schedule) when 'OS_Schedule_Compact' schedule = schedule.to_ScheduleCompact.get result = OpenstudioStandards::Schedules.schedule_compact_get_min_max(schedule) when 'OS_Schedule_Year' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleYear schedules.") result = { 'min' => nil, 'max' => nil } when 'OS_Schedule_Interval' OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} does not yet support ScheduleInterval schedules.") result = { 'min' => nil, 'max' => nil } else OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "unrecognized schedule type #{schedule.iddObjectType.valueName} for #{__method__}.") result = { 'min' => nil, 'max' => nil } end return result end |
.schedule_ruleset_add_rule(schedule_ruleset, values, start_date: nil, end_date: nil, day_names: nil, rule_name: nil) ⇒ OpenStudio::Model::ScheduleRule
Add a ScheduleRule to a ScheduleRuleset object from an array of hourly values
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 |
# 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 = {}) ⇒ OpenStudio::Model::ScheduleRuleset
Adjust hours of operation
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 395 396 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 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 |
# File 'lib/openstudio-standards/schedules/modify.rb', line 350 def self.schedule_ruleset_adjust_hours_of_operation(schedule_ruleset, = {}) 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 = defaults.merge() # 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 ['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 == ['mon'] then alter_rule = true end if rule.applyTuesday && rule.applyTuesday == ['tue'] then alter_rule = true end if rule.applyWednesday && rule.applyWednesday == ['wed'] then alter_rule = true end if rule.applyThursday && rule.applyThursday == ['thur'] then alter_rule = true end if rule.applyFriday && rule.applyFriday == ['fri'] then alter_rule = true end if rule.applySaturday && rule.applySaturday == ['sat'] then alter_rule = true end if rule.applySunday && rule.applySunday == ['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 ['summer'] profiles << schedule.summerDesignDaySchedule end if ['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 #{['delta_length_hoo']} shift #{['shift_hoo']}") # break time args into hours and minutes start_hoo_hours = (['base_start_hoo']).to_i start_hoo_minutes = (((['base_start_hoo']) - (['base_start_hoo']).to_i) * 60).to_i finish_hoo_hours = (['base_finish_hoo']).to_i finish_hoo_minutes = (((['base_finish_hoo']) - (['base_finish_hoo']).to_i) * 60).to_i delta_hours = (['delta_length_hoo']).to_i delta_minutes = (((['delta_length_hoo']) - (['delta_length_hoo']).to_i) * 60).to_i shift_hours = (['shift_hoo']).to_i shift_minutes = (((['shift_hoo']) - (['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 ['base_start_hoo'] <= ['base_finish_hoo'] base_opp_day_length = ['base_finish_hoo'] - ['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 = ['base_finish_hoo'] - ['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 + ['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 = ((temp_time - adj_mid_hoo) * hoo_time_multiplier) - (temp_time - adj_mid_hoo) else = ((temp_time - adj_mid_non_hoo) * non_hoo_time_multiplier) - (temp_time - adj_mid_non_hoo) end new_time = time + shift_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) ⇒ OpenStudio::Model::ScheduleRuleset
this will use parametric inputs contained in schedule and profiles along with inferred hours of operation to generate updated ruleset schedule profiles
948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 |
# File 'lib/openstudio-standards/schedules/parametric.rb', line 948 def self.schedule_ruleset_apply_parametric_inputs(schedule_ruleset, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order, parametric_inputs = nil) # Check if parametric inputs were supplied and generate them if not if parametric_inputs.nil? OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Parametric.ScheduleRuleset', "For #{schedule_ruleset.name}, no parametric inputs were not supplied so they will be generated now.") parametric_inputs = OpenstudioStandards::Schedules.model_setup_parametric_schedules(schedule.model, gather_data_only: true) end # Check that parametric inputs exist for this schedule after generation if parametric_inputs[schedule_ruleset].nil? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.ScheduleRuleset', "For #{schedule_ruleset.name}, no parametric inputs exists so schedule will not be changed.") return schedule_ruleset end # Check that an hours of operation schedule is associated with this schedule if parametric_inputs[schedule_ruleset][:hoo_inputs].nil? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.ScheduleRuleset', "For #{schedule_ruleset.name}, no associated hours of operation schedule was found so schedule will not be changed.") return schedule_ruleset end # Get the hours of operation schedule hours_of_operation = parametric_inputs[schedule_ruleset][:hoo_inputs] # OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.ScheduleRuleset', "For #{schedule_ruleset.name} hours_of_operation = #{hours_of_operation}.") starting_aeflh = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_ruleset) # store floor and ceiling value val_flr = nil if schedule_ruleset.hasAdditionalProperties && schedule_ruleset.additionalProperties.hasFeature('param_sch_floor') val_flr = schedule_ruleset.additionalProperties.getFeatureAsDouble('param_sch_floor').get end val_clg = nil if schedule_ruleset.hasAdditionalProperties && schedule_ruleset.additionalProperties.hasFeature('param_sch_ceiling') val_clg = schedule_ruleset.additionalProperties.getFeatureAsDouble('param_sch_ceiling').get end # loop through schedule days from highest to lowest priority (with default as lowest priority) # if rule needs to be split to address hours of operation rules add new rule next to relevant existing rule profiles = {} schedule_ruleset.scheduleRules.each do |rule| # remove any use manually generated non parametric rules or any auto-generated rules from prior application of formulas and hoo sch_day = rule.daySchedule if !sch_day.hasAdditionalProperties || !sch_day.additionalProperties.hasFeature('param_day_tag') || (sch_day.additionalProperties.getFeatureAsString('param_day_tag').get == 'autogen') sch_day.remove # remove day schedule for this rule rule.remove # remove the rule elsif !sch_day.additionalProperties.hasFeature('param_day_profile') OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.ScheduleRuleset', "#{schedule.name} doesn't have a parametric formula for #{rule.name} This profile will not be altered.") next else profiles[sch_day] = rule end end profiles[schedule_ruleset.defaultDaySchedule] = nil # get indices for current schedule year_description = schedule_ruleset.model.yearDescription.get year = year_description.assumedYear year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) indices_vector = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date) # process profiles profiles.each do |sch_day, rule| # for current profile index identify hours of operation index that contains all days if rule.nil? current_rule_index = -1 else current_rule_index = rule.ruleIndex end # loop through indices looking of rule in hoo that contains days in the rule hoo_target_index = nil days_used = [] indices_vector.each_with_index do |profile_index, i| if profile_index == current_rule_index then days_used << (i + 1) end end # find days_used in hoo profiles that contains all days used from this profile hoo_profile_match_hash = {} best_fit_check = {} hours_of_operation.each do |profile_index, value| days_for_rule_not_in_hoo_profile = days_used - value[:days_used] hoo_profile_match_hash[profile_index] = days_for_rule_not_in_hoo_profile best_fit_check[profile_index] = days_for_rule_not_in_hoo_profile.size if days_for_rule_not_in_hoo_profile.empty? hoo_target_index = profile_index end end clone_needed = false hoo_target_index = best_fit_check.key(best_fit_check.values.min) if best_fit_check[hoo_target_index] > 0 clone_needed = true end # get hours of operation for this specific profile hoo_start = hours_of_operation[hoo_target_index][:hoo_start] # puts hoo_start hoo_end = hours_of_operation[hoo_target_index][:hoo_end] # puts hoo_end # update scheduleDay OpenstudioStandards::Schedules.schedule_day_adjust_from_parameters(sch_day, hoo_start, hoo_end, val_flr, val_clg, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order) # clone new rule if needed if clone_needed # make list of new rules needed as has or array autogen_rules = {} days_to_fill = hoo_profile_match_hash[hoo_target_index] hours_of_operation.each do |profile_index, value| remainder = days_to_fill - value[:days_used] day_for_rule = days_to_fill - remainder if remainder.size < days_to_fill.size autogen_rules[profile_index] = { days_to_fill: day_for_rule, hoo_start: hoo_start, hoo_end: hoo_end} end days_to_fill = remainder end # loop through new rules to make and process autogen_rules.each do |autogen_rule, hash| # generate new rule sch_rule_autogen = OpenStudio::Model::ScheduleRule.new(schedule_ruleset) if current_rule_index target_index = schedule_ruleset.scheduleRules.size - 1 # just above default else target_index = current_rule_index - 1 # confirm just above orig rule end current_rule_index = target_index if rule.nil? sch_rule_autogen.setName("autogen #{schedule_ruleset.name} #{target_index}") else sch_rule_autogen.setName("autogen #{rule.name} #{target_index}") end schedule_ruleset.setScheduleRuleIndex(sch_rule_autogen, target_index) # @todo confirm this is higher priority than the non-auto-generated rule hash[:days_to_fill].each do |day| date = OpenStudio::Date.fromDayOfYear(day, year) sch_rule_autogen.addSpecificDate(date) end sch_rule_autogen.setApplySunday(true) sch_rule_autogen.setApplyMonday(true) sch_rule_autogen.setApplyTuesday(true) sch_rule_autogen.setApplyWednesday(true) sch_rule_autogen.setApplyThursday(true) sch_rule_autogen.setApplyFriday(true) sch_rule_autogen.setApplySaturday(true) # match profile from source rule (don't add time/values need a formula to process) sch_day_auto_gen = sch_rule_autogen.daySchedule sch_day_auto_gen.setName("#{sch_rule_autogen.name}_day_sch") sch_day_auto_gen.additionalProperties.setFeature('param_day_tag', 'autogen') val = sch_day.additionalProperties.getFeatureAsString('param_day_profile').get sch_day_auto_gen.additionalProperties.setFeature('param_day_profile', val) val = sch_day.additionalProperties.getFeatureAsString('param_day_secondary_logic').get sch_day_auto_gen.additionalProperties.setFeature('param_day_secondary_logic', val) val = sch_day.additionalProperties.getFeatureAsString('param_day_secondary_logic_arg_val').get sch_day_auto_gen.additionalProperties.setFeature('param_day_secondary_logic_arg_val', val) # get hours of operation for this specific profile hoo_start = hash[:hoo_start] hoo_end = hash[:hoo_end] # process new rule OpenstudioStandards::Schedules.schedule_day_adjust_from_parameters(sch_day_auto_gen, hoo_start, hoo_end, val_flr, val_clg, ramp_frequency, infer_hoo_for_non_assigned_objects, error_on_out_of_order) end end end # @todo create summer and winter design day profiles (make sure scheduleDay objects parametric) # @todo should they have their own formula, or should this be hard coded logic by schedule type # check orig vs. updated aeflh final_aeflh = OpenstudioStandards::Schedules.schedule_ruleset_get_equivalent_full_load_hours(schedule_ruleset) percent_change = ((starting_aeflh - final_aeflh) / starting_aeflh) * 100.0 if percent_change.abs > 0.05 OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.Parametric.ScheduleRuleset', "For #{schedule_ruleset.name}, applying parametric schedules made a #{percent_change.round(1)}% change in annual equivalent full load hours. (from #{starting_aeflh.round(2)} to #{final_aeflh.round(2)})") end return schedule_ruleset end |
.schedule_ruleset_cleanup_profiles(schedule_ruleset) ⇒ OpenStudio::Model::ScheduleRuleset
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.
Remove unused profiles and set most prevalent profile as default. This method expands on the functionality of the RemoveUnusedDefaultProfiles measure.
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 |
# 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') ⇒ OpenStudio::Model::ScheduleRuleset
add in design day adjustments, maybe as an optional argument
provide option to clone existing schedule
Increase/decrease by percentage or static value change value when value passes/fails test
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 |
# 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) ⇒ Array
creates a minimal set of ScheduleRules that applies to all days in a given array of day of year indices
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 |
# 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) ⇒ Hash
Return the annual days of year that covered by each rule of a schedule ruleset
889 890 891 892 893 894 895 896 897 898 899 900 |
# File 'lib/openstudio-standards/schedules/information.rb', line 889 def self.schedule_ruleset_get_annual_days_used(schedule_ruleset) year_description = schedule_ruleset.model.getYearDescription year = year_description.assumedYear year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) sch_indices_vector = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date) days_used_hash = Hash.new { |h, k| h[k] = [] } sch_indices_vector.uniq.sort.each do |rule_i| sch_indices_vector.each_with_index { |rule, i| days_used_hash[rule_i] << (i + 1) if rule_i == rule } end return days_used_hash end |
.schedule_ruleset_get_day_schedules(schedule_ruleset, include_design_days: false) ⇒ Array<OpenStudio::Model::ScheduleDay>
Returns the day schedules associated with a schedule ruleset Optionally includes summer and winter design days
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 |
# File 'lib/openstudio-standards/schedules/information.rb', line 859 def self.schedule_ruleset_get_day_schedules(schedule_ruleset, include_design_days: false) profiles = [] profiles << schedule_ruleset.defaultDaySchedule schedule_ruleset.scheduleRules.each do |rule| profiles << rule.daySchedule end if include_design_days if schedule_ruleset.isSummerDesignDayScheduleDefaulted OpenStudio.logFree(OpenStudio::Warning, 'openstudio.standards.Schedules.Information', "#{__method__} called for #{schedule_ruleset.name.get} with include_design_days: true, but the summer design day is defaulted. Duplicate design day will not be added.") else profiles << rule.summerDesignDaySchedule end if schedule_ruleset.isWinterDesignDayScheduleDefaulted OpenStudio.logFree(OpenStudio::Warning, 'openstudio.standards.Schedules.Information', "#{__method__} called for #{schedule_ruleset.name.get} with include_design_days: true, but the winter design day is defaulted. Duplicate design day will not be added.") else profiles << rule.winterDesignDaySchedule end end return profiles end |
.schedule_ruleset_get_design_day_min_max(schedule_ruleset, type = 'winter') ⇒ Object
Returns the ScheduleRuleset minimum and maximum values during the winter or summer design day.
return [Hash] returns a hash with ‘min’ and ‘max’ values
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 |
# File 'lib/openstudio-standards/schedules/information.rb', line 490 def self.schedule_ruleset_get_design_day_min_max(schedule_ruleset, type = 'winter') # validate schedule unless schedule_ruleset.to_ScheduleRuleset.is_initialized OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} failed because object #{schedule_ruleset.name.get} is not a ScheduleRuleset.") return nil end if type == 'winter' schedule = schedule_ruleset.winterDesignDaySchedule elsif type == 'summer' schedule = schedule_ruleset.summerDesignDaySchedule end if !schedule OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', "#{schedule_ruleset.name.get} is missing #{type} design day schedule, use default day schedule to process the min max search") schedule = schedule_ruleset.defaultDaySchedule end min = nil max = nil values = schedule.values values.each do |value| if min.nil? min = value else min = value if min > value end if max.nil? max = value else max = value if max < value end end result = { 'min' => min, 'max' => max } return result end |
.schedule_ruleset_get_equivalent_full_load_hours(schedule_ruleset) ⇒ Object
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.
return [Double] The total equivalent full load hours for this schedule
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 |
# File 'lib/openstudio-standards/schedules/information.rb', line 534 def self.schedule_ruleset_get_equivalent_full_load_hours(schedule_ruleset) # validate schedule unless schedule_ruleset.to_ScheduleRuleset.is_initialized OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} failed because object #{schedule_ruleset.name.get} is not a ScheduleRuleset.") return nil end # define the start and end date year_start_date = nil year_end_date = nil if schedule_ruleset.model.yearDescription.is_initialized year_description = schedule_ruleset.model.yearDescription.get year = year_description.assumedYear year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', 'Year description is not specified. Full load hours calculation will assume 2009, the default year OS uses.') year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009) end # Get the ordered list of all the day schedules day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date) # Get the array of which schedule is used on each day of the year day_schs_used_each_day = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date) # Create a map that shows how many days each schedule is used day_sch_freq = day_schs_used_each_day.group_by { |n| n } # Build a hash that maps schedule day index to schedule day schedule_index_to_day = {} day_schs.each_with_index do |day_sch, i| schedule_index_to_day[day_schs_used_each_day[i]] = day_sch end # Loop through each of the schedules that is used, figure out the # full load hours for that day, then multiply this by the number # of days that day schedule applies and add this to the total. annual_flh = 0.0 max_daily_flh = 0.0 default_day_sch = schedule_ruleset.defaultDaySchedule day_sch_freq.each do |freq| sch_index = freq[0] number_of_days_sch_used = freq[1].size # Get the day schedule at this index day_sch = nil if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule) day_sch = default_day_sch else day_sch = schedule_index_to_day[sch_index] end daily_flh = OpenstudioStandards::Schedules.schedule_day_get_equivalent_full_load_hours(day_sch) # Multiply the daily EFLH by the number # of days this schedule is used per year # and add this to the overall total annual_flh += daily_flh * number_of_days_sch_used end # Warn if the max daily EFLH is more than 24, # which would indicate that this isn't a fractional schedule. if max_daily_flh > 24 OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', "#{schedule_ruleset.name.get} has more than 24 EFLH in one day schedule, indicating that it is not a fractional schedule.") end return annual_flh end |
.schedule_ruleset_get_hourly_values(schedule_ruleset) ⇒ Array<Double>
Returns an array of average hourly values from a ScheduleRuleset object Returns 8760 values, 8784 for leap years.
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 |
# File 'lib/openstudio-standards/schedules/information.rb', line 609 def self.schedule_ruleset_get_hourly_values(schedule_ruleset) # validate schedule unless schedule_ruleset.to_ScheduleRuleset.is_initialized OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} failed because object #{schedule_ruleset.name.get} is not a ScheduleRuleset.") return nil end model = schedule_ruleset.model # define the start and end date year_start_date = nil year_end_date = nil if model.yearDescription.is_initialized year_description = model.yearDescription.get year = year_description.assumedYear year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', 'Year description is not specified. Annual hours above value calculation will assume 2009, the default year OS uses.') year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009) end # Get the ordered list of all the day schedules day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date) # Loop through each day schedule and add its hours to total # @todo store the 24 hourly average values for each day schedule instead of recalculating for all days annual_hourly_values = [] day_schs.each do |day_sch| # add daily average hourly values to annual hourly values array daily_hours = OpenstudioStandards::Schedules.schedule_day_get_hourly_values(day_sch, model) annual_hourly_values += daily_hours end return annual_hourly_values end |
.schedule_ruleset_get_hours_above_value(schedule_ruleset, lower_limit) ⇒ Double
Returns the total number of hours where the schedule is greater than the specified value. This method includes leap days on leap years.
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 |
# File 'lib/openstudio-standards/schedules/information.rb', line 653 def self.schedule_ruleset_get_hours_above_value(schedule_ruleset, lower_limit) # validate schedule unless schedule_ruleset.to_ScheduleRuleset.is_initialized OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} failed because object #{schedule_ruleset.name.get} is not a ScheduleRuleset.") return nil end # define the start and end date year_start_date = nil year_end_date = nil if schedule_ruleset.model.yearDescription.is_initialized year_description = schedule_ruleset.model.yearDescription.get year = year_description.assumedYear year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', 'Year description is not specified. Annual hours above value calculation will assume 2009, the default year OS uses.') year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009) end # Get the ordered list of all the day schedules day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date) # Get the array of which schedule is used on each day of the year day_schs_used_each_day = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date) # Create a map that shows how many days each schedule is used day_sch_freq = day_schs_used_each_day.group_by { |n| n } # Build a hash that maps schedule day index to schedule day schedule_index_to_day = {} day_schs.each_with_index do |day_sch, i| schedule_index_to_day[day_schs_used_each_day[i]] = day_sch end # Loop through each of the schedules that is used, figure out the # hours for that day, then multiply this by the number # of days that day schedule applies and add this to the total. annual_hrs = 0.0 default_day_sch = schedule_ruleset.defaultDaySchedule day_sch_freq.each do |freq| sch_index = freq[0] number_of_days_sch_used = freq[1].size # Get the day schedule at this index day_sch = nil day_sch = if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule) default_day_sch else schedule_index_to_day[sch_index] end # Determine the hours for just one day daily_hrs = 0.0 values = day_sch.values times = day_sch.times previous_time_decimal = 0.0 times.each_with_index do |time, i| time_decimal = (time.days * 24.0) + time.hours + (time.minutes / 60.0) + (time.seconds / 3600.0) duration_of_value = time_decimal - previous_time_decimal if values[i] > lower_limit daily_hrs += duration_of_value end previous_time_decimal = time_decimal end # Multiply the daily hours by the number # of days this schedule is used per year # and add this to the overall total annual_hrs += daily_hrs * number_of_days_sch_used end return annual_hrs end |
.schedule_ruleset_get_min_max(schedule_ruleset, only_run_period_values: false) ⇒ Hash
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.
392 393 394 395 396 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 |
# File 'lib/openstudio-standards/schedules/information.rb', line 392 def self.schedule_ruleset_get_min_max(schedule_ruleset, only_run_period_values: false) # validate schedule unless schedule_ruleset.to_ScheduleRuleset.is_initialized OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} failed because object #{schedule_ruleset.name.get} is not a ScheduleRuleset.") return nil end # day schedules day_schedules = [] # check only day schedules in the run period if only_run_period_values # get year if schedule_ruleset.model.yearDescription.is_initialized year_description = schedule_ruleset.model.yearDescription.get year = year_description.assumedYear else OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Schedules.Information', 'Year description is not specified. Full load hours calculation will assume 2009, the default year OS uses.') year = 2009 end # get start and end month and day run_period = schedule_ruleset.model.getRunPeriod start_month = run_period.getBeginMonth start_day = run_period.getBeginDayOfMonth end_month = run_period.getEndMonth end_day = run_period.getEndDayOfMonth # set the start and end date start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(start_month), start_day, year) end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new(end_month), end_day, year) # Get the ordered list of all the day schedules day_schs = schedule_ruleset.getDaySchedules(start_date, end_date) # Get the array of which schedule is used on each day of the year day_schs_used_each_day = schedule_ruleset.getActiveRuleIndices(start_date, end_date) # Create a map that shows how many days each schedule is used day_sch_freq = day_schs_used_each_day.group_by { |n| n } # Build a hash that maps schedule day index to schedule day schedule_index_to_day = {} day_schs.each_with_index do |day_sch, i| schedule_index_to_day[day_schs_used_each_day[i]] = day_sch end # Loop through each of the schedules and record which ones are used day_sch_freq.each do |freq| sch_index = freq[0] number_of_days_sch_used = freq[1].size next unless number_of_days_sch_used > 0 # Get the day schedule at this index day_sch = nil if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule) day_sch = schedule_ruleset.defaultDaySchedule else day_sch = schedule_index_to_day[sch_index] end # add day schedule to array day_schedules << day_sch end else # use all day schedules day_schedules << schedule_ruleset.defaultDaySchedule schedule_ruleset.scheduleRules.each { |rule| day_schedules << rule.daySchedule } end # get min and max from day schedules array min = nil max = nil day_schedules.each do |day_schedule| values = day_schedule.values values.each do |value| if min.nil? min = value else if min > value then min = value end end if max.nil? max = value else if max < value then max = value end end end end result = { 'min' => min, 'max' => max } return result end |
.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') ⇒ Hash
Method to process space load instance schedules for model_setup_parametric_schedules
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 |
# File 'lib/openstudio-standards/schedules/parametric.rb', line 491 def self.schedule_ruleset_get_parametric_inputs(schedule_ruleset, space_load_instance, parametric_inputs, hours_of_operation, ramp: true, min_ramp_dur_hr: 2.0, gather_data_only: false, hoo_var_method: 'hours') if parametric_inputs.key?(schedule_ruleset) && (hours_of_operation != parametric_inputs[schedule_ruleset][:hoo_inputs]) # don't warn if the hours of operation between old and new schedule are equivalent # OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.Schedule', "#{space_load_instance.name} uses #{schedule_ruleset.name} but parametric inputs have already been setup based on hours of operation for #{parametric_inputs[schedule_ruleset][:target].name}.") return nil end # gather and store data for scheduleRuleset min_max = OpenstudioStandards::Schedules.schedule_ruleset_get_min_max(schedule_ruleset) ruleset_hash = { floor: min_max['min'], ceiling: min_max['max'], target: space_load_instance, hoo_inputs: hours_of_operation } parametric_inputs[schedule_ruleset] = ruleset_hash # stop here if only gathering information otherwise will continue and generate additional parametric properties for schedules and rules if gather_data_only then return parametric_inputs end # set scheduleRuleset properties props = schedule_ruleset.additionalProperties # don't need to gather more than once return parametric_inputs if props.getFeatureAsString('param_sch_ver') == '0.0.1' props.setFeature('param_sch_ver', '0.0.1') # this is needed to see if formulas are in sync with version of standards that processes them also used to flag schedule as parametric props.setFeature('param_sch_floor', min_max['min']) props.setFeature('param_sch_ceiling', min_max['max']) # cleanup existing profiles OpenstudioStandards::Schedules.schedule_ruleset_cleanup_profiles(schedule_ruleset) # get initial hash of schedule days => rule index values schedule_days = OpenstudioStandards::Schedules.schedule_ruleset_get_schedule_day_rule_indices(schedule_ruleset) # get all day schedule equivalent full load hours to tag daily_flhs = schedule_days.keys.map { |day_sch| OpenstudioStandards::Schedules.schedule_day_get_equivalent_full_load_hours(day_sch) } # collect initial rule index => array of days used hash sch_ruleset_days_used = OpenstudioStandards::Schedules.schedule_ruleset_get_annual_days_used(schedule_ruleset) # match up schedule rule days with hours of operation days # sch_day_map is a hash where keys are the rule index values of the schedule # and values are hashes where keys are the hours of operation rule index, and values are arrays of days that the schedule sch_day_map = {} sch_ruleset_days_used.each do |sch_index, sch_days| # first create a hash that maps each day index to the hoo index that covers that day day_map = {} sch_days.each do |day| # find the hour of operation rule that contains the day number hoo_keys = hours_of_operation.find { |_, val| val[:days_used].include?(day) } if hoo_keys.nil? OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.Schedule', "In #{__method__}, cannot find schedule #{schedule_days.key(sch_index).name.get} day #{day} in hour of operation profiles. Something went wrong.") end hoo_key = hoo_keys.first day_map[day] = hoo_key end # group days with the same hour of operation index grouped_days = Hash.new { |h, k| h[k] = [] } day_map.each { |day, hoo_idx| grouped_days[hoo_idx] << day } # group by schedule rule index sch_day_map[sch_index] = grouped_days end # create new rule corresponding to the hour of operation rules new_rule_ct = 0 rule_idxs_to_keep = [] sch_day_map.each do |sch_index, hoo_group| hoo_group.each do |hoo_index, day_group| # skip common default days next if sch_index == -1 && hoo_index == -1 # skip if rules already match if (sch_ruleset_days_used[sch_index] - day_group).empty? # OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.Schedules', "in #{__method__}: #{schedule_ruleset.name} rule #{sch_index} already matches hours of operation rule #{hoo_index}; new rule won't be created.") # keep these rules index values to avoid deleting later rule_idxs_to_keep << sch_index unless sch_index == -1 next end # create new rules new_rules = OpenstudioStandards::Schedules.schedule_ruleset_create_rules_from_day_list(schedule_ruleset, day_group, schedule_day: schedule_days.key(sch_index)) new_rule_ct += new_rules.size end end # new rules are created at top of list - cleanup old rules that have been replaced if !(new_rule_ct == 0 || new_rule_ct == schedule_ruleset.scheduleRules.size) # increase index values by the number of new rules rule_idxs_adjusted = rule_idxs_to_keep.map { |v| v + new_rule_ct } rules_to_remove = [] schedule_ruleset.scheduleRules.each_with_index do |rule, i| # don't remove new rules or rules that already match if (rule.ruleIndex > new_rule_ct - 1) && !rule_idxs_adjusted.include?(rule.ruleIndex) rules_to_remove << rule end end rules_to_remove.each(&:remove) end # re-collect new schedule rules schedule_days = OpenstudioStandards::Schedules.schedule_ruleset_get_schedule_day_rule_indices(schedule_ruleset) # re-collect new rule index => days used array sch_ruleset_days_used = OpenstudioStandards::Schedules.schedule_ruleset_get_annual_days_used(schedule_ruleset) # step through profiles and add additional properties to describe profiles schedule_days.each_with_index do |(schedule_day, current_rule_index), i| hoo_target_index = nil days_used = sch_ruleset_days_used[current_rule_index] # find days_used in hoo profiles that contains all days used from this profile hoo_profile_match_hash = {} best_fit_check = {} # loop through indices looking of rule in hoo that contains all days in the rule hours_of_operation.each do |profile_index, value| if (days_used - value[:days_used]).empty? hoo_target_index = profile_index end end # if schedule day days used can't be mapped to single hours of operation then do not use hoo variables, otherwise would have to split rule and alter model if hoo_target_index.nil? hoo_start = nil hoo_end = nil occ = nil vac = nil OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Parametric.Schedules', "In #{__method__}, schedule #{schedule_day.name} has no hours_of_operation target index. Won't be modified") # OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.Schedules', "In #{__method__}, schedule #{schedule_day.name} has no hours_of_operation target index. Won't be modified") else # get hours of operation for this specific profile hoo_start = hours_of_operation[hoo_target_index][:hoo_start] hoo_end = hours_of_operation[hoo_target_index][:hoo_end] occ = hours_of_operation[hoo_target_index][:hoo_hours] vac = 24.0 - hours_of_operation[hoo_target_index][:hoo_hours] end props = schedule_day.additionalProperties par_val_time_hash = {} # time is key, value is value in and optional value out as a one or two object array times = schedule_day.times values = schedule_day.values values.each_with_index do |value, j| # don't add value until 24 if it is the same as first value for non constant profiles if values.size > 1 && j == values.size - 1 && value == values.first next end current_time = times[j].totalHours # if step height goes floor to ceiling then do not ramp. if !ramp || (values.uniq.size < 3) # this will result in steps like old profiles, update to ramp in most cases if j == values.size - 1 par_val_time_hash[current_time] = [value, values.first] else par_val_time_hash[current_time] = [value, values[j + 1]] end else if j == 0 prev_time = times.last.totalHours - 24 # e.g. 24 would show as until 0 else prev_time = times[j - 1].totalHours end if j == values.size - 1 next_time = times.first.totalHours + 24 # e.g. 6 would show as until 30 next_value = values.first # do nothing if value is same as first value if value == next_value next end else next_time = times[j + 1].totalHours next_value = values[j + 1] end # delta time is min min_ramp_dur_hr, half of previous dur, half of next dur # todo - would be nice to change to 0.25 for vally less than 2 hours multiplier = 0.5 delta = [min_ramp_dur_hr, (current_time - prev_time) * multiplier, (next_time - current_time) * multiplier].min # add value to left if not already added if !par_val_time_hash.key?(current_time - delta) time_left = current_time - delta if time_left < 0.0 then time_left += 24.0 end par_val_time_hash[time_left] = [value] end # add value to right time_right = current_time + delta if time_right > 24.0 then time_right -= 24.0 end par_val_time_hash[time_right] = [next_value] end end # sort hash by keys par_val_time_hash.sort.to_h # calculate estimated value (not including any secondary logic) est_daily_flh = 0.0 prev_time = par_val_time_hash.keys.max - 24.0 prev_value = par_val_time_hash.values.last.last # last value in last optional pair of values par_val_time_hash.sort.each do |time, value_array| segment_length = time - prev_time avg_value = (value_array.first + prev_value) * 0.5 est_daily_flh += segment_length * avg_value prev_time = time prev_value = value_array.last end # test expected value against estimated value daily_flh = OpenstudioStandards::Schedules.schedule_day_get_equivalent_full_load_hours(schedule_day) percent_change = ((daily_flh - est_daily_flh) / daily_flh) * 100.0 if percent_change.abs > 0.05 # @todo this estimation can have flaws. Fix or remove it, make sure to update for secondary logic (if we implement that here) # post application checks compares against actual instead of estimated values OpenStudio.logFree(OpenStudio::Debug, 'openstudio.standards.Parametric.Schedule', "For day schedule #{schedule_day.name} in #{schedule_ruleset.name} there was a #{percent_change.round(4)}% change. Expected full load hours is #{daily_flh.round(4)}, but estimated value is #{est_daily_flh.round(4)}") end # puts "#{schedule_day.name}: par_val_time_hash: #{par_val_time_hash}" raw_string = [] # flags to control variable settings for tstats start_set = false end_set = false par_val_time_hash.sort.each do |time, value_array| # add in value variables # not currently using range, only using min max for constant schedules or schedules with just two values value_array_var = [] value_array.each do |val| if val == min_max['min'] && values.uniq.size < 3 value_array_var << 'val_flr' elsif val == min_max['max'] && values.uniq.size < 3 value_array_var << 'val_clg' else value_array_var << val end end # add in hoo variables when matching profile found if !hoo_start.nil? # identify which identifier (star,mid,end) time is closest to, which will impact formula structure # includes code to identify delta for wrap around of 24 formula_identifier = {} start_delta_array = [hoo_start - time, hoo_start - time + 24, hoo_start - time - 24] start_delta_array_abs = [(hoo_start - time).abs, (hoo_start - time + 24).abs, (hoo_start - time - 24).abs] start_delta_h = start_delta_array[start_delta_array_abs.index(start_delta_array_abs.min)] formula_identifier['start'] = start_delta_h mid_calc = hoo_start + (occ * 0.5) mid_delta_array = [mid_calc - time, mid_calc - time + 24, mid_calc - time - 24] mid_delta_array_abs = [(mid_calc - time).abs, (mid_calc - time + 24).abs, (mid_calc - time - 24).abs] mid_delta_h = mid_delta_array[mid_delta_array_abs.index(mid_delta_array_abs.min)] formula_identifier['mid'] = mid_delta_h end_delta_array = [hoo_end - time, hoo_end - time + 24, hoo_end - time - 24] end_delta_array_abs = [(hoo_end - time).abs, (hoo_end - time + 24).abs, (hoo_end - time - 24).abs] end_delta_h = end_delta_array[end_delta_array_abs.index(end_delta_array_abs.min)] formula_identifier['end'] = end_delta_h # need to store min absolute value to pick the best fit formula_identifier_min_abs = {} formula_identifier.each do |k, v| formula_identifier_min_abs[k] = v.abs end # puts formula_identifier # puts formula_identifier_min_abs # pick from possible formula approaches for any datapoint where x is hour value min_key = formula_identifier_min_abs.key(formula_identifier_min_abs.values.min) min_value = formula_identifier[min_key] case hoo_var_method when 'hours' # minimize x, which should be no greater than 12, see if rounding to 2 decimal places works min_value = min_value.round(2) if min_key == 'start' if min_value == 0 time = 'hoo_start' elsif min_value < 0 time = "hoo_start + #{min_value.abs}" else # greater than 0 time = "hoo_start - #{min_value}" end # puts time elsif min_key == 'mid' if min_value == 0 time = 'mid' # converted to variable for simplicity but could also be described like this # time = "hoo_start + occ * 0.5" elsif min_value < 0 time = "mid + #{min_value.abs}" else # greater than 0 time = "mid - #{min_value}" end # puts time else # min_key == "end" if min_value == 0 time = 'hoo_end' elsif min_value < 0 time = "hoo_end + #{min_value.abs}" else # greater than 0 time = "hoo_end - #{min_value}" end # puts time end when 'fractional' # minimize x(hour before converted to fraction), which should be no greater than 0.5 as fraction, see if rounding to 3 decimal places works if occ > 0 min_value_occ_fract = min_value.abs / occ else min_value_occ_fract = 0.0 end if vac > 0 min_value_vac_fract = min_value.abs / vac else min_value_vac_fract = 0.0 end if min_key == 'start' if min_value == 0 time = 'hoo_start' elsif min_value < 0 time = "hoo_start + occ * #{min_value_occ_fract.round(3)}" else # greater than 0 time = "hoo_start - vac * #{min_value_vac_fract.round(3)}" end elsif min_key == 'mid' # @todo see what is going wrong with after mid in formula if min_value == 0 time = 'mid' # converted to variable for simplicity but could also be described like this # time = "hoo_start + occ * 0.5" elsif min_value < 0 time = "mid + occ * #{min_value_occ_fract.round(3)}" else # greater than 0 time = "mid - occ * #{min_value_occ_fract.round(3)}" end else # min_key == "end" if min_value == 0 time = 'hoo_end' elsif min_value < 0 time = "hoo_end + vac * #{min_value_vac_fract.round(3)}" else # greater than 0 time = "hoo_end - occ * #{min_value_occ_fract.round(3)}" end end when 'tstat' # puts formula_identifier if min_key == 'start' && !start_set time = 'hoo_start + 0' start_set = true else time = 'hoo_end + 0' end end end # populate string if value_array_var.size == 1 raw_string << "#{time} ~ #{value_array_var.first}" else # should only have 1 or two values (value in and optional value out) raw_string << "#{time} ~ #{value_array_var.first} ~ #{value_array_var.last}" end end # puts "#{schedule_day.name}: param_day_profile: #{raw_string.join(' | ')}" # store profile formula with hoo and value variables props.setFeature('param_day_profile', raw_string.join(' | ')) # @todo not used yet, but will add methods described below and others # @todo lower infiltration based on air loop hours of operation if air loop has outdoor air object # @todo lower lighting or plug loads based on occupancy at given time steps in a space # @todo set elevator fraction based multiple factors such as trips, occupants per trip, and elevator type to determine floor consumption when not in use. props.setFeature('param_day_secondary_logic', '') # secondary logic method such as occupancy impacting schedule values props.setFeature('param_day_secondary_logic_arg_val', '') # optional argument used for some secondary logic applied to values # tag profile type # may be useful for parametric changes to tag typical, medium, minimal, or same ones with off_peak prefix # todo - I would like to use these same tags for hours of operation and have parametric tags then ignore the days of week and date range from the rule object # tagging min/max makes sense in fractional schedules but not temperature schedules like thermostats (specifically cooling setpoints) # todo - I think these tags should come from occpancy schedule for space(s) schedule. That way all schedules in a space will refer to same profile from hours of operation # todo - add school specific logic hear or in post processing, currently default profile for school may not be most prevalent one if current_rule_index == -1 props.setFeature('param_day_tag', 'typical_operation') elsif daily_flh == daily_flhs.min props.setFeature('param_day_tag', 'minimal_operation') elsif daily_flh == daily_flhs.max props.setFeature('param_day_tag', 'maximum_operation') # normally this should not be used as typical should be the most active day else props.setFeature('param_day_tag', 'medium_operation') # not min max or typical end end return parametric_inputs end |
.schedule_ruleset_get_schedule_day_rule_indices(schedule_ruleset) ⇒ Hash
Returns the rule indices associated with defaultDay and Rule days for a given ScheduleRuleset
906 907 908 909 910 911 |
# File 'lib/openstudio-standards/schedules/information.rb', line 906 def self.schedule_ruleset_get_schedule_day_rule_indices(schedule_ruleset) schedule_day_hash = {} schedule_day_hash[schedule_ruleset.defaultDaySchedule] = -1 schedule_ruleset.scheduleRules.each { |rule| schedule_day_hash[rule.daySchedule] = rule.ruleIndex } return schedule_day_hash end |
.schedule_ruleset_get_start_and_end_times(schedule_ruleset) ⇒ Hash<OpenStudio:Time>
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.
765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 |
# File 'lib/openstudio-standards/schedules/information.rb', line 765 def self.schedule_ruleset_get_start_and_end_times(schedule_ruleset) # validate schedule unless schedule_ruleset.to_ScheduleRuleset.is_initialized OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{__method__} failed because object #{schedule_ruleset.name.get} is not a ScheduleRuleset.") return [nil, nil] end # Define the start and end date if schedule_ruleset.model.yearDescription.is_initialized year_description = schedule_ruleset.model.yearDescription.get year = year_description.assumedYear year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, year) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, year) else year_start_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('January'), 1, 2009) year_end_date = OpenStudio::Date.new(OpenStudio::MonthOfYear.new('December'), 31, 2009) end # Get the ordered list of all the day schedules that are used by this schedule ruleset day_schs = schedule_ruleset.getDaySchedules(year_start_date, year_end_date) # Get a 365-value array of which schedule is used on each day of the year, day_schs_used_each_day = schedule_ruleset.getActiveRuleIndices(year_start_date, year_end_date) # Create a map that shows how many days each schedule is used day_sch_freq = day_schs_used_each_day.group_by { |n| n } day_sch_freq = day_sch_freq.sort_by { |freq| freq[1].size } common_day_freq = day_sch_freq.last # Build a hash that maps schedule day index to schedule day schedule_index_to_day = {} day_schs.each_with_index do |day_sch, i| schedule_index_to_day[day_schs_used_each_day[i]] = day_sch end # Get the most common day schedule sch_index = common_day_freq[0] number_of_days_sch_used = common_day_freq[1].size # Get the day schedule at this index day_sch = if sch_index == -1 # If index = -1, this day uses the default day schedule (not a rule) schedule_ruleset.defaultDaySchedule else schedule_index_to_day[sch_index] end # Determine the full load hours for just one day values = [] times = [] day_sch.times.each_with_index do |time, i| times << day_sch.times[i] values << day_sch.values[i] end # Get the minimum value start_val = values.first end_val = values.last # Get the start time (first time value goes above minimum) start_time = nil values.each_with_index do |val, i| break if i == values.size - 1 # Stop if we reach end of array if val == start_val && values[i + 1] > start_val start_time = times[i] break end end # Get the end time (first time value goes back down to minimum) end_time = nil values.each_with_index do |val, i| if i < values.size - 1 if val > end_val && values[i + 1] == end_val end_time = times[i] break end else if val > end_val && values[0] == start_val # Check first hour of day for schedules that end at midnight end_time = OpenStudio::Time.new(0, 24, 0, 0) break end end end return { 'start_time' => start_time, 'end_time' => end_time } end |
.schedule_ruleset_get_timeseries(schedule_ruleset) ⇒ OpenStudio::TimeSeries
create OpenStudio TimeSeries object from ScheduleRuleset values
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 |
# File 'lib/openstudio-standards/schedules/information.rb', line 734 def self.schedule_ruleset_get_timeseries(schedule_ruleset) # validate schedule unless schedule_ruleset.to_ScheduleRuleset.is_initialized OpenStudio.logFree(OpenStudio::Error, 'openstudio.standards.Schedules.Information', "#{method} failed because object #{schedule_ruleset.name.get} is not a ScheduleRuleset.") return nil end yd = schedule_ruleset.model.getYearDescription start_date = yd.makeDate(1, 1) end_date = yd.makeDate(12, 31) values = OpenStudio::DoubleVector.new day = OpenStudio::Time.new(1.0) interval = OpenStudio::Time.new(1.0 / 48.0) day_schedules = schedule_ruleset.getDaySchedules(start_date, end_date) day_schedules.each do |day_schedule| time = interval while time < day values << day_schedule.getValue(time) time += interval end end timeseries = OpenStudio::TimeSeries.new(start_date, interval, OpenStudio.createVector(values), '') return timeseries end |
.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) ⇒ Boolean
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.
904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 |
# File 'lib/openstudio-standards/schedules/parametric.rb', line 904 def self.schedule_ruleset_set_hours_of_operation(schedule_ruleset, wkdy_start_time: nil, wkdy_end_time: nil, sat_start_time: nil, sat_end_time: nil, sun_start_time: nil, sun_end_time: nil) # Default day is assumed to represent weekdays if wkdy_start_time && wkdy_end_time schedule_day_set_hours_of_operation(schedule_ruleset.defaultDaySchedule, wkdy_start_time, wkdy_end_time) # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ScheduleRuleset', "For #{schedule_ruleset.name}, set default operating hours to #{wkdy_start_time}-#{wkdy_end_time}.") end # Modify each rule schedule_ruleset.scheduleRules.each do |rule| if rule.applyMonday || rule.applyTuesday || rule.applyWednesday || rule.applyThursday || rule.applyFriday if wkdy_start_time && wkdy_end_time schedule_day_set_hours_of_operation(rule.daySchedule, wkdy_start_time, wkdy_end_time) # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ScheduleRuleset', "For #{schedule_ruleset.name}, set Saturday rule operating hours to #{wkdy_start_time}-#{wkdy_end_time}.") end elsif rule.applySaturday if sat_start_time && sat_end_time schedule_day_set_hours_of_operation(rule.daySchedule, sat_start_time, sat_end_time) # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ScheduleRuleset', "For #{schedule_ruleset.name}, set Saturday rule operating hours to #{sat_start_time}-#{sat_end_time}.") end elsif rule.applySunday if sun_start_time && sun_end_time schedule_day_set_hours_of_operation(rule.daySchedule, sun_start_time, sun_end_time) # OpenStudio.logFree(OpenStudio::Info, 'openstudio.standards.ScheduleRuleset', "For #{schedule_ruleset.name}, set Sunday rule operating hours to #{sun_start_time}-#{sun_end_time}.") end end end return true end |
.schedule_ruleset_simple_value_adjust(schedule_ruleset, value, modification_type = 'Multiplier') ⇒ OpenStudio::Model::ScheduleRuleset
add in design day adjustments, maybe as an optional argument
provide option to clone existing schedule
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.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# 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') ⇒ OpenStudio::Model::ScheduleRuleset
Increase/decrease by percentage or static value change value when time passes test
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 |
# 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) ⇒ Hash
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.
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 |
# File 'lib/openstudio-standards/schedules/parametric.rb', line 410 def self.spaces_space_types_get_parametric_schedule_inputs(spaces_space_types, parametric_inputs, gather_data_only) spaces_space_types.each do |space_type| # get hours of operation for space type once next if space_type.instance_of?(OpenStudio::Model::SpaceType) && space_type.floorArea == 0 hours_of_operation = Space.space_hours_of_operation(space_type) if hours_of_operation.nil? OpenStudio.logFree(OpenStudio::Warn, 'openstudio.standards.Parametric.Space', "Can't evaluate schedules for #{space_type.name}, doesn't have hours of operation.") next end # loop through internal load instances space_type.lights.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.luminaires.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.electricEquipment.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.gasEquipment.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.steamEquipment.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.otherEquipment.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.people.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) if space_load_instance.activityLevelSchedule.is_initialized && space_load_instance.activityLevelSchedule.get.to_ScheduleRuleset.is_initialized act_sch = space_load_instance.activityLevelSchedule.get.to_ScheduleRuleset.get OpenstudioStandards::Schedules.schedule_ruleset_get_parametric_inputs(act_sch, space_load_instance, parametric_inputs, hours_of_operation, gather_data_only: gather_data_only, hoo_var_method: 'hours') end end space_type.spaceInfiltrationDesignFlowRates.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end space_type.spaceInfiltrationEffectiveLeakageAreas.each do |space_load_instance| OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(space_load_instance, parametric_inputs, hours_of_operation, gather_data_only) end dsgn_spec_oa = space_type.designSpecificationOutdoorAir if dsgn_spec_oa.is_initialized OpenstudioStandards::Space.space_load_instance_get_parametric_schedule_inputs(dsgn_spec_oa.get, parametric_inputs, hours_of_operation, gather_data_only) end end return parametric_inputs end |