openstudio-analysis 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/openstudio/analysis/translator/excel.rb +83 -18
- data/lib/openstudio/analysis/version.rb +2 -2
- data/lib/openstudio/templates/analysis.json.erb +5 -2
- data/spec/files/measures/IncreaseInsulationRValueForRoofs/measure.json +36 -0
- data/spec/files/measures/IncreaseInsulationRValueForRoofs/measure.rb +375 -0
- data/spec/files/measures/IncreaseInsulationRValueForRoofs/measure.xml +5 -0
- data/spec/files/no_variables.xlsx +0 -0
- data/spec/files/partial_weather.epw +32 -0
- data/spec/files/small_list.xlsx +0 -0
- data/spec/files/small_list_incomplete.xlsx +0 -0
- data/spec/files/small_list_repeat_vars.xlsx +0 -0
- data/spec/files/small_list_validation_errors.xlsx +0 -0
- data/spec/files/small_seed.osm +4622 -0
- data/spec/{openstudio-analysis → openstudio/analysis}/server_api_spec.rb +0 -3
- data/spec/openstudio/analysis/translator/excel_spec.rb +89 -0
- data/spec/spec_helper.rb +10 -0
- metadata +26 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0aca7ae94b4ed75738defa00e6044a374c68b0ee
|
4
|
+
data.tar.gz: c4f630cb8897524e16b021546e93f0c5b148aeb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
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,16 +1,19 @@
|
|
1
1
|
{
|
2
2
|
"analysis": {
|
3
|
-
"display_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
|