Skip to content

Commit

Permalink
Merge pull request #1571 from NREL/feature/radiant_controls_zone_occu…
Browse files Browse the repository at this point in the history
…pancy

Feature/radiant controls zone occupancy
  • Loading branch information
mdahlhausen authored Aug 29, 2023
2 parents 1d4095f + 0fd35ed commit 5e6293f
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4528,14 +4528,21 @@ def model_add_four_pipe_fan_coil(model,
# options are 'ZeroFlowPower', 'HalfFlowPower'
# @param include_carpet [Bool] boolean to include thin carpet tile over radiant slab, default to true
# @param carpet_thickness_in [Double] thickness of carpet in inches
# @param model_occ_hr_start [Double] (Optional) Only applies if control_strategy is 'proportional_control'.
# Starting hour of building occupancy.
# @param model_occ_hr_end [Double] (Optional) Only applies if control_strategy is 'proportional_control'.
# Ending hour of building occupancy.
# @param control_strategy [String] name of control strategy. Options are 'proportional_control' and 'none'.
# If control strategy is 'proportional_control', the method will apply the CBE radiant control sequences
# detailed in Raftery et al. (2017), 'A new control strategy for high thermal mass radiant systems'.
# Otherwise no control strategy will be applied and the radiant system will assume the EnergyPlus default controls.
# @param use_zone_occupancy_for_control [Bool] Set to true if radiant system is to use specific zone occupancy objects
# for CBE control strategy. If false, then it will use values in model_occ_hr_start and model_occ_hr_end
# for all radiant zones. default to true.
# @param occupied_percentage_threshold [Double] the minimum fraction (0 to 1) that counts as occupied
# if this parameter is set, the returned ScheduleRuleset will be 0 = unoccupied, 1 = occupied
# otherwise the ScheduleRuleset will be the weighted fractional occupancy schedule.
# Only used if use_zone_occupancy_for_control is set to true.
# @param model_occ_hr_start [Double] (Optional) Only applies if control_strategy is 'proportional_control'.
# Starting hour of building occupancy.
# @param model_occ_hr_end [Double] (Optional) Only applies if control_strategy is 'proportional_control'.
# Ending hour of building occupancy.
# @param proportional_gain [Double] (Optional) Only applies if control_strategy is 'proportional_control'.
# Proportional gain constant (recommended 0.3 or less).
# @param switch_over_time [Double] Time limitation for when the system can switch between heating and cooling
Expand All @@ -4561,9 +4568,11 @@ def model_add_low_temp_radiant(model,
radiant_setpoint_control_type: 'ZeroFlowPower',
include_carpet: true,
carpet_thickness_in: 0.25,
control_strategy: 'proportional_control',
use_zone_occupancy_for_control: true,
occupied_percentage_threshold: 0.10,
model_occ_hr_start: 6.0,
model_occ_hr_end: 18.0,
control_strategy: 'proportional_control',
proportional_gain: 0.3,
switch_over_time: 24.0,
radiant_availability_type: 'precool',
Expand Down Expand Up @@ -4926,6 +4935,8 @@ def model_add_low_temp_radiant(model,
if control_strategy == 'proportional_control'
model_add_radiant_proportional_controls(model, zone, radiant_loop,
radiant_temperature_control_type: radiant_temperature_control_type,
use_zone_occupancy_for_control: use_zone_occupancy_for_control,
occupied_percentage_threshold: occupied_percentage_threshold,
model_occ_hr_start: model_occ_hr_start,
model_occ_hr_end: model_occ_hr_end,
proportional_gain: proportional_gain,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,28 @@ class Standard
# @param radiant_loop [OpenStudio::Model::ZoneHVACLowTempRadiantVarFlow>] radiant loop in thermal zone
# @param radiant_temperature_control_type [String] determines the controlled temperature for the radiant system
# options are 'SurfaceFaceTemperature', 'SurfaceInteriorTemperature'
# @param model_occ_hr_start [Double] Starting hour of building occupancy
# @param model_occ_hr_end [Double] Ending hour of building occupancy
# @param use_zone_occupancy_for_control [Bool] Set to true if radiant system is to use specific zone occupancy objects
# for CBE control strategy. If false, then it will use values in model_occ_hr_start and model_occ_hr_end
# for all radiant zones. default to true.
# @param occupied_percentage_threshold [Double] the minimum fraction (0 to 1) that counts as occupied
# if this parameter is set, the returned ScheduleRuleset will be 0 = unoccupied, 1 = occupied
# otherwise the ScheduleRuleset will be the weighted fractional occupancy schedule
# @param model_occ_hr_start [Double] Starting decimal hour of whole building occupancy
# @param model_occ_hr_end [Double] Ending decimal hour of whole building occupancy
# @todo model_occ_hr_start and model_occ_hr_end from zone occupancy schedules
# @param proportional_gain [Double] Proportional gain constant (recommended 0.3 or less).
# @param switch_over_time [Double] Time limitation for when the system can switch between heating and cooling
def model_add_radiant_proportional_controls(model, zone, radiant_loop,
radiant_temperature_control_type: 'SurfaceFaceTemperature',
use_zone_occupancy_for_control: true,
occupied_percentage_threshold: 0.10,
model_occ_hr_start: 6.0,
model_occ_hr_end: 18.0,
proportional_gain: 0.3,
switch_over_time: 24.0)


zone_name = zone.name.to_s.gsub(/[ +-.]/, '_')
zone_name = ems_friendly_name(zone.name)
zone_timestep = model.getTimestep.numberOfTimestepsPerHour

if model.version < OpenStudio::VersionString.new('3.1.1')
Expand Down Expand Up @@ -103,22 +111,6 @@ def model_add_radiant_proportional_controls(model, zone, radiant_loop,
# List of global variables used in EMS scripts
####

# Start of occupied time of zone. Valid from 1-24.
occ_hr_start = model.getEnergyManagementSystemGlobalVariableByName('occ_hr_start')
if occ_hr_start.is_initialized
occ_hr_start = occ_hr_start.get
else
occ_hr_start = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'occ_hr_start')
end

# End of occupied time of zone. Valid from 1-24.
occ_hr_end = model.getEnergyManagementSystemGlobalVariableByName('occ_hr_end')
if occ_hr_end.is_initialized
occ_hr_end = occ_hr_end.get
else
occ_hr_end = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'occ_hr_end')
end

# Proportional gain constant (recommended 0.3 or less).
prp_k = model.getEnergyManagementSystemGlobalVariableByName('prp_k')
if prp_k.is_initialized
Expand Down Expand Up @@ -151,6 +143,14 @@ def model_add_radiant_proportional_controls(model, zone, radiant_loop,
ctrl_temp_offset = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'ctrl_temp_offset')
end

# Hour where slab setpoint is to be changed
hour_of_slab_sp_change = model.getEnergyManagementSystemGlobalVariableByName('hour_of_slab_sp_change')
if hour_of_slab_sp_change.is_initialized
hour_of_slab_sp_change = hour_of_slab_sp_change.get
else
hour_of_slab_sp_change = OpenStudio::Model::EnergyManagementSystemGlobalVariable.new(model, 'hour_of_slab_sp_change')
end

#####
# List of zone specific variables used in EMS scripts
####
Expand Down Expand Up @@ -250,29 +250,73 @@ def model_add_radiant_proportional_controls(model, zone, radiant_loop,
zone_rad_heat_operation_trend.setName("#{zone_name}_rad_heat_operation_trend")
zone_rad_heat_operation_trend.setNumberOfTimestepsToBeLogged(zone_timestep * 48)

# use zone occupancy objects for radiant system control if selected
if use_zone_occupancy_for_control

# get annual occupancy schedule for zone
occ_schedule_ruleset = thermal_zone_get_occupancy_schedule(zone,
sch_name: "#{zone.name} Radiant System Occupied Schedule",
occupied_percentage_threshold: occupied_percentage_threshold)
else

occ_schedule_ruleset = model.getScheduleRulesetByName("Whole Building Radiant System Occupied Schedule")
if occ_schedule_ruleset.is_initialized
occ_schedule_ruleset = occ_schedule_ruleset.get
else
# create occupancy schedules
occ_schedule_ruleset = OpenStudio::Model::ScheduleRuleset.new(model)
occ_schedule_ruleset.setName("Whole Building Radiant System Occupied Schedule")

start_hour = model_occ_hr_end.to_i
start_minute = ((model_occ_hr_end % 1) * 60).to_i
end_hour = model_occ_hr_start.to_i
end_minute = ((model_occ_hr_start % 1) * 60).to_i

if end_hour > start_hour
occ_schedule_ruleset.defaultDaySchedule.addValue(OpenStudio::Time.new(0, start_hour, start_minute, 0), 1.0)
occ_schedule_ruleset.defaultDaySchedule.addValue(OpenStudio::Time.new(0, end_hour, end_minute, 0), 0.0)
occ_schedule_ruleset.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1.0) if end_hour < 24
elsif start_hour > end_hour
occ_schedule_ruleset.defaultDaySchedule.addValue(OpenStudio::Time.new(0, end_hour, end_minute, 0), 0.0)
occ_schedule_ruleset.defaultDaySchedule.addValue(OpenStudio::Time.new(0, start_hour, start_minute, 0), 1.0)
occ_schedule_ruleset.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 0.0) if start_hour < 24
else
occ_schedule_ruleset.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 1.0)
end
end
end

# create ems sensor for zone occupied status
zone_occupied_status = OpenStudio::Model::EnergyManagementSystemSensor.new(model, 'Schedule Value')
zone_occupied_status.setName("#{zone_name}_occupied_status")
zone_occupied_status.setKeyName(occ_schedule_ruleset.name.get)

# Last 24 hours trend for zone occupied status
zone_occupied_status_trend = OpenStudio::Model::EnergyManagementSystemTrendVariable.new(model, zone_occupied_status)
zone_occupied_status_trend.setName("#{zone_name}_occupied_status_trend")
zone_occupied_status_trend.setNumberOfTimestepsToBeLogged(zone_timestep * 48)

#####
# List of EMS programs to implement the proportional control for the radiant system.
####

# Initialize global constant values used in EMS programs.
set_constant_values_prg = model.getEnergyManagementSystemTrendVariableByName('Set_Constant_Values')
set_constant_values_prg_body = <<-EMS
SET prp_k = #{proportional_gain},
SET ctrl_temp_offset = 0.5,
SET upper_slab_sp_lim = 29,
SET lower_slab_sp_lim = 19,
SET hour_of_slab_sp_change = 18
EMS

set_constant_values_prg = model.getEnergyManagementSystemProgramByName('Set_Constant_Values')
unless set_constant_values_prg.is_initialized
set_constant_values_prg = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
set_constant_values_prg.setName('Set_Constant_Values')
set_constant_values_prg_body = <<-EMS
SET occ_hr_start = #{model_occ_hr_start},
SET occ_hr_end = #{model_occ_hr_end},
SET prp_k = #{proportional_gain},
SET ctrl_temp_offset = 0.5,
SET upper_slab_sp_lim = 29,
SET lower_slab_sp_lim = 19
EMS
set_constant_values_prg.setBody(set_constant_values_prg_body)
end

# Initialize zone specific constant values used in EMS programs.
set_constant_zone_values_prg = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
set_constant_zone_values_prg.setName("#{zone_name}_Set_Constant_Values")
set_constant_zone_values_prg_body = <<-EMS
SET #{zone_name}_max_ctrl_temp = #{zone_name}_lower_comfort_limit,
SET #{zone_name}_min_ctrl_temp = #{zone_name}_upper_comfort_limit,
Expand All @@ -281,13 +325,16 @@ def model_add_radiant_proportional_controls(model, zone, radiant_loop,
SET #{zone_name}_cmd_cold_water_ctrl = #{zone_name}_upper_comfort_limit,
SET #{zone_name}_cmd_hot_water_ctrl = #{zone_name}_lower_comfort_limit
EMS

set_constant_zone_values_prg = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
set_constant_zone_values_prg.setName("#{zone_name}_Set_Constant_Values")
set_constant_zone_values_prg.setBody(set_constant_zone_values_prg_body)

# Calculate maximum and minimum 'measured' controlled temperature in the zone
calculate_minmax_ctrl_temp_prg = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
calculate_minmax_ctrl_temp_prg.setName("#{zone_name}_Calculate_Extremes_In_Zone")
calculate_minmax_ctrl_temp_prg_body = <<-EMS
IF ((CurrentTime >= occ_hr_start) && (CurrentTime <= occ_hr_end)),
IF (#{zone_name}_occupied_status == 1),
IF #{zone_name}_ctrl_temperature > #{zone_name}_max_ctrl_temp,
SET #{zone_name}_max_ctrl_temp = #{zone_name}_ctrl_temperature,
ENDIF,
Expand All @@ -305,7 +352,7 @@ def model_add_radiant_proportional_controls(model, zone, radiant_loop,
calculate_errors_from_comfort_prg = OpenStudio::Model::EnergyManagementSystemProgram.new(model)
calculate_errors_from_comfort_prg.setName("#{zone_name}_Calculate_Errors_From_Comfort")
calculate_errors_from_comfort_prg_body = <<-EMS
IF (CurrentTime >= (occ_hr_end - ZoneTimeStep)) && (CurrentTime <= (occ_hr_end)),
IF (CurrentTime == (hour_of_slab_sp_change - ZoneTimeStep)),
SET #{zone_name}_cmd_csp_error = (#{zone_name}_upper_comfort_limit - ctrl_temp_offset) - #{zone_name}_max_ctrl_temp,
SET #{zone_name}_cmd_hsp_error = (#{zone_name}_lower_comfort_limit + ctrl_temp_offset) - #{zone_name}_min_ctrl_temp,
ENDIF
Expand All @@ -318,9 +365,10 @@ def model_add_radiant_proportional_controls(model, zone, radiant_loop,
calculate_slab_ctrl_setpoint_prg_body = <<-EMS
SET #{zone_name}_cont_cool_oper = @TrendSum #{zone_name}_rad_cool_operation_trend radiant_switch_over_time/ZoneTimeStep,
SET #{zone_name}_cont_heat_oper = @TrendSum #{zone_name}_rad_heat_operation_trend radiant_switch_over_time/ZoneTimeStep,
IF (#{zone_name}_cont_cool_oper > 0) && (CurrentTime == occ_hr_end),
SET #{zone_name}_occupied_hours = @TrendSum #{zone_name}_occupied_status_trend 24/ZoneTimeStep,
IF (#{zone_name}_cont_cool_oper > 0) && (#{zone_name}_occupied_hours > 0) && (CurrentTime == hour_of_slab_sp_change),
SET #{zone_name}_cmd_hot_water_ctrl = #{zone_name}_cmd_hot_water_ctrl + (#{zone_name}_cmd_csp_error*prp_k),
ELSEIF (#{zone_name}_cont_heat_oper > 0) && (CurrentTime == occ_hr_end),
ELSEIF (#{zone_name}_cont_heat_oper > 0) && (#{zone_name}_occupied_hours > 0) && (CurrentTime == hour_of_slab_sp_change),
SET #{zone_name}_cmd_hot_water_ctrl = #{zone_name}_cmd_hot_water_ctrl + (#{zone_name}_cmd_hsp_error*prp_k),
ELSE,
SET #{zone_name}_cmd_hot_water_ctrl = #{zone_name}_cmd_hot_water_ctrl,
Expand All @@ -338,7 +386,7 @@ def model_add_radiant_proportional_controls(model, zone, radiant_loop,
# List of EMS program manager objects
####

initialize_constant_parameters = model.getEnergyManagementSystemProgramCallingManagerByName('Set_Constant_Values')
initialize_constant_parameters = model.getEnergyManagementSystemProgramCallingManagerByName('Initialize_Constant_Parameters')
if initialize_constant_parameters.is_initialized
initialize_constant_parameters = initialize_constant_parameters.get
else
Expand All @@ -348,7 +396,7 @@ def model_add_radiant_proportional_controls(model, zone, radiant_loop,
initialize_constant_parameters.addProgram(set_constant_values_prg)
end

initialize_constant_parameters_after_warmup = model.getEnergyManagementSystemProgramCallingManagerByName('Set_Constant_Values')
initialize_constant_parameters_after_warmup = model.getEnergyManagementSystemProgramCallingManagerByName('Initialize_Constant_Parameters_After_Warmup')
if initialize_constant_parameters_after_warmup.is_initialized
initialize_constant_parameters_after_warmup = initialize_constant_parameters_after_warmup.get
else
Expand Down
2 changes: 1 addition & 1 deletion openstudio-standards.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require 'openstudio-standards/version'
Gem::Specification.new do |spec|
spec.name = 'openstudio-standards'
spec.version = OpenstudioStandards::VERSION
spec.authors = ['Andrew Parker', 'Yixing Chen', 'Mark Adams', 'Kaiyu Sun', 'Mini Maholtra', 'David Goldwasser', 'Phylroy Lopez', 'Maria Mottillo', 'Kamel Haddad', 'Julien Marrec', 'Matt Leach', 'Matt Steen', 'Eric Ringold', 'Daniel Macumber', 'Matthew Dahlhausen', 'Jian Zhang', 'Doug Maddox', 'Yunyang Ye', 'Xuechen (Jerry) Lei', 'Juan Gonzalez Matamoros', 'Jeremy Lerond']
spec.authors = ['Andrew Parker', 'Yixing Chen', 'Mark Adams', 'Kaiyu Sun', 'Mini Maholtra', 'David Goldwasser', 'Phylroy Lopez', 'Maria Mottillo', 'Kamel Haddad', 'Julien Marrec', 'Matt Leach', 'Matt Steen', 'Eric Ringold', 'Daniel Macumber', 'Matthew Dahlhausen', 'Jian Zhang', 'Doug Maddox', 'Yunyang Ye', 'Xuechen (Jerry) Lei', 'Juan Gonzalez Matamoros', 'Jeremy Lerond', 'Carlos Duarte']
spec.email = ['andrew.parker@nrel.gov']
spec.homepage = 'http://openstudio.net'
spec.summary = 'Creates DOE Prototype building models and transforms proposed OpenStudio models to baseline OpenStudio models.'
Expand Down
1 change: 1 addition & 0 deletions test/circleci_tests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ os_stds_methods/test_service_water_heating.rb
os_stds_methods/test_daylighting_controls.rb
os_stds_methods/test_space_type.rb
os_stds_methods/test_misc.rb
os_stds_methods/test_radiant_controls.rb

90_1_prm/test_prm_baseline_bldg.rb
90_1_prm/test_prm_baseline_bldg4.rb
Expand Down
Loading

0 comments on commit 5e6293f

Please sign in to comment.