openstudio-analysis 0.1.3 → 0.1.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: be9085031fd83339356ac4cab40cf3bdf81cae72
4
- data.tar.gz: bb97893884c7f03736007c404b1cd45428ea90bb
3
+ metadata.gz: 0aca7ae94b4ed75738defa00e6044a374c68b0ee
4
+ data.tar.gz: c4f630cb8897524e16b021546e93f0c5b148aeb8
5
5
  SHA512:
6
- metadata.gz: 3fbaacff064b7527ed31a31fd971c000a37c2c0ad79e078e09a6eed89d1d504797e3bdef7db3649310f2b1db3e63a0cbe64eb19c80570db12fd6c87d85aa0667
7
- data.tar.gz: b6b7388412ea35b65dc0a2705f7b176059e2df2def43fc3bf0eaac34dab89f25190374a9b124339e56b5538c253c78a72ecce4277862ba72200ea9968adba515
6
+ metadata.gz: 16d7f355acd2f253b27dd48fdff4fec5244f9eb5afcd99bd1c9403b7d0f6db15e30908625c27ddf28062ce4cb1d4207a3900aa65b3c539031fc9d39521a15b06
7
+ data.tar.gz: de301fb8cb527f079fb5b4aa3a7927aa772a1f77691c7d07e7b078941331bbb8218f0918ad5fee498fbee57779fbc707d545858bbb4d31e8da0bc45f070954e8
@@ -5,11 +5,17 @@ module OpenStudio
5
5
  attr_reader :variables
6
6
  attr_reader :models
7
7
  attr_reader :weather_files
8
-
8
+ attr_reader :measure_path
9
+ attr_reader :export_path
10
+ attr_reader :variables
11
+
9
12
  # remove these once we have classes to construct the JSON file
10
13
  attr_reader :name
11
14
  attr_reader :number_of_samples
15
+ attr_reader :template_json
12
16
 
17
+ # methods to override instance variables
18
+
13
19
  # pass in the filename to read
14
20
  def initialize(xls_filename)
15
21
  @root_path = File.expand_path(File.dirname(xls_filename))
@@ -29,7 +35,10 @@ module OpenStudio
29
35
  @export_path = "./export"
30
36
  @measure_path = "./measures"
31
37
  @number_of_samples = 0
32
-
38
+ @template_json = nil
39
+ end
40
+
41
+ def process
33
42
  @setup = parse_setup()
34
43
  @variables = parse_variables()
35
44
 
@@ -43,7 +52,6 @@ module OpenStudio
43
52
  File.open(filename, 'w') { |f| f << JSON.pretty_generate(@variables) }
44
53
  end
45
54
 
46
-
47
55
  def validate_analysis
48
56
  # Setup the paths and do some error checking
49
57
  raise "Measures directory '#{@measure_path}' does not exist" unless Dir.exists?(@measure_path)
@@ -68,28 +76,71 @@ module OpenStudio
68
76
  end
69
77
 
70
78
  FileUtils.mkdir_p(@export_path)
79
+
80
+ # verify that all continuous variables have all the data needed and create a name maps
81
+ variable_names = []
82
+ @variables['data'].each do |measure|
83
+ if measure['enabled'] && measure['name'] != 'baseline'
84
+ measure['variables'].each do |variable|
85
+ # Determine if row is suppose to be an argument or a variable to be perturbed.
86
+ if variable['variable_type'] == 'variable'
87
+ variable_names << variable['display_name']
88
+ if variable['method'] == 'static'
89
+ # add this as an argument
90
+ # check something
91
+ elsif variable['method'] == 'lhs'
92
+ if variable['type'] == 'enum'
93
+ # check something
94
+ else # must be an integer or double
95
+ if variable['distribution']['min'].nil? || variable['distribution']['min'] == ""
96
+ raise "Variable #{measure['name']}:#{variable['name']} must have a minimum"
97
+ end
98
+ if variable['distribution']['max'].nil? || variable['distribution']['max'] == ""
99
+ raise "Variable #{measure['name']}:#{variable['name']} must have a maximum"
100
+ end
101
+ if variable['distribution']['mean'].nil? || variable['distribution']['mean'] == ""
102
+ raise "Variable #{measure['name']}:#{variable['name']} must have a mean"
103
+ end
104
+ if variable['distribution']['stddev'].nil? || variable['distribution']['stddev'] == ""
105
+ raise "Variable #{measure['name']}:#{variable['name']} must have a stddev"
106
+ end
107
+ if variable['distribution']['min'] > variable['distribution']['max']
108
+ raise "Variable min is greater than variable max for #{measure['name']}:#{variable['name']}"
109
+ end
110
+ end
111
+ elsif variable['method'] == 'pivot'
112
+ # check something
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ dupes = variable_names.find_all { |e| variable_names.count(e) > 1 }.uniq
120
+ if dupes.count > 0
121
+ raise "duplicate variable names found in list #{dupes.inspect}"
122
+ end
123
+
124
+ # most of the checks will raise a runtime exception, so this true will never be called
125
+ true
71
126
  end
72
127
 
73
128
  def save_analysis
74
- # save the format in the OpenStudio analysis json format
75
- new_json = translate_to_analysis_json()
129
+ # save the format in the OpenStudio analysis json format template without
130
+ # the correct weather files or models
131
+ @template_json = translate_to_analysis_json_template()
132
+
133
+ #validate_template_json
76
134
 
77
135
  # iterate over each model and save the zip and json
78
136
  @models.each do |model|
79
137
  save_analysis_zip(model)
80
- save_analysis_json(new_json, model)
138
+ analysis_json = create_analysis_json(@template_json, model)
81
139
  end
82
140
  end
83
141
 
84
- protected
85
-
86
- # helper method for ERB
87
- def get_binding
88
- binding
89
- end
90
-
91
142
  # TODO: move this into a new class that helps construct this file
92
- def translate_to_analysis_json
143
+ def translate_to_analysis_json_template
93
144
  # Load in the templates for constructing the JSON file
94
145
  template_root = File.join(File.dirname(__FILE__), "../../templates")
95
146
  analysis_template = ERB.new(File.open("#{template_root}/analysis.json.erb", 'r').read)
@@ -148,8 +199,9 @@ module OpenStudio
148
199
  end
149
200
  ag = JSON.parse(argument_template.result(get_binding))
150
201
  end
202
+ raise "Argument '#{@variable['name']}' did not process. Most likely it did not have all parameters defined." if ag.nil?
151
203
  wf['arguments'] << ag
152
- else # must be a vriable
204
+ else # must be a variable
153
205
  vr = nil
154
206
  if @variable['method'] == 'static'
155
207
  # add this as an argument
@@ -165,6 +217,7 @@ module OpenStudio
165
217
  @values_and_weights = @variable['distribution']['enumerations'].map { |v| {value: v} }.to_json
166
218
  vr =JSON.parse(pivot_variable_template.result(get_binding))
167
219
  end
220
+ raise "variable was nil after processing" if vr.nil?
168
221
  wf['variables'] << vr
169
222
  end
170
223
  end
@@ -176,6 +229,14 @@ module OpenStudio
176
229
  openstudio_analysis_json
177
230
  end
178
231
 
232
+ protected
233
+
234
+ # helper method for ERB
235
+ def get_binding
236
+ binding
237
+ end
238
+
239
+
179
240
  # Package up the seed, weather files, and measures
180
241
  def save_analysis_zip(model)
181
242
  zipfile_name = "#{@export_path}/#{model[:name]}.zip"
@@ -206,7 +267,7 @@ module OpenStudio
206
267
  end
207
268
  end
208
269
 
209
- def save_analysis_json(analysis_json, model)
270
+ def create_analysis_json(analysis_json, model)
210
271
  # Set the seed model in the analysis_json
211
272
  analysis_json['analysis']['seed']['file_type'] = model[:type]
212
273
  # This is the path that will be seen on the server when this runs
@@ -227,8 +288,7 @@ module OpenStudio
227
288
 
228
289
  File.open("#{@export_path}/#{model[:name]}.json", "w") { |f| f << JSON.pretty_generate(analysis_json) }
229
290
  end
230
-
231
-
291
+
232
292
  # parse_setup will pull out the data on the "setup" tab and store it in memory for later use
233
293
  def parse_setup()
234
294
  rows = @xls.sheet('Setup').parse()
@@ -302,6 +362,11 @@ module OpenStudio
302
362
  # be omitted as an intermediate step
303
363
  def parse_variables()
304
364
  rows = @xls.sheet('Variables').parse()
365
+
366
+ if !rows
367
+ raise "Could not find the sheet name 'Variables' in excel file #{@root_path}"
368
+ end
369
+
305
370
  data = {}
306
371
  data['data'] = []
307
372
 
@@ -1,6 +1,6 @@
1
1
  module OpenStudio
2
2
  module Analysis
3
- VERSION = "0.1.3"
4
- OPENSTUDIO_VERSION = "1.1.2"
3
+ VERSION = "0.1.4"
4
+ OPENSTUDIO_VERSION = "1.1.4"
5
5
  end
6
6
  end
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "analysis": {
3
- "display_name": "<%= @name %>",
3
+ "display_name": "<%= @name %>",
4
4
  "name": "<%= @name.downcase.gsub(" ", "_") %>",
5
5
  "algorithm": {
6
6
  "sample_method": "lhs",
7
- "sensitivity_method": "single_measure",
8
7
  "number_of_samples": <%= @number_of_samples %>
9
8
  },
10
9
  "parameter_space": [],
11
10
  "problem": {
12
11
  "number_of_samples_KEEP_HERE_UNTIL_ALGO_IS_IMPLEMENTED": <%= @number_of_samples %>,
13
12
  "number_of_samples": <%= @number_of_samples %>,
13
+ "algorithm": {
14
+ "number_of_samples": <%= @number_of_samples %>,
15
+ "sample_method": "all_variables"
16
+ },
14
17
  "name": "Problem",
15
18
  "workflow": []
16
19
  },
@@ -0,0 +1,36 @@
1
+ {
2
+ "classname": "IncreaseInsulationRValueForRoofs",
3
+ "path": "./measures/IncreaseInsulationRValueForRoofs",
4
+ "name": "Set R-value of Insulation for Roofs to a Specific Value",
5
+ "measure_type": "RubyMeasure",
6
+ "arguments": [
7
+ {
8
+ "local_variable": "r_value",
9
+ "variable_type": "Double",
10
+ "name": "r_value",
11
+ "display_name": "Insulation R-value (ft^2*h*R/Btu).",
12
+ "default_value": 30.0
13
+ },
14
+ {
15
+ "local_variable": "material_cost_increase_ip",
16
+ "variable_type": "Double",
17
+ "name": "material_cost_increase_ip",
18
+ "display_name": "Increase in Material and Installation Costs for Construction per Area Used ($/ft^2).",
19
+ "default_value": 0.0
20
+ },
21
+ {
22
+ "local_variable": "one_time_retrofit_cost_ip",
23
+ "variable_type": "Double",
24
+ "name": "one_time_retrofit_cost_ip",
25
+ "display_name": "One Time Retrofit Cost to Add Insulation to Construction ($/ft^2).",
26
+ "default_value": 0.0
27
+ },
28
+ {
29
+ "local_variable": "years_until_retrofit_cost",
30
+ "variable_type": "Integer",
31
+ "name": "years_until_retrofit_cost",
32
+ "display_name": "Year to Incur One Time Retrofit Cost (whole years).",
33
+ "default_value": 0
34
+ }
35
+ ]
36
+ }
@@ -0,0 +1,375 @@
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