openstudio-analysis 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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