Source code for honeybee_openstudio.hvac.standards.radiant_system_controls

# coding=utf-8
"""Module taken from OpenStudio-standards.

https://github.com/NREL/openstudio-standards/blob/master/
lib/openstudio-standards/prototypes/common/objects/Prototype.radiant_system_controls.rb
"""
from __future__ import division

from ladybug.datatype.temperature import Temperature

from honeybee_openstudio.openstudio import openstudio, openstudio_model
from .utilities import ems_friendly_name
from .schedule import create_constant_schedule_ruleset, create_schedule_type_limits
from .thermal_zone import thermal_zone_get_occupancy_schedule

TEMPERATURE = Temperature()


[docs] 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): """Implement a proportional control for a single thermal zone with a radiant system. Args: model: Openstudio Model. zone: [OpenStudio::Model::ThermalZone>] zone to add radiant controls. radiant_loop: [OpenStudio::Model::ZoneHVACLowTempRadiantVarFlow>] radiant loop in thermal zone. radiant_temperature_control_type: [String] determines the controlled temperature for the radiant system. Options include the following: * SurfaceFaceTemperature * SurfaceInteriorTemperature 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 for all radiant zones. default to true. 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. model_occ_hr_start: [Double] Starting decimal hour of whole building occupancy. model_occ_hr_end: [Double] Ending decimal hour of whole building occupancy. proportional_gain: [Double] Proportional gain constant (recommended 0.3 or less). switch_over_time: [Double] Time limitation for when the system can switch between heating and cooling. """ zone_name = ems_friendly_name(zone.nameString()) zone_timestep = model.getTimestep().numberOfTimestepsPerHour() if model.version() < openstudio.VersionString('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() ##### # Define radiant system parameters #### # set radiant system temperature and setpoint control type control_types = ('surfacefacetemperature', 'surfaceinteriortemperature') if not radiant_temperature_control_type.lower() in control_types: msg = 'Control sequences not compatible with "{}" radiant system control. ' \ 'Defaulting to "SurfaceFaceTemperature".'.format(radiant_temperature_control_type) print(msg) radiant_temperature_control_type = 'SurfaceFaceTemperature' radiant_loop.setTemperatureControlType(radiant_temperature_control_type) ##### # List of schedule objects used to hold calculation results #### # 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 = create_constant_schedule_ruleset( model, switch_over_time, name='Radiant System Switchover', schedule_type_limit='Dimensionless') # set radiant system switchover schedule radiant_loop.setChangeoverDelayTimePeriodSchedule( sch_radiant_switchover.to_Schedule().get()) # Calculated active slab heating and cooling temperature setpoint. # radiant system cooling control actuator sch_radiant_clgsetp = create_constant_schedule_ruleset( model, 26.0, name='{}_Sch_Radiant_ClgSetP'.format(zone_name), schedule_type_limit='Temperature') coil_cooling_radiant.setCoolingControlTemperatureSchedule(sch_radiant_clgsetp) cmd_cold_water_ctrl = openstudio_model.EnergyManagementSystemActuator( sch_radiant_clgsetp, 'Schedule:Year', 'Schedule Value') cmd_cold_water_ctrl.setName('{}_cmd_cold_water_ctrl'.format(zone_name)) # radiant system heating control actuator sch_radiant_htgsetp = create_constant_schedule_ruleset( model, 20.0, name='{}_Sch_Radiant_HtgSetP'.format(zone_name), schedule_type_limit='Temperature') coil_heating_radiant.setHeatingControlTemperatureSchedule(sch_radiant_htgsetp) cmd_hot_water_ctrl = openstudio_model.EnergyManagementSystemActuator( sch_radiant_htgsetp, 'Schedule:Year', 'Schedule Value') cmd_hot_water_ctrl.setName('{}_cmd_hot_water_ctrl'.format(zone_name)) # Calculated cooling setpoint error. Calculated from upper comfort limit minus # setpoint offset and 'measured' controlled zone temperature. sch_csp_error = create_constant_schedule_ruleset( model, 0.0, name='{}_Sch_CSP_Error'.format(zone_name), schedule_type_limit='Temperature') cmd_csp_error = openstudio_model.EnergyManagementSystemActuator( sch_csp_error, 'Schedule:Year', 'Schedule Value') cmd_csp_error.setName('{}_cmd_csp_error'.format(zone_name)) # Calculated heating setpoint error. Calculated from lower comfort limit plus # setpoint offset and 'measured' controlled zone temperature. sch_hsp_error = create_constant_schedule_ruleset( model, 0.0, name='{}_Sch_HSP_Error'.format(zone_name), schedule_type_limit='Temperature') cmd_hsp_error = openstudio_model.EnergyManagementSystemActuator( sch_hsp_error, 'Schedule:Year', 'Schedule Value') cmd_hsp_error.setName('{}_cmd_hsp_error'.format(zone_name)) ##### # List of global variables used in EMS scripts #### # Proportional gain constant (recommended 0.3 or less). prp_k = model.getEnergyManagementSystemGlobalVariableByName('prp_k') if prp_k.is_initialized(): prp_k = prp_k.get() else: prp_k = openstudio_model.EnergyManagementSystemGlobalVariable(model, 'prp_k') # Upper slab temperature setpoint limit (recommended no higher than 29C (84F)) upper_slab_sp_lim = model.getEnergyManagementSystemGlobalVariableByName( 'upper_slab_sp_lim') if upper_slab_sp_lim.is_initialized(): upper_slab_sp_lim = upper_slab_sp_lim.get() else: upper_slab_sp_lim = openstudio_model.EnergyManagementSystemGlobalVariable( model, 'upper_slab_sp_lim') # Lower slab temperature setpoint limit (recommended no lower than 19C (66F)) lower_slab_sp_lim = model.getEnergyManagementSystemGlobalVariableByName( 'lower_slab_sp_lim') if lower_slab_sp_lim.is_initialized(): lower_slab_sp_lim = lower_slab_sp_lim.get() else: lower_slab_sp_lim = openstudio_model.EnergyManagementSystemGlobalVariable( model, 'lower_slab_sp_lim') # Temperature offset used as a safety factor for thermal control (recommend 0.5C (1F)). ctrl_temp_offset = model.getEnergyManagementSystemGlobalVariableByName( 'ctrl_temp_offset') if ctrl_temp_offset.is_initialized(): ctrl_temp_offset = ctrl_temp_offset.get() else: ctrl_temp_offset = openstudio_model.EnergyManagementSystemGlobalVariable( model, 'ctrl_temp_offset') # 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( model, 'hour_of_slab_sp_change') ##### # List of zone specific variables used in EMS scripts #### # Maximum 'measured' temperature in zone during occupied times. # Default setup uses mean air temperature. # Other possible choices are operative and mean radiant temperature. zone_max_ctrl_temp = openstudio_model.EnergyManagementSystemGlobalVariable( model, '{}_max_ctrl_temp'.format(zone_name)) # Minimum 'measured' temperature in zone during occupied times. Default setup uses mean air temperature. # Other possible choices are operative and mean radiant temperature. zone_min_ctrl_temp = openstudio_model.EnergyManagementSystemGlobalVariable( model, '{}_min_ctrl_temp'.format(zone_name)) ##### # List of 'sensors' used in the EMS programs #### # Controlled zone temperature for the zone. zone_ctrl_temperature = openstudio_model.EnergyManagementSystemSensor( model, 'Zone Air Temperature') zone_ctrl_temperature.setName('{}_ctrl_temperature'.format(zone_name)) zone_ctrl_temperature.setKeyName(zone.nameString()) # check for zone thermostat and replace heat/cool schedules for radiant system control # if there is no zone thermostat, then create one zone_thermostat = zone.thermostatSetpointDualSetpoint() if zone_thermostat.is_initialized(): zone_thermostat = zone_thermostat.get() else: zone_thermostat = openstudio_model.ThermostatSetpointDualSetpoint(model) zone_thermostat.setName('{}_Thermostat_DualSetpoint'.format(zone_name)) # create new heating and cooling schedules to be used with all radiant systems zone_htg_thermostat = model.getScheduleRulesetByName('Radiant System Heating Setpoint') if zone_htg_thermostat.is_initialized(): zone_htg_thermostat = zone_htg_thermostat.get() else: zone_htg_thermostat = create_constant_schedule_ruleset( model, 20.0, name='Radiant System Heating Setpoint', schedule_type_limit='Temperature') zone_clg_thermostat = model.getScheduleRulesetByName('Radiant System Cooling Setpoint') if zone_clg_thermostat.is_initialized(): zone_clg_thermostat = zone_clg_thermostat.get() else: zone_clg_thermostat = create_constant_schedule_ruleset( model, 26.0, name='Radiant System Cooling Setpoint', schedule_type_limit='Temperature') # implement new heating and cooling schedules zone_thermostat.setHeatingSetpointTemperatureSchedule(zone_htg_thermostat) zone_thermostat.setCoolingSetpointTemperatureSchedule(zone_clg_thermostat) # Upper comfort limit for the zone. Taken from existing thermostat schedules in the zone. zone_upper_comfort_limit = openstudio_model.EnergyManagementSystemSensor( model, 'Schedule Value') zone_upper_comfort_limit.setName('{}_upper_comfort_limit'.format(zone_name)) zone_upper_comfort_limit.setKeyName(zone_clg_thermostat.nameString()) # Lower comfort limit for the zone. Taken from existing thermostat schedules in the zone. zone_lower_comfort_limit = openstudio_model.EnergyManagementSystemSensor( model, 'Schedule Value') zone_lower_comfort_limit.setName('{}_lower_comfort_limit'.format(zone_name)) zone_lower_comfort_limit.setKeyName(zone_htg_thermostat.nameString()) # Radiant system water flow rate used to determine if there is active # hydronic cooling in the radiant system. zone_rad_cool_operation = openstudio_model.EnergyManagementSystemSensor( model, 'System Node Mass Flow Rate') zone_rad_cool_operation.setName('{}_rad_cool_operation'.format(zone_name)) cool_in = coil_cooling_radiant.to_StraightComponent().get().inletModelObject().get() zone_rad_cool_operation.setKeyName(cool_in.nameString()) # Radiant system water flow rate used to determine if there is active # hydronic heating in the radiant system. zone_rad_heat_operation = openstudio_model.EnergyManagementSystemSensor( model, 'System Node Mass Flow Rate') zone_rad_heat_operation.setName('{}_rad_heat_operation'.format(zone_name)) heat_in = coil_heating_radiant.to_StraightComponent().get().inletModelObject().get() zone_rad_heat_operation.setKeyName(heat_in.nameString()) # Radiant system switchover delay time period schedule # used to determine if there is active hydronic cooling/heating in the radiant system. zone_rad_switch_over = model.getEnergyManagementSystemSensorByName('radiant_switch_over_time') if not zone_rad_switch_over.is_initialized(): zone_rad_switch_over = openstudio_model.EnergyManagementSystemSensor( model, 'Schedule Value') zone_rad_switch_over.setName('radiant_switch_over_time') zone_rad_switch_over.setKeyName(sch_radiant_switchover.nameString()) # Last 24 hours trend for radiant system in cooling mode. zone_rad_cool_operation_trend = openstudio_model.EnergyManagementSystemTrendVariable( model, zone_rad_cool_operation) zone_rad_cool_operation_trend.setName('{}_rad_cool_operation_trend'.format(zone_name)) zone_rad_cool_operation_trend.setNumberOfTimestepsToBeLogged(zone_timestep * 48) # Last 24 hours trend for radiant system in heating mode. zone_rad_heat_operation_trend = openstudio_model.EnergyManagementSystemTrendVariable( model, zone_rad_heat_operation) zone_rad_heat_operation_trend.setName('{}_rad_heat_operation_trend'.format(zone_name)) 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 = thermal_zone_get_occupancy_schedule(model, zone) else: occ_schedule = model.getScheduleRulesetByName( 'Whole Building Radiant System Occupied Schedule') if occ_schedule.is_initialized(): occ_schedule = occ_schedule.get() else: # create occupancy schedules occ_schedule = openstudio_model.ScheduleRuleset(model) occ_schedule.setName( 'Whole Building Radiant System Occupied Schedule') start_hour = int(model_occ_hr_end) start_minute = int((model_occ_hr_end % 1) * 60) end_hour = int(model_occ_hr_start) end_minute = int((model_occ_hr_start % 1) * 60) if end_hour > start_hour: occ_schedule.defaultDaySchedule.addValue( openstudio.Time(0, start_hour, start_minute, 0), 1.0) occ_schedule.defaultDaySchedule.addValue( openstudio.Time(0, end_hour, end_minute, 0), 0.0) if end_hour < 24: occ_schedule.defaultDaySchedule.addValue( openstudio.Time(0, 24, 0, 0), 1.0) elif start_hour > end_hour: occ_schedule.defaultDaySchedule.addValue( openstudio.Time(0, end_hour, end_minute, 0), 0.0) occ_schedule.defaultDaySchedule.addValue( openstudio.Time(0, start_hour, start_minute, 0), 1.0) if start_hour < 24: occ_schedule.defaultDaySchedule.addValue( openstudio.Time(0, 24, 0, 0), 0.0) else: occ_schedule.defaultDaySchedule.addValue( openstudio.Time(0, 24, 0, 0), 1.0) # create ems sensor for zone occupied status zone_occupied_status = \ openstudio_model.EnergyManagementSystemSensor(model, 'Schedule Value') zone_occupied_status.setName('{}_occupied_status'.format(zone_name)) zone_occupied_status.setKeyName(occ_schedule.nameString()) # Last 24 hours trend for zone occupied status zone_occupied_status_trend = \ openstudio_model.EnergyManagementSystemTrendVariable(model, zone_occupied_status) zone_occupied_status_trend.setName('{}_occupied_status_trend'.format(zone_name)) 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_body = \ 'SET prp_k = {},\n' \ 'SET ctrl_temp_offset = 0.5,\n' \ 'SET upper_slab_sp_lim = 29,\n' \ 'SET lower_slab_sp_lim = 19,\n' \ 'SET hour_of_slab_sp_change = 18'.format(proportional_gain) set_constant_values_prg = \ model.getEnergyManagementSystemProgramByName('Set_Constant_Values') if set_constant_values_prg.is_initialized(): set_constant_values_prg = set_constant_values_prg.get() else: set_constant_values_prg = openstudio_model.EnergyManagementSystemProgram(model) set_constant_values_prg.setName('Set_Constant_Values') set_constant_values_prg.setBody(set_constant_values_prg_body) # Initialize zone specific constant values used in EMS programs. set_constant_zone_values_prg_body = \ 'SET {zone_name}_max_ctrl_temp = {zone_name}_lower_comfort_limit,\n' \ 'SET {zone_name}_min_ctrl_temp = {zone_name}_upper_comfort_limit,\n' \ 'SET {zone_name}_cmd_csp_error = 0,\n' \ 'SET {zone_name}_cmd_hsp_error = 0,\n' \ 'SET {zone_name}_cmd_cold_water_ctrl = {zone_name}_upper_comfort_limit,\n' \ 'SET {zone_name}_cmd_hot_water_ctrl = {zone_name}_lower_comfort_limit'.format( zone_name=zone_name) set_constant_zone_values_prg = openstudio_model.EnergyManagementSystemProgram(model) set_constant_zone_values_prg.setName('{}_Set_Constant_Values'.format(zone_name)) 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(model) calculate_minmax_ctrl_temp_prg.setName('{}_Calculate_Extremes_In_Zone'.format(zone_name)) calculate_minmax_ctrl_temp_prg_body = \ 'IF ({zone_name}_occupied_status >= {occ_threshold}),\n' \ ' IF {zone_name}_ctrl_temperature > {zone_name}_max_ctrl_temp,\n' \ ' SET {zone_name}_max_ctrl_temp = {zone_name}_ctrl_temperature,\n' \ ' ENDIF,\n' \ ' IF {zone_name}_ctrl_temperature < {zone_name}_min_ctrl_temp,\n' \ ' SET {zone_name}_min_ctrl_temp = {zone_name}_ctrl_temperature,\n' \ ' ENDIF,\n' \ 'ELSE,\n' \ ' SET {zone_name}_max_ctrl_temp = {zone_name}_lower_comfort_limit,\n' \ ' SET {zone_name}_min_ctrl_temp = {zone_name}_upper_comfort_limit,\n' \ 'ENDIF'.format( zone_name=zone_name, occ_threshold=occupied_percentage_threshold) calculate_minmax_ctrl_temp_prg.setBody(calculate_minmax_ctrl_temp_prg_body) # Calculate errors from comfort zone limits and 'measured' temperature in the zone. calculate_errors_from_comfort_prg = openstudio_model.EnergyManagementSystemProgram(model) calculate_errors_from_comfort_prg.setName( '{}_Calculate_Errors_From_Comfort'.format(zone_name)) calculate_errors_from_comfort_prg_body = \ 'IF (CurrentTime == (hour_of_slab_sp_change - ZoneTimeStep)),\n' \ ' SET {zone_name}_cmd_csp_error = ' \ '({zone_name}_upper_comfort_limit - ctrl_temp_offset) - {zone_name}_max_ctrl_temp,\n' \ ' SET {zone_name}_cmd_hsp_error = ' \ '({zone_name}_lower_comfort_limit + ctrl_temp_offset) - {zone_name}_min_ctrl_temp,\n' \ 'ENDIF'.format(zone_name=zone_name) calculate_errors_from_comfort_prg.setBody(calculate_errors_from_comfort_prg_body) # Calculate the new active slab temperature setpoint for heating and cooling calculate_slab_ctrl_setpoint_prg = \ openstudio_model.EnergyManagementSystemProgram(model) calculate_slab_ctrl_setpoint_prg.setName( '{}_Calculate_Slab_Ctrl_Setpoint'.format(zone_name)) calculate_slab_ctrl_setpoint_prg_body = \ 'SET {z}_cont_cool_oper = ' \ '@TrendSum {z}_rad_cool_operation_trend radiant_switch_over_time/ZoneTimeStep,\n' \ 'SET {z}_cont_heat_oper = ' \ '@TrendSum {z}_rad_heat_operation_trend radiant_switch_over_time/ZoneTimeStep,\n' \ 'SET {z}_occupied_hours = @TrendSum {z}_occupied_status_trend 24/ZoneTimeStep,\n' \ 'IF ({z}_cont_cool_oper > 0) && ({z}_occupied_hours > 0) && ' \ '(CurrentTime == hour_of_slab_sp_change),\n' \ ' SET {z}_cmd_hot_water_ctrl = ' \ '{z}_cmd_hot_water_ctrl + ({z}_cmd_csp_error*prp_k),\n' \ 'ELSEIF ({z}_cont_heat_oper > 0) && ({z}_occupied_hours > 0) && ' \ '(CurrentTime == hour_of_slab_sp_change),\n' \ ' SET {z}_cmd_hot_water_ctrl = ' \ '{z}_cmd_hot_water_ctrl + ({z}_cmd_hsp_error*prp_k),\n' \ 'ELSE,\n' \ ' SET {z}_cmd_hot_water_ctrl = {z}_cmd_hot_water_ctrl,\n' \ 'ENDIF,\n' \ 'IF ({z}_cmd_hot_water_ctrl < lower_slab_sp_lim),\n' \ ' SET {z}_cmd_hot_water_ctrl = lower_slab_sp_lim,\n' \ 'ELSEIF ({z}_cmd_hot_water_ctrl > upper_slab_sp_lim),\n' \ ' SET {z}_cmd_hot_water_ctrl = upper_slab_sp_lim,\n' \ 'ENDIF,\n' \ 'SET {z}_cmd_cold_water_ctrl = {z}_cmd_hot_water_ctrl + 0.01'.format(z=zone_name) calculate_slab_ctrl_setpoint_prg.setBody(calculate_slab_ctrl_setpoint_prg_body) ##### # List of EMS program manager objects #### initialize_constant_parameters = \ model.getEnergyManagementSystemProgramCallingManagerByName( 'Initialize_Constant_Parameters') if initialize_constant_parameters.is_initialized(): initialize_constant_parameters = initialize_constant_parameters.get() # add program if it does not exist in manager existing_program_names = [] for prg in initialize_constant_parameters.programs(): existing_program_names.append(prg.nameString().lower()) if set_constant_values_prg.nameString().lower() not in existing_program_names: initialize_constant_parameters.addProgram(set_constant_values_prg) else: initialize_constant_parameters = \ openstudio_model.EnergyManagementSystemProgramCallingManager(model) initialize_constant_parameters.setName('Initialize_Constant_Parameters') initialize_constant_parameters.setCallingPoint('BeginNewEnvironment') initialize_constant_parameters.addProgram(set_constant_values_prg) 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() # add program if it does not exist in manager existing_program_names = [] for prg in initialize_constant_parameters_after_warmup.programs(): existing_program_names.append(prg.nameString().lower()) if set_constant_values_prg.nameString().lower() not in existing_program_names: initialize_constant_parameters_after_warmup.addProgram(set_constant_values_prg) else: initialize_constant_parameters_after_warmup = \ openstudio_model.EnergyManagementSystemProgramCallingManager(model) initialize_constant_parameters_after_warmup.setName( 'Initialize_Constant_Parameters_After_Warmup') initialize_constant_parameters_after_warmup.setCallingPoint( 'AfterNewEnvironmentWarmUpIsComplete') initialize_constant_parameters_after_warmup.addProgram(set_constant_values_prg) zone_initialize_constant_parameters = \ openstudio_model.EnergyManagementSystemProgramCallingManager(model) zone_initialize_constant_parameters.setName( '{}_Initialize_Constant_Parameters'.format(zone_name)) zone_initialize_constant_parameters.setCallingPoint('BeginNewEnvironment') zone_initialize_constant_parameters.addProgram(set_constant_zone_values_prg) zone_initialize_constant_parameters_after_warmup = \ openstudio_model.EnergyManagementSystemProgramCallingManager(model) zone_initialize_constant_parameters_after_warmup.setName( '{}_Initialize_Constant_Parameters_After_Warmup'.format(zone_name)) zone_initialize_constant_parameters_after_warmup.setCallingPoint( 'AfterNewEnvironmentWarmUpIsComplete') zone_initialize_constant_parameters_after_warmup.addProgram( set_constant_zone_values_prg) average_building_temperature = \ openstudio_model.EnergyManagementSystemProgramCallingManager(model) average_building_temperature.setName( '{}_Average_Building_Temperature'.format(zone_name)) average_building_temperature.setCallingPoint('EndOfZoneTimestepAfterZoneReporting') average_building_temperature.addProgram(calculate_minmax_ctrl_temp_prg) average_building_temperature.addProgram(calculate_errors_from_comfort_prg) programs_at_beginning_of_timestep = \ openstudio_model.EnergyManagementSystemProgramCallingManager(model) programs_at_beginning_of_timestep.setName( '{}_Programs_At_Beginning_Of_Timestep'.format(zone_name)) programs_at_beginning_of_timestep.setCallingPoint('BeginTimestepBeforePredictor') programs_at_beginning_of_timestep.addProgram(calculate_slab_ctrl_setpoint_prg) ##### # List of variables for output. #### zone_max_ctrl_temp_output = openstudio_model.EnergyManagementSystemOutputVariable( model, zone_max_ctrl_temp) zone_max_ctrl_temp_output.setName( '{} Maximum occupied temperature in zone'.format(zone_name)) zone_min_ctrl_temp_output = openstudio_model.EnergyManagementSystemOutputVariable( model, zone_min_ctrl_temp) zone_min_ctrl_temp_output.setName( '{} Minimum occupied temperature in zone'.format(zone_name))
[docs] 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): """Native EnergyPlus objects implement control for a single zone with a radiant system. Args: model: OpenStudio Model. zone: [OpenStudio::Model::ThermalZone>] zone to add radiant controls. radiant_loop: [OpenStudio::Model::ZoneHVACLowTempRadiantVarFlow>] radiant loop in thermal zone. radiant_temperature_control_type: [String] determines the controlled temperature for the radiant system. Options include the following. * SurfaceFaceTemperature * SurfaceInteriorTemperature slab_setpoint_oa_control: [Bool] True if slab setpoint is to be varied based on outdoor air temperature. switch_over_time: [Double] Time limitation for when the system can switch between heating and cooling. slab_sp_at_oat_low: [Double] radiant slab temperature setpoint, in F, at the outdoor high temperature. slab_oat_low: [Double] outdoor drybulb air temperature, in F, for low radiant slab setpoint. slab_sp_at_oat_high: [Double] radiant slab temperature setpoint, in F, at the outdoor low temperature. slab_oat_high: [Double] outdoor drybulb air temperature, in F, for high radiant slab setpoint. """ zone_name = zone.nameString() if model.version() < openstudio.VersionString('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() ##### # Define radiant system parameters #### # set radiant system temperature and setpoint control type control_types = ('surfacefacetemperature', 'surfaceinteriortemperature') if not radiant_temperature_control_type.lower() in control_types: msg = 'Control sequences not compatible with "{}" radiant system control. ' \ 'Defaulting to "SurfaceFaceTemperature".'.format(radiant_temperature_control_type) print(msg) radiant_temperature_control_type = 'SurfaceFaceTemperature' 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 = create_constant_schedule_ruleset( model, switch_over_time, name='Radiant System Switchover', schedule_type_limit='Dimensionless') # set radiant system switchover schedule radiant_loop.setChangeoverDelayTimePeriodSchedule( sch_radiant_switchover.to_Schedule().get()) if slab_setpoint_oa_control: schedule_interval = model.getScheduleByName( 'Sch_Radiant_SlabSetP_Based_On_Rolling_Mean_OAT') if schedule_interval.is_initialized() and \ schedule_interval.get().to_ScheduleFixedInterval().is_initialized(): schedule_interval = schedule_interval.get().to_ScheduleFixedInterval().get() coil_heating_radiant.setHeatingControlTemperatureSchedule(schedule_interval) coil_cooling_radiant.setCoolingControlTemperatureSchedule(schedule_interval) else: # get weather file from model weather_file = model.getWeatherFile() if weather_file.initialized(): # get annual outdoor dry bulb temperature epw_data = weather_file.file().get().data() annual_oat = [dat.dryBulbTemperature().get() for dat in epw_data] # calculate a nhrs rolling average from annual outdoor dry bulb temperature nhrs = 24 oat_rolling_average = [] annual_oat = list(annual_oat[-nhrs:]) + annual_oat for i in range(nhrs, len(annual_oat)): avg = sum(annual_oat[i - nhrs: i]) / nhrs oat_rolling_average.append(round(avg, 2)) # use rolling average to calculate slab setpoint temperature # convert temperature from IP to SI units slab_sp_at_oat_low_si = \ TEMPERATURE.to_unit([slab_sp_at_oat_low], 'C', 'F')[0] slab_oat_low_si = TEMPERATURE.to_unit([slab_oat_low], 'C', 'F')[0] slab_sp_at_oat_high_si = \ TEMPERATURE.to_unit([slab_sp_at_oat_high], 'C', 'F')[0] slab_oat_high_si = TEMPERATURE.to_unit([slab_oat_high], 'C', 'F')[0] # 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 = round(slope_num / slope_den, 4) slab_setpoint = [] for e in oat_rolling_average: sl_pt = slab_sp_at_oat_low_si + ((e - slab_oat_low_si) * sp_and_oat_slope) slab_setpoint.append(round(sl_pt, 1)) # input upper limits on slab setpoint slab_sp_upper_limit = max([slab_sp_at_oat_high_si, slab_sp_at_oat_low_si]) slab_sp_upper_limit = round(slab_sp_upper_limit, 1) slab_setpoint = [e if e < slab_sp_upper_limit else slab_sp_upper_limit for e in slab_setpoint] # input lower limits on slab setpoint slab_sp_lower_limit = min([slab_sp_at_oat_high_si, slab_sp_at_oat_low_si]) slab_sp_lower_limit = round(slab_sp_lower_limit, 1) slab_setpoint = [e if e > slab_sp_lower_limit else slab_sp_lower_limit for e in slab_setpoint] # convert to timeseries interval = openstudio.Time(0, 0, 60) start_date = model.makeDate(1, 1) series_values = openstudio.Vector(len(slab_setpoint)) for i, val in enumerate(slab_setpoint): series_values[i] = val time_series = openstudio.TimeSeries(start_date, interval, series_values, 'C') # create fixed interval schedule for slab setpoint schedule_interval = openstudio_model.ScheduleFixedInterval(model) schedule_interval.setName('Sch_Radiant_SlabSetP_Based_On_Rolling_Mean_OAT') schedule_interval.setTimeSeries(time_series) sch_type_limits_obj = create_schedule_type_limits( model, standard_schedule_type_limit='Temperature') schedule_interval.setScheduleTypeLimits(sch_type_limits_obj) # assign slab setpoint schedule coil_heating_radiant.setHeatingControlTemperatureSchedule(schedule_interval) coil_cooling_radiant.setCoolingControlTemperatureSchedule(schedule_interval) else: msg = 'Model does not have a weather file associated with it. ' \ 'Define to implement slab setpoint based on outdoor weather.' raise ValueError(msg) else: slab_setpoint = 22 # constant setpoint for the slab surface temperature # radiant system cooling control setpoint sch_radiant_clgsetp = create_constant_schedule_ruleset( model, slab_setpoint + 0.1, name='{}_Sch_Radiant_ClgSetP'.format(zone_name), schedule_type_limit='Temperature') coil_cooling_radiant.setCoolingControlTemperatureSchedule(sch_radiant_clgsetp) # radiant system heating control setpoint sch_radiant_htgsetp = create_constant_schedule_ruleset( model, slab_setpoint, name='{}_Sch_Radiant_HtgSetP'.format(zone_name), schedule_type_limit='Temperature') coil_heating_radiant.setHeatingControlTemperatureSchedule(sch_radiant_htgsetp)