honeybee-openstudio 2.4.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -100,7 +100,7 @@ module FromHoneybee
100
100
  end
101
101
 
102
102
  # assign limits to the system's heating capacity
103
- if @hash[:heating_limit] == {'type': 'NoLimit'}
103
+ if @hash[:heating_limit] == {:type => 'NoLimit'}
104
104
  os_ideal_air.setHeatingLimit('NoLimit')
105
105
  else
106
106
  os_ideal_air.setHeatingLimit('LimitCapacity')
@@ -112,7 +112,7 @@ module FromHoneybee
112
112
  end
113
113
 
114
114
  # assign limits to the system's cooling capacity
115
- if @hash[:cooling_limit] == {'type': 'NoLimit'}
115
+ if @hash[:cooling_limit] == {:type => 'NoLimit'}
116
116
  os_ideal_air.setCoolingLimit('NoLimit')
117
117
  else
118
118
  os_ideal_air.setCoolingLimit('LimitFlowRateAndCapacity')
@@ -0,0 +1,201 @@
1
+ # *******************************************************************************
2
+ # Honeybee OpenStudio Gem, Copyright (c) 2020, Alliance for Sustainable
3
+ # Energy, LLC, Ladybug Tools LLC and other contributors. All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # (1) Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # (3) Neither the name of the copyright holder nor the names of any contributors
16
+ # may be used to endorse or promote products derived from this software without
17
+ # specific prior written permission from the respective party.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
20
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
23
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
24
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
26
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ # *******************************************************************************
31
+
32
+ require 'openstudio-standards'
33
+ require_relative 'Model.hvac'
34
+
35
+ require 'from_honeybee/extension'
36
+ require 'from_honeybee/model_object'
37
+
38
+ module FromHoneybee
39
+ class TemplateHVAC < ModelObject
40
+ attr_reader :errors, :warnings
41
+
42
+ @@all_air_types = ['VAV', 'PVAV', 'PSZ', 'PTAC', 'ForcedAirFurnace']
43
+ @@doas_types = ['FCUwithDOAS', 'WSHPwithDOAS', 'VRFwithDOAS']
44
+ @@heat_cool_types = ['FCU', 'WSHP', 'VRF', 'Baseboard', 'EvaporativeCooler',
45
+ 'Residential', 'WindowAC', 'GasUnitHeater']
46
+ @@types = @@all_air_types + @@doas_types + @@heat_cool_types
47
+
48
+ def initialize(hash = {})
49
+ super(hash)
50
+ end
51
+
52
+ def self.types
53
+ # array of all supported template HVAC systems
54
+ @@types
55
+ end
56
+
57
+ def self.all_air_types
58
+ # array of the All Air HVAC types
59
+ @@all_air_types
60
+ end
61
+
62
+ def self.doas_types
63
+ # array of the DOAS HVAC types
64
+ @@doas_types
65
+ end
66
+
67
+ def self.heat_cool_types
68
+ # array of the system types providing heating and cooling only
69
+ @@heat_cool_types
70
+ end
71
+
72
+ def defaults(system_type)
73
+ @@schema[:components][:schemas][system_type.to_sym][:properties]
74
+ end
75
+
76
+ def to_openstudio(openstudio_model, room_ids)
77
+ # get the defaults for the specific system type
78
+ hvac_defaults = defaults(@hash[:type])
79
+
80
+ # make the standard applier
81
+ if @hash[:vintage]
82
+ standard = Standard.build(@hash[:vintage])
83
+ else
84
+ standard = Standard.build(hvac_defaults[:vintage][:default])
85
+ end
86
+
87
+ # get the default equipment type
88
+ if @hash[:equipment_type]
89
+ equipment_type = @hash[:equipment_type]
90
+ else
91
+ equipment_type = hvac_defaults[:equipment_type][:default]
92
+ end
93
+
94
+ # get all of the thermal zones from the Model using the room identifiers
95
+ zones = []
96
+ room_ids.each do |room_id|
97
+ zone_get = openstudio_model.getThermalZoneByName(room_id)
98
+ unless zone_get.empty?
99
+ os_thermal_zone = zone_get.get
100
+ zones << os_thermal_zone
101
+ end
102
+ end
103
+
104
+ # create the HVAC system and assign the display name to the air loop name if it exists
105
+ os_hvac = openstudio_model.add_cbecs_hvac_system(standard, equipment_type, zones)
106
+ os_air_loop = nil
107
+ air_loops = openstudio_model.getAirLoopHVACs
108
+ unless air_loops.length == $air_loop_count # check if any new loops were added
109
+ $air_loop_count = air_loops.length
110
+ os_air_loop = air_loops[-1]
111
+ loop_name = os_air_loop.name
112
+ unless loop_name.empty?
113
+ if @hash[:display_name]
114
+ os_air_loop.setName(@hash[:display_name] + ' - ' + loop_name.get)
115
+ end
116
+ end
117
+ end
118
+
119
+ # TODO: consider adding the ability to decentralize the plant by changing loop names
120
+ #os_hvac.each do |hvac_loop|
121
+ # loop_name = hvac_loop.name
122
+ # unless loop_name.empty?
123
+ # hvac_loop.setName(@hash[:identifier] + ' - ' + loop_name.get)
124
+ # end
125
+ #end
126
+
127
+ # assign the economizer type if there's an air loop and the economizer is specified
128
+ if @hash[:economizer_type] && @hash[:economizer_type] != 'Inferred' && os_air_loop
129
+ oasys = os_air_loop.airLoopHVACOutdoorAirSystem
130
+ unless oasys.empty?
131
+ os_oasys = oasys.get
132
+ oactrl = os_oasys.getControllerOutdoorAir
133
+ oactrl.setEconomizerControlType(@hash[:economizer_type])
134
+ end
135
+ end
136
+
137
+ # set the sensible heat recovery if there's an air loop and the heat recovery is specified
138
+ if @hash[:sensible_heat_recovery] && @hash[:sensible_heat_recovery] != {:type => 'Autosize'} && os_air_loop
139
+ erv = get_existing_erv(os_air_loop)
140
+ unless erv
141
+ erv = create_erv(openstudio_model, os_air_loop)
142
+ end
143
+ eff_at_max = @hash[:sensible_heat_recovery] * (0.76 / 0.81) # taken from OpenStudio defaults
144
+ erv.setSensibleEffectivenessat100CoolingAirFlow(eff_at_max)
145
+ erv.setSensibleEffectivenessat100HeatingAirFlow(eff_at_max)
146
+ erv.setSensibleEffectivenessat75CoolingAirFlow(@hash[:sensible_heat_recovery])
147
+ erv.setSensibleEffectivenessat75HeatingAirFlow(@hash[:sensible_heat_recovery])
148
+ end
149
+
150
+ # set the latent heat recovery if there's an air loop and the heat recovery is specified
151
+ if @hash[:latent_heat_recovery] && @hash[:latent_heat_recovery] != {:type => 'Autosize'} && os_air_loop
152
+ erv = get_existing_erv(os_air_loop)
153
+ unless erv
154
+ erv = create_erv(openstudio_model, os_air_loop)
155
+ end
156
+ eff_at_max = @hash[:latent_heat_recovery] * (0.68 / 0.73) # taken from OpenStudio defaults
157
+ erv.setLatentEffectivenessat100CoolingAirFlow(eff_at_max)
158
+ erv.setLatentEffectivenessat100HeatingAirFlow(eff_at_max)
159
+ erv.setLatentEffectivenessat75CoolingAirFlow(@hash[:latent_heat_recovery])
160
+ erv.setLatentEffectivenessat75HeatingAirFlow(@hash[:latent_heat_recovery])
161
+ end
162
+
163
+ os_hvac
164
+ end
165
+
166
+ def get_existing_erv(os_air_loop)
167
+ # get an existing heat ecovery unit from an air loop; will be nil if there is none
168
+ os_air_loop.oaComponents.each do |supply_component|
169
+ if not supply_component.to_HeatExchangerAirToAirSensibleAndLatent.empty?
170
+ erv = supply_component.to_HeatExchangerAirToAirSensibleAndLatent.get
171
+ return erv
172
+ end
173
+ end
174
+ nil
175
+ end
176
+
177
+ def create_erv(model, os_air_loop)
178
+ # create a heat recovery unit with default zero efficiencies
179
+ heat_ex = OpenStudio::Model::HeatExchangerAirToAirSensibleAndLatent.new(model)
180
+ heat_ex.setEconomizerLockout(false)
181
+ heat_ex.setName(@hash[:identifier] + '_Heat Recovery Unit')
182
+ heat_ex.setSensibleEffectivenessat100CoolingAirFlow(0)
183
+ heat_ex.setSensibleEffectivenessat100HeatingAirFlow(0)
184
+ heat_ex.setSensibleEffectivenessat75CoolingAirFlow(0)
185
+ heat_ex.setSensibleEffectivenessat75HeatingAirFlow(0)
186
+ heat_ex.setLatentEffectivenessat100CoolingAirFlow(0)
187
+ heat_ex.setLatentEffectivenessat100HeatingAirFlow(0)
188
+ heat_ex.setLatentEffectivenessat75CoolingAirFlow(0)
189
+ heat_ex.setLatentEffectivenessat75HeatingAirFlow(0)
190
+
191
+ # add the heat exchanger to the air loop
192
+ outdoor_node = os_air_loop.reliefAirNode
193
+ unless outdoor_node.empty?
194
+ os_outdoor_node = outdoor_node.get
195
+ heat_ex.addToNode(os_outdoor_node)
196
+ end
197
+ heat_ex
198
+ end
199
+
200
+ end #TemplateHVAC
201
+ end #FromHoneybee
@@ -46,6 +46,7 @@ require 'from_honeybee/geometry/room'
46
46
 
47
47
  # import the HVAC objects
48
48
  require 'from_honeybee/hvac/ideal_air'
49
+ require 'from_honeybee/hvac/template'
49
50
 
50
51
  # import the construction objects
51
52
  require 'from_honeybee/construction/opaque'
@@ -74,6 +75,9 @@ require 'from_honeybee/schedule/ruleset'
74
75
  require 'from_honeybee/load/setpoint_thermostat'
75
76
  require 'from_honeybee/load/setpoint_humidistat'
76
77
 
78
+ # import the ventilative cooling objects
79
+ require 'from_honeybee/ventcool/simulation'
80
+
77
81
  require 'openstudio'
78
82
 
79
83
 
@@ -157,11 +161,23 @@ module FromHoneybee
157
161
  building = @openstudio_model.getBuilding
158
162
  building.setStandardsBuildingType('MediumOffice')
159
163
 
164
+ # initialize a global variable for whether the AFN is used instead of simple ventilation
165
+ $use_simple_vent = true
166
+ if @hash[:properties][:energy][:ventilation_simulation_control]
167
+ vent_sim_control = @hash[:properties][:energy][:ventilation_simulation_control]
168
+ if vent_sim_control[:vent_control_type] && vent_sim_control[:vent_control_type] != 'SingleZone'
169
+ $use_simple_vent = false
170
+ vsim_cntrl = VentilationSimulationControl.new(vent_sim_control)
171
+ $afn_reference_crack = vsim_cntrl.to_openstudio(@openstudio_model)
172
+ end
173
+ end
174
+
160
175
  # initialize global hashes for various model properties
161
176
  $gas_gap_hash = Hash.new # hash to track gas gaps in case they are split by shades
162
177
  $air_boundary_hash = Hash.new # hash to track any air boundary constructions
163
178
  $window_shade_hash = Hash.new # hash to track any window constructions with shade
164
179
  $programtype_setpoint_hash = Hash.new # hash to track Setpoint objects
180
+ $interior_afn_srf_hash = Hash.new # track whether an adjacent surface is already in the AFN
165
181
 
166
182
  # create all of the non-geometric model elements
167
183
  if log_report
@@ -497,13 +513,14 @@ module FromHoneybee
497
513
 
498
514
  def create_hvacs
499
515
  if @hash[:properties][:energy][:hvacs]
516
+ $air_loop_count = 0 # track the total number of air loops in the model
500
517
  # gather all of the hashes of the HVACs
501
518
  hvac_hashes = Hash.new
502
519
  @hash[:properties][:energy][:hvacs].each do |hvac|
503
520
  hvac_hashes[hvac[:identifier]] = hvac
504
521
  hvac_hashes[hvac[:identifier]]['rooms'] = []
505
522
  end
506
- # loop through the rooms and trach which are assigned to each HVAC
523
+ # loop through the rooms and track which are assigned to each HVAC
507
524
  if @hash[:rooms]
508
525
  @hash[:rooms].each do |room|
509
526
  if room[:properties][:energy][:hvac]
@@ -514,8 +531,7 @@ module FromHoneybee
514
531
 
515
532
  hvac_hashes.each_value do |hvac|
516
533
  system_type = hvac[:type]
517
- case system_type
518
- when 'IdealAirSystemAbridged'
534
+ if system_type == 'IdealAirSystemAbridged'
519
535
  ideal_air_system = IdealAirSystemAbridged.new(hvac)
520
536
  os_ideal_air_system = ideal_air_system.to_openstudio(@openstudio_model)
521
537
  hvac['rooms'].each do |room_id|
@@ -525,6 +541,9 @@ module FromHoneybee
525
541
  os_ideal_air_system.addToThermalZone(os_thermal_zone)
526
542
  end
527
543
  end
544
+ elsif TemplateHVAC.types.include?(system_type)
545
+ template_system = TemplateHVAC.new(hvac)
546
+ os_template_system = template_system.to_openstudio(@openstudio_model, hvac['rooms'])
528
547
  end
529
548
  end
530
549
  end
@@ -94,14 +94,14 @@ module FromHoneybee
94
94
  os_gas_equipment = gas_equipment.to_openstudio(openstudio_model)
95
95
  os_gas_equipment.setSpaceType(os_space_type)
96
96
  end
97
-
97
+
98
98
  # assign infiltration
99
- if @hash[:infiltration]
99
+ if @hash[:infiltration] && $use_simple_vent # only use infiltration with simple ventilation
100
100
  infiltration = InfiltrationAbridged.new(@hash[:infiltration])
101
101
  os_infiltration = infiltration.to_openstudio(openstudio_model)
102
102
  os_infiltration.setSpaceType(os_space_type)
103
103
  end
104
-
104
+
105
105
  # assign ventilation
106
106
  if @hash[:ventilation]
107
107
  ventilation = VentilationAbridged.new(@hash[:ventilation])
@@ -59,11 +59,11 @@ module FromHoneybee
59
59
  os_type_limit = OpenStudio::Model::ScheduleTypeLimits.new(openstudio_model)
60
60
  os_type_limit.setName(@hash[:identifier])
61
61
 
62
- if @hash[:lower_limit] != nil and @hash[:lower_limit] != {'type': 'NoLimit'}
62
+ if @hash[:lower_limit] != nil and @hash[:lower_limit] != {:type => 'NoLimit'}
63
63
  os_type_limit.setLowerLimitValue(@hash[:lower_limit])
64
64
  end
65
65
 
66
- if @hash[:upper_limit] != nil and @hash[:upper_limit] != {'type': 'NoLimit'}
66
+ if @hash[:upper_limit] != nil and @hash[:upper_limit] != {:type => 'NoLimit'}
67
67
  os_type_limit.setUpperLimitValue(@hash[:upper_limit])
68
68
  end
69
69
 
@@ -0,0 +1,161 @@
1
+ # *******************************************************************************
2
+ # Honeybee OpenStudio Gem, Copyright (c) 2020, Alliance for Sustainable
3
+ # Energy, LLC, Ladybug Tools LLC and other contributors. All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # (1) Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # (3) Neither the name of the copyright holder nor the names of any contributors
16
+ # may be used to endorse or promote products derived from this software without
17
+ # specific prior written permission from the respective party.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND ANY CONTRIBUTORS
20
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S), ANY CONTRIBUTORS, THE
23
+ # UNITED STATES GOVERNMENT, OR THE UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF
24
+ # THEIR EMPLOYEES, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
26
+ # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+ # *******************************************************************************
31
+
32
+ require 'from_honeybee/model_object'
33
+
34
+ require 'openstudio'
35
+
36
+ module FromHoneybee
37
+ class VentilationControl < ModelObject
38
+ attr_reader :errors, :warnings
39
+ @@outdoor_node = nil
40
+ @@program_manager = nil
41
+
42
+ def initialize(hash = {})
43
+ super(hash)
44
+ end
45
+
46
+ def defaults
47
+ @@schema[:components][:schemas][:VentilationControlAbridged][:properties]
48
+ end
49
+
50
+ def get_outdoor_node(openstudio_model)
51
+ # get the EMS sensor for the outdoor node if it exists or generate it if it doesn't
52
+ if @@outdoor_node.nil?
53
+ out_var = OpenStudio::Model::OutputVariable.new(
54
+ 'Site Outdoor Air Drybulb Temperature', openstudio_model)
55
+ out_var.setReportingFrequency('Timestep')
56
+ out_var.setKeyValue('Environment')
57
+ @@outdoor_node = OpenStudio::Model::EnergyManagementSystemSensor.new(
58
+ openstudio_model, out_var)
59
+ @@outdoor_node.setName('Outdoor_Sensor')
60
+ end
61
+ @@outdoor_node
62
+ end
63
+
64
+ def get_program_manager(openstudio_model)
65
+ # get the EMS Program Manager for all window opening if it exists or generate it if it doesn't
66
+ if @@program_manager.nil?
67
+ @@program_manager = OpenStudio::Model::EnergyManagementSystemProgramCallingManager.new(
68
+ openstudio_model)
69
+ @@program_manager.setName('Temperature_Controlled_Window_Opening')
70
+ @@program_manager.setCallingPoint('BeginTimestepBeforePredictor')
71
+ end
72
+ @@program_manager
73
+ end
74
+
75
+ def to_openstudio(openstudio_model, parent_zone, vent_opening_surfaces, vent_opening_factors)
76
+ # Get the outdoor temperature sensor and the room air temperature sensor
77
+ out_air_temp = get_outdoor_node(openstudio_model)
78
+ in_var = OpenStudio::Model::OutputVariable.new('Zone Air Temperature', openstudio_model)
79
+ in_var.setReportingFrequency('Timestep')
80
+ zone_name = parent_zone.name
81
+ os_zone_name = 'Indoor'
82
+ unless zone_name.empty?
83
+ os_zone_name = zone_name.get
84
+ in_var.setKeyValue(os_zone_name)
85
+ end
86
+ in_air_temp = OpenStudio::Model::EnergyManagementSystemSensor.new(openstudio_model, in_var)
87
+ in_sensor_name = os_zone_name.delete(' ').delete('.') + '_Sensor'
88
+ in_air_temp.setName(in_sensor_name)
89
+
90
+ # create the actuators for each of the operaable windows
91
+ actuator_names = []
92
+ vent_opening_surfaces.each do |vent_srf|
93
+ window_act = OpenStudio::Model::EnergyManagementSystemActuator.new(
94
+ vent_srf, 'AirFlow Network Window/Door Opening', 'Venting Opening Factor')
95
+ vent_srf_name = vent_srf.name
96
+ unless vent_srf_name.empty?
97
+ act_name = vent_srf_name.get.delete(' ').delete('.') + '_OpenFactor'
98
+ window_act.setName(act_name)
99
+ actuator_names << act_name
100
+ end
101
+ end
102
+
103
+ # create the first line of the EMS Program to open each window according to the control logic
104
+ logic_statements = []
105
+ # check the minimum indoor tempertaure for ventilation
106
+ min_in = @hash[:min_indoor_temperature]
107
+ if min_in && min_in != defaults[:min_indoor_temperature][:default]
108
+ logic_statements << '(' + in_sensor_name + ' > ' + min_in.to_s + ')'
109
+ end
110
+ # check the maximum indoor tempertaure for ventilation
111
+ max_in = @hash[:max_indoor_temperature]
112
+ if max_in && max_in != defaults[:max_indoor_temperature][:default]
113
+ logic_statements << '(' + in_sensor_name + ' < ' + max_in.to_s + ')'
114
+ end
115
+ # check the minimum outdoor tempertaure for ventilation
116
+ min_out = @hash[:min_outdoor_temperature]
117
+ if min_out && min_out != defaults[:min_outdoor_temperature][:default]
118
+ logic_statements << '(Outdoor_Sensor > ' + min_out.to_s + ')'
119
+ end
120
+ # check the maximum outdoor tempertaure for ventilation
121
+ max_out = @hash[:max_outdoor_temperature]
122
+ if max_out && max_out != defaults[:max_outdoor_temperature][:default]
123
+ logic_statements << '(Outdoor_Sensor < ' + max_out.to_s + ')'
124
+ end
125
+ # check the delta tempertaure for ventilation
126
+ delta_in_out = @hash[:delta_temperature]
127
+ if delta_in_out && delta_in_out != defaults[:delta_temperature][:default]
128
+ logic_statements << '((' + in_sensor_name + ' - Outdoor_Sensor) > ' + delta_in_out.to_s + ')'
129
+ end
130
+ # create the complete logic statement for opening windows
131
+ if logic_statements.empty?
132
+ complete_logic = 'IF (Outdoor_Sensor < 100)' # no logic has been provided; always open windows
133
+ else
134
+ complete_logic = 'IF ' + logic_statements.join(' && ')
135
+ end
136
+
137
+ # initialize the program and add the complete logic
138
+ ems_program = OpenStudio::Model::EnergyManagementSystemProgram.new(openstudio_model)
139
+ ems_program.setName(os_zone_name.delete(' ').delete('.') + '_WindowOpening')
140
+ ems_program.addLine(complete_logic)
141
+
142
+ # loop through each of the actuators and open each window
143
+ actuator_names.zip(vent_opening_factors).each do |act_name, open_factor|
144
+ ems_program.addLine('SET ' + act_name + ' = ' + open_factor.to_s)
145
+ end
146
+ # loop through each of the actuators and close each window
147
+ ems_program.addLine('ELSE')
148
+ actuator_names.each do |act_name|
149
+ ems_program.addLine('SET ' + act_name + ' = 0')
150
+ end
151
+ ems_program.addLine('ENDIF')
152
+
153
+ # add the program object the the global program manager for all window opening
154
+ prog_manager = get_program_manager(openstudio_model)
155
+ prog_manager.addProgram(ems_program)
156
+
157
+ ems_program
158
+ end
159
+
160
+ end #VentilationControl
161
+ end #FromHoneybee