openstudio-analysis 0.1.16 → 0.1.17

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