openstudio-analysis 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,362 +8,12 @@ class IncreaseInsulationRValueForRoofs < OpenStudio::Ruleset::ModelUserScript
8
8
  # define the arguments that the user will input
9
9
  def arguments(model)
10
10
  args = OpenStudio::Ruleset::OSArgumentVector.new
11
-
12
- # make an argument insulation R-value
13
- r_value = OpenStudio::Ruleset::OSArgument.makeDoubleArgument('r_value', true)
14
- r_value.setDisplayName('Insulation R-value (ft^2*h*R/Btu).')
15
- r_value.setDefaultValue(30.0)
16
- args << r_value
17
-
18
- # make an argument for material and installation cost
19
- material_cost_increase_ip = OpenStudio::Ruleset::OSArgument.makeDoubleArgument('material_cost_increase_ip', true)
20
- material_cost_increase_ip.setDisplayName('Increase in Material and Installation Costs for Construction per Area Used ($/ft^2).')
21
- material_cost_increase_ip.setDefaultValue(0.0)
22
- args << material_cost_increase_ip
23
-
24
- # make an argument for demolition cost
25
- one_time_retrofit_cost_ip = OpenStudio::Ruleset::OSArgument.makeDoubleArgument('one_time_retrofit_cost_ip', true)
26
- one_time_retrofit_cost_ip.setDisplayName('One Time Retrofit Cost to Add Insulation to Construction ($/ft^2).')
27
- one_time_retrofit_cost_ip.setDefaultValue(0.0)
28
- args << one_time_retrofit_cost_ip
29
-
30
- # make an argument for duration in years until costs start
31
- years_until_retrofit_cost = OpenStudio::Ruleset::OSArgument.makeIntegerArgument('years_until_retrofit_cost', true)
32
- years_until_retrofit_cost.setDisplayName('Year to Incur One Time Retrofit Cost (whole years).')
33
- years_until_retrofit_cost.setDefaultValue(0)
34
- args << years_until_retrofit_cost
35
-
36
- args
37
11
  end # end the arguments method
38
12
 
39
13
  # define what happens when the measure is run
40
14
  def run(model, runner, user_arguments)
41
15
  super(model, runner, user_arguments)
42
16
 
43
- # use the built-in error checking
44
- unless runner.validateUserArguments(arguments(model), user_arguments)
45
- return false
46
- end
47
-
48
- # assign the user inputs to variables
49
- r_value = runner.getDoubleArgumentValue('r_value', user_arguments)
50
- material_cost_increase_ip = runner.getDoubleArgumentValue('material_cost_increase_ip', user_arguments)
51
- one_time_retrofit_cost_ip = runner.getDoubleArgumentValue('one_time_retrofit_cost_ip', user_arguments)
52
- years_until_retrofit_cost = runner.getIntegerArgumentValue('years_until_retrofit_cost', user_arguments)
53
-
54
- # set limit for minimum insulation. This is used to limit input and for inferring insulation layer in construction.
55
- min_expected_r_value_ip = 1 # ip units
56
-
57
- # check the R-value for reasonableness
58
- if r_value < 0 or r_value > 500
59
- runner.registerError("The requested roof insulation R-value of #{r_value} ft^2*h*R/Btu was above the measure limit.")
60
- return false
61
- elsif r_value > 60
62
- runner.registerWarning("The requested roof insulation R-value of #{r_value} ft^2*h*R/Btu is abnormally high.")
63
- elsif r_value < min_expected_r_value_ip
64
- runner.registerWarning("The requested roof insulation R-value of #{r_value} ft^2*h*R/Btu is abnormally low.")
65
- end
66
-
67
- # check lifecycle arguments for reasonableness
68
- if not years_until_retrofit_cost >= 0 and not years_until_retrofit_cost <= 100
69
- runner.registerError('Year to incur one time retrofit cost should be a non-negative integer less than or equal to 100.')
70
- end
71
-
72
- # short def to make numbers pretty (converts 4125001.25641 to 4,125,001.26 or 4,125,001). The definition be called through this measure
73
- def neat_numbers(number, roundto = 2) # round to 0 or 2)
74
- if roundto == 2
75
- number = sprintf '%.2f', number
76
- else
77
- number = number.round
78
- end
79
- # regex to add commas
80
- number.to_s.reverse.gsub(%r{([0-9]{3}(?=([0-9])))}, '\\1,').reverse
81
- end # end def neat_numbers
82
-
83
- # helper to make it easier to do unit conversions on the fly
84
- def unit_helper(number, from_unit_string, to_unit_string)
85
- converted_number = OpenStudio.convert(OpenStudio::Quantity.new(number, OpenStudio.createUnit(from_unit_string).get), OpenStudio.createUnit(to_unit_string).get).get.value
86
- end
87
-
88
- # convert r_value and material_cost to si for future use
89
- r_value_si = unit_helper(r_value, 'ft^2*h*R/Btu', 'm^2*K/W')
90
- material_cost_increase_si = unit_helper(material_cost_increase_ip, '1/ft^2', '1/m^2')
91
-
92
- # create an array of roofs and find range of starting construction R-value (not just insulation layer)
93
- surfaces = model.getSurfaces
94
- exterior_surfaces = []
95
- exterior_surface_constructions = []
96
- exterior_surface_construction_names = []
97
- roof_resistance = []
98
- surfaces.each do |surface|
99
- if surface.outsideBoundaryCondition == 'Outdoors' and surface.surfaceType == 'RoofCeiling'
100
- exterior_surfaces << surface
101
- roof_const = surface.construction.get
102
- # only add construction if it hasn't been added yet
103
- unless exterior_surface_construction_names.include?(roof_const.name.to_s)
104
- exterior_surface_constructions << roof_const.to_Construction.get
105
- end
106
- exterior_surface_construction_names << roof_const.name.to_s
107
- roof_resistance << 1 / roof_const.thermalConductance.to_f
108
- end
109
- end
110
-
111
- # nothing will be done if there are no exterior surfaces
112
- if exterior_surfaces.empty?
113
- runner.registerAsNotApplicable('Model does not have any roofs.')
114
- end
115
-
116
- # report strings for initial condition
117
- initial_string = []
118
- exterior_surface_constructions.uniq.each do |exterior_surface_construction|
119
- # unit conversion of roof insulation from SI units (M^2*K/W) to IP units (ft^2*h*R/Btu)
120
- initial_conductance_ip = unit_helper(1 / exterior_surface_construction.thermalConductance.to_f, 'm^2*K/W', 'ft^2*h*R/Btu')
121
- initial_string << "#{exterior_surface_construction.name} (R-#{(sprintf "%.1f", initial_conductance_ip)})"
122
- end
123
- runner.registerInitialCondition("The building had #{initial_string.size} roof constructions: #{initial_string.sort.join(", ")}.")
124
-
125
- # hashes to track constructions and materials made by the measure, to avoid duplicates
126
- constructions_hash_old_new = {}
127
- constructions_hash_new_old = {} # used to get netArea of new construction and then cost objects of construction it replaced
128
- materials_hash = {}
129
-
130
- # array and counter for new constructions that are made, used for reporting final condition
131
- final_constructions_array = []
132
-
133
- # loop through all constructions and materials used on roofs, edit and clone
134
- exterior_surface_constructions.each do |exterior_surface_construction|
135
- construction_layers = exterior_surface_construction.layers
136
- max_thermal_resistance_material = ''
137
- max_thermal_resistance_material_index = ''
138
- counter = 0
139
- thermal_resistance_values = []
140
-
141
- # loop through construction layers and infer insulation layer/material
142
- construction_layers.each do |construction_layer|
143
- construction_layer_r_value = construction_layer.to_OpaqueMaterial.get.thermalResistance
144
- unless thermal_resistance_values.empty?
145
- if construction_layer_r_value > thermal_resistance_values.max
146
- max_thermal_resistance_material = construction_layer
147
- max_thermal_resistance_material_index = counter
148
- end
149
- end
150
- thermal_resistance_values << construction_layer_r_value
151
- counter = counter + 1
152
- end
153
-
154
- if not thermal_resistance_values.max > unit_helper(min_expected_r_value_ip, 'ft^2*h*R/Btu', 'm^2*K/W')
155
- runner.registerWarning("Construction '#{exterior_surface_construction.name}' does not appear to have an insulation layer and was not altered.")
156
- elsif not thermal_resistance_values.max < r_value_si
157
- runner.registerInfo("The insulation layer of construction #{exterior_surface_construction.name} exceeds the requested R-Value. It was not altered.")
158
- else
159
- # clone the construction
160
- final_construction = exterior_surface_construction.clone(model)
161
- final_construction = final_construction.to_Construction.get
162
- final_construction.setName("#{exterior_surface_construction.name} adj roof insulation")
163
- final_constructions_array << final_construction
164
-
165
- # loop through lifecycle costs getting total costs under "Construction" or "Salvage" category and add to counter if occurs during year 0
166
- const_LCCs = final_construction.lifeCycleCosts
167
- cost_added = false
168
- const_LCC_cat_const = false
169
- updated_cost_si = 0
170
- const_LCCs.each do |const_LCC|
171
- if const_LCC.category == 'Construction' and material_cost_increase_si != 0
172
- const_LCC_cat_const = true # need this test to add proper lcc if it didn't exist to start with
173
- # if multiple LCC objects associated with construction only adjust the cost of one of them.
174
- if not cost_added
175
- const_LCC.setCost(const_LCC.cost + material_cost_increase_si)
176
- else
177
- runner.registerInfo("More than one LifeCycleCost object with a category of Construction was associated with #{final_construction.name}. Cost was only adjusted for one of the LifeCycleCost objects.")
178
- end
179
- updated_cost_si += const_LCC.cost
180
- end
181
- end # end of const_LCCs.each
182
-
183
- # add construction object if it didnt exist to start with and a cost increase was requested
184
- if const_LCC_cat_const == false and material_cost_increase_si != 0
185
- lcc_for_uncosted_const = OpenStudio::Model::LifeCycleCost.createLifeCycleCost('LCC_increase_insulation', final_construction, material_cost_increase_si, 'CostPerArea', 'Construction', 20, 0).get
186
- runner.registerInfo("No material or installation costs existed for #{final_construction.name}. Created a new LifeCycleCost object with a material and installation cost of #{neat_numbers(unit_helper(lcc_for_uncosted_const.cost, "1/m^2", "1/ft^2"))} ($/ft^2). Assumed capitol cost in first year, an expected life of 20 years, and no O & M costs.")
187
- end
188
-
189
- if cost_added
190
- runner.registerInfo("Adjusting material and installation cost for #{final_construction.name} to #{neat_numbers(unit_helper(updated_cost_si, "1/m^2", "1/ft^2"))} ($/ft^2).")
191
- end
192
-
193
- # add one time cost if requested
194
- if one_time_retrofit_cost_ip > 0
195
- one_time_retrofit_cost_si = unit_helper(one_time_retrofit_cost_ip, '1/ft^2', '1/m^2')
196
- lcc_retrofit_specific = OpenStudio::Model::LifeCycleCost.createLifeCycleCost('LCC_retrofit_specific', final_construction, one_time_retrofit_cost_si, 'CostPerArea', 'Construction', 0, years_until_retrofit_cost).get # using 0 for repeat period since one time cost.
197
- runner.registerInfo("Adding one time cost of #{neat_numbers(unit_helper(lcc_retrofit_specific.cost, "1/m^2", "1/ft^2"))} ($/ft^2) related to retrofit of roof insulation.")
198
- end
199
-
200
- # push to hashes
201
- constructions_hash_old_new[exterior_surface_construction.name.to_s] = final_construction
202
- constructions_hash_new_old[final_construction] = exterior_surface_construction # push the object to hash key vs. name
203
-
204
- # find already cloned insulation material and link to construction
205
- target_material = max_thermal_resistance_material
206
- found_material = false
207
- materials_hash.each do |orig, new|
208
- if target_material.name.to_s == orig
209
- new_material = new
210
- materials_hash[max_thermal_resistance_material.name.to_s] = new_material
211
- final_construction.eraseLayer(max_thermal_resistance_material_index)
212
- final_construction.insertLayer(max_thermal_resistance_material_index, new_material)
213
- found_material = true
214
- end
215
- end
216
-
217
- # clone and edit insulation material and link to construction
218
- if found_material == false
219
- new_material = max_thermal_resistance_material.clone(model)
220
- new_material = new_material.to_OpaqueMaterial.get
221
- new_material.setName("#{max_thermal_resistance_material.name}_R-value #{r_value} (ft^2*h*R/Btu)")
222
- materials_hash[max_thermal_resistance_material.name.to_s] = new_material
223
- final_construction.eraseLayer(max_thermal_resistance_material_index)
224
- final_construction.insertLayer(max_thermal_resistance_material_index, new_material)
225
- runner.registerInfo("For construction'#{final_construction.name}', material'#{new_material.name}' was altered.")
226
-
227
- # edit insulation material
228
- new_material_matt = new_material.to_Material
229
- unless new_material_matt.empty?
230
- starting_thickness = new_material_matt.get.thickness
231
- target_thickness = starting_thickness * r_value_si / thermal_resistance_values.max
232
- final_thickness = new_material_matt.get.setThickness(target_thickness)
233
- end
234
- new_material_massless = new_material.to_MasslessOpaqueMaterial
235
- unless new_material_massless.empty?
236
- final_thermal_resistance = new_material_massless.get.setThermalResistance(r_value_si)
237
- end
238
- new_material_airgap = new_material.to_AirGap
239
- unless new_material_airgap.empty?
240
- final_thermal_resistance = new_material_airgap.get.setThermalResistance(r_value_si)
241
- end
242
- end # end of if found material is false
243
- end # end of if not thermal_resistance_values.max >
244
- end # end of loop through unique roof constructions
245
-
246
- # loop through construction sets used in the model
247
- default_construction_sets = model.getDefaultConstructionSets
248
- default_construction_sets.each do |default_construction_set|
249
- if default_construction_set.directUseCount > 0
250
- default_surface_const_set = default_construction_set.defaultExteriorSurfaceConstructions
251
- unless default_surface_const_set.empty?
252
- starting_construction = default_surface_const_set.get.roofCeilingConstruction
253
-
254
- # creating new default construction set
255
- new_default_construction_set = default_construction_set.clone(model)
256
- new_default_construction_set = new_default_construction_set.to_DefaultConstructionSet.get
257
- new_default_construction_set.setName("#{default_construction_set.name} adj roof insulation")
258
-
259
- # create new surface set and link to construction set
260
- new_default_surface_const_set = default_surface_const_set.get.clone(model)
261
- new_default_surface_const_set = new_default_surface_const_set.to_DefaultSurfaceConstructions.get
262
- new_default_surface_const_set.setName("#{default_surface_const_set.get.name} adj roof insulation")
263
- new_default_construction_set.setDefaultExteriorSurfaceConstructions(new_default_surface_const_set)
264
-
265
- # use the hash to find the proper construction and link to new_default_surface_const_set
266
- target_const = new_default_surface_const_set.roofCeilingConstruction
267
- unless target_const.empty?
268
- target_const = target_const.get.name.to_s
269
- found_const_flag = false
270
- constructions_hash_old_new.each do |orig, new|
271
- if target_const == orig
272
- final_construction = new
273
- new_default_surface_const_set.setRoofCeilingConstruction(final_construction)
274
- found_const_flag = true
275
- end
276
- end
277
- if found_const_flag == false # this should never happen but is just an extra test in case something goes wrong with the measure code
278
- runner.registerWarning("Measure couldn't find the construction named '#{target_const}' in the exterior surface hash.")
279
- end
280
- end
281
-
282
- # swap all uses of the old construction set for the new
283
- construction_set_sources = default_construction_set.sources
284
- construction_set_sources.each do |construction_set_source|
285
- building_source = construction_set_source.to_Building
286
- # if statement for each type of object than can use a DefaultConstructionSet
287
- unless building_source.empty?
288
- building_source = building_source.get
289
- building_source.setDefaultConstructionSet(new_default_construction_set)
290
- end
291
- building_story_source = construction_set_source.to_BuildingStory
292
- unless building_story_source.empty?
293
- building_story_source = building_story_source.get
294
- building_story_source.setDefaultConstructionSet(new_default_construction_set)
295
- end
296
- space_type_source = construction_set_source.to_SpaceType
297
- unless space_type_source.empty?
298
- space_type_source = space_type_source.get
299
- space_type_source.setDefaultConstructionSet(new_default_construction_set)
300
- end
301
- space_source = construction_set_source.to_Space
302
- unless space_source.empty?
303
- space_source = space_source.get
304
- space_source.setDefaultConstructionSet(new_default_construction_set)
305
- end
306
- end # end of construction_set_sources.each do
307
-
308
- end # end of if not default_surface_const_set.empty?
309
- end # end of if default_construction_set.directUseCount > 0
310
- end # end of loop through construction sets
311
-
312
- # link cloned and edited constructions for surfaces with hard assigned constructions
313
- exterior_surfaces.each do |exterior_surface|
314
- if not exterior_surface.isConstructionDefaulted and not exterior_surface.construction.empty?
315
-
316
- # use the hash to find the proper construction and link to surface
317
- target_const = exterior_surface.construction
318
- unless target_const.empty?
319
- target_const = target_const.get.name.to_s
320
- constructions_hash_old_new.each do |orig, new|
321
- if target_const == orig
322
- final_construction = new
323
- exterior_surface.setConstruction(final_construction)
324
- end
325
- end
326
- end
327
-
328
- end # end of if not exterior_surface.isConstructionDefaulted and not exterior_surface.construction.empty?
329
- end # end of exterior_surfaces.each do
330
-
331
- # report strings for final condition
332
- final_string = [] # not all exterior roof constructions, but only new ones made. If roof didn't have insulation and was not altered we don't want to show it
333
- affected_area_si = 0
334
- totalCost_of_affected_area = 0
335
- yr0_capital_totalCosts = 0
336
- final_constructions_array.each do |final_construction|
337
-
338
- # unit conversion of roof insulation from SI units (M^2*K/W) to IP units (ft^2*h*R/Btu)
339
- final_conductance_ip = unit_helper(1 / final_construction.thermalConductance.to_f, 'm^2*K/W', 'ft^2*h*R/Btu')
340
- final_string << "#{final_construction.name} (R-#{(sprintf "%.1f", final_conductance_ip)})"
341
- affected_area_si = affected_area_si + final_construction.getNetArea
342
-
343
- # loop through lifecycle costs getting total costs under "Construction" or "Salvage" category and add to counter if occurs during year 0
344
- const_LCCs = final_construction.lifeCycleCosts
345
- const_LCCs.each do |const_LCC|
346
- if const_LCC.category == 'Construction' or const_LCC.category == 'Salvage'
347
- if const_LCC.yearsFromStart == 0
348
- yr0_capital_totalCosts += const_LCC.totalCost
349
- end
350
- end
351
- end
352
-
353
- end # end of final_constructions_array.each do
354
-
355
- # add not applicable test if there were exterior roof constructions but non of them were altered (already enough insulation or doesn't look like insulated wall)
356
- if affected_area_si == 0
357
- runner.registerAsNotApplicable('No roofs were altered.')
358
- affected_area_ip = affected_area_si
359
- else
360
- # ip construction area for reporting
361
- affected_area_ip = unit_helper(affected_area_si, 'm^2', 'ft^2')
362
- end
363
-
364
- # report final condition
365
- runner.registerFinalCondition("The existing insulation for roofs was increased to R-#{r_value}. This was accomplished for an initial cost of #{one_time_retrofit_cost_ip} ($/sf) and an increase of #{material_cost_increase_ip} ($/sf) for construction. This was applied to #{neat_numbers(affected_area_ip, 0)} (ft^2) across #{final_string.size} roof constructions: #{final_string.sort.join(", ")}.")
366
-
367
17
  true
368
18
  end # end the run method
369
19
  end # end the measure
@@ -62,7 +62,8 @@ describe OpenStudio::Analysis::Translator::Excel do
62
62
  end
63
63
 
64
64
  it 'should fail to process' do
65
- expect { @excel.process }.to raise_error('Variable min is greater than variable max for adjust_thermostat_setpoints_by_degrees:heating_adjustment')
65
+ error_message = 'Variable min is greater than variable max for adjust_thermostat_setpoints_by_degrees:heating_adjustment'
66
+ expect { @excel.process }.to raise_error(error_message)
66
67
  end
67
68
  end
68
69
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openstudio-analysis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicholas Long