Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/radiant controls no ems for slab sp control #1584

Merged
merged 6 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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