Class: ShiftScheduleByType

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

Overview

Start the measure

Instance Method Summary collapse

Instance Method Details

#arguments(model) ⇒ Object

Define the arguments that the user will input



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/measures/ShiftScheduleByType/measure.rb', line 35

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

  # Argument to specify the amount of time by which the chosen schedule(s) will be shifted
  shift_value = OpenStudio::Measure::OSArgument.makeDoubleArgument('shift_value', true)
  shift_value.setDisplayName('Shift Schedule Profiles Forward (24hr, use decimal for sub hour and negative values for backward shift).')
  shift_value.setDefaultValue(1)
  args << shift_value

  # Argument to choose which schedules will be shifted
  choices = OpenStudio::StringVector.new
  choices << 'Cooling' 
  choices << 'Heating'
  choices << 'CoolHeat'
  schedchoice = OpenStudio::Measure::OSArgument.makeChoiceArgument('schedchoice', choices)
  schedchoice.setDisplayName('Choose which schedule class(es) to shift by the specified shift value')
  schedchoice.setDefaultValue('CoolHeat')
  args << schedchoice

  return args
end

#descriptionObject

human readable description



25
26
27
# File 'lib/measures/ShiftScheduleByType/measure.rb', line 25

def description
  return 'This measure was developed for the URBANopt Class Project and shifts specific building schedules if they include cooling ("CLG"), heating ("HTG"), or air ("Air") strings. The measure will shift these chosen schedules by an amount specified by the user and will also output a .csv file of the schedules before and after the shift.'
end

#modeler_descriptionObject

human readable description of modeling approach



30
31
32
# File 'lib/measures/ShiftScheduleByType/measure.rb', line 30

def modeler_description
  return "Depending on the model's thermostat deadband settings, shifting of exclusively cooling or heating schedules can result in EnergyPlus deadband errors. It is recommended to shift both cooling and heating schedules using the 'coolheat' option for schedchoice. If no schedules for the current model include the cooling, heating, or air strings, none will be shifted. Schedules including the string 'setback' are intentionally excluded from shifts in order to prevent EnergyPlus errors."
end

#nameObject

define the name that a user will see, this method may be deprecated as the display name in PAT comes from the name field in measure.xml



20
21
22
# File 'lib/measures/ShiftScheduleByType/measure.rb', line 20

def name
  return 'ShiftScheduleByType'
end

#run(model, runner, user_arguments) ⇒ Object

define what happens when the measure is run



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
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/measures/ShiftScheduleByType/measure.rb', line 58

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

  # use the built-in error checking
  if !runner.validateUserArguments(arguments(model), user_arguments)
    return false
  end
  
  # Export CSV file with Schedule setpoints before schedule shifts
  #model1 = runner.lastOpenStudioModel
  #model = model1.get
  interval = 60
  header = []
  header << 'Time'
  schedules = []
  model.getScheduleDays.each do |schedule|
    header << schedule.name.get
    schedules << schedule
  end

  dt = OpenStudio::Time.new(0, 0, interval, 0)
  time = OpenStudio::Time.new(0, 0, 0, 0)
  stop = OpenStudio::Time.new(1, 0, 0, 0)
  values = []
  while time <= stop
    row = []
    row << time.to_s
    schedules.each do |schedule|
      row << schedule.getValue(time)
    end
    values << row

    time += dt
  end
  
  runner.registerInfo("Writing CSV report 'schedulereportbefore.csv'")
  File.open('schedulereportbefore.csv', 'w') do |file|
    file.puts header.join(',')
    values.each do |row|
      file.puts row.join(',')
    end
  end
  
  # populate choice argument for schedules that are applied to surfaces in the model
  schedule_handles = OpenStudio::StringVector.new
  schedule_display_names = OpenStudio::StringVector.new

  # putting space types and names into hash
  schedule_args = model.getScheduleRulesets
  schedule_args_hash = {}
  schedule_args.each do |schedule_arg|
    schedule_args_hash[schedule_arg.name.to_s] = schedule_arg
  end

  # looping through sorted hash of schedules
  schedule_args_hash.sort.map do |key, value|
    # only include if schedule use count > 0
    if value.directUseCount > 0
      schedule_handles << value.handle.to_s
      schedule_display_names << key
    end
  end

  # Assign the user inputs to variables
  shift_value = runner.getDoubleArgumentValue('shift_value', user_arguments)
  schedchoice = runner.getStringArgumentValue('schedchoice', user_arguments)

  apply_to_all_schedules = true

  # Check shift value for reasonableness
  if (shift_value / 24) == (shift_value / 24).to_i
    runner.registerAsNotApplicable('No schedule shift was requested, the model was not changed.')
  end

  # Get schedules for measure
  schedules = []
  if apply_to_all_schedules
    raw_schedules = model.getScheduleRulesets
    raw_schedules_hash = {}
    raw_schedules.each do |raw_schedule|
      raw_schedules_hash[raw_schedule.name.to_s] = raw_schedule
    end

    # Looping through sorted hash of schedules
    raw_schedules_hash.sort.map do |name, value|
      # Only include if the schedule is used in the model
      if value.directUseCount > 0
        schedule_handles << value.handle.to_s
        schedule_display_names << name
        runner.registerInfo("Searching Schedule: #{name}")
        if !name.downcase.include?("setback")
          if schedchoice == "Cool" # Cooling and Air Only
            if (name.to_s.include?("CLG") || name.to_s.include?("Air"))
              # ADD v to Cooling list
              schedules << value
              runner.registerInfo("Schedule #{name} does contain 'CLG' or 'Air'")
            else
              runner.registerInfo("Schedule #{name} does not contain 'CLG' or 'Air'")
            end
          elsif schedchoice == "Heat" # Heating and Air Only
            if (name.to_s.include?("HTG") || name.to_s.include?("Air"))
              # ADD v to Heating list
              schedules << value
              runner.registerInfo("Schedule #{name} does contain 'HTG' or 'Air'")
            else
              runner.registerInfo("Schedule #{name} does not contain 'HTG' or 'Air'")
            end
          elsif schedchoice == "CoolHeat" # Cooling, Heating, and Air
            if (name.to_s.include?("CLG") || name.to_s.include?("HTG") || name.to_s.include?("Air"))
              # ADD v to Cooling/Heating list
              schedules << value
              runner.registerInfo("Schedule #{name} does contain 'CLG' or 'HTG' or 'Air'")
            else
              runner.registerInfo("Schedule #{name} does not contain 'CLG' or 'HTG' or 'Air'")
            end
          else
            runner.registerError('Unexpected value of schedchoice: ' + schedchoice + '.')
            return false
          end
        end
      end
    end

  else
    runner.registerAsNotApplicable('No schedules included in shift')
  end

  # Loop through all chosen schedules
  schedules.each do |schedule|
    # Array of all profiles to change
    profiles = []

    # Push default profiles to array
    default_rule = schedule.defaultDaySchedule
    profiles << default_rule

    # Push profiles to array
    rules = schedule.scheduleRules
    rules.each do |rule|
      day_sch = rule.daySchedule
      profiles << day_sch
    end

    # Add design days to array
    summer_design = schedule.summerDesignDaySchedule
    winter_design = schedule.winterDesignDaySchedule
    profiles << summer_design
    profiles << winter_design

    # Reporting initial condition of model
    if apply_to_all_schedules
      runner.registerInitialCondition("#{schedules.size} schedules are shifted in this model.")
    else
      runner.registerInitialCondition("Schedule #{schedule.name} has #{profiles.size} profiles including design days.")
    end

    # Rename schedule
    schedule.setName("#{schedule.name} - (shifted #{shift_value} hours)")

    shift_hours = shift_value.to_i
    shift_minutes = ((shift_value - shift_value.to_i) * 60).to_i

    # Give info messages as I change specific profiles
    runner.registerInfo("Adjusting #{schedule.name}")

    # Edit profiles
    profiles.each do |day_sch|
      times = day_sch.times
      values = day_sch.values

      runner.registerInfo("Old day_sch: #{day_sch}")
      runner.registerInfo("Old Times: #{times}")
      runner.registerInfo("Old Values: #{values}")

      # Time objects to use in measure
      time_0 = OpenStudio::Time.new(0, 0, 0, 0)
      time_24 =  OpenStudio::Time.new(0, 24, 0, 0)
      shift_time = OpenStudio::Time.new(0, shift_hours, shift_minutes, 0)

      # Arrays for values to avoid overlap conflict of times
      new_times = []
      new_values = []

      if values.length < 2 # Avoid adjusting schedules with only one cooling/heating/air setpoint
        new_times = times
        new_values = values
      else # Adjust schedules with more than 3 setpoints
        # Create a pair of times and values for what will be 0 time after adjustment
        new_times << time_24
        if shift_time > time_0
          new_values << day_sch.getValue(time_24 - shift_time)
        else
          new_values << day_sch.getValue(time_0 - shift_time)
        end

        # Clear values
        day_sch.clearValues

        # Unfreeze arrays before editing
        times = times.clone(freeze: false) if times.frozen?
        values = values.clone(freeze: false) if values.frozen?

        if values.length == 2
          timeschg = values.length - 2
        else 
          timeschg = values.length - 3
        end

        # Count number of arrays
        for i in 0..timeschg 
          new_time = times[i] + shift_time
          # Adjust wrap around times for times that are t > 24 or t < 0
          if new_time < time_0
            new_time = new_time + time_24
            values.rotate(1) # Move first value to last value in array
            runner.registerWarning("Times adjusted for wrap around due to new time < 0.")
          elsif new_time > time_24
            new_time = new_time - time_24
            values.rotate(-1) # Move last value to first value in array
            runner.registerWarning("Times adjusted for wrap around due to new time > 24.")
          else # If 0 < new_time < 24
            new_time = new_time
          end
          # Make new values
          new_times = times.insert(i, new_time)
          new_times.delete_at(i+1)
        end

        new_values = values # Set new values equal to original schedule values
        new_times.freeze

      end

      if values.length <= 4
        timeschg = values.length - 1
      else
        timeschg = values.length - 2
      end

      for i in 0..(timeschg)
        day_sch.addValue(new_times[i], new_values[i])
      end
      runner.registerInfo("New day_sch: #{day_sch}")
      runner.registerInfo("New Times: #{new_times}")
      runner.registerInfo("New Values: #{new_values}")
    end
  end

  # Report final condition of model
  if apply_to_all_schedules
    runner.registerFinalCondition('Shifted time for all profiles for all schedules.')
  else
    runner.registerFinalCondition("Shifted time for all profiles used by this schedule.")
  end
  
  # Export CSV file with Schedule setpoints after schedule shifts
  #model = runner.lastOpenStudioModel
  #model = model.get
  interval = 60

  header = []
  header << 'Time'
  schedules = []

  model.getScheduleDays.each do |schedule|
    header << schedule.name.get
    schedules << schedule
  end

  dt = OpenStudio::Time.new(0, 0, interval, 0)
  time = OpenStudio::Time.new(0, 0, 0, 0)
  stop = OpenStudio::Time.new(1, 0, 0, 0)
  values = []
  while time <= stop
    row = []
    row << time.to_s
    schedules.each do |schedule|
      row << schedule.getValue(time)
    end
    values << row

    time += dt
  end

  runner.registerInfo("Writing CSV report 'schedulereportafter.csv'")
  File.open('schedulereportafter.csv', 'w') do |file|
    file.puts header.join(',')
    values.each do |row|
      file.puts row.join(',')
    end
  end
  
  return true
end