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 +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
|