From 18d48e84b1e4a60e4fb83cecf1d1f27f7db6177a Mon Sep 17 00:00:00 2001 From: nigusse Date: Fri, 26 Jan 2024 11:32:30 -0500 Subject: [PATCH 01/24] NFP Air-To-Water Heat Pump with Heat Recovery --- .../NFP-AirToWaterHeatPumpHeatRecovery.md | 353 ++++++++++++++++++ design/FY2024/chiller_with_heat_recovery.png | Bin 0 -> 12517 bytes design/FY2024/heater_with_heat_recovery.png | Bin 0 -> 10185 bytes 3 files changed, 353 insertions(+) create mode 100644 design/FY2024/NFP-AirToWaterHeatPumpHeatRecovery.md create mode 100644 design/FY2024/chiller_with_heat_recovery.png create mode 100644 design/FY2024/heater_with_heat_recovery.png diff --git a/design/FY2024/NFP-AirToWaterHeatPumpHeatRecovery.md b/design/FY2024/NFP-AirToWaterHeatPumpHeatRecovery.md new file mode 100644 index 00000000000..4bd465db656 --- /dev/null +++ b/design/FY2024/NFP-AirToWaterHeatPumpHeatRecovery.md @@ -0,0 +1,353 @@ +Air-To-Water Heat Pump Heat Recovery Mode +====================== + +**Bereket Nigusse** + +**Florida Solar Energy Center** + + - First draft: January 26, 2024 + - Modified Date: NA + - Added Design Document: NA + + +## Justification for New Feature ## + +Modern air-to-water heat pumps for commercial applications often include heat recovery for simultaneous heating and cooling. The air-to-water heat pump simulation in EnergyPlus was improved during the FY23 development cycle; however, the current model supports heat-only and cool-only modes of operation. EnergyPlus should be able to support heat recovery mode for the air-to-water heat pump simulations. + +**- The new feature was requested by Design Builder ** + +## E-mail and Conference Call Conclusions ## + +- NA + + +## Overview ## + +### Current Code ### + +The existing Air-to-Water Heat Pump model supports heat-only and cool-only mode simulations. + +**- This enhancement allows simultaneous cooling and heating modes of operation for Air-to-Water Heat Pump model. + +## Implementation Approach ## + +*(1) This new feature can be implemented by modifying the following existing objects: + + Existing objects: `HeatPump:PlantLoop:EIR:Cooling` and `HeatPump:PlantLoop:EIR:Heating` + + +*(2) Adds four new input fields to support heat recovery operation mode for each HeatPump:PlantLoop:EIR:* object. + + Adds new heat recovery fluid inlet and outlet nodes: + New input fields `Heat Recovery Inlet Node Name` and `Heat Recovery Outlet Node Name` will be added to the `HeatPump:PlantLoop:EIR:Cooling` object. + New input fields `Heat Recovery Inlet Node Name` and `Heat Recovery Outlet Node Name` will be added to the `HeatPump:PlantLoop:EIR:Heating` object. + + Adds heat recovery side reference fluid flow rate: + New input field `Heat Recovery Side Reference Flow Rate` will be added to the `HeatPump:PlantLoop:EIR:Cooling` and `HeatPump:PlantLoop:EIR:Heating` objects. + + Adds heat recovery fluid temperature limits: + New input field `Maximum Heat Recovery Fluid Inlet Temperature` will be added to the `HeatPump:PlantLoop:EIR:Cooling` object. + New input field `Minimum Heat Recovery Fluid Inlet Temperature` will be added to the `HeatPump:PlantLoop:EIR:Heating` object. + + +*(3) Adds new code that supports calculating the heat rate and outlet node fluid temperature of the heat recovery system + + Hot Water Recovery Rate Calculation: + {CAPFT_Val} = CAPFT(T_lSide_Cool, T_sSide_Heat) + {Q_dot_{HR,heat,avail}} = {Q_dot_{HR,heat,ref} * {CAPFT_Val} + + Heat Recovery Outlet Node Fluid Temperature Calculation: + {T_{HR,heat,out}} = {T_{HR,heat,in}} + {Q_dot_{HR,heat}} / {{Cp_{heat}} * {m_dot_{HR,heat}}} + + Chilled Water Recovery Rate Calculation: + {CAPFT_Val} = CAPFT(T_lSide_Heat, T_sSide_Cool) + {Q_dot_{HR,cool,avail}} = {Q_dot_{HR,cool,ref}} * {CAPFT_Val} + + Heat Recovery Outlet Node Fluid Temperature Calculation: + {T_{HR,cool,out}} = {T_{HR,cool,in}} - {Q_dot_{HR,cool}} / {{Cp_{cool}} * {m_dot_{HR,cool}}} + + where, + CAPFT = capacity modifier normalized curve as a function of temperature + CAPFT_val = capacity modifier normalized curve value + {Q_dot_{HR,heat,avail}} = heat recovery available heating rate, W + {Q_dot_{HR,heat,ref}} = reference capacity of hot water heat recovery, W + {Q_dot_{HR,cool,avail}} = heat recovery available cooling rate, W + {Q_dot_{HR,cool,ref}} = reference capacity of chilled water heat recovery, W + {T_{HR,heat,in}} = hot water heat recovery inlet node temperature, C + {T_{HR,heat,out}} = hot water heat recovery outlet node temperature, C + {{Cp_{heat}} = hot water specific heat capacity, J/kgK + {m_dot_{HR,heat}}} = hot water heat recovery fluid mass flow rate, kg/s + {T_{HR,cool,in}} = chilled water heat recovery inlet node temperature, C + {T_{HR,cool,out}} = chilled water heat recovery outlet node temperature, C + {{Cp_{cool}} = chilled water specific heat capacity, J/kgK + {m_dot_{HR,cool}}} = chilled water heat recovery fluid mass flow rate, kg/s + T_lSide_Cool = chiller load side entering fluid temperature, C + T_sSide_Heat = chiller source side entering fluid temperature, C + T_lSide_Heat = Heater (heat Pump) load side entering fluid temperature, C + T_sSide_Cool = Heater (heat Pump) source side entering fluid temperature, C + + +*(4) Heat recovery operating mode simulation control. + + ** the heat recovery mode operation is activated if heat recovery nodes are specified. ** + + The heat recovery mode is essentially a passive operation. Hence, the HR delivered capacity is determined by the actual + capacity of the chiller or heater operation. Thus, the actual HR rate delivered can be estimated using the chiller or + heater part-load ratio. + + Hot Water Heat Recovery: + {Q_dot_{HR,heat,actual}} = {PLR_{cool}} * {Q_dot_{HR,heat,avail}} + + ** the available capacity must be prorated by the chiller PLR to get the delivered hot water recovery rate. ** + + + Chilled Water Heat Recovery: + {Q_dot_{HR,cool,actual}} = {PLR_{heat}} * {Q_dot_{HR,cool,avail}} + + ** the available capacity must be prorated by the heater PLR to get the delivered chilled water recovery rate. ** + + ** required to check the user specified temperature limit at the heat recovery outlet node is not violated. ** + + where, + {Q_dot_{HR,heat,actual}} = heat recovery actual heating rate, W + {Q_dot_{HR,cool,actual}} = heat recovery actual cooling rate, W + {PLR_{cool}} = chiller part-load ratio, - + {PLR_{heat}} = heater part-load ratio, - + +*(5) Adds capacity and fluid flow rates sizing calculation for the heat recovery system. + + Heat Recovery Reference Capacities Sizing: + Hot Water Heating Reference Capacity: + {Q_dot_{HR,heat,ref}} = {Q_{ref,cool,coil}} * [1 + 1/{COP_cool}}] + + Chilled Water Cooling Reference Capacity: + {Q_dot_{HR,cool,ref}} = {Q_{ref,heat,coil}} * [(1 - 1/{COP_heat})] + + Heat Recovery Fluid Flow Rates Sizing: + Hot Water Recovery Design Flow Rate: + {m_dot_{HR,design,heat}} = {Sizing_Factor_{cool}} * {Q_dot_{HR,heat,ref}} / [{Cp_{heat}} * {DeltaT_{heat}}] + + Chilled Water Recovery Design Flow Rate: + {m_dot_{HR,design,cool}} = {Sizing_Factor_{heat}} * {Q_dot_{HR,cool,ref}} / [{Cp_{cool}} * {DeltaT_{cool}}] + + where, + {Q_{ref,cool,coil}} = reference capacity of the chiller, W + {Q_{ref,heat,coil}} = reference capacity of the heater, W + {COP_cool}) = reference COP of the chiller, W/W + {COP_heat}) = reference COP of the heater, W/W + {DeltaT_{heat}} = hot water loop design temperature difference, K + {DeltaT_{cool}} = chilled water loop design temperature difference, K + {Sizing_Factor_{cool}} = sizing factor of the chiller, (-) + {Sizing_Factor_{heat}} = sizing factor of the heater (heat pump), (-) + + +*(6) Schematic diagram of cooling and heating dominated heat recovery operating modes: + + +![chiller_with_heat_recovery](chiller_with_heat_recovery.png) +

Figure 1. Air-To-Water Heat Pump (Chiller) with Heat Recovery Mode Diagram.

+ + +![heater_with_heat_recovery](heater_with_heat_recovery.png) +

Figure 2. Air-To-Water Heat Pump (Heater) with Heat Recovery Mode Diagram.

+ + +### Existing Object HeatPump:PlantLoop:EIR:Cooling ### + + +HeatPump:PlantLoop:EIR:Cooling, + \memo An EIR formulated water to water heat pump model, cooling operation. + \min-fields 15 + A1, \field Name + \type alpha + \reference PLHPCoolingNames + \required-field + \reference-class-name validPlantEquipmentTypes + \reference validPlantEquipmentNames + \reference-class-name validBranchEquipmentTypes + \reference validBranchEquipmentNames + A2, \field Load Side Inlet Node Name + \required-field + \type node + A3, \field Load Side Outlet Node Name + \required-field + \type node + A4, \field Condenser Type + \type choice + \key WaterSource + \key AirSource + \default WaterSource + A5, \field Source Side Inlet Node Name + \required-field + \type node + A6, \field Source Side Outlet Node Name + \required-field + \type node + + + + A7, \field Hot Water Recovery Side Inlet Node Name + \required-field + \type node + A8, \field Hot Water Recovery Side Outlet Node Name + \required-field + \type node + + ... + + N6, \field Heat Recovery Side Reference Flow Rate + \type real + \minimum> 0.0 + \units m3/s + \ip-units gal/min + \autosizable + \default autosize + + + ... + + N8, \field Minimum Source Inlet Temperature + \type real + \units C + \default -100.0 + \note Enter the minimum inlet outdoor air dry-bulb temperature + \note for air-cooled units or minimum inlet water temperature for water-cooled units. + \note The unit is disabled below this temperature. + N9, \field Maximum Source Inlet Temperature + \type real + \units C + \default 100.0 + \note Enter the maximum inlet outdoor air dry-bulb temperature + \note for air-cooled units or maximum inlet water temperature for water-cooled units. + \note The unit is disabled above this temperature. + N10, \field Minimum Supply Water Temperature Curve Name + \type object-list + \object-list UniVariateFunctions + \note quadratic curve = a + b*OAT is typical, other univariate curves may be used + \note OAT = Outdoor Dry-Bulb Temperature + N11; \field Maximum Supply Water Temperature Curve Name + \type object-list + \object-list UniVariateFunctions + \note quadratic curve = a + b*OAT is typical, other univariate curves may be used + \note OAT = Outdoor Dry-Bulb Temperature + + + + N12; \field Maximum Heat Recovery Fluid Inlet Temperature + \type real + \units C + \default 60.0 + \note Enter the maximum chiller inlet fluid temperature for hot water recovery + \note The heat recovery operation will be disabled above this temperature. + + +### Existing Object HeatPump:PlantLoop:EIR:Heating ### + + +HeatPump:PlantLoop:EIR:Heating, + \memo An EIR formulated water to water heat pump model, heating operation + \min-fields 15 + A1, \field Name + \type alpha + \reference PLHPHeatingNames + \required-field + \reference-class-name validPlantEquipmentTypes + \reference validPlantEquipmentNames + \reference-class-name validBranchEquipmentTypes + \reference validBranchEquipmentNames + A2, \field Load Side Inlet Node Name + \required-field + \type node + A3, \field Load Side Outlet Node Name + \required-field + \type node + A4, \field Condenser Type + \type choice + \key WaterSource + \key AirSource + \default WaterSource + A5, \field Source Side Inlet Node Name + \required-field + \type node + A6, \field Source Side Outlet Node Name + \required-field + \type node + + + + A7, \field Chilled Water Recovery Side Inlet Node Name + \required-field + \type node + A8, \field Chilled Water Recovery Side Outlet Node Name + \required-field + \type node + + ... + + N6, \field Heat Recovery Side Reference Flow Rate + \type real + \minimum> 0.0 + \units m3/s + \ip-units gal/min + \autosizable + \default autosize + + ... + + A22, \field Timed Empirical Defrost Heat Load Penalty Curve Name + \type object-list + \object-list UniVariateFunctions + \object-list BivariateFunctions + \note univariate curve = a + b*OAT is typical, other univariate curves may be used + \note bivariate curve = a + b*WB + c*WB**2 + d*OAT + e*OAT**2 + f*WB*OAT + \note OAT = outdoor air dry-bulb temperature (C) + \note WB = wet-bulb temperature (C) of air entering the indoor coil + \note Timed Empirical Defrost Heat Load Penalty in watts = hot load * curve output + \note only applicable if TimedEmpirical defrost control is specified + A23; \field Timed Empirical Defrost Heat Input Energy Fraction Curve Name + \type object-list + \object-list UniVariateFunctions + \object-list BivariateFunctions + \note univariate curve = a + b*OAT is typical, other univariate curves may be used + \note bivariate curve = a + b*WB + c*WB**2 + d*OAT + e*OAT**2 + f*WB*OAT + \note OAT = outdoor air dry-bulb temperature (C) + \note WB = wet-bulb temperature (C) of air entering the indoor coil + \note Timed Empirical Defrost Heat Input Energy in watts = rated hot load * curve output + \note only applicable if TimedEmpirical defrost control is specified + + + + N13; \field Minimum Heat Recovery Fluid Inlet Temperature + \type real + \units C + \default 6.7 + \note Enter the minimum heater inlet fluid temperature for chilled water recovery + \note The heat recovery operation will be disabled below this temperature. + + + +## Testing/Validation/Data Source(s): ## + +Demonstrate that the air-to-water heat pump object supports heat recovery mode simulation. Unit tests will be added to demonstrate the new feature. + +## Input Output Reference Documentation ## + +Documentation for the new input fields will be added to the I/O reference guide of `HeatPump:PlantLoop:EIR:Cooling` and `HeatPump:PlantLoop:EIR:Heating` objects. + +## Engineering Reference ## +As needed. + +## Example File and Transition Changes ## + +A new example file will be created for the heat recovery mode of air-to-water heat pumps. Simulation results will be examined, and sample results will be provided. + +Transition is required to handle the two node input fields. + +## Proposed Report Variables: ## + +Add output variables as needed. + + +## References ## +NA + diff --git a/design/FY2024/chiller_with_heat_recovery.png b/design/FY2024/chiller_with_heat_recovery.png new file mode 100644 index 0000000000000000000000000000000000000000..7a2af228cc67c9e09c63ac64e23f7f9c149609ac GIT binary patch literal 12517 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 literal 0 HcmV?d00001 diff --git a/design/FY2024/heater_with_heat_recovery.png b/design/FY2024/heater_with_heat_recovery.png new file mode 100644 index 0000000000000000000000000000000000000000..718835494260109413fddbe1183beeacda3972ea GIT binary patch literal 10185 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 literal 0 HcmV?d00001 From cc06e192068cdac52fc63686809d1ab02b6eeaca Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 29 Jan 2024 16:09:35 -0500 Subject: [PATCH 02/24] refactor defrost and curve check codes --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 216 +++++++++++++------------ src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 4 + 2 files changed, 121 insertions(+), 99 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 3608ab32c07..37d0c9eb735 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -407,6 +407,79 @@ void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) this->partLoadRatio = partLoadRatio; this->cyclingRatio = cyclingRatio; + // do defrost calculation if applicable + // Init defrost power adjustment factors + Real64 InputPowerMultiplier = 1.0; + this->doDefrost(state, operatingPLR, availableCapacity, InputPowerMultiplier); + this->defrostEnergy = this->defrostEnergyRate * reportingInterval; + + // evaluate the actual current operating load side heat transfer rate + auto &thisLoadPlantLoop = state.dataPlnt->PlantLoop(this->loadSidePlantLoc.loopNum); + Real64 CpLoad = FluidProperties::GetSpecificHeatGlycol(state, + thisLoadPlantLoop.FluidName, + state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp, + thisLoadPlantLoop.FluidIndex, + "PLHPEIR::simulate()"); + this->loadSideHeatTransfer = availableCapacity * operatingPLR; + this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval; + + // calculate load side outlet conditions + Real64 const loadMCp = this->loadSideMassFlowRate * CpLoad; + this->loadSideOutletTemp = this->calcLoadOutletTemp(this->loadSideInletTemp, this->loadSideHeatTransfer / loadMCp); + + // now what to do here if outlet water temp exceeds limit based on HW supply temp limit curves? + // currentLoad will be met and there should? be some adjustment based on outlet water temp limit? + + // calculate power usage from EIR curves + Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); + Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); + // check curves value + this->doCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp, eirModifierFuncTemp, eirModifierFuncPLR); + + this->powerUsage = + (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * InputPowerMultiplier * this->cyclingRatio; + this->powerEnergy = this->powerUsage * reportingInterval; + + // energy balance on heat pump + this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); + this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; + + // calculate source side outlet conditions + Real64 CpSrc; + if (this->waterSource) { + auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); + CpSrc = FluidProperties::GetSpecificHeatGlycol( + state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "PLHPEIR::simulate()"); + } else { + CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat); + } + // this->sourceSideCp = CpSrc; // debuging variable + Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; + this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); + + if (this->waterSource && abs(this->sourceSideOutletTemp - this->sourceSideInletTemp) > 100.0) { // whoaa out of range happenings on water loop + // + // TODO setup recurring error warning? + // lets do something different than fatal the simulation + if ((this->sourceSideMassFlowRate / this->sourceSideDesignMassFlowRate) < 0.01) { // current source side flow is 1% of design max + // just send it all to skin losses and leave the fluid temperature alone + this->sourceSideOutletTemp = this->sourceSideInletTemp; + } else if (this->sourceSideOutletTemp > this->sourceSideInletTemp) { + this->sourceSideOutletTemp = this->sourceSideInletTemp + 100.0; // cap it at 100C delta + + } else if (this->sourceSideOutletTemp < this->sourceSideInletTemp) { + this->sourceSideOutletTemp = this->sourceSideInletTemp - 100.0; // cap it at 100C delta + } + } +} + + +void EIRPlantLoopHeatPump::doCurveCheck(EnergyPlusData &state, + const Real64 loadSideOutletSetpointTemp, + Real64 &capacityModifierFuncTemp, + Real64 &eirModifierFuncTemp, + Real64 &eirModifierFuncPLR) +{ if (capacityModifierFuncTemp < 0.0) { if (this->capModFTErrorIndex == 0) { ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast(this->EIRHPType)], this->name)); @@ -428,11 +501,52 @@ void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) capacityModifierFuncTemp = 0.0; } + if (eirModifierFuncTemp < 0.0) { + if (this->eirModFTErrorIndex == 0) { + ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast(this->EIRHPType)], this->name)); + ShowContinueError(state, format(" EIR Modifier curve (function of Temperatures) output is negative ({:.3T}).", eirModifierFuncTemp)); + ShowContinueError(state, + format(" Negative value occurs using a water temperature of {:.2T}C and an outdoor air temperature of {:.2T}C.", + this->loadSideOutletTemp, + this->sourceSideInletTemp)); + ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation."); + } + ShowRecurringWarningErrorAtEnd(state, + format("{} \"{}\": EIR Modifier curve (function of Temperatures) output is negative warning continues...", + DataPlant::PlantEquipTypeNames[static_cast(this->EIRHPType)], + this->name), + this->eirModFTErrorIndex, + eirModifierFuncTemp, + eirModifierFuncTemp); + eirModifierFuncTemp = 0.0; + } + + if (eirModifierFuncPLR < 0.0) { + if (this->eirModFPLRErrorIndex == 0) { + ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast(this->EIRHPType)], this->name)); + ShowContinueError(state, format(" EIR Modifier curve (function of PLR) output is negative ({:.3T}).", eirModifierFuncPLR)); + ShowContinueError(state, format(" Negative value occurs using a Part Load Ratio of {:.2T}", this->partLoadRatio)); + ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation."); + } + ShowRecurringWarningErrorAtEnd(state, + format("{} \"{}\": EIR Modifier curve (function of PLR) output is negative warning continues...", + DataPlant::PlantEquipTypeNames[static_cast(this->EIRHPType)], + this->name), + this->eirModFPLRErrorIndex, + eirModifierFuncPLR, + eirModifierFuncPLR); + eirModifierFuncPLR = 0.0; + } +} + +void EIRPlantLoopHeatPump::doDefrost( + EnergyPlusData &state, const Real64 operatingPLR, Real64 &availableCapacity, Real64 &InputPowerMultiplier) +{ // Initializing defrost adjustment factors Real64 HeatingCapacityMultiplier = 1.0; - Real64 InputPowerMultiplier = 1.0; + //Real64 InputPowerMultiplier = 1.0; - // Check outdoor temperature to determine of defrost is active + // Check outdoor temperature to determine of defrost is active if (this->defrostAvailable && state.dataEnvrn->OutDryBulbTemp <= this->maxOutdoorTemperatureDefrost) { // Calculate defrost adjustment factors depending on defrost control type // Calculate delta w through outdoor coil by assuming a coil temp of 0.82*DBT-9.7(F) per DOE2.1E @@ -504,104 +618,8 @@ void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) this->defrostEnergyRate = 0.0; this->loadDueToDefrost = 0.0; this->fractionalDefrostTime = 0.0; - } - availableCapacity *= HeatingCapacityMultiplier; - this->defrostEnergy = this->defrostEnergyRate * reportingInterval; - - // evaluate the actual current operating load side heat transfer rate - auto &thisLoadPlantLoop = state.dataPlnt->PlantLoop(this->loadSidePlantLoc.loopNum); - Real64 CpLoad = FluidProperties::GetSpecificHeatGlycol(state, - thisLoadPlantLoop.FluidName, - state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp, - thisLoadPlantLoop.FluidIndex, - "PLHPEIR::simulate()"); - this->loadSideHeatTransfer = availableCapacity * operatingPLR; - this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval; - - // calculate load side outlet conditions - Real64 const loadMCp = this->loadSideMassFlowRate * CpLoad; - this->loadSideOutletTemp = this->calcLoadOutletTemp(this->loadSideInletTemp, this->loadSideHeatTransfer / loadMCp); - - // now what to do here if outlet water temp exceeds limit based on HW supply temp limit curves? - // currentLoad will be met and there should? be some adjustment based on outlet water temp limit? - - // calculate power usage from EIR curves - Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); + } - if (eirModifierFuncTemp < 0.0) { - if (this->eirModFTErrorIndex == 0) { - ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast(this->EIRHPType)], this->name)); - ShowContinueError(state, format(" EIR Modifier curve (function of Temperatures) output is negative ({:.3T}).", eirModifierFuncTemp)); - ShowContinueError(state, - format(" Negative value occurs using a water temperature of {:.2T}C and an outdoor air temperature of {:.2T}C.", - this->loadSideOutletTemp, - this->sourceSideInletTemp)); - ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation."); - } - ShowRecurringWarningErrorAtEnd(state, - format("{} \"{}\": EIR Modifier curve (function of Temperatures) output is negative warning continues...", - DataPlant::PlantEquipTypeNames[static_cast(this->EIRHPType)], - this->name), - this->eirModFTErrorIndex, - eirModifierFuncTemp, - eirModifierFuncTemp); - eirModifierFuncTemp = 0.0; - } - - Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); - - if (eirModifierFuncPLR < 0.0) { - if (this->eirModFPLRErrorIndex == 0) { - ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast(this->EIRHPType)], this->name)); - ShowContinueError(state, format(" EIR Modifier curve (function of PLR) output is negative ({:.3T}).", eirModifierFuncPLR)); - ShowContinueError(state, format(" Negative value occurs using a Part Load Ratio of {:.2T}", this->partLoadRatio)); - ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation."); - } - ShowRecurringWarningErrorAtEnd(state, - format("{} \"{}\": EIR Modifier curve (function of PLR) output is negative warning continues...", - DataPlant::PlantEquipTypeNames[static_cast(this->EIRHPType)], - this->name), - this->eirModFPLRErrorIndex, - eirModifierFuncPLR, - eirModifierFuncPLR); - eirModifierFuncPLR = 0.0; - } - - this->powerUsage = - (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * InputPowerMultiplier * this->cyclingRatio; - this->powerEnergy = this->powerUsage * reportingInterval; - - // energy balance on heat pump - this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); - this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; - - // calculate source side outlet conditions - Real64 CpSrc; - if (this->waterSource) { - auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); - CpSrc = FluidProperties::GetSpecificHeatGlycol( - state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "PLHPEIR::simulate()"); - } else { - CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat); - } - // this->sourceSideCp = CpSrc; // debuging variable - Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; - this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); - - if (this->waterSource && abs(this->sourceSideOutletTemp - this->sourceSideInletTemp) > 100.0) { // whoaa out of range happenings on water loop - // - // TODO setup recurring error warning? - // lets do something different than fatal the simulation - if ((this->sourceSideMassFlowRate / this->sourceSideDesignMassFlowRate) < 0.01) { // current source side flow is 1% of design max - // just send it all to skin losses and leave the fluid temperature alone - this->sourceSideOutletTemp = this->sourceSideInletTemp; - } else if (this->sourceSideOutletTemp > this->sourceSideInletTemp) { - this->sourceSideOutletTemp = this->sourceSideInletTemp + 100.0; // cap it at 100C delta - - } else if (this->sourceSideOutletTemp < this->sourceSideInletTemp) { - this->sourceSideOutletTemp = this->sourceSideInletTemp - 100.0; // cap it at 100C delta - } - } } void EIRPlantLoopHeatPump::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index 970987c95bf..d31c1e0b102 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -235,6 +235,10 @@ namespace EIRPlantLoopHeatPumps { void sizeSrcSideASHP(EnergyPlusData &state); + void doDefrost(EnergyPlusData &state, const Real64 operatingPLR, Real64 &AvailableCapacity, Real64 &InputPowerMultiplier); + + void doCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSPTemp, Real64 &capModFTemp, Real64 &eirModFTemp, Real64 &eirModFPLR); + Real64 getLoadSideOutletSetPointTemp(EnergyPlusData &state) const; void setOperatingFlowRatesASHP(EnergyPlusData &state, bool FirstHVACIteration); From 4f9669f86a249936d4e21fc7992980031c952557 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 29 Jan 2024 17:00:06 -0500 Subject: [PATCH 03/24] corrected defrost refactor --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 37d0c9eb735..d90fbf0b9fc 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -619,7 +619,7 @@ void EIRPlantLoopHeatPump::doDefrost( this->loadDueToDefrost = 0.0; this->fractionalDefrostTime = 0.0; } - + availableCapacity *= HeatingCapacityMultiplier; } void EIRPlantLoopHeatPump::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation) From 5836b367bfd52b8238ec99913a907a0e51fdf5a5 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Wed, 31 Jan 2024 10:18:17 -0500 Subject: [PATCH 04/24] doPhysics refactor --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 153 ++++++++++++++++++++++--- src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 4 + 2 files changed, 143 insertions(+), 14 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index d90fbf0b9fc..be490376c2c 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -128,8 +128,9 @@ void EIRPlantLoopHeatPump::simulate( state.dataPlnt->PlantLoop(this->loadSidePlantLoc.loopNum).FluidIndex, "EIRPlantLoopHeatPump::simulate"); Real64 controlLoad = this->loadSideMassFlowRate * CurSpecHeat * (leavingSetpoint - loadSideInletTemp); + this->doPhysics(state, controlLoad); - } else { + } else { this->doPhysics(state, CurLoad); } } else { @@ -313,14 +314,10 @@ void EIRPlantLoopHeatPump::setOperatingFlowRatesASHP(EnergyPlusData &state, bool } } + void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) { - Real64 constexpr RH90 = 90.0; - Real64 constexpr RH60 = 60.0; - Real64 constexpr rangeRH = 30.0; - Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; - // ideally the plant is going to ensure that we don't have a runflag=true when the load is invalid, but // I'm not sure we can count on that so we will do one check here to make sure we don't calculate things badly if ((this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling && currentLoad >= 0.0) || @@ -329,6 +326,22 @@ void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) return; } + if (this->waterSource) { + this->doPhysicsWSHP(state, currentLoad); + } else if (this->airSource) { + this->doPhysicsASHP(state, currentLoad); + } +} + + +void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLoad) +{ + + Real64 constexpr RH90 = 90.0; + Real64 constexpr RH60 = 60.0; + Real64 constexpr rangeRH = 30.0; + Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; + // get setpoint on the load side outlet Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state); Real64 originalLoadSideOutletSPTemp = loadSideOutletSetpointTemp; @@ -445,14 +458,9 @@ void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; // calculate source side outlet conditions - Real64 CpSrc; - if (this->waterSource) { - auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); - CpSrc = FluidProperties::GetSpecificHeatGlycol( - state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "PLHPEIR::simulate()"); - } else { - CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat); - } + auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); + Real64 const CpSrc = FluidProperties::GetSpecificHeatGlycol( + state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "PLHPEIR::simulate()"); // this->sourceSideCp = CpSrc; // debuging variable Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); @@ -474,6 +482,123 @@ void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) } +void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLoad) +{ + + Real64 constexpr RH90 = 90.0; + Real64 constexpr RH60 = 60.0; + Real64 constexpr rangeRH = 30.0; + Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; + + // get setpoint on the load side outlet + Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state); + Real64 originalLoadSideOutletSPTemp = loadSideOutletSetpointTemp; + + // add free cooling at some point, compressor is off during free cooling, temp limits restrict free cooling range + + Real64 capacityModifierFuncTemp = 1.0; + Real64 availableCapacity = this->referenceCapacity; + Real64 partLoadRatio = 0.0; + bool waterTempExceeded = false; + + // evaluate capacity modifier curve and determine load side heat transfer + // any adjustment to outlet water temp set point requires some form of iteration + for (int loop = 0; loop < 2; ++loop) { + capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, this->sourceSideInletTemp); + + availableCapacity = this->referenceCapacity * capacityModifierFuncTemp; + + // apply heating mode dry outdoor (evaporator) coil correction factor for air-cooled equipment + if (this->capacityDryAirCurveIndex > 0 && this->airSource && state.dataEnvrn->OutRelHum < RH90) { // above 90% RH yields full capacity + Real64 dryCorrectionFactor = std::min(1.0, Curve::CurveValue(state, this->capacityDryAirCurveIndex, state.dataEnvrn->OutDryBulbTemp)); + if (state.dataEnvrn->OutRelHum <= RH60) { + // dry heating capacity correction factor is a function of outdoor dry-bulb temperature + availableCapacity *= dryCorrectionFactor; + } else { + // interpolation of heating capacity between wet and dry is based on outdoor relative humidity over 60%-90% range + Real64 semiDryFactor = dryCorrectionFactor + (1.0 - dryCorrectionFactor) * (1.0 - ((RH90 - state.dataEnvrn->OutRelHum) / rangeRH)); + availableCapacity *= semiDryFactor; + } + } + + if (availableCapacity > 0) { + partLoadRatio = std::clamp(std::abs(currentLoad) / availableCapacity, 0.0, 1.0); + } + + if (this->minSupplyWaterTempCurveIndex > 0) { + Real64 minWaterTemp = Curve::CurveValue(state, this->minSupplyWaterTempCurveIndex, state.dataEnvrn->OutDryBulbTemp); + if (loadSideOutletSetpointTemp < minWaterTemp) { + loadSideOutletSetpointTemp = originalLoadSideOutletSPTemp + (1.0 - partLoadRatio) * (minWaterTemp - originalLoadSideOutletSPTemp); + waterTempExceeded = true; + } + } + if (this->maxSupplyWaterTempCurveIndex > 0) { + Real64 maxWaterTemp = Curve::CurveValue(state, this->maxSupplyWaterTempCurveIndex, state.dataEnvrn->OutDryBulbTemp); + if (loadSideOutletSetpointTemp > maxWaterTemp) { + loadSideOutletSetpointTemp = maxWaterTemp + (1.0 - partLoadRatio) * (originalLoadSideOutletSPTemp - maxWaterTemp); + waterTempExceeded = true; + } + } + if (!waterTempExceeded) { + break; + } + } + + Real64 cyclingRatio = 1.0; + Real64 operatingPLR = partLoadRatio; + if (partLoadRatio < this->minimumPLR) { + cyclingRatio = partLoadRatio / this->minimumPLR; + partLoadRatio = this->minimumPLR; + operatingPLR = partLoadRatio * cyclingRatio; + } + this->partLoadRatio = partLoadRatio; + this->cyclingRatio = cyclingRatio; + + // do defrost calculation if applicable + // Init defrost power adjustment factors + Real64 InputPowerMultiplier = 1.0; + this->doDefrost(state, operatingPLR, availableCapacity, InputPowerMultiplier); + this->defrostEnergy = this->defrostEnergyRate * reportingInterval; + + // evaluate the actual current operating load side heat transfer rate + auto &thisLoadPlantLoop = state.dataPlnt->PlantLoop(this->loadSidePlantLoc.loopNum); + Real64 CpLoad = FluidProperties::GetSpecificHeatGlycol(state, + thisLoadPlantLoop.FluidName, + state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp, + thisLoadPlantLoop.FluidIndex, + "PLHPEIR::simulate()"); + this->loadSideHeatTransfer = availableCapacity * operatingPLR; + this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval; + + // calculate load side outlet conditions + Real64 const loadMCp = this->loadSideMassFlowRate * CpLoad; + this->loadSideOutletTemp = this->calcLoadOutletTemp(this->loadSideInletTemp, this->loadSideHeatTransfer / loadMCp); + + // now what to do here if outlet water temp exceeds limit based on HW supply temp limit curves? + // currentLoad will be met and there should? be some adjustment based on outlet water temp limit? + + // calculate power usage from EIR curves + Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); + Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); + // check curves value + this->doCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp, eirModifierFuncTemp, eirModifierFuncPLR); + + this->powerUsage = + (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * InputPowerMultiplier * this->cyclingRatio; + this->powerEnergy = this->powerUsage * reportingInterval; + + // energy balance on heat pump + this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); + this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; + + // calculate source side outlet conditions + Real64 const CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat); + // this->sourceSideCp = CpSrc; // debuging variable + Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; + this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); + +} + void EIRPlantLoopHeatPump::doCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSetpointTemp, Real64 &capacityModifierFuncTemp, diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index d31c1e0b102..bedd2538c3d 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -229,6 +229,10 @@ namespace EIRPlantLoopHeatPumps { virtual void doPhysics(EnergyPlusData &state, Real64 currentLoad); + void doPhysicsWSHP(EnergyPlusData &state, Real64 currentLoad); + + void doPhysicsASHP(EnergyPlusData &state, Real64 currentLoad); + void sizeLoadSide(EnergyPlusData &state); void sizeSrcSideWSHP(EnergyPlusData &state); From 1cac7c77fd1047eb1f8d0f8a41fb2dc43e6f8b02 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Fri, 9 Feb 2024 16:45:00 -0500 Subject: [PATCH 05/24] more doPhysics refactor --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 204 +++++++++++-------------- src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 2 + 2 files changed, 88 insertions(+), 118 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index be490376c2c..b7ad344417b 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -337,78 +337,13 @@ void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLoad) { - Real64 constexpr RH90 = 90.0; - Real64 constexpr RH60 = 60.0; - Real64 constexpr rangeRH = 30.0; - Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; - - // get setpoint on the load side outlet - Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state); - Real64 originalLoadSideOutletSPTemp = loadSideOutletSetpointTemp; - // add free cooling at some point, compressor is off during free cooling, temp limits restrict free cooling range - Real64 capacityModifierFuncTemp = 1.0; Real64 availableCapacity = this->referenceCapacity; Real64 partLoadRatio = 0.0; bool waterTempExceeded = false; - // evaluate capacity modifier curve and determine load side heat transfer - // any adjustment to outlet water temp set point requires some form of iteration - for (int loop = 0; loop < 2; ++loop) { - capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, this->sourceSideInletTemp); - - availableCapacity = this->referenceCapacity * capacityModifierFuncTemp; - - // apply heating mode dry outdoor (evaporator) coil correction factor for air-cooled equipment - if (this->capacityDryAirCurveIndex > 0 && this->airSource && state.dataEnvrn->OutRelHum < RH90) { // above 90% RH yields full capacity - Real64 dryCorrectionFactor = std::min(1.0, Curve::CurveValue(state, this->capacityDryAirCurveIndex, state.dataEnvrn->OutDryBulbTemp)); - if (state.dataEnvrn->OutRelHum <= RH60) { - // dry heating capacity correction factor is a function of outdoor dry-bulb temperature - availableCapacity *= dryCorrectionFactor; - } else { - // interpolation of heating capacity between wet and dry is based on outdoor relative humidity over 60%-90% range - Real64 semiDryFactor = dryCorrectionFactor + (1.0 - dryCorrectionFactor) * (1.0 - ((RH90 - state.dataEnvrn->OutRelHum) / rangeRH)); - availableCapacity *= semiDryFactor; - } - } - - if (availableCapacity > 0) { - partLoadRatio = std::clamp(std::abs(currentLoad) / availableCapacity, 0.0, 1.0); - } - - if (this->minSupplyWaterTempCurveIndex > 0) { - Real64 minWaterTemp = Curve::CurveValue(state, this->minSupplyWaterTempCurveIndex, state.dataEnvrn->OutDryBulbTemp); - if (loadSideOutletSetpointTemp < minWaterTemp) { - loadSideOutletSetpointTemp = originalLoadSideOutletSPTemp + (1.0 - partLoadRatio) * (minWaterTemp - originalLoadSideOutletSPTemp); - waterTempExceeded = true; - } - } - if (this->maxSupplyWaterTempCurveIndex > 0) { - Real64 maxWaterTemp = Curve::CurveValue(state, this->maxSupplyWaterTempCurveIndex, state.dataEnvrn->OutDryBulbTemp); - if (loadSideOutletSetpointTemp > maxWaterTemp) { - loadSideOutletSetpointTemp = maxWaterTemp + (1.0 - partLoadRatio) * (originalLoadSideOutletSPTemp - maxWaterTemp); - waterTempExceeded = true; - } - } - if (this->heatRecoveryHeatPump) { - // check to see if souce side outlet temp exceeds limit and reduce PLR if necessary - auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); - Real64 const CpSrc = FluidProperties::GetSpecificHeatGlycol( - state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "PLHPEIR::simulate()"); - Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; - Real64 const tempSourceOutletTemp = - this->calcSourceOutletTemp(this->sourceSideInletTemp, (availableCapacity * partLoadRatio) / sourceMCp); - if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating && tempSourceOutletTemp < this->minSourceTempLimit) { - partLoadRatio *= (this->sourceSideInletTemp - this->minSourceTempLimit) / (this->sourceSideInletTemp - tempSourceOutletTemp); - } else if (tempSourceOutletTemp > this->maxSourceTempLimit) { - partLoadRatio *= (this->maxSourceTempLimit - this->sourceSideInletTemp) / (tempSourceOutletTemp - this->sourceSideInletTemp); - } - } - if (!waterTempExceeded) { - break; - } - } + this->getAvailableCapacity(state, currentLoad, availableCapacity, partLoadRatio); Real64 cyclingRatio = 1.0; Real64 operatingPLR = partLoadRatio; @@ -424,6 +359,7 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo // Init defrost power adjustment factors Real64 InputPowerMultiplier = 1.0; this->doDefrost(state, operatingPLR, availableCapacity, InputPowerMultiplier); + Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; this->defrostEnergy = this->defrostEnergyRate * reportingInterval; // evaluate the actual current operating load side heat transfer rate @@ -447,6 +383,9 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); // check curves value + Real64 capacityModifierFuncTemp = 1.0; + // get setpoint on the load side outlet + Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state); this->doCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp, eirModifierFuncTemp, eirModifierFuncPLR); this->powerUsage = @@ -485,15 +424,6 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLoad) { - Real64 constexpr RH90 = 90.0; - Real64 constexpr RH60 = 60.0; - Real64 constexpr rangeRH = 30.0; - Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; - - // get setpoint on the load side outlet - Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state); - Real64 originalLoadSideOutletSPTemp = loadSideOutletSetpointTemp; - // add free cooling at some point, compressor is off during free cooling, temp limits restrict free cooling range Real64 capacityModifierFuncTemp = 1.0; @@ -501,49 +431,7 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo Real64 partLoadRatio = 0.0; bool waterTempExceeded = false; - // evaluate capacity modifier curve and determine load side heat transfer - // any adjustment to outlet water temp set point requires some form of iteration - for (int loop = 0; loop < 2; ++loop) { - capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, this->sourceSideInletTemp); - - availableCapacity = this->referenceCapacity * capacityModifierFuncTemp; - - // apply heating mode dry outdoor (evaporator) coil correction factor for air-cooled equipment - if (this->capacityDryAirCurveIndex > 0 && this->airSource && state.dataEnvrn->OutRelHum < RH90) { // above 90% RH yields full capacity - Real64 dryCorrectionFactor = std::min(1.0, Curve::CurveValue(state, this->capacityDryAirCurveIndex, state.dataEnvrn->OutDryBulbTemp)); - if (state.dataEnvrn->OutRelHum <= RH60) { - // dry heating capacity correction factor is a function of outdoor dry-bulb temperature - availableCapacity *= dryCorrectionFactor; - } else { - // interpolation of heating capacity between wet and dry is based on outdoor relative humidity over 60%-90% range - Real64 semiDryFactor = dryCorrectionFactor + (1.0 - dryCorrectionFactor) * (1.0 - ((RH90 - state.dataEnvrn->OutRelHum) / rangeRH)); - availableCapacity *= semiDryFactor; - } - } - - if (availableCapacity > 0) { - partLoadRatio = std::clamp(std::abs(currentLoad) / availableCapacity, 0.0, 1.0); - } - - if (this->minSupplyWaterTempCurveIndex > 0) { - Real64 minWaterTemp = Curve::CurveValue(state, this->minSupplyWaterTempCurveIndex, state.dataEnvrn->OutDryBulbTemp); - if (loadSideOutletSetpointTemp < minWaterTemp) { - loadSideOutletSetpointTemp = originalLoadSideOutletSPTemp + (1.0 - partLoadRatio) * (minWaterTemp - originalLoadSideOutletSPTemp); - waterTempExceeded = true; - } - } - if (this->maxSupplyWaterTempCurveIndex > 0) { - Real64 maxWaterTemp = Curve::CurveValue(state, this->maxSupplyWaterTempCurveIndex, state.dataEnvrn->OutDryBulbTemp); - if (loadSideOutletSetpointTemp > maxWaterTemp) { - loadSideOutletSetpointTemp = maxWaterTemp + (1.0 - partLoadRatio) * (originalLoadSideOutletSPTemp - maxWaterTemp); - waterTempExceeded = true; - } - } - if (!waterTempExceeded) { - break; - } - } - + this->getAvailableCapacity(state, currentLoad, availableCapacity, partLoadRatio); Real64 cyclingRatio = 1.0; Real64 operatingPLR = partLoadRatio; if (partLoadRatio < this->minimumPLR) { @@ -557,6 +445,7 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo // do defrost calculation if applicable // Init defrost power adjustment factors Real64 InputPowerMultiplier = 1.0; + Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; this->doDefrost(state, operatingPLR, availableCapacity, InputPowerMultiplier); this->defrostEnergy = this->defrostEnergyRate * reportingInterval; @@ -581,6 +470,8 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); // check curves value + // get setpoint on the load side outlet + Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state); this->doCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp, eirModifierFuncTemp, eirModifierFuncPLR); this->powerUsage = @@ -599,6 +490,83 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo } +void EIRPlantLoopHeatPump::getAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio) +{ + + Real64 constexpr RH90 = 90.0; + Real64 constexpr RH60 = 60.0; + Real64 constexpr rangeRH = 30.0; + // get setpoint on the load side outlet + Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state); + Real64 originalLoadSideOutletSPTemp = loadSideOutletSetpointTemp; + + // add free cooling at some point, compressor is off during free cooling, temp limits restrict free cooling range + + Real64 capacityModifierFuncTemp = 1.0; + bool waterTempExceeded = false; + + // evaluate capacity modifier curve and determine load side heat transfer + // any adjustment to outlet water temp set point requires some form of iteration + for (int loop = 0; loop < 2; ++loop) { + capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, this->sourceSideInletTemp); + + availableCapacity = this->referenceCapacity * capacityModifierFuncTemp; + + // apply heating mode dry outdoor (evaporator) coil correction factor for air-cooled equipment + if (this->capacityDryAirCurveIndex > 0 && this->airSource && state.dataEnvrn->OutRelHum < RH90) { // above 90% RH yields full capacity + Real64 dryCorrectionFactor = std::min(1.0, Curve::CurveValue(state, this->capacityDryAirCurveIndex, state.dataEnvrn->OutDryBulbTemp)); + if (state.dataEnvrn->OutRelHum <= RH60) { + // dry heating capacity correction factor is a function of outdoor dry-bulb temperature + availableCapacity *= dryCorrectionFactor; + } else { + // interpolation of heating capacity between wet and dry is based on outdoor relative humidity over 60%-90% range + Real64 semiDryFactor = dryCorrectionFactor + (1.0 - dryCorrectionFactor) * (1.0 - ((RH90 - state.dataEnvrn->OutRelHum) / rangeRH)); + availableCapacity *= semiDryFactor; + } + } + + if (availableCapacity > 0) { + partLoadRatio = std::clamp(std::abs(currentLoad) / availableCapacity, 0.0, 1.0); + } + + if (this->minSupplyWaterTempCurveIndex > 0) { + Real64 minWaterTemp = Curve::CurveValue(state, this->minSupplyWaterTempCurveIndex, state.dataEnvrn->OutDryBulbTemp); + if (loadSideOutletSetpointTemp < minWaterTemp) { + loadSideOutletSetpointTemp = originalLoadSideOutletSPTemp + (1.0 - partLoadRatio) * (minWaterTemp - originalLoadSideOutletSPTemp); + waterTempExceeded = true; + } + } + if (this->maxSupplyWaterTempCurveIndex > 0) { + Real64 maxWaterTemp = Curve::CurveValue(state, this->maxSupplyWaterTempCurveIndex, state.dataEnvrn->OutDryBulbTemp); + if (loadSideOutletSetpointTemp > maxWaterTemp) { + loadSideOutletSetpointTemp = maxWaterTemp + (1.0 - partLoadRatio) * (originalLoadSideOutletSPTemp - maxWaterTemp); + waterTempExceeded = true; + } + } + if (this->heatRecoveryHeatPump) { + // check to see if souce side outlet temp exceeds limit and reduce PLR if necessary + auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); + Real64 const CpSrc = FluidProperties::GetSpecificHeatGlycol( + state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "PLHPEIR::simulate()"); + Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; + Real64 const tempSourceOutletTemp = + this->calcSourceOutletTemp(this->sourceSideInletTemp, (availableCapacity * partLoadRatio) / sourceMCp); + if (this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRHeating && tempSourceOutletTemp < this->minSourceTempLimit) { + partLoadRatio *= (this->sourceSideInletTemp - this->minSourceTempLimit) / (this->sourceSideInletTemp - tempSourceOutletTemp); + } else if (tempSourceOutletTemp > this->maxSourceTempLimit) { + partLoadRatio *= (this->maxSourceTempLimit - this->sourceSideInletTemp) / (tempSourceOutletTemp - this->sourceSideInletTemp); + } + } + if (!waterTempExceeded) { + break; + } + } + + Real64 eirFT_dummy = 1.0; + Real64 eirFPLR_dummy = 1.0; + this->doCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp, eirFT_dummy, eirFPLR_dummy); +} + void EIRPlantLoopHeatPump::doCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSetpointTemp, Real64 &capacityModifierFuncTemp, diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index bedd2538c3d..7ed3287b85e 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -233,6 +233,8 @@ namespace EIRPlantLoopHeatPumps { void doPhysicsASHP(EnergyPlusData &state, Real64 currentLoad); + void getAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio); + void sizeLoadSide(EnergyPlusData &state); void sizeSrcSideWSHP(EnergyPlusData &state); From a4504b0fe6d3fcc636426d258111cbfccb5cea04 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 12 Feb 2024 08:51:28 -0500 Subject: [PATCH 06/24] cleanup --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index b7ad344417b..1e48bf48946 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -637,7 +637,6 @@ void EIRPlantLoopHeatPump::doDefrost( { // Initializing defrost adjustment factors Real64 HeatingCapacityMultiplier = 1.0; - //Real64 InputPowerMultiplier = 1.0; // Check outdoor temperature to determine of defrost is active if (this->defrostAvailable && state.dataEnvrn->OutDryBulbTemp <= this->maxOutdoorTemperatureDefrost) { From 7cb3797cd8350ee64502fcd54fffa220f374ee11 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 12 Feb 2024 09:23:04 -0500 Subject: [PATCH 07/24] refactor Load Side Heat Transfer Calc --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 59 ++++++++++++-------------- src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 2 + 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 1e48bf48946..9719508aeaa 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -363,21 +363,7 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo this->defrostEnergy = this->defrostEnergyRate * reportingInterval; // evaluate the actual current operating load side heat transfer rate - auto &thisLoadPlantLoop = state.dataPlnt->PlantLoop(this->loadSidePlantLoc.loopNum); - Real64 CpLoad = FluidProperties::GetSpecificHeatGlycol(state, - thisLoadPlantLoop.FluidName, - state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp, - thisLoadPlantLoop.FluidIndex, - "PLHPEIR::simulate()"); - this->loadSideHeatTransfer = availableCapacity * operatingPLR; - this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval; - - // calculate load side outlet conditions - Real64 const loadMCp = this->loadSideMassFlowRate * CpLoad; - this->loadSideOutletTemp = this->calcLoadOutletTemp(this->loadSideInletTemp, this->loadSideHeatTransfer / loadMCp); - - // now what to do here if outlet water temp exceeds limit based on HW supply temp limit curves? - // currentLoad will be met and there should? be some adjustment based on outlet water temp limit? + doLoadSideHeatTransfer(state, availableCapacity, partLoadRatio); // calculate power usage from EIR curves Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); @@ -450,21 +436,7 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo this->defrostEnergy = this->defrostEnergyRate * reportingInterval; // evaluate the actual current operating load side heat transfer rate - auto &thisLoadPlantLoop = state.dataPlnt->PlantLoop(this->loadSidePlantLoc.loopNum); - Real64 CpLoad = FluidProperties::GetSpecificHeatGlycol(state, - thisLoadPlantLoop.FluidName, - state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp, - thisLoadPlantLoop.FluidIndex, - "PLHPEIR::simulate()"); - this->loadSideHeatTransfer = availableCapacity * operatingPLR; - this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval; - - // calculate load side outlet conditions - Real64 const loadMCp = this->loadSideMassFlowRate * CpLoad; - this->loadSideOutletTemp = this->calcLoadOutletTemp(this->loadSideInletTemp, this->loadSideHeatTransfer / loadMCp); - - // now what to do here if outlet water temp exceeds limit based on HW supply temp limit curves? - // currentLoad will be met and there should? be some adjustment based on outlet water temp limit? + doLoadSideHeatTransfer(state, availableCapacity, partLoadRatio); // calculate power usage from EIR curves Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); @@ -567,6 +539,30 @@ void EIRPlantLoopHeatPump::getAvailableCapacity(EnergyPlusData &state, Real64 co this->doCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp, eirFT_dummy, eirFPLR_dummy); } +void EIRPlantLoopHeatPump::doLoadSideCalculation(EnergyPlusData &state, Real64 const availableCapacity, Real64 const operatingPLR) +{ + Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; + + // evaluate the actual current operating load side heat transfer rate + auto &thisLoadPlantLoop = state.dataPlnt->PlantLoop(this->loadSidePlantLoc.loopNum); + Real64 CpLoad = FluidProperties::GetSpecificHeatGlycol(state, + thisLoadPlantLoop.FluidName, + state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp, + thisLoadPlantLoop.FluidIndex, + "PLHPEIR::simulate()"); + this->loadSideHeatTransfer = availableCapacity * operatingPLR; + this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval; + + // calculate load side outlet conditions + Real64 const loadMCp = this->loadSideMassFlowRate * CpLoad; + this->loadSideOutletTemp = this->calcLoadOutletTemp(this->loadSideInletTemp, this->loadSideHeatTransfer / loadMCp); + + // now what to do here if outlet water temp exceeds limit based on HW supply temp limit curves? + // currentLoad will be met and there should? be some adjustment based on outlet water temp limit? + +} + + void EIRPlantLoopHeatPump::doCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSetpointTemp, Real64 &capacityModifierFuncTemp, @@ -713,8 +709,7 @@ void EIRPlantLoopHeatPump::doDefrost( } availableCapacity *= HeatingCapacityMultiplier; } - -void EIRPlantLoopHeatPump::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation) + void EIRPlantLoopHeatPump::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation) { // This function does all one-time and begin-environment initialization std::string static const routineName = std::string("EIRPlantLoopHeatPump :") + __FUNCTION__; diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index 7ed3287b85e..26025bf3e00 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -235,6 +235,8 @@ namespace EIRPlantLoopHeatPumps { void getAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio); + void doLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity, Real64 const operatingPLR); + void sizeLoadSide(EnergyPlusData &state); void sizeSrcSideWSHP(EnergyPlusData &state); From 72373fb7753ff720f5417a63bc2b289cb8f06ee7 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 12 Feb 2024 09:47:27 -0500 Subject: [PATCH 08/24] refactor curve checks function --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 31 +++++++++----------------- src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 4 +++- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 9719508aeaa..574b1dfad2b 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -369,10 +369,7 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); // check curves value - Real64 capacityModifierFuncTemp = 1.0; - // get setpoint on the load side outlet - Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state); - this->doCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp, eirModifierFuncTemp, eirModifierFuncPLR); + this->eirModFTCurveCheck(state, eirModifierFuncTemp, eirModifierFuncPLR); this->powerUsage = (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * InputPowerMultiplier * this->cyclingRatio; @@ -412,7 +409,6 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo // add free cooling at some point, compressor is off during free cooling, temp limits restrict free cooling range - Real64 capacityModifierFuncTemp = 1.0; Real64 availableCapacity = this->referenceCapacity; Real64 partLoadRatio = 0.0; bool waterTempExceeded = false; @@ -442,9 +438,7 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); // check curves value - // get setpoint on the load side outlet - Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state); - this->doCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp, eirModifierFuncTemp, eirModifierFuncPLR); + this->eirModFTCurveCheck(state, eirModifierFuncTemp, eirModifierFuncPLR); this->powerUsage = (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * InputPowerMultiplier * this->cyclingRatio; @@ -533,13 +527,10 @@ void EIRPlantLoopHeatPump::getAvailableCapacity(EnergyPlusData &state, Real64 co break; } } - - Real64 eirFT_dummy = 1.0; - Real64 eirFPLR_dummy = 1.0; - this->doCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp, eirFT_dummy, eirFPLR_dummy); + this->capModFTCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp); } -void EIRPlantLoopHeatPump::doLoadSideCalculation(EnergyPlusData &state, Real64 const availableCapacity, Real64 const operatingPLR) +void EIRPlantLoopHeatPump::doLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity, Real64 const operatingPLR) { Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; @@ -562,12 +553,7 @@ void EIRPlantLoopHeatPump::doLoadSideCalculation(EnergyPlusData &state, Real64 c } - -void EIRPlantLoopHeatPump::doCurveCheck(EnergyPlusData &state, - const Real64 loadSideOutletSetpointTemp, - Real64 &capacityModifierFuncTemp, - Real64 &eirModifierFuncTemp, - Real64 &eirModifierFuncPLR) +void EIRPlantLoopHeatPump::capModFTCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSetpointTemp, Real64 &capacityModifierFuncTemp) { if (capacityModifierFuncTemp < 0.0) { if (this->capModFTErrorIndex == 0) { @@ -589,7 +575,11 @@ void EIRPlantLoopHeatPump::doCurveCheck(EnergyPlusData &state, capacityModifierFuncTemp); capacityModifierFuncTemp = 0.0; } +} + +void EIRPlantLoopHeatPump::eirModFTCurveCheck(EnergyPlusData &state, Real64 &eirModifierFuncTemp, Real64 &eirModifierFuncPLR) +{ if (eirModifierFuncTemp < 0.0) { if (this->eirModFTErrorIndex == 0) { ShowSevereMessage(state, format("{} \"{}\":", DataPlant::PlantEquipTypeNames[static_cast(this->EIRHPType)], this->name)); @@ -628,8 +618,7 @@ void EIRPlantLoopHeatPump::doCurveCheck(EnergyPlusData &state, } } -void EIRPlantLoopHeatPump::doDefrost( - EnergyPlusData &state, const Real64 operatingPLR, Real64 &availableCapacity, Real64 &InputPowerMultiplier) +void EIRPlantLoopHeatPump::doDefrost(EnergyPlusData &state, const Real64 operatingPLR, Real64 &availableCapacity, Real64 &InputPowerMultiplier) { // Initializing defrost adjustment factors Real64 HeatingCapacityMultiplier = 1.0; diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index 26025bf3e00..baf964f51e9 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -244,8 +244,10 @@ namespace EIRPlantLoopHeatPumps { void sizeSrcSideASHP(EnergyPlusData &state); void doDefrost(EnergyPlusData &state, const Real64 operatingPLR, Real64 &AvailableCapacity, Real64 &InputPowerMultiplier); + + void capModFTCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSPTemp, Real64 &capModFTemp); - void doCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSPTemp, Real64 &capModFTemp, Real64 &eirModFTemp, Real64 &eirModFPLR); + void eirModFTCurveCheck(EnergyPlusData &state, Real64 &eirModFTemp, Real64 &eirModFPLR); Real64 getLoadSideOutletSetPointTemp(EnergyPlusData &state) const; From 89b0cd90db1d3cee64ebbdd5a12ec8854415fd50 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 12 Feb 2024 11:13:44 -0500 Subject: [PATCH 09/24] refactor power usage calculation --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 42 ++++++++++++++------------ src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 4 ++- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 574b1dfad2b..14298ac672b 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -363,17 +363,10 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo this->defrostEnergy = this->defrostEnergyRate * reportingInterval; // evaluate the actual current operating load side heat transfer rate - doLoadSideHeatTransfer(state, availableCapacity, partLoadRatio); + calcLoadSideHeatTransfer(state, availableCapacity, partLoadRatio); // calculate power usage from EIR curves - Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); - Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); - // check curves value - this->eirModFTCurveCheck(state, eirModifierFuncTemp, eirModifierFuncPLR); - - this->powerUsage = - (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * InputPowerMultiplier * this->cyclingRatio; - this->powerEnergy = this->powerUsage * reportingInterval; + calcPowerUsage(state, InputPowerMultiplier); // energy balance on heat pump this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); @@ -432,17 +425,10 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo this->defrostEnergy = this->defrostEnergyRate * reportingInterval; // evaluate the actual current operating load side heat transfer rate - doLoadSideHeatTransfer(state, availableCapacity, partLoadRatio); + calcLoadSideHeatTransfer(state, availableCapacity, partLoadRatio); - // calculate power usage from EIR curves - Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); - Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); - // check curves value - this->eirModFTCurveCheck(state, eirModifierFuncTemp, eirModifierFuncPLR); - - this->powerUsage = - (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * InputPowerMultiplier * this->cyclingRatio; - this->powerEnergy = this->powerUsage * reportingInterval; + // calculate power usage from EIR curves + calcPowerUsage(state, InputPowerMultiplier); // energy balance on heat pump this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); @@ -530,7 +516,7 @@ void EIRPlantLoopHeatPump::getAvailableCapacity(EnergyPlusData &state, Real64 co this->capModFTCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp); } -void EIRPlantLoopHeatPump::doLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity, Real64 const operatingPLR) +void EIRPlantLoopHeatPump::calcLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity, Real64 const operatingPLR) { Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; @@ -553,6 +539,22 @@ void EIRPlantLoopHeatPump::doLoadSideHeatTransfer(EnergyPlusData &state, Real64 } +void EIRPlantLoopHeatPump::calcPowerUsage(EnergyPlusData &state, Real64 const InputPowerMultiplier) +{ + Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; + + // calculate power usage from EIR curves + Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); + Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); + // check curves value + this->eirModFTCurveCheck(state, eirModifierFuncTemp, eirModifierFuncPLR); + + this->powerUsage = + (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * InputPowerMultiplier * this->cyclingRatio; + this->powerEnergy = this->powerUsage * reportingInterval; + +} + void EIRPlantLoopHeatPump::capModFTCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSetpointTemp, Real64 &capacityModifierFuncTemp) { if (capacityModifierFuncTemp < 0.0) { diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index baf964f51e9..e77ef322834 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -235,7 +235,9 @@ namespace EIRPlantLoopHeatPumps { void getAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio); - void doLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity, Real64 const operatingPLR); + void calcLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity, Real64 const operatingPLR); + + void calcPowerUsage(EnergyPlusData &state, Real64 const InputPowerMultiplier); void sizeLoadSide(EnergyPlusData &state); From 5fbf44cf2998994006301496bd19fafd2de9ff79 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 12 Feb 2024 11:43:12 -0500 Subject: [PATCH 10/24] refactor source side heat transfer calc --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 90 ++++++++++++++++---------- src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 4 ++ 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 14298ac672b..07b279e4905 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -368,32 +368,9 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo // calculate power usage from EIR curves calcPowerUsage(state, InputPowerMultiplier); - // energy balance on heat pump - this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); - this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; - - // calculate source side outlet conditions - auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); - Real64 const CpSrc = FluidProperties::GetSpecificHeatGlycol( - state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "PLHPEIR::simulate()"); - // this->sourceSideCp = CpSrc; // debuging variable - Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; - this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); - - if (this->waterSource && abs(this->sourceSideOutletTemp - this->sourceSideInletTemp) > 100.0) { // whoaa out of range happenings on water loop - // - // TODO setup recurring error warning? - // lets do something different than fatal the simulation - if ((this->sourceSideMassFlowRate / this->sourceSideDesignMassFlowRate) < 0.01) { // current source side flow is 1% of design max - // just send it all to skin losses and leave the fluid temperature alone - this->sourceSideOutletTemp = this->sourceSideInletTemp; - } else if (this->sourceSideOutletTemp > this->sourceSideInletTemp) { - this->sourceSideOutletTemp = this->sourceSideInletTemp + 100.0; // cap it at 100C delta + // evaluate the source side heat transfer rate + calcSourceSideHeatTransferWSHP(state); - } else if (this->sourceSideOutletTemp < this->sourceSideInletTemp) { - this->sourceSideOutletTemp = this->sourceSideInletTemp - 100.0; // cap it at 100C delta - } - } } @@ -430,15 +407,8 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo // calculate power usage from EIR curves calcPowerUsage(state, InputPowerMultiplier); - // energy balance on heat pump - this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); - this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; - - // calculate source side outlet conditions - Real64 const CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat); - // this->sourceSideCp = CpSrc; // debuging variable - Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; - this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); + // evaluate the source side heat transfer rate + calcSourceSideHeatTransferASHP(state); } @@ -555,6 +525,58 @@ void EIRPlantLoopHeatPump::calcPowerUsage(EnergyPlusData &state, Real64 const In } +void EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP(EnergyPlusData &state) +{ + Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; + + // energy balance on heat pump + this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); + this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; + + // calculate source side outlet conditions + Real64 CpSrc; + auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); + CpSrc = FluidProperties::GetSpecificHeatGlycol( + state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "PLHPEIR::simulate()"); + + // this->sourceSideCp = CpSrc; // debuging variable + Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; + this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); + + if (this->waterSource && abs(this->sourceSideOutletTemp - this->sourceSideInletTemp) > 100.0) { // whoaa out of range happenings on water loop + // + // TODO setup recurring error warning? + // lets do something different than fatal the simulation + if ((this->sourceSideMassFlowRate / this->sourceSideDesignMassFlowRate) < 0.01) { // current source side flow is 1% of design max + // just send it all to skin losses and leave the fluid temperature alone + this->sourceSideOutletTemp = this->sourceSideInletTemp; + } else if (this->sourceSideOutletTemp > this->sourceSideInletTemp) { + this->sourceSideOutletTemp = this->sourceSideInletTemp + 100.0; // cap it at 100C delta + + } else if (this->sourceSideOutletTemp < this->sourceSideInletTemp) { + this->sourceSideOutletTemp = this->sourceSideInletTemp - 100.0; // cap it at 100C delta + } + } + +} + +void EIRPlantLoopHeatPump::calcSourceSideHeatTransferASHP(EnergyPlusData &state) +{ + Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; + + // energy balance on heat pump + this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); + this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; + + // calculate source side outlet conditions + Real64 CpSrc; + CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat); + Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; + this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); + +} + + void EIRPlantLoopHeatPump::capModFTCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSetpointTemp, Real64 &capacityModifierFuncTemp) { if (capacityModifierFuncTemp < 0.0) { diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index e77ef322834..1df2968689b 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -239,6 +239,10 @@ namespace EIRPlantLoopHeatPumps { void calcPowerUsage(EnergyPlusData &state, Real64 const InputPowerMultiplier); + void calcSourceSideHeatTransferWSHP(EnergyPlusData &state); + + void calcSourceSideHeatTransferASHP(EnergyPlusData &state); + void sizeLoadSide(EnergyPlusData &state); void sizeSrcSideWSHP(EnergyPlusData &state); From 68fa27da3d57d159771f379bdc59063ce1cfc803 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 12 Feb 2024 14:19:24 -0500 Subject: [PATCH 11/24] refactor reporting variables update --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 81 ++++++++----------- src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 2 + .../unit/PlantLoopHeatPumpEIR.unit.cc | 1 + 3 files changed, 37 insertions(+), 47 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 07b279e4905..618ab413c54 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -128,22 +128,17 @@ void EIRPlantLoopHeatPump::simulate( state.dataPlnt->PlantLoop(this->loadSidePlantLoc.loopNum).FluidIndex, "EIRPlantLoopHeatPump::simulate"); Real64 controlLoad = this->loadSideMassFlowRate * CurSpecHeat * (leavingSetpoint - loadSideInletTemp); - + this->doPhysics(state, controlLoad); - } else { + } else { this->doPhysics(state, CurLoad); } } else { this->resetReportingVariables(); } - // update nodes - PlantUtilities::SafeCopyPlantNode(state, this->loadSideNodes.inlet, this->loadSideNodes.outlet); - state.dataLoopNodes->Node(this->loadSideNodes.outlet).Temp = this->loadSideOutletTemp; - if (this->waterSource) { - PlantUtilities::SafeCopyPlantNode(state, this->sourceSideNodes.inlet, this->sourceSideNodes.outlet); - } - state.dataLoopNodes->Node(this->sourceSideNodes.outlet).Temp = this->sourceSideOutletTemp; + // update report variables and nodes + this->report(state); } Real64 EIRPlantLoopHeatPump::getLoadSideOutletSetPointTemp(EnergyPlusData &state) const @@ -314,7 +309,6 @@ void EIRPlantLoopHeatPump::setOperatingFlowRatesASHP(EnergyPlusData &state, bool } } - void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) { @@ -332,7 +326,6 @@ void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) this->doPhysicsASHP(state, currentLoad); } } - void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLoad) { @@ -355,25 +348,21 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo this->partLoadRatio = partLoadRatio; this->cyclingRatio = cyclingRatio; - // do defrost calculation if applicable - // Init defrost power adjustment factors + //// do defrost calculation if applicable + //// Init defrost power adjustment factors Real64 InputPowerMultiplier = 1.0; - this->doDefrost(state, operatingPLR, availableCapacity, InputPowerMultiplier); - Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; - this->defrostEnergy = this->defrostEnergyRate * reportingInterval; + //this->doDefrost(state, operatingPLR, availableCapacity, InputPowerMultiplier); // evaluate the actual current operating load side heat transfer rate - calcLoadSideHeatTransfer(state, availableCapacity, partLoadRatio); + calcLoadSideHeatTransfer(state, availableCapacity, operatingPLR); // calculate power usage from EIR curves calcPowerUsage(state, InputPowerMultiplier); // evaluate the source side heat transfer rate calcSourceSideHeatTransferWSHP(state); - } - void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLoad) { @@ -397,19 +386,16 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo // do defrost calculation if applicable // Init defrost power adjustment factors Real64 InputPowerMultiplier = 1.0; - Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; this->doDefrost(state, operatingPLR, availableCapacity, InputPowerMultiplier); - this->defrostEnergy = this->defrostEnergyRate * reportingInterval; // evaluate the actual current operating load side heat transfer rate - calcLoadSideHeatTransfer(state, availableCapacity, partLoadRatio); + calcLoadSideHeatTransfer(state, availableCapacity, operatingPLR); // calculate power usage from EIR curves calcPowerUsage(state, InputPowerMultiplier); // evaluate the source side heat transfer rate calcSourceSideHeatTransferASHP(state); - } void EIRPlantLoopHeatPump::getAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio) @@ -429,8 +415,8 @@ void EIRPlantLoopHeatPump::getAvailableCapacity(EnergyPlusData &state, Real64 co // evaluate capacity modifier curve and determine load side heat transfer // any adjustment to outlet water temp set point requires some form of iteration - for (int loop = 0; loop < 2; ++loop) { - capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, this->sourceSideInletTemp); + for (int loop = 0; loop < 2; ++loop) { + capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, this->sourceSideInletTemp); availableCapacity = this->referenceCapacity * capacityModifierFuncTemp; @@ -488,8 +474,6 @@ void EIRPlantLoopHeatPump::getAvailableCapacity(EnergyPlusData &state, Real64 co void EIRPlantLoopHeatPump::calcLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity, Real64 const operatingPLR) { - Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; - // evaluate the actual current operating load side heat transfer rate auto &thisLoadPlantLoop = state.dataPlnt->PlantLoop(this->loadSidePlantLoc.loopNum); Real64 CpLoad = FluidProperties::GetSpecificHeatGlycol(state, @@ -498,7 +482,6 @@ void EIRPlantLoopHeatPump::calcLoadSideHeatTransfer(EnergyPlusData &state, Real6 thisLoadPlantLoop.FluidIndex, "PLHPEIR::simulate()"); this->loadSideHeatTransfer = availableCapacity * operatingPLR; - this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval; // calculate load side outlet conditions Real64 const loadMCp = this->loadSideMassFlowRate * CpLoad; @@ -506,33 +489,25 @@ void EIRPlantLoopHeatPump::calcLoadSideHeatTransfer(EnergyPlusData &state, Real6 // now what to do here if outlet water temp exceeds limit based on HW supply temp limit curves? // currentLoad will be met and there should? be some adjustment based on outlet water temp limit? - } void EIRPlantLoopHeatPump::calcPowerUsage(EnergyPlusData &state, Real64 const InputPowerMultiplier) { - Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; // calculate power usage from EIR curves Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); // check curves value this->eirModFTCurveCheck(state, eirModifierFuncTemp, eirModifierFuncPLR); - this->powerUsage = (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * InputPowerMultiplier * this->cyclingRatio; - this->powerEnergy = this->powerUsage * reportingInterval; - } void EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP(EnergyPlusData &state) { - Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; // energy balance on heat pump this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); - this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; - // calculate source side outlet conditions Real64 CpSrc; auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); @@ -557,26 +532,19 @@ void EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP(EnergyPlusData &state) this->sourceSideOutletTemp = this->sourceSideInletTemp - 100.0; // cap it at 100C delta } } - } void EIRPlantLoopHeatPump::calcSourceSideHeatTransferASHP(EnergyPlusData &state) { - Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; - // energy balance on heat pump this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); - this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; - // calculate source side outlet conditions Real64 CpSrc; CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat); Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); - } - void EIRPlantLoopHeatPump::capModFTCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSetpointTemp, Real64 &capacityModifierFuncTemp) { if (capacityModifierFuncTemp < 0.0) { @@ -601,7 +569,6 @@ void EIRPlantLoopHeatPump::capModFTCurveCheck(EnergyPlusData &state, const Real6 } } - void EIRPlantLoopHeatPump::eirModFTCurveCheck(EnergyPlusData &state, Real64 &eirModifierFuncTemp, Real64 &eirModifierFuncPLR) { if (eirModifierFuncTemp < 0.0) { @@ -647,7 +614,7 @@ void EIRPlantLoopHeatPump::doDefrost(EnergyPlusData &state, const Real64 operati // Initializing defrost adjustment factors Real64 HeatingCapacityMultiplier = 1.0; - // Check outdoor temperature to determine of defrost is active + // Check outdoor temperature to determine of defrost is active if (this->defrostAvailable && state.dataEnvrn->OutDryBulbTemp <= this->maxOutdoorTemperatureDefrost) { // Calculate defrost adjustment factors depending on defrost control type // Calculate delta w through outdoor coil by assuming a coil temp of 0.82*DBT-9.7(F) per DOE2.1E @@ -719,10 +686,11 @@ void EIRPlantLoopHeatPump::doDefrost(EnergyPlusData &state, const Real64 operati this->defrostEnergyRate = 0.0; this->loadDueToDefrost = 0.0; this->fractionalDefrostTime = 0.0; - } + } availableCapacity *= HeatingCapacityMultiplier; } - void EIRPlantLoopHeatPump::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation) + +void EIRPlantLoopHeatPump::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation) { // This function does all one-time and begin-environment initialization std::string static const routineName = std::string("EIRPlantLoopHeatPump :") + __FUNCTION__; @@ -1940,6 +1908,25 @@ void EIRPlantLoopHeatPump::oneTimeInit(EnergyPlusData &state) } } +void EIRPlantLoopHeatPump::report(EnergyPlusData &state) +{ + + Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; + + this->defrostEnergy = this->defrostEnergyRate * reportingInterval; + this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval; + this->powerEnergy = this->powerUsage * reportingInterval; + this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; + + // update nodes + PlantUtilities::SafeCopyPlantNode(state, this->loadSideNodes.inlet, this->loadSideNodes.outlet); + state.dataLoopNodes->Node(this->loadSideNodes.outlet).Temp = this->loadSideOutletTemp; + if (this->waterSource) { + PlantUtilities::SafeCopyPlantNode(state, this->sourceSideNodes.inlet, this->sourceSideNodes.outlet); + } + state.dataLoopNodes->Node(this->sourceSideNodes.outlet).Temp = this->sourceSideOutletTemp; +} + // From here on, the Fuel Fired Heat Pump module EIRFuelFiredHeatPump // Enum string definitions static constexpr std::array(EIRFuelFiredHeatPump::OATempCurveVar::Num)> OATempCurveVarNamesUC = {"DRYBULB", diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index 1df2968689b..5e3142c39f6 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -243,6 +243,8 @@ namespace EIRPlantLoopHeatPumps { void calcSourceSideHeatTransferASHP(EnergyPlusData &state); + void report(EnergyPlusData &state); + void sizeLoadSide(EnergyPlusData &state); void sizeSrcSideWSHP(EnergyPlusData &state); diff --git a/tst/EnergyPlus/unit/PlantLoopHeatPumpEIR.unit.cc b/tst/EnergyPlus/unit/PlantLoopHeatPumpEIR.unit.cc index dd721d5b3dd..7432376024c 100644 --- a/tst/EnergyPlus/unit/PlantLoopHeatPumpEIR.unit.cc +++ b/tst/EnergyPlus/unit/PlantLoopHeatPumpEIR.unit.cc @@ -3496,6 +3496,7 @@ TEST_F(EnergyPlusFixture, Test_Curve_Negative_Energy) thisCoolingPLHP->loadSideInletTemp = 20; thisCoolingPLHP->sourceSideInletTemp = 20; thisCoolingPLHP->doPhysics(*state, curLoad); + thisCoolingPLHP->reportPLHPEIR(*state); // Power and energy are now zero since the curve is reset with zero values EXPECT_NEAR(thisCoolingPLHP->powerUsage, 0.000, 1e-3); From fa89743b1f1ae19b988c5d3075c3a47fdacc46b1 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 12 Feb 2024 14:53:23 -0500 Subject: [PATCH 12/24] fixed unit test failed due to refactor --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 4 ++-- src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 2 +- tst/EnergyPlus/unit/PlantLoopHeatPumpEIR.unit.cc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 618ab413c54..f65f28a3bd8 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -498,7 +498,7 @@ void EIRPlantLoopHeatPump::calcPowerUsage(EnergyPlusData &state, Real64 const In Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); // check curves value - this->eirModFTCurveCheck(state, eirModifierFuncTemp, eirModifierFuncPLR); + this->eirModCurveCheck(state, eirModifierFuncTemp, eirModifierFuncPLR); this->powerUsage = (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * InputPowerMultiplier * this->cyclingRatio; } @@ -569,7 +569,7 @@ void EIRPlantLoopHeatPump::capModFTCurveCheck(EnergyPlusData &state, const Real6 } } -void EIRPlantLoopHeatPump::eirModFTCurveCheck(EnergyPlusData &state, Real64 &eirModifierFuncTemp, Real64 &eirModifierFuncPLR) +void EIRPlantLoopHeatPump::eirModCurveCheck(EnergyPlusData &state, Real64 &eirModifierFuncTemp, Real64 &eirModifierFuncPLR) { if (eirModifierFuncTemp < 0.0) { if (this->eirModFTErrorIndex == 0) { diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index 5e3142c39f6..0f6de0aeaf8 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -255,7 +255,7 @@ namespace EIRPlantLoopHeatPumps { void capModFTCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSPTemp, Real64 &capModFTemp); - void eirModFTCurveCheck(EnergyPlusData &state, Real64 &eirModFTemp, Real64 &eirModFPLR); + void eirModCurveCheck(EnergyPlusData &state, Real64 &eirModFTemp, Real64 &eirModFPLR); Real64 getLoadSideOutletSetPointTemp(EnergyPlusData &state) const; diff --git a/tst/EnergyPlus/unit/PlantLoopHeatPumpEIR.unit.cc b/tst/EnergyPlus/unit/PlantLoopHeatPumpEIR.unit.cc index 7432376024c..7f45c48475b 100644 --- a/tst/EnergyPlus/unit/PlantLoopHeatPumpEIR.unit.cc +++ b/tst/EnergyPlus/unit/PlantLoopHeatPumpEIR.unit.cc @@ -3496,7 +3496,7 @@ TEST_F(EnergyPlusFixture, Test_Curve_Negative_Energy) thisCoolingPLHP->loadSideInletTemp = 20; thisCoolingPLHP->sourceSideInletTemp = 20; thisCoolingPLHP->doPhysics(*state, curLoad); - thisCoolingPLHP->reportPLHPEIR(*state); + thisCoolingPLHP->report(*state); // Power and energy are now zero since the curve is reset with zero values EXPECT_NEAR(thisCoolingPLHP->powerUsage, 0.000, 1e-3); From bd49ca0f1bd3a78a0bfbe4ca92bfd8bbdabfb588 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 12 Feb 2024 15:31:06 -0500 Subject: [PATCH 13/24] cleanup --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index f65f28a3bd8..3e83a0ae153 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -348,14 +348,11 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo this->partLoadRatio = partLoadRatio; this->cyclingRatio = cyclingRatio; - //// do defrost calculation if applicable - //// Init defrost power adjustment factors - Real64 InputPowerMultiplier = 1.0; - //this->doDefrost(state, operatingPLR, availableCapacity, InputPowerMultiplier); - // evaluate the actual current operating load side heat transfer rate calcLoadSideHeatTransfer(state, availableCapacity, operatingPLR); + // no do defrost calculation for WSHP + Real64 InputPowerMultiplier = 1.0; // calculate power usage from EIR curves calcPowerUsage(state, InputPowerMultiplier); From 237b4c68c0d569b84913938df830d67fbcf69b03 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 12 Feb 2024 16:29:49 -0500 Subject: [PATCH 14/24] more cleanup --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index f9121ebab00..cac8df14881 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -477,7 +477,7 @@ void EIRPlantLoopHeatPump::calcLoadSideHeatTransfer(EnergyPlusData &state, Real6 thisLoadPlantLoop.FluidName, state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp, thisLoadPlantLoop.FluidIndex, - "PLHPEIR::simulate()"); + "EIRPlantLoopHeatPump::calcLoadSideHeatTransfer()"); this->loadSideHeatTransfer = availableCapacity * operatingPLR; // calculate load side outlet conditions @@ -509,7 +509,7 @@ void EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP(EnergyPlusData &state) Real64 CpSrc; auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); CpSrc = FluidProperties::GetSpecificHeatGlycol( - state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "PLHPEIR::simulate()"); + state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP()"); // this->sourceSideCp = CpSrc; // debuging variable Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; From 98949aa24dfe9aaf85a8d7c96a4b93a0d4de0a62 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Tue, 13 Feb 2024 09:46:46 -0500 Subject: [PATCH 15/24] refactor PLR and Cycling Ratio Reset --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 47 ++++++++++++-------------- src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 6 ++-- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index cac8df14881..77dcf385a34 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -334,22 +334,12 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo Real64 availableCapacity = this->referenceCapacity; Real64 partLoadRatio = 0.0; - bool waterTempExceeded = false; this->getAvailableCapacity(state, currentLoad, availableCapacity, partLoadRatio); - - Real64 cyclingRatio = 1.0; - Real64 operatingPLR = partLoadRatio; - if (partLoadRatio < this->minimumPLR) { - cyclingRatio = partLoadRatio / this->minimumPLR; - partLoadRatio = this->minimumPLR; - operatingPLR = partLoadRatio * cyclingRatio; - } - this->partLoadRatio = partLoadRatio; - this->cyclingRatio = cyclingRatio; + this->setPartLoadAndCyclingRatio(state, partLoadRatio); // evaluate the actual current operating load side heat transfer rate - calcLoadSideHeatTransfer(state, availableCapacity, operatingPLR); + calcLoadSideHeatTransfer(state, availableCapacity); // no do defrost calculation for WSHP Real64 InputPowerMultiplier = 1.0; @@ -367,26 +357,17 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo Real64 availableCapacity = this->referenceCapacity; Real64 partLoadRatio = 0.0; - bool waterTempExceeded = false; this->getAvailableCapacity(state, currentLoad, availableCapacity, partLoadRatio); - Real64 cyclingRatio = 1.0; - Real64 operatingPLR = partLoadRatio; - if (partLoadRatio < this->minimumPLR) { - cyclingRatio = partLoadRatio / this->minimumPLR; - partLoadRatio = this->minimumPLR; - operatingPLR = partLoadRatio * cyclingRatio; - } - this->partLoadRatio = partLoadRatio; - this->cyclingRatio = cyclingRatio; + this->setPartLoadAndCyclingRatio(state, partLoadRatio); // do defrost calculation if applicable // Init defrost power adjustment factors Real64 InputPowerMultiplier = 1.0; - this->doDefrost(state, operatingPLR, availableCapacity, InputPowerMultiplier); + this->doDefrost(state, availableCapacity, InputPowerMultiplier); // evaluate the actual current operating load side heat transfer rate - calcLoadSideHeatTransfer(state, availableCapacity, operatingPLR); + calcLoadSideHeatTransfer(state, availableCapacity); // calculate power usage from EIR curves calcPowerUsage(state, InputPowerMultiplier); @@ -469,7 +450,18 @@ void EIRPlantLoopHeatPump::getAvailableCapacity(EnergyPlusData &state, Real64 co this->capModFTCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp); } -void EIRPlantLoopHeatPump::calcLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity, Real64 const operatingPLR) +void EIRPlantLoopHeatPump::setPartLoadAndCyclingRatio(EnergyPlusData& state, Real64& partLoadRatio) +{ + Real64 cyclingRatio = 1.0; + if (partLoadRatio < this->minimumPLR) { + cyclingRatio = partLoadRatio / this->minimumPLR; + partLoadRatio = this->minimumPLR; + } + this->partLoadRatio = partLoadRatio; + this->cyclingRatio = cyclingRatio; +} + +void EIRPlantLoopHeatPump::calcLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity) { // evaluate the actual current operating load side heat transfer rate auto &thisLoadPlantLoop = state.dataPlnt->PlantLoop(this->loadSidePlantLoc.loopNum); @@ -478,6 +470,8 @@ void EIRPlantLoopHeatPump::calcLoadSideHeatTransfer(EnergyPlusData &state, Real6 state.dataLoopNodes->Node(this->loadSideNodes.inlet).Temp, thisLoadPlantLoop.FluidIndex, "EIRPlantLoopHeatPump::calcLoadSideHeatTransfer()"); + + Real64 const operatingPLR = this->partLoadRatio * this->cyclingRatio; this->loadSideHeatTransfer = availableCapacity * operatingPLR; // calculate load side outlet conditions @@ -606,10 +600,11 @@ void EIRPlantLoopHeatPump::eirModCurveCheck(EnergyPlusData &state, Real64 &eirMo } } -void EIRPlantLoopHeatPump::doDefrost(EnergyPlusData &state, const Real64 operatingPLR, Real64 &availableCapacity, Real64 &InputPowerMultiplier) +void EIRPlantLoopHeatPump::doDefrost(EnergyPlusData &state, Real64 &availableCapacity, Real64 &InputPowerMultiplier) { // Initializing defrost adjustment factors Real64 HeatingCapacityMultiplier = 1.0; + Real64 const operatingPLR = this->partLoadRatio * this->cyclingRatio; // Check outdoor temperature to determine of defrost is active if (this->defrostAvailable && state.dataEnvrn->OutDryBulbTemp <= this->maxOutdoorTemperatureDefrost) { diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index 0f6de0aeaf8..9bc45b7dee2 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -235,7 +235,9 @@ namespace EIRPlantLoopHeatPumps { void getAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio); - void calcLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity, Real64 const operatingPLR); + void setPartLoadAndCyclingRatio(EnergyPlusData &state, Real64 &partLoadRatio); + + void calcLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity); void calcPowerUsage(EnergyPlusData &state, Real64 const InputPowerMultiplier); @@ -251,7 +253,7 @@ namespace EIRPlantLoopHeatPumps { void sizeSrcSideASHP(EnergyPlusData &state); - void doDefrost(EnergyPlusData &state, const Real64 operatingPLR, Real64 &AvailableCapacity, Real64 &InputPowerMultiplier); + void doDefrost(EnergyPlusData &state, Real64 &AvailableCapacity, Real64 &InputPowerMultiplier); void capModFTCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSPTemp, Real64 &capModFTemp); From b6fd1def848b705d6e4b614f84db39281af86f1f Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Tue, 13 Feb 2024 11:03:01 -0500 Subject: [PATCH 16/24] more cleanup --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 24 ++++++++++++------------ src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 5 +++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 77dcf385a34..c961b987367 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -194,6 +194,7 @@ void EIRPlantLoopHeatPump::resetReportingVariables() this->fractionalDefrostTime = 0.0; this->partLoadRatio = 0.0; this->cyclingRatio = 0.0; + this->defrostPowerMultiplier = 1.0; } void EIRPlantLoopHeatPump::setOperatingFlowRatesWSHP(EnergyPlusData &state, bool FirstHVACIteration) @@ -341,10 +342,9 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo // evaluate the actual current operating load side heat transfer rate calcLoadSideHeatTransfer(state, availableCapacity); - // no do defrost calculation for WSHP - Real64 InputPowerMultiplier = 1.0; + // no defrost calculation for WSHP // calculate power usage from EIR curves - calcPowerUsage(state, InputPowerMultiplier); + calcPowerUsage(state); // evaluate the source side heat transfer rate calcSourceSideHeatTransferWSHP(state); @@ -362,15 +362,13 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo this->setPartLoadAndCyclingRatio(state, partLoadRatio); // do defrost calculation if applicable - // Init defrost power adjustment factors - Real64 InputPowerMultiplier = 1.0; - this->doDefrost(state, availableCapacity, InputPowerMultiplier); + this->doDefrost(state, availableCapacity); // evaluate the actual current operating load side heat transfer rate calcLoadSideHeatTransfer(state, availableCapacity); // calculate power usage from EIR curves - calcPowerUsage(state, InputPowerMultiplier); + calcPowerUsage(state); // evaluate the source side heat transfer rate calcSourceSideHeatTransferASHP(state); @@ -482,7 +480,7 @@ void EIRPlantLoopHeatPump::calcLoadSideHeatTransfer(EnergyPlusData &state, Real6 // currentLoad will be met and there should? be some adjustment based on outlet water temp limit? } -void EIRPlantLoopHeatPump::calcPowerUsage(EnergyPlusData &state, Real64 const InputPowerMultiplier) +void EIRPlantLoopHeatPump::calcPowerUsage(EnergyPlusData &state) { // calculate power usage from EIR curves @@ -490,8 +488,8 @@ void EIRPlantLoopHeatPump::calcPowerUsage(EnergyPlusData &state, Real64 const In Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); // check curves value this->eirModCurveCheck(state, eirModifierFuncTemp, eirModifierFuncPLR); - this->powerUsage = - (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * InputPowerMultiplier * this->cyclingRatio; + this->powerUsage = (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * this->defrostPowerMultiplier * + this->cyclingRatio; } void EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP(EnergyPlusData &state) @@ -600,11 +598,11 @@ void EIRPlantLoopHeatPump::eirModCurveCheck(EnergyPlusData &state, Real64 &eirMo } } -void EIRPlantLoopHeatPump::doDefrost(EnergyPlusData &state, Real64 &availableCapacity, Real64 &InputPowerMultiplier) +void EIRPlantLoopHeatPump::doDefrost(EnergyPlusData &state, Real64 &availableCapacity) { // Initializing defrost adjustment factors + Real64 InputPowerMultiplier = 1.0; Real64 HeatingCapacityMultiplier = 1.0; - Real64 const operatingPLR = this->partLoadRatio * this->cyclingRatio; // Check outdoor temperature to determine of defrost is active if (this->defrostAvailable && state.dataEnvrn->OutDryBulbTemp <= this->maxOutdoorTemperatureDefrost) { @@ -647,6 +645,7 @@ void EIRPlantLoopHeatPump::doDefrost(EnergyPlusData &state, Real64 &availableCap // cycles of defrost per hour Real64 thisHourDefrostCycles = Curve::CurveValue(state, this->defrostFreqCurveIndex, state.dataEnvrn->OutDryBulbTemp); // is directly proportional to the ratio of capacity used for that hour (PLR) + Real64 const operatingPLR = this->partLoadRatio * this->cyclingRatio; thisHourDefrostCycles *= operatingPLR; // fraction of heat load per cycle of defrost Real64 thisHourDefrostHeatLoad = 0.0; @@ -680,6 +679,7 @@ void EIRPlantLoopHeatPump::doDefrost(EnergyPlusData &state, Real64 &availableCap this->fractionalDefrostTime = 0.0; } availableCapacity *= HeatingCapacityMultiplier; + this->defrostPowerMultiplier = InputPowerMultiplier; } void EIRPlantLoopHeatPump::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index 9bc45b7dee2..e256393d089 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -206,6 +206,7 @@ namespace EIRPlantLoopHeatPumps { Real64 defrostEnergy = 0.0; Real64 fractionalDefrostTime = 0.0; Real64 maxOutdoorTemperatureDefrost = 0.0; + Real64 defrostPowerMultiplier = 1.0; // defrost power adjustment factor // a couple worker functions to easily allow merging of cooling and heating operations std::function calcLoadOutletTemp; @@ -239,7 +240,7 @@ namespace EIRPlantLoopHeatPumps { void calcLoadSideHeatTransfer(EnergyPlusData &state, Real64 const availableCapacity); - void calcPowerUsage(EnergyPlusData &state, Real64 const InputPowerMultiplier); + void calcPowerUsage(EnergyPlusData &state); void calcSourceSideHeatTransferWSHP(EnergyPlusData &state); @@ -253,7 +254,7 @@ namespace EIRPlantLoopHeatPumps { void sizeSrcSideASHP(EnergyPlusData &state); - void doDefrost(EnergyPlusData &state, Real64 &AvailableCapacity, Real64 &InputPowerMultiplier); + void doDefrost(EnergyPlusData &state, Real64 &AvailableCapacity); void capModFTCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSPTemp, Real64 &capModFTemp); From 6ea87e7e569bffddab42d4bdebaabdf06ac572d9 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Tue, 13 Feb 2024 15:20:56 -0500 Subject: [PATCH 17/24] refactor dryAir heating capacity modifier --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 46 +++++++++++++++----------- src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 4 ++- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index c961b987367..2f7e94e80aa 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -336,7 +336,7 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo Real64 availableCapacity = this->referenceCapacity; Real64 partLoadRatio = 0.0; - this->getAvailableCapacity(state, currentLoad, availableCapacity, partLoadRatio); + this->calcAvailableCapacity(state, currentLoad, availableCapacity, partLoadRatio); this->setPartLoadAndCyclingRatio(state, partLoadRatio); // evaluate the actual current operating load side heat transfer rate @@ -358,7 +358,7 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo Real64 availableCapacity = this->referenceCapacity; Real64 partLoadRatio = 0.0; - this->getAvailableCapacity(state, currentLoad, availableCapacity, partLoadRatio); + this->calcAvailableCapacity(state, currentLoad, availableCapacity, partLoadRatio); this->setPartLoadAndCyclingRatio(state, partLoadRatio); // do defrost calculation if applicable @@ -374,12 +374,9 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo calcSourceSideHeatTransferASHP(state); } -void EIRPlantLoopHeatPump::getAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio) +void EIRPlantLoopHeatPump::calcAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio) { - Real64 constexpr RH90 = 90.0; - Real64 constexpr RH60 = 60.0; - Real64 constexpr rangeRH = 30.0; // get setpoint on the load side outlet Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state); Real64 originalLoadSideOutletSPTemp = loadSideOutletSetpointTemp; @@ -395,19 +392,8 @@ void EIRPlantLoopHeatPump::getAvailableCapacity(EnergyPlusData &state, Real64 co capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, this->sourceSideInletTemp); availableCapacity = this->referenceCapacity * capacityModifierFuncTemp; - - // apply heating mode dry outdoor (evaporator) coil correction factor for air-cooled equipment - if (this->capacityDryAirCurveIndex > 0 && this->airSource && state.dataEnvrn->OutRelHum < RH90) { // above 90% RH yields full capacity - Real64 dryCorrectionFactor = std::min(1.0, Curve::CurveValue(state, this->capacityDryAirCurveIndex, state.dataEnvrn->OutDryBulbTemp)); - if (state.dataEnvrn->OutRelHum <= RH60) { - // dry heating capacity correction factor is a function of outdoor dry-bulb temperature - availableCapacity *= dryCorrectionFactor; - } else { - // interpolation of heating capacity between wet and dry is based on outdoor relative humidity over 60%-90% range - Real64 semiDryFactor = dryCorrectionFactor + (1.0 - dryCorrectionFactor) * (1.0 - ((RH90 - state.dataEnvrn->OutRelHum) / rangeRH)); - availableCapacity *= semiDryFactor; - } - } + // air source HP dry air heating capacity correction + availableCapacity *= heatingCapacityModifierASHP(state); if (availableCapacity > 0) { partLoadRatio = std::clamp(std::abs(currentLoad) / availableCapacity, 0.0, 1.0); @@ -448,6 +434,28 @@ void EIRPlantLoopHeatPump::getAvailableCapacity(EnergyPlusData &state, Real64 co this->capModFTCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp); } +Real64 EIRPlantLoopHeatPump::heatingCapacityModifierASHP(EnergyPlusData &state) const +{ + Real64 constexpr RH90 = 90.0; + Real64 constexpr RH60 = 60.0; + Real64 constexpr rangeRH = 30.0; + + // apply heating mode dry outdoor (evaporator) coil correction factor for air-cooled equipment + if (this->capacityDryAirCurveIndex > 0 && this->airSource && state.dataEnvrn->OutRelHum < RH90) { // above 90% RH yields full capacity + Real64 dryCorrectionFactor = std::min(1.0, Curve::CurveValue(state, this->capacityDryAirCurveIndex, state.dataEnvrn->OutDryBulbTemp)); + if (state.dataEnvrn->OutRelHum <= RH60) { + // dry heating capacity correction factor is a function of outdoor dry-bulb temperature + return dryCorrectionFactor; + } else { + // interpolation of heating capacity between wet and dry is based on outdoor relative humidity over 60%-90% range + Real64 semiDryFactor = dryCorrectionFactor + (1.0 - dryCorrectionFactor) * (1.0 - ((RH90 - state.dataEnvrn->OutRelHum) / rangeRH)); + return semiDryFactor; + } + } else { + return 1.0; + } +} + void EIRPlantLoopHeatPump::setPartLoadAndCyclingRatio(EnergyPlusData& state, Real64& partLoadRatio) { Real64 cyclingRatio = 1.0; diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index e256393d089..36e0a435aa6 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -234,7 +234,9 @@ namespace EIRPlantLoopHeatPumps { void doPhysicsASHP(EnergyPlusData &state, Real64 currentLoad); - void getAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio); + void calcAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio); + + Real64 heatingCapacityModifierASHP(EnergyPlusData &state) const; void setPartLoadAndCyclingRatio(EnergyPlusData &state, Real64 &partLoadRatio); From e170bf5878452c24d2592ac764d762990d7f1dd3 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Wed, 14 Feb 2024 08:34:41 -0500 Subject: [PATCH 18/24] more cleanup --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 2f7e94e80aa..530721df4b4 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -340,14 +340,14 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo this->setPartLoadAndCyclingRatio(state, partLoadRatio); // evaluate the actual current operating load side heat transfer rate - calcLoadSideHeatTransfer(state, availableCapacity); + this->calcLoadSideHeatTransfer(state, availableCapacity); // no defrost calculation for WSHP // calculate power usage from EIR curves - calcPowerUsage(state); + this->calcPowerUsage(state); // evaluate the source side heat transfer rate - calcSourceSideHeatTransferWSHP(state); + this->calcSourceSideHeatTransferWSHP(state); } void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLoad) @@ -365,13 +365,13 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo this->doDefrost(state, availableCapacity); // evaluate the actual current operating load side heat transfer rate - calcLoadSideHeatTransfer(state, availableCapacity); + this->calcLoadSideHeatTransfer(state, availableCapacity); // calculate power usage from EIR curves - calcPowerUsage(state); + this->calcPowerUsage(state); // evaluate the source side heat transfer rate - calcSourceSideHeatTransferASHP(state); + this->calcSourceSideHeatTransferASHP(state); } void EIRPlantLoopHeatPump::calcAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio) From 2343a449ce4df316830bb9b5bb240d4a7c7b0131 Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Mon, 19 Feb 2024 15:51:54 -0500 Subject: [PATCH 19/24] more code cleanup --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 34 +++++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 530721df4b4..9444edb9e57 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -312,7 +312,6 @@ void EIRPlantLoopHeatPump::setOperatingFlowRatesASHP(EnergyPlusData &state, bool void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) { - // ideally the plant is going to ensure that we don't have a runflag=true when the load is invalid, but // I'm not sure we can count on that so we will do one check here to make sure we don't calculate things badly if ((this->EIRHPType == DataPlant::PlantEquipmentType::HeatPumpEIRCooling && currentLoad >= 0.0) || @@ -321,6 +320,7 @@ void EIRPlantLoopHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) return; } + // dispatch to specific physics calculations based on the heat pump type if (this->waterSource) { this->doPhysicsWSHP(state, currentLoad); } else if (this->airSource) { @@ -352,7 +352,6 @@ void EIRPlantLoopHeatPump::doPhysicsWSHP(EnergyPlusData &state, Real64 currentLo void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLoad) { - // add free cooling at some point, compressor is off during free cooling, temp limits restrict free cooling range Real64 availableCapacity = this->referenceCapacity; @@ -376,7 +375,6 @@ void EIRPlantLoopHeatPump::doPhysicsASHP(EnergyPlusData &state, Real64 currentLo void EIRPlantLoopHeatPump::calcAvailableCapacity(EnergyPlusData &state, Real64 const currentLoad, Real64 &availableCapacity, Real64 &partLoadRatio) { - // get setpoint on the load side outlet Real64 loadSideOutletSetpointTemp = this->getLoadSideOutletSetPointTemp(state); Real64 originalLoadSideOutletSPTemp = loadSideOutletSetpointTemp; @@ -392,7 +390,8 @@ void EIRPlantLoopHeatPump::calcAvailableCapacity(EnergyPlusData &state, Real64 c capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, this->sourceSideInletTemp); availableCapacity = this->referenceCapacity * capacityModifierFuncTemp; - // air source HP dry air heating capacity correction + + // apply air source HP dry air heating capacity correction availableCapacity *= heatingCapacityModifierASHP(state); if (availableCapacity > 0) { @@ -431,6 +430,8 @@ void EIRPlantLoopHeatPump::calcAvailableCapacity(EnergyPlusData &state, Real64 c break; } } + + // check the curve values, reset to zero if negative this->capModFTCurveCheck(state, loadSideOutletSetpointTemp, capacityModifierFuncTemp); } @@ -452,17 +453,24 @@ Real64 EIRPlantLoopHeatPump::heatingCapacityModifierASHP(EnergyPlusData &state) return semiDryFactor; } } else { + // no correction needed, use full capacity return 1.0; } } -void EIRPlantLoopHeatPump::setPartLoadAndCyclingRatio(EnergyPlusData& state, Real64& partLoadRatio) +void EIRPlantLoopHeatPump::setPartLoadAndCyclingRatio(EnergyPlusData &state, Real64 &partLoadRatio) { + // Initialize cycling ratio to 1.0 Real64 cyclingRatio = 1.0; + + // Check if part load ratio is below the minimum threshold if (partLoadRatio < this->minimumPLR) { + // Adjust cycling ratio and set part load ratio to minimum cyclingRatio = partLoadRatio / this->minimumPLR; partLoadRatio = this->minimumPLR; } + + // update class member variables this->partLoadRatio = partLoadRatio; this->cyclingRatio = cyclingRatio; } @@ -494,8 +502,11 @@ void EIRPlantLoopHeatPump::calcPowerUsage(EnergyPlusData &state) // calculate power usage from EIR curves Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); - // check curves value + + // check curves value and resets to zero if negative this->eirModCurveCheck(state, eirModifierFuncTemp, eirModifierFuncPLR); + + // compute power usage this->powerUsage = (this->loadSideHeatTransfer / this->referenceCOP) * eirModifierFuncPLR * eirModifierFuncTemp * this->defrostPowerMultiplier * this->cyclingRatio; } @@ -505,13 +516,11 @@ void EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP(EnergyPlusData &state) // energy balance on heat pump this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); + // calculate source side outlet conditions - Real64 CpSrc; auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); - CpSrc = FluidProperties::GetSpecificHeatGlycol( + Real64 const CpSrc = FluidProperties::GetSpecificHeatGlycol( state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP()"); - - // this->sourceSideCp = CpSrc; // debuging variable Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); @@ -535,9 +544,9 @@ void EIRPlantLoopHeatPump::calcSourceSideHeatTransferASHP(EnergyPlusData &state) { // energy balance on heat pump this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); + // calculate source side outlet conditions - Real64 CpSrc; - CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat); + Real64 const CpSrc = Psychrometrics::PsyCpAirFnW(state.dataEnvrn->OutHumRat); Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); } @@ -687,6 +696,7 @@ void EIRPlantLoopHeatPump::doDefrost(EnergyPlusData &state, Real64 &availableCap this->fractionalDefrostTime = 0.0; } availableCapacity *= HeatingCapacityMultiplier; + // update class member variables this->defrostPowerMultiplier = InputPowerMultiplier; } From b639d5a51925c2b9ad964c99cfcdd5284926fc2b Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Tue, 20 Feb 2024 09:59:13 -0500 Subject: [PATCH 20/24] Added reporting function for EIRFuelFiredHeatPump --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 20 +++++++++++++++----- src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 1 + 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 9444edb9e57..6da0e3e0f2b 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -2198,7 +2198,6 @@ void EIRFuelFiredHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) // this->loadSideHeatTransfer = availableCapacity * partLoadRatio; this->loadSideHeatTransfer = availableCapacity * partLoadRatio; // (partLoadRatio >= this->minPLR ? partLoadRatio : 0.0); - this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval; // calculate load side outlet conditions Real64 const loadMCp = this->loadSideMassFlowRate * CpLoad; @@ -2364,13 +2363,9 @@ void EIRFuelFiredHeatPump::doPhysics(EnergyPlusData &state, Real64 currentLoad) } this->powerUsage += this->standbyElecPower; - this->fuelEnergy = this->fuelRate * reportingInterval; - this->powerEnergy = this->powerEnergy * reportingInterval; - // energy balance on heat pump // this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->powerUsage); this->sourceSideHeatTransfer = this->calcQsource(this->loadSideHeatTransfer, this->fuelRate + this->powerUsage - this->standbyElecPower); - this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; // calculate source side outlet conditions Real64 CpSrc = 0.0; @@ -3173,4 +3168,19 @@ void EIRFuelFiredHeatPump::oneTimeInit(EnergyPlusData &state) } } +void EIRFuelFiredHeatPump::report(EnergyPlusData &state) +{ + Real64 const reportingInterval = state.dataHVACGlobal->TimeStepSysSec; + + this->fuelEnergy = this->fuelRate * reportingInterval; + this->loadSideEnergy = this->loadSideHeatTransfer * reportingInterval; + this->powerEnergy = this->powerUsage * reportingInterval; + this->sourceSideEnergy = this->sourceSideHeatTransfer * reportingInterval; + + // update nodes + PlantUtilities::SafeCopyPlantNode(state, this->loadSideNodes.inlet, this->loadSideNodes.outlet); + state.dataLoopNodes->Node(this->loadSideNodes.outlet).Temp = this->loadSideOutletTemp; + state.dataLoopNodes->Node(this->sourceSideNodes.outlet).Temp = this->sourceSideOutletTemp; +} + } // namespace EnergyPlus::EIRPlantLoopHeatPumps diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index 36e0a435aa6..9fe01d6d377 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -377,6 +377,7 @@ namespace EIRPlantLoopHeatPumps { static void pairUpCompanionCoils(EnergyPlusData &state); static void processInputForEIRPLHP(EnergyPlusData &state); void oneTimeInit(EnergyPlusData &state); + void report(EnergyPlusData &state); // New or specialized functions for derived struct virtual ~EIRFuelFiredHeatPump() = default; From edde21a963448b1402898a8473d9df3e5245a71c Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Tue, 20 Feb 2024 10:40:19 -0500 Subject: [PATCH 21/24] Apply clang-format --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 6da0e3e0f2b..848f89755bf 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -390,8 +390,8 @@ void EIRPlantLoopHeatPump::calcAvailableCapacity(EnergyPlusData &state, Real64 c capacityModifierFuncTemp = Curve::CurveValue(state, this->capFuncTempCurveIndex, loadSideOutletSetpointTemp, this->sourceSideInletTemp); availableCapacity = this->referenceCapacity * capacityModifierFuncTemp; - - // apply air source HP dry air heating capacity correction + + // apply air source HP dry air heating capacity correction availableCapacity *= heatingCapacityModifierASHP(state); if (availableCapacity > 0) { From 08165efd6705281bbe0a24a979cfd4e9d86d133d Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Tue, 20 Feb 2024 13:24:47 -0500 Subject: [PATCH 22/24] fixed unit test and an example file failed due to refactoring --- src/EnergyPlus/PlantLoopHeatPumpEIR.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh index 9fe01d6d377..4e252c295a0 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.hh +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.hh @@ -248,7 +248,7 @@ namespace EIRPlantLoopHeatPumps { void calcSourceSideHeatTransferASHP(EnergyPlusData &state); - void report(EnergyPlusData &state); + virtual void report(EnergyPlusData &state); void sizeLoadSide(EnergyPlusData &state); @@ -257,7 +257,7 @@ namespace EIRPlantLoopHeatPumps { void sizeSrcSideASHP(EnergyPlusData &state); void doDefrost(EnergyPlusData &state, Real64 &AvailableCapacity); - + void capModFTCurveCheck(EnergyPlusData &state, const Real64 loadSideOutletSPTemp, Real64 &capModFTemp); void eirModCurveCheck(EnergyPlusData &state, Real64 &eirModFTemp, Real64 &eirModFPLR); From b52b748e3e8981c13088425ce3075dc338c38eef Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Tue, 20 Feb 2024 13:41:00 -0500 Subject: [PATCH 23/24] Clang-formatting again --- src/EnergyPlus/PlantLoopHeatPumpEIR.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc index 848f89755bf..5f04fce0ab7 100644 --- a/src/EnergyPlus/PlantLoopHeatPumpEIR.cc +++ b/src/EnergyPlus/PlantLoopHeatPumpEIR.cc @@ -502,7 +502,7 @@ void EIRPlantLoopHeatPump::calcPowerUsage(EnergyPlusData &state) // calculate power usage from EIR curves Real64 eirModifierFuncTemp = Curve::CurveValue(state, this->powerRatioFuncTempCurveIndex, this->loadSideOutletTemp, this->sourceSideInletTemp); Real64 eirModifierFuncPLR = Curve::CurveValue(state, this->powerRatioFuncPLRCurveIndex, this->partLoadRatio); - + // check curves value and resets to zero if negative this->eirModCurveCheck(state, eirModifierFuncTemp, eirModifierFuncPLR); @@ -519,8 +519,11 @@ void EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP(EnergyPlusData &state) // calculate source side outlet conditions auto &thisSourcePlantLoop = state.dataPlnt->PlantLoop(this->sourceSidePlantLoc.loopNum); - Real64 const CpSrc = FluidProperties::GetSpecificHeatGlycol( - state, thisSourcePlantLoop.FluidName, this->sourceSideInletTemp, thisSourcePlantLoop.FluidIndex, "EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP()"); + Real64 const CpSrc = FluidProperties::GetSpecificHeatGlycol(state, + thisSourcePlantLoop.FluidName, + this->sourceSideInletTemp, + thisSourcePlantLoop.FluidIndex, + "EIRPlantLoopHeatPump::calcSourceSideHeatTransferWSHP()"); Real64 const sourceMCp = this->sourceSideMassFlowRate * CpSrc; this->sourceSideOutletTemp = this->calcSourceOutletTemp(this->sourceSideInletTemp, this->sourceSideHeatTransfer / sourceMCp); From ca37cb8267076c9b0387b51fea35b227d6ff16eb Mon Sep 17 00:00:00 2001 From: Bereket Nigusse Date: Wed, 21 Feb 2024 09:55:40 -0500 Subject: [PATCH 24/24] Removed NFP documents --- .../NFP-AirToWaterHeatPumpHeatRecovery.md | 353 ------------------ design/FY2024/chiller_with_heat_recovery.png | Bin 12517 -> 0 bytes design/FY2024/heater_with_heat_recovery.png | Bin 10185 -> 0 bytes 3 files changed, 353 deletions(-) delete mode 100644 design/FY2024/NFP-AirToWaterHeatPumpHeatRecovery.md delete mode 100644 design/FY2024/chiller_with_heat_recovery.png delete mode 100644 design/FY2024/heater_with_heat_recovery.png diff --git a/design/FY2024/NFP-AirToWaterHeatPumpHeatRecovery.md b/design/FY2024/NFP-AirToWaterHeatPumpHeatRecovery.md deleted file mode 100644 index 4bd465db656..00000000000 --- a/design/FY2024/NFP-AirToWaterHeatPumpHeatRecovery.md +++ /dev/null @@ -1,353 +0,0 @@ -Air-To-Water Heat Pump Heat Recovery Mode -====================== - -**Bereket Nigusse** - -**Florida Solar Energy Center** - - - First draft: January 26, 2024 - - Modified Date: NA - - Added Design Document: NA - - -## Justification for New Feature ## - -Modern air-to-water heat pumps for commercial applications often include heat recovery for simultaneous heating and cooling. The air-to-water heat pump simulation in EnergyPlus was improved during the FY23 development cycle; however, the current model supports heat-only and cool-only modes of operation. EnergyPlus should be able to support heat recovery mode for the air-to-water heat pump simulations. - -**- The new feature was requested by Design Builder ** - -## E-mail and Conference Call Conclusions ## - -- NA - - -## Overview ## - -### Current Code ### - -The existing Air-to-Water Heat Pump model supports heat-only and cool-only mode simulations. - -**- This enhancement allows simultaneous cooling and heating modes of operation for Air-to-Water Heat Pump model. - -## Implementation Approach ## - -*(1) This new feature can be implemented by modifying the following existing objects: - - Existing objects: `HeatPump:PlantLoop:EIR:Cooling` and `HeatPump:PlantLoop:EIR:Heating` - - -*(2) Adds four new input fields to support heat recovery operation mode for each HeatPump:PlantLoop:EIR:* object. - - Adds new heat recovery fluid inlet and outlet nodes: - New input fields `Heat Recovery Inlet Node Name` and `Heat Recovery Outlet Node Name` will be added to the `HeatPump:PlantLoop:EIR:Cooling` object. - New input fields `Heat Recovery Inlet Node Name` and `Heat Recovery Outlet Node Name` will be added to the `HeatPump:PlantLoop:EIR:Heating` object. - - Adds heat recovery side reference fluid flow rate: - New input field `Heat Recovery Side Reference Flow Rate` will be added to the `HeatPump:PlantLoop:EIR:Cooling` and `HeatPump:PlantLoop:EIR:Heating` objects. - - Adds heat recovery fluid temperature limits: - New input field `Maximum Heat Recovery Fluid Inlet Temperature` will be added to the `HeatPump:PlantLoop:EIR:Cooling` object. - New input field `Minimum Heat Recovery Fluid Inlet Temperature` will be added to the `HeatPump:PlantLoop:EIR:Heating` object. - - -*(3) Adds new code that supports calculating the heat rate and outlet node fluid temperature of the heat recovery system - - Hot Water Recovery Rate Calculation: - {CAPFT_Val} = CAPFT(T_lSide_Cool, T_sSide_Heat) - {Q_dot_{HR,heat,avail}} = {Q_dot_{HR,heat,ref} * {CAPFT_Val} - - Heat Recovery Outlet Node Fluid Temperature Calculation: - {T_{HR,heat,out}} = {T_{HR,heat,in}} + {Q_dot_{HR,heat}} / {{Cp_{heat}} * {m_dot_{HR,heat}}} - - Chilled Water Recovery Rate Calculation: - {CAPFT_Val} = CAPFT(T_lSide_Heat, T_sSide_Cool) - {Q_dot_{HR,cool,avail}} = {Q_dot_{HR,cool,ref}} * {CAPFT_Val} - - Heat Recovery Outlet Node Fluid Temperature Calculation: - {T_{HR,cool,out}} = {T_{HR,cool,in}} - {Q_dot_{HR,cool}} / {{Cp_{cool}} * {m_dot_{HR,cool}}} - - where, - CAPFT = capacity modifier normalized curve as a function of temperature - CAPFT_val = capacity modifier normalized curve value - {Q_dot_{HR,heat,avail}} = heat recovery available heating rate, W - {Q_dot_{HR,heat,ref}} = reference capacity of hot water heat recovery, W - {Q_dot_{HR,cool,avail}} = heat recovery available cooling rate, W - {Q_dot_{HR,cool,ref}} = reference capacity of chilled water heat recovery, W - {T_{HR,heat,in}} = hot water heat recovery inlet node temperature, C - {T_{HR,heat,out}} = hot water heat recovery outlet node temperature, C - {{Cp_{heat}} = hot water specific heat capacity, J/kgK - {m_dot_{HR,heat}}} = hot water heat recovery fluid mass flow rate, kg/s - {T_{HR,cool,in}} = chilled water heat recovery inlet node temperature, C - {T_{HR,cool,out}} = chilled water heat recovery outlet node temperature, C - {{Cp_{cool}} = chilled water specific heat capacity, J/kgK - {m_dot_{HR,cool}}} = chilled water heat recovery fluid mass flow rate, kg/s - T_lSide_Cool = chiller load side entering fluid temperature, C - T_sSide_Heat = chiller source side entering fluid temperature, C - T_lSide_Heat = Heater (heat Pump) load side entering fluid temperature, C - T_sSide_Cool = Heater (heat Pump) source side entering fluid temperature, C - - -*(4) Heat recovery operating mode simulation control. - - ** the heat recovery mode operation is activated if heat recovery nodes are specified. ** - - The heat recovery mode is essentially a passive operation. Hence, the HR delivered capacity is determined by the actual - capacity of the chiller or heater operation. Thus, the actual HR rate delivered can be estimated using the chiller or - heater part-load ratio. - - Hot Water Heat Recovery: - {Q_dot_{HR,heat,actual}} = {PLR_{cool}} * {Q_dot_{HR,heat,avail}} - - ** the available capacity must be prorated by the chiller PLR to get the delivered hot water recovery rate. ** - - - Chilled Water Heat Recovery: - {Q_dot_{HR,cool,actual}} = {PLR_{heat}} * {Q_dot_{HR,cool,avail}} - - ** the available capacity must be prorated by the heater PLR to get the delivered chilled water recovery rate. ** - - ** required to check the user specified temperature limit at the heat recovery outlet node is not violated. ** - - where, - {Q_dot_{HR,heat,actual}} = heat recovery actual heating rate, W - {Q_dot_{HR,cool,actual}} = heat recovery actual cooling rate, W - {PLR_{cool}} = chiller part-load ratio, - - {PLR_{heat}} = heater part-load ratio, - - -*(5) Adds capacity and fluid flow rates sizing calculation for the heat recovery system. - - Heat Recovery Reference Capacities Sizing: - Hot Water Heating Reference Capacity: - {Q_dot_{HR,heat,ref}} = {Q_{ref,cool,coil}} * [1 + 1/{COP_cool}}] - - Chilled Water Cooling Reference Capacity: - {Q_dot_{HR,cool,ref}} = {Q_{ref,heat,coil}} * [(1 - 1/{COP_heat})] - - Heat Recovery Fluid Flow Rates Sizing: - Hot Water Recovery Design Flow Rate: - {m_dot_{HR,design,heat}} = {Sizing_Factor_{cool}} * {Q_dot_{HR,heat,ref}} / [{Cp_{heat}} * {DeltaT_{heat}}] - - Chilled Water Recovery Design Flow Rate: - {m_dot_{HR,design,cool}} = {Sizing_Factor_{heat}} * {Q_dot_{HR,cool,ref}} / [{Cp_{cool}} * {DeltaT_{cool}}] - - where, - {Q_{ref,cool,coil}} = reference capacity of the chiller, W - {Q_{ref,heat,coil}} = reference capacity of the heater, W - {COP_cool}) = reference COP of the chiller, W/W - {COP_heat}) = reference COP of the heater, W/W - {DeltaT_{heat}} = hot water loop design temperature difference, K - {DeltaT_{cool}} = chilled water loop design temperature difference, K - {Sizing_Factor_{cool}} = sizing factor of the chiller, (-) - {Sizing_Factor_{heat}} = sizing factor of the heater (heat pump), (-) - - -*(6) Schematic diagram of cooling and heating dominated heat recovery operating modes: - - -![chiller_with_heat_recovery](chiller_with_heat_recovery.png) -

Figure 1. Air-To-Water Heat Pump (Chiller) with Heat Recovery Mode Diagram.

- - -![heater_with_heat_recovery](heater_with_heat_recovery.png) -

Figure 2. Air-To-Water Heat Pump (Heater) with Heat Recovery Mode Diagram.

- - -### Existing Object HeatPump:PlantLoop:EIR:Cooling ### - - -HeatPump:PlantLoop:EIR:Cooling, - \memo An EIR formulated water to water heat pump model, cooling operation. - \min-fields 15 - A1, \field Name - \type alpha - \reference PLHPCoolingNames - \required-field - \reference-class-name validPlantEquipmentTypes - \reference validPlantEquipmentNames - \reference-class-name validBranchEquipmentTypes - \reference validBranchEquipmentNames - A2, \field Load Side Inlet Node Name - \required-field - \type node - A3, \field Load Side Outlet Node Name - \required-field - \type node - A4, \field Condenser Type - \type choice - \key WaterSource - \key AirSource - \default WaterSource - A5, \field Source Side Inlet Node Name - \required-field - \type node - A6, \field Source Side Outlet Node Name - \required-field - \type node - - - - A7, \field Hot Water Recovery Side Inlet Node Name - \required-field - \type node - A8, \field Hot Water Recovery Side Outlet Node Name - \required-field - \type node - - ... - - N6, \field Heat Recovery Side Reference Flow Rate - \type real - \minimum> 0.0 - \units m3/s - \ip-units gal/min - \autosizable - \default autosize - - - ... - - N8, \field Minimum Source Inlet Temperature - \type real - \units C - \default -100.0 - \note Enter the minimum inlet outdoor air dry-bulb temperature - \note for air-cooled units or minimum inlet water temperature for water-cooled units. - \note The unit is disabled below this temperature. - N9, \field Maximum Source Inlet Temperature - \type real - \units C - \default 100.0 - \note Enter the maximum inlet outdoor air dry-bulb temperature - \note for air-cooled units or maximum inlet water temperature for water-cooled units. - \note The unit is disabled above this temperature. - N10, \field Minimum Supply Water Temperature Curve Name - \type object-list - \object-list UniVariateFunctions - \note quadratic curve = a + b*OAT is typical, other univariate curves may be used - \note OAT = Outdoor Dry-Bulb Temperature - N11; \field Maximum Supply Water Temperature Curve Name - \type object-list - \object-list UniVariateFunctions - \note quadratic curve = a + b*OAT is typical, other univariate curves may be used - \note OAT = Outdoor Dry-Bulb Temperature - - - - N12; \field Maximum Heat Recovery Fluid Inlet Temperature - \type real - \units C - \default 60.0 - \note Enter the maximum chiller inlet fluid temperature for hot water recovery - \note The heat recovery operation will be disabled above this temperature. - - -### Existing Object HeatPump:PlantLoop:EIR:Heating ### - - -HeatPump:PlantLoop:EIR:Heating, - \memo An EIR formulated water to water heat pump model, heating operation - \min-fields 15 - A1, \field Name - \type alpha - \reference PLHPHeatingNames - \required-field - \reference-class-name validPlantEquipmentTypes - \reference validPlantEquipmentNames - \reference-class-name validBranchEquipmentTypes - \reference validBranchEquipmentNames - A2, \field Load Side Inlet Node Name - \required-field - \type node - A3, \field Load Side Outlet Node Name - \required-field - \type node - A4, \field Condenser Type - \type choice - \key WaterSource - \key AirSource - \default WaterSource - A5, \field Source Side Inlet Node Name - \required-field - \type node - A6, \field Source Side Outlet Node Name - \required-field - \type node - - - - A7, \field Chilled Water Recovery Side Inlet Node Name - \required-field - \type node - A8, \field Chilled Water Recovery Side Outlet Node Name - \required-field - \type node - - ... - - N6, \field Heat Recovery Side Reference Flow Rate - \type real - \minimum> 0.0 - \units m3/s - \ip-units gal/min - \autosizable - \default autosize - - ... - - A22, \field Timed Empirical Defrost Heat Load Penalty Curve Name - \type object-list - \object-list UniVariateFunctions - \object-list BivariateFunctions - \note univariate curve = a + b*OAT is typical, other univariate curves may be used - \note bivariate curve = a + b*WB + c*WB**2 + d*OAT + e*OAT**2 + f*WB*OAT - \note OAT = outdoor air dry-bulb temperature (C) - \note WB = wet-bulb temperature (C) of air entering the indoor coil - \note Timed Empirical Defrost Heat Load Penalty in watts = hot load * curve output - \note only applicable if TimedEmpirical defrost control is specified - A23; \field Timed Empirical Defrost Heat Input Energy Fraction Curve Name - \type object-list - \object-list UniVariateFunctions - \object-list BivariateFunctions - \note univariate curve = a + b*OAT is typical, other univariate curves may be used - \note bivariate curve = a + b*WB + c*WB**2 + d*OAT + e*OAT**2 + f*WB*OAT - \note OAT = outdoor air dry-bulb temperature (C) - \note WB = wet-bulb temperature (C) of air entering the indoor coil - \note Timed Empirical Defrost Heat Input Energy in watts = rated hot load * curve output - \note only applicable if TimedEmpirical defrost control is specified - - - - N13; \field Minimum Heat Recovery Fluid Inlet Temperature - \type real - \units C - \default 6.7 - \note Enter the minimum heater inlet fluid temperature for chilled water recovery - \note The heat recovery operation will be disabled below this temperature. - - - -## Testing/Validation/Data Source(s): ## - -Demonstrate that the air-to-water heat pump object supports heat recovery mode simulation. Unit tests will be added to demonstrate the new feature. - -## Input Output Reference Documentation ## - -Documentation for the new input fields will be added to the I/O reference guide of `HeatPump:PlantLoop:EIR:Cooling` and `HeatPump:PlantLoop:EIR:Heating` objects. - -## Engineering Reference ## -As needed. - -## Example File and Transition Changes ## - -A new example file will be created for the heat recovery mode of air-to-water heat pumps. Simulation results will be examined, and sample results will be provided. - -Transition is required to handle the two node input fields. - -## Proposed Report Variables: ## - -Add output variables as needed. - - -## References ## -NA - diff --git a/design/FY2024/chiller_with_heat_recovery.png b/design/FY2024/chiller_with_heat_recovery.png deleted file mode 100644 index 7a2af228cc67c9e09c63ac64e23f7f9c149609ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12517 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 diff --git a/design/FY2024/heater_with_heat_recovery.png b/design/FY2024/heater_with_heat_recovery.png deleted file mode 100644 index 718835494260109413fddbe1183beeacda3972ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10185 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