Skip to content

Commit

Permalink
Merge pull request #1584 from NREL/feature/radiant_controls_no_ems
Browse files Browse the repository at this point in the history
Feature/radiant controls no ems for slab sp control
  • Loading branch information
mdahlhausen authored Sep 19, 2023
2 parents ee81697 + a019516 commit 8dc71da
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4668,9 +4668,14 @@ def model_add_four_pipe_fan_coil(model,
# options are 'ZeroFlowPower', 'HalfFlowPower'
# @param include_carpet [Boolean] boolean to include thin carpet tile over radiant slab, default to true
# @param carpet_thickness_in [Double] thickness of carpet in inches
# @param control_strategy [String] name of control strategy. Options are 'proportional_control' and 'none'.
# @param control_strategy [String] name of control strategy. Options are 'proportional_control', 'oa_based_control',
# 'constant_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'.
# If control strategy is 'oa_based_control', the method will apply native EnergyPlus objects/parameters
# to vary slab setpoint based on outdoor weather.
# If control strategy is 'constant_control', the method will apply native EnergyPlus objects/parameters to
# maintain a constant slab setpoint.
# Otherwise no control strategy will be applied and the radiant system will assume the EnergyPlus default controls.
# @param use_zone_occupancy_for_control [Boolean] 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
Expand All @@ -4686,6 +4691,10 @@ def model_add_four_pipe_fan_coil(model,
# @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
# @param slab_sp_at_oat_low [Double] radiant slab temperature setpoint, in F, at the outdoor high temperature.
# @param slab_oat_low [Double] outdoor drybulb air temperature, in F, for low radiant slab setpoint.
# @param slab_sp_at_oat_high [Double] radiant slab temperature setpoint, in F, at the outdoor low temperature.
# @param slab_oat_high [Double] outdoor drybulb air temperature, in F, for high radiant slab setpoint.
# @param radiant_availability_type [String] a preset that determines the availability of the radiant system
# options are 'all_day', 'precool', 'afternoon_shutoff', 'occupancy'
# If preset is set to 'all_day' radiant system is available 24 hours a day, 'precool' primarily operates
Expand Down Expand Up @@ -4728,6 +4737,10 @@ def model_add_low_temp_radiant(model,
model_occ_hr_end: 18.0,
proportional_gain: 0.3,
switch_over_time: 24.0,
slab_sp_at_oat_low: 73,
slab_oat_low: 65,
slab_sp_at_oat_high: 68,
slab_oat_high: 80,
radiant_availability_type: 'precool',
radiant_lockout: false,
radiant_lockout_start_time: 12.0,
Expand Down Expand Up @@ -5114,7 +5127,9 @@ def model_add_low_temp_radiant(model,
rename_plant_loop_nodes(model)

# set radiant loop controls
if control_strategy == 'proportional_control'
case control_strategy.downcase
when 'proportional_control'
# slab setpoint varies based on previous day zone conditions
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,
Expand All @@ -5123,6 +5138,28 @@ def model_add_low_temp_radiant(model,
model_occ_hr_end: model_occ_hr_end,
proportional_gain: proportional_gain,
switch_over_time: switch_over_time)
when 'oa_based_control'
# slab setpoint varies based on outdoor weather
model_add_radiant_basic_controls(model, zone, radiant_loop,
radiant_temperature_control_type: radiant_temperature_control_type,
slab_setpoint_oa_control: true,
switch_over_time: switch_over_time,
slab_sp_at_oat_low: slab_sp_at_oat_low,
slab_oat_low: slab_oat_low,
slab_sp_at_oat_high: slab_sp_at_oat_high,
slab_oat_high: slab_oat_high)
when 'constant_control'
# constant slab setpoint control
model_add_radiant_basic_controls(model, zone, radiant_loop,
radiant_temperature_control_type: radiant_temperature_control_type,
slab_setpoint_oa_control: false,
switch_over_time: switch_over_time,
slab_sp_at_oat_low: slab_sp_at_oat_low,
slab_oat_low: slab_oat_low,
slab_sp_at_oat_high: slab_sp_at_oat_high,
slab_oat_high: slab_oat_high)
else
# 'none'; use energyplus default controls
end
end
return radiant_loops
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -446,4 +446,124 @@ def model_add_radiant_proportional_controls(model, zone, radiant_loop,
zone_min_ctrl_temp_output = OpenStudio::Model::EnergyManagementSystemOutputVariable.new(model, zone_min_ctrl_temp)
zone_min_ctrl_temp_output.setName("#{zone_name} Minimum occupied temperature in zone")
end

# Native EnergyPlus objects implement a control for a single thermal zone with a radiant system.
# @param zone [OpenStudio::Model::ThermalZone>] zone to add radiant controls
# @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 slab_setpoint_oa_control [Bool] True if slab setpoint is to be varied based on outdoor air temperature
# @param switch_over_time [Double] Time limitation for when the system can switch between heating and cooling
# @param slab_sp_at_oat_low [Double] radiant slab temperature setpoint, in F, at the outdoor high temperature.
# @param slab_oat_low [Double] outdoor drybulb air temperature, in F, for low radiant slab setpoint.
# @param slab_sp_at_oat_high [Double] radiant slab temperature setpoint, in F, at the outdoor low temperature.
# @param slab_oat_high [Double] outdoor drybulb air temperature, in F, for high radiant slab setpoint.
def model_add_radiant_basic_controls(model, zone, radiant_loop,
radiant_temperature_control_type: 'SurfaceFaceTemperature',
slab_setpoint_oa_control: false,
switch_over_time: 24.0,
slab_sp_at_oat_low: 73,
slab_oat_low: 65,
slab_sp_at_oat_high: 68,
slab_oat_high: 80)

zone_name = zone.name.to_s.gsub(/[ +-.]/, '_')

if model.version < OpenStudio::VersionString.new('3.1.1')
coil_cooling_radiant = radiant_loop.coolingCoil.to_CoilCoolingLowTempRadiantVarFlow.get
coil_heating_radiant = radiant_loop.heatingCoil.to_CoilHeatingLowTempRadiantVarFlow.get
else
coil_cooling_radiant = radiant_loop.coolingCoil.get.to_CoilCoolingLowTempRadiantVarFlow.get
coil_heating_radiant = radiant_loop.heatingCoil.get.to_CoilHeatingLowTempRadiantVarFlow.get
end

#####
# Define radiant system parameters
####
# set radiant system temperature and setpoint control type
unless ['surfacefacetemperature', 'surfaceinteriortemperature'].include? radiant_temperature_control_type.downcase
OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model',
"Control sequences not compatible with '#{radiant_temperature_control_type}' radiant system control. Defaulting to 'SurfaceFaceTemperature'.")
radiant_temperature_control_type = 'SurfaceFaceTemperature'
end

radiant_loop.setTemperatureControlType(radiant_temperature_control_type)

# get existing switchover time schedule or create one if needed
sch_radiant_switchover = model.getScheduleRulesetByName('Radiant System Switchover')
if sch_radiant_switchover.is_initialized
sch_radiant_switchover = sch_radiant_switchover.get
else
sch_radiant_switchover = model_add_constant_schedule_ruleset(model,
switch_over_time,
name = 'Radiant System Switchover',
sch_type_limit: 'Dimensionless')
end

# set radiant system switchover schedule
radiant_loop.setChangeoverDelayTimePeriodSchedule(sch_radiant_switchover.to_Schedule.get)

if slab_setpoint_oa_control
# get weather file from model
weather_file = model.getWeatherFile
if weather_file.initialized
# get annual outdoor dry bulb temperature
annual_oat = weather_file.file.get.data.collect { |dat| dat.dryBulbTemperature.get }

# calculate a nhrs rolling average from annual outdoor dry bulb temperature
nhrs = 24
last_nhrs_oat_in_year = annual_oat.last(nhrs - 1)
combined_oat = last_nhrs_oat_in_year + annual_oat
oat_rolling_average = combined_oat.each_cons(nhrs).map { |e| e.reduce(&:+).fdiv(nhrs).round(2) }

# use rolling average to calculate slab setpoint temperature

# convert temperature from IP to SI units
slab_sp_at_oat_low_si = OpenStudio.convert(slab_sp_at_oat_low, 'F', 'C').get
slab_oat_low_si = OpenStudio.convert(slab_oat_low, 'F', 'C').get
slab_sp_at_oat_high_si = OpenStudio.convert(slab_sp_at_oat_high, 'F', 'C').get
slab_oat_high_si = OpenStudio.convert(slab_oat_high, 'F', 'C').get

# calculate relationship between slab setpoint and slope
slope_num = slab_sp_at_oat_high_si - slab_sp_at_oat_low_si
slope_den = slab_oat_high_si - slab_oat_low_si
sp_and_oat_slope = slope_num.fdiv(slope_den).round(4)

slab_setpoint = oat_rolling_average.map { |e| (slab_sp_at_oat_low_si + ((e - slab_oat_low_si) * sp_and_oat_slope)).round(1) }

# input upper limits on slab setpoint
slab_sp_upper_limit = [slab_sp_at_oat_high_si, slab_sp_at_oat_low_si].max
slab_sp_lower_limit = [slab_sp_at_oat_high_si, slab_sp_at_oat_low_si].min
slab_setpoint.map! { |e| e > slab_sp_upper_limit ? slab_sp_upper_limit.round(1) : e }

# input lower limits on slab setpoint
slab_setpoint.map! { |e| e < slab_sp_lower_limit ? slab_sp_lower_limit.round(1) : e }

# create ruleset for slab setpoint
sch_type_limits_obj = model_add_schedule_type_limits(model, standard_sch_type_limit: 'Temperature')
sch_radiant_slab_setp = make_ruleset_sched_from_8760(model, slab_setpoint,
'Sch_Radiant_SlabSetP_Based_On_Rolling_Mean_OAT',
sch_type_limits_obj)

coil_heating_radiant.setHeatingControlTemperatureSchedule(sch_radiant_slab_setp)
coil_cooling_radiant.setCoolingControlTemperatureSchedule(sch_radiant_slab_setp)
else
OpenStudio.logFree(OpenStudio::Error, 'openstudio.Model.Model',
'Model does not have a weather file associated with it. Define to implement slab setpoint based on outdoor weather.')
end
else
# radiant system cooling control setpoint
slab_setpoint = 22
sch_radiant_clgsetp = model_add_constant_schedule_ruleset(model,
slab_setpoint + 0.1,
name = "#{zone_name}_Sch_Radiant_ClgSetP")
coil_cooling_radiant.setCoolingControlTemperatureSchedule(sch_radiant_clgsetp)

# radiant system heating control setpoint
sch_radiant_htgsetp = model_add_constant_schedule_ruleset(model,
slab_setpoint,
name = "#{zone_name}_Sch_Radiant_HtgSetP")
coil_heating_radiant.setHeatingControlTemperatureSchedule(sch_radiant_htgsetp)
end
end
end

0 comments on commit 8dc71da

Please sign in to comment.