diff --git a/doc/engineering-reference/src/simulation-models-encyclopedic-reference-003/centralheatpumpsystem.tex b/doc/engineering-reference/src/simulation-models-encyclopedic-reference-003/centralheatpumpsystem.tex index 7d1142d21a0..c35972a2f14 100644 --- a/doc/engineering-reference/src/simulation-models-encyclopedic-reference-003/centralheatpumpsystem.tex +++ b/doc/engineering-reference/src/simulation-models-encyclopedic-reference-003/centralheatpumpsystem.tex @@ -1,153 +1,82 @@ -\section{CentralHeatPumpSystem }\label{centralheatpumpsystem} +\section{CentralHeatPumpSystem } +\label{centralheatpumpsystem} -\subsection{Overview}\label{overview-007} +\subsection{Overview} +\label{overview-007} -The CentralHeatPumpSystem object simulates the performance of a central heat pump system containing one or more chiller-heater objects. The main function of the object is to call relevant calculation methods for the chiller-heater objects depending on their operating modes, and to calculate temperatures at the outlet nodes and the total energy transfer. The object can be connected to plant loops and a source loop (see Input-Output Reference document), and the node connections are solely dependent on individual chiller-heaters' operating modes. The central system receives water flows from each plant and source loop and then distributes them to individual chiller-heaters as requested. The conditioned water flows leaving the chiller-heaters are then returned to the system, and in turn flow back to the plant loops and source loop to produce heating and cooling, or exchange heat with the source loop. +The \emph{CentralHeatPumpSystem} object represents a bank of one or more \emph{ChillerHeaterPerformance:Electric:EIR} modules connected to chilled-water, hot-water, and source plant loops. The wrapper receives the three loop flows, stages available modules, allocates the remaining load and flow to each module, mixes module and bypass outlet streams, and reports the assembled useful loads, source transfer, and electric consumption. -\subsection{Model Description}\label{model-description-004} +The source connection may serve either side of the refrigerant cycle. It receives condenser heat during cooling-dominant operation and supplies evaporator heat during heating-dominant operation. In simultaneous operation a single module can exchange heat with all three loops; the source receives only the residual after useful heat recovery. -The model first determines operating modes based on the cooling and heating loads on the system, and then decides which calculation algorithm is called. It calls the cooling calculation subroutine when cooling load is demanded, the heating calculation subroutine when heating load is demanded, and both cooling and heating calculation subroutines when both cooling and heating loads are demanded, i.e., simultaneous cooling-heating mode. It then calculates mass-weighed temperatures and heat transfer energy. Six different operating modes (0 through 5) are possible: +\subsection{Operating Modes and Sequential Dispatch} +\label{operating-modes-and-sequential-dispatch} -0: off +Six reported operating states are possible: -1: cooling-only mode +\begin{itemize} +\item Mode 0: off. +\item Mode 1: cooling only. +\item Mode 2: heating only. +\item Mode 3: balanced heat recovery with no source transfer. +\item Mode 4: cooling-dominant simultaneous cooling and heating, with residual heat rejected to the source. +\item Mode 5: heating-dominant simultaneous cooling and heating, with residual heat extracted from the source. +\end{itemize} -2: heating-only mode +The wrapper caches the current cooling and heating requests from the two load-side plant connections. A single-mode solver is used when only one request is active. When both are active, the simultaneous solver resolves one refrigerant-cycle result and explicitly routes it among all three connections. Results are independent of whether the cooling or heating plant loop supplies the second request. -3: heat recovery mode +Modules are staged sequentially in the order entered. Each module schedule controls its availability. After a module is solved, its useful cooling and heating are subtracted from the remaining loads, and its actual connection flows are subtracted from the remaining wrapper flows. A later module therefore cannot consume load or flow already assigned to an earlier one. Different module capacities, performance curves, schedules, and design flows are retained when a bank is heterogeneous. -4: cooling dominant simultaneous cooling-heating mode +The mode of each simultaneous module and the cooling- or heating-dominant state of the wrapper are based on signed source heat transfer, not on a reference capacity ratio. Source heat transfer is positive when heat is rejected to the source and negative when heat is extracted from the source. -5: heating dominant simultaneous cooling-heating mode. +\subsection{Thermodynamic and Routing Balance} +\label{thermodynamic-and-routing-balance} -The model reads different node information over the operating mode so that outlet water conditions at each node of the system can be appropriately determined. - -In cooling-only mode (mode 1), the system is connected to the chilled water loop and source loop. It thus reads the evaporator outlet temperatures and mass flow rates of the chiller-heaters to calculate a mass-weighed chilled water temperature at the system outlet node, and the condenser outlet temperatures and mass flow rates of the chiller-heaters to calculate a mass-weighed source water temperature at the system outlet node. In heating-only mode (mode 2), the system is connected to the hot water loop and source loop. It thus reads the condenser temperatures and mass flow rates of the chiller-heaters to calculate a mass-weighed hot water temperature at the system outlet node, and the evaporator temperatures and mass flow rates of the chiller-heaters to calculate a mass-weighed source temperature at the system outlet node. - -In simultaneous cooling-heating mode, three different operating modes (mode 3 through 5) are possible. The model checks which simultaneous cooling-heating mode (3, 4, or 5) each chiller-heater is in, and calculates relevant mass-weighed temperatures. The system may be connected to three loops such as chilled water loop, hot water loop, and source water loop in mode 4 or mode 5. - -In heat recovery mode, both evaporator temperatures and condenser temperatures are read and a mass-weighed temperature for both chilled water and hot water is calculated. The chiller-heaters in mode 3 do not exchange heat with source water. At least one of the chiller-heaters within the system is in heat recovery mode during simultaneous cooling-heating mode. The system may be only connected to both chilled water loop and hot water loop if all operating chiller-heaters are in heat recovery mode. - -The following nomenclature is used in the equations for this model shown below: - -\({\dot m_{cw,bypass}}\) is the chilled water bypass mass flow rate in the system (kg/s) - -\({\dot m_{hw,bypass}}\) is the hot water bypass mass flow rate in the system (kg/s) - -\({\dot m_{src,bypass}}\) is the source water bypass mass flow rate in the system (kg/s) - -\({\dot m_{cw,CH}}\) is the chilled water mass flow rate of i\(^{th}\) chiller-heater's evaporator {[}kg/s{]} - -\({\dot m_{hw,CH}}\) is the hot water mass flow rate of i\(^{th}\) chiller-heater's condenser (kg/s) - -\({\dot m_{src,CH}}\) is the source water mass flow rate of i\(^{th}\) chiller-heater, which varies with operating modes (kg/s) - -\({\dot m_{cw,sys}}\) is the chilled water mass flow rate of the system (kg/s) - -\({\dot m_{hw,sys}}\) is the hot water mass flow rate of the system (kg/s) - -\({\dot m_{src,sys}}\) is the source water mass flow rate of the system (kg/s) - -\({T_{cw,sys}}\) is the chilled water outlet temperature of the system (\(^{\circ}\)C) - -\({T_{hw,sys}}\) is the hot water outlet temperature of the system (\(^{\circ}\)C) - -\({T_{src,sys}}\) is the source water outlet temperature of the system (\(^{\circ}\)C) - -\({T_{cw,bypass}}\) is the mass-weighed bypass chilled water temperature in the system (\(^{\circ}\)C) - -\({T_{hw,bypass}}\) is the mass-weighed bypass hot water temperature in the system (\(^{\circ}\)C) - -\({T_{src,bypass}}\) is the mass-weighed bypass source water temperature in the system (\(^{\circ}\)C) - -\({T_{cw,CH}}\) is the mass-weighed chilled water outlet temperature from chiller-heaters (\(^{\circ}\)C) - -\({T_{hw,CH}}\) is the mass-weighed hot water outlet temperature from chiller-heaters (\(^{\circ}\)C) - -\({T_{src,CH}}\) is the mass-weighed source water outlet temperature from chiller-heaters (\(^{\circ}\)C) - -\({T_{cw,i}}\) is the chilled water inlet temperature of the system (\(^{\circ}\)C) - -\({T_{hw,i}}\) is the hot water inlet temperature of the system (\(^{\circ}\)C) - -\({T_{src,i}}\) is the source water inlet temperature of the system (\(^{\circ}\)C) - -\({T_{cw,out}}\) is the chilled water outlet temperature of i\(^{th}\) chiller-heater (\(^{\circ}\)C) - -\({T_{hw,out}}\) is the hot water outlet temperature of i\(^{th}\) chiller-heater (\(^{\circ}\)C) - -\({T_{src,out}}\) is the source water outlet temperature of i\(^{th}\) chiller-heater (\(^{\circ}\)C). - -The model reads node information and local variables of individual chiller-heaters. The nodes and local variables vary with the operating modes as described above in order to calculate mass-weighed temperatures. In the cooling-only mode, it calculates a mass-weighed chilled water temperature (\emph{T\(_{cw,CH}\)}) and a source water temperature (\emph{T\(_{src,CH}\)}) as follows: +For each active module, compressor power delivered to the refrigerant, evaporator heat, false load, and condenser heat satisfy \begin{equation} -{T_{cw,CH}} = \sum\limits_{CH = 1}^n {{T_{cw,out}}} \frac{{{{\dot m}_{cw,CH}}}}{{{m_{cw,sys}}}} +\dot Q_{cond}=\dot Q_{evap}+\dot Q_{false}+\eta_{motor}P. \end{equation} -\begin{equation} -{T_{src,CH}} = \sum\limits_{CH = 1}^n {{T_{src,out}}} \frac{{{{\dot m}_{src,CH}}}}{{{m_{src,sys}}}} -\end{equation} - -In heating-only mode, it calculates a mass-weighed hot water temperature (\emph{T\(_{hw,CH}\)}) and a source water temperature (\emph{T\(_{src,CH}\)}) as follows: +The module result is then routed without altering those thermodynamic quantities. With the source sign convention above, every mode satisfies \begin{equation} -{T_{hw,CH}} = \sum\limits_{CH = 1}^n {{T_{hw,out}}} \frac{{{{\dot m}_{hw,CH}}}}{{{m_{hw,sys}}}} +\dot Q_{heat}+\dot Q_{source}=\dot Q_{cool}+\dot Q_{false}+\eta_{motor}P. \end{equation} -\begin{equation} -{T_{src,CH}} = \sum\limits_{CH = 1}^n {{T_{src,out}}} \frac{{{{\dot m}_{src,CH}}}}{{{m_{src,sys}}}} -\end{equation} - -When all chiller-heaters are in heat recovery mode, it calculates a mass-weighed chilled water temperature (\emph{T\(_{cw,CH}\)}) and hot water temperature (\emph{T\(_{hw,CH}\)}) as follows: +Cooling-only operation routes evaporator heat to chilled water and all condenser heat to the source. Heating-only operation routes all condenser heat to hot water and extracts evaporator heat from the source. In mode 3, chilled water receives the useful evaporator result and hot water receives the complete condenser result. In mode 4, hot water receives its requested useful heat and the rest of the condenser result goes to the source. In mode 5, chilled water retains its useful cooling while the source supplies the remaining evaporator heat required to produce the hot-water result. -\begin{equation} -{T_{cw,CH}} = \sum\limits_{CH = 1}^n {{T_{cw,out}}} \frac{{{{\dot m}_{cw,CH}}}}{{{m_{cw,sys}}}} -\end{equation} +The wrapper reports the sums of module useful cooling, useful heating, signed source heat transfer, and compressor electric power. Ancillary power is added once according to its schedule. Heating-only and heating-dominant operation assign ancillary power to the heating end use; the remaining active modes assign it to cooling. A blank ancillary schedule means continuously available. All runtime values and cached simultaneous results are cleared when the plant does not run the component. -\begin{equation} -{T_{hw,CH}} = \sum\limits_{CH = 1}^n {{T_{hw,out}}} \frac{{{{\dot m}_{hw,CH}}}}{{{m_{hw,sys}}}} -\end{equation} +\subsection{Connection Flow and Outlet Mixing} +\label{connection-flow-and-outlet-mixing} -In cooling or heating dominant simultaneous cooling-heating mode (mode 4 and 5), at least one chiller-heater should be in heat recovery mode, and the other(s) are in either mode 4 or mode 5. The system is connected to three loops such as chilled water loop, hot water loop, and source water loop. The model thus calculates a mass-weighed chilled water temperature (\emph{T\(_{cw,CH}\)}), hot water temperature (\emph{T\(_{hw,CH}\)}), and source water temperature (\emph{T\(_{src,CH}\)}) as follows: +The chilled-water connection uses each module's evaporator design flow, the hot-water connection uses its explicit hot-water design flow, and the source connection allows the larger of its source-extraction and source-rejection design flows. Each heat exchanger uses density and specific heat from its connected plant loop, so a glycol source loop is not evaluated with chilled-water or hot-water properties. -\begin{equation} -{T_{cw,CH}} = \sum\limits_{CH = 1}^n {{T_{cw,out}}} \frac{{{{\dot m}_{cw,CH}}}}{{{m_{cw,sys}}}} -\end{equation} +If every module performance object in a wrapper specifies variable flow, the wrapper operates as variable flow. If any module specifies constant flow, the wrapper is constant flow. A legacy bank containing both types is accepted with a warning and every module in that bank uses the constant-flow fallback. -\begin{equation} -{T_{hw,CH}} = \sum\limits_{CH = 1}^n {{T_{hw,out}}} \frac{{{{\dot m}_{hw,CH}}}}{{{m_{hw,sys}}}} -\end{equation} +For connection \(k\), let \(\dot m_{k,sys}\) be the plant flow through the wrapper and \(\dot m_{k,i}\) and \(T_{k,i,out}\) be the actual flow and outlet temperature of module \(i\). Sequential allocation ensures \begin{equation} -{T_{src,CH}} = \sum\limits_{CH = 1}^n {{T_{src,out}}} \frac{{{{\dot m}_{src,CH}}}}{{{m_{src,sys}}}} +\sum_i\dot m_{k,i}\leq\dot m_{k,sys}. \end{equation} -The model then calculates a mass-weighed temperature for the by-pass flows remained in the system as follows: +The bypass flow is \begin{equation} -{T_{cw,bypass}} = {T_{cw,i}}\frac{{{{\dot m}_{cw,bypass}}}}{{{m_{cw,sys}}}} +\dot m_{k,bypass}=\dot m_{k,sys}-\sum_i\dot m_{k,i}, \end{equation} -\begin{equation} -{T_{hw,bypass}} = {T_{hw,i}}\frac{{{{\dot m}_{hw,bypass}}}}{{{m_{hw,sys}}}} -\end{equation} +and the wrapper outlet temperature is \begin{equation} -{T_{src,bypass}} = {T_{src,i}}\frac{{{{\dot m}_{src,bypass}}}}{{{m_{src,sys}}}} +T_{k,sys,out}=\frac{\sum_i\dot m_{k,i}T_{k,i,out}+\dot m_{k,bypass}T_{k,sys,in}}{\dot m_{k,sys}}. \end{equation} -The outlet temperatures at each outlet node of the system are then determined as it sums both mass-weighed temperatures up as follows: +When the connection has no flow or no active module flow, its outlet temperature remains at the inlet temperature. The same mixing calculation is applied independently to chilled water, hot water, and the source fluid using the final connection-specific module results. -\begin{equation} -{T_{cw,sys}} = {T_{cw,CH}} + {T_{cw,bypass}} -\end{equation} - -\begin{equation} -{T_{hw,sys}} = {T_{hw,CH}} + {T_{hw,bypass}} -\end{equation} +\subsection{Plant Capacity and Design Flow Reporting} +\label{plant-capacity-and-design-flow-reporting} -\begin{equation} -{T_{src,sys}} = {T_{src,CH}} + {T_{src,bypass}} -\end{equation} +Plant design-capacity queries are connection specific. The chilled-water connection reports useful evaporator cooling capacity. The hot-water connection reports useful condenser heating capacity, including the compressor power fraction delivered to the refrigerant. The source connection reports a positive capacity-magnitude envelope that covers either cooling heat rejection or heating heat extraction; signed source transfer is used only for runtime reporting. -The total heat transfer energy of the system is also calculated in the same manner as the temperature calculations. The model simply sums all heat transfer energy of the chiller-heaters depending on their operating modes. +Maximum and optimum capacities are bank totals. Minimum capacity is the smallest independently stageable module load rather than the sum of every module minimum. This preserves the actual staging behavior of identical and heterogeneous banks. diff --git a/doc/engineering-reference/src/simulation-models-encyclopedic-reference-003/chillerheaterperformance-electric-eir.tex b/doc/engineering-reference/src/simulation-models-encyclopedic-reference-003/chillerheaterperformance-electric-eir.tex index fb03a0c51a3..cc04a43955f 100644 --- a/doc/engineering-reference/src/simulation-models-encyclopedic-reference-003/chillerheaterperformance-electric-eir.tex +++ b/doc/engineering-reference/src/simulation-models-encyclopedic-reference-003/chillerheaterperformance-electric-eir.tex @@ -1,386 +1,200 @@ -\section{ChillerHeaterPerformance:Electric:EIR }\label{chillerheaterperformanceelectriceir} +\section{ChillerHeaterPerformance:Electric:EIR } +\label{chillerheaterperformanceelectriceir} -\subsection{Overview}\label{overview-008} +\subsection{Overview} +\label{overview-008} -The object simulates the performance of a chiller-heater which can receive pre-cooled or pre-heated water from the source loop, and provide cooling, heating, or simultaneous cooling-heating. The object needs to work with the Central Heat Pump System object to be controlled properly. This model does not simulate the thermal performance or the power consumption of associated pumps or cooling towers. The Central Heat Pump System object holds the input/output nodes connection of the chiller-heater and its control scheme, once the chiller-heater is properly referred. +The \emph{ChillerHeaterPerformance:Electric:EIR} object describes one electric vapor-compression module in a \emph{CentralHeatPumpSystem}. The module can provide chilled-water cooling, hot-water heating, or simultaneous cooling and heating while rejecting heat to or extracting heat from the source loop. Pump, ground heat exchanger, and cooling-tower power are not included in this performance model. -\subsection{Model Description}\label{model-description-005} +The model uses two sets of reference data and empirical curves. The cooling set applies to cooling-only operation. The heating set applies to heating-only and all simultaneous modes. Every active calculation produces one thermodynamic module result; the parent system then routes that result to the chilled-water, hot-water, and source connections without changing its heat balance. -The model uses user-input performance information at design conditions along with three performance curves (curve objects) for cooling capacity and efficiency to determine chiller operation at off-design conditions. Three additional performance curves for heating capacity and efficiency are used when the chiller is operating in a heating-only mode or simultaneous cooling-heating mode. +\subsection{Reference Performance and Curve Inputs} +\label{reference-performance-and-curve-inputs} -\subsubsection{Cooling-only mode}\label{cooling-only-mode} - -The following nomenclature is used in the cooling equations: - -\emph{CompMotorEffic} is the compressor motor efficiency - -\emph{CompPower\(_{clg}\)} is the compressor power (W) - -\emph{CompPower\(_{@PLRmin}\)} is the compressor power at the minimum part-load ratio (W) - -\({C_{p,cw}}\) is the chilled water specific heat (J/kg-K) - -\emph{CyclingRatio} is the compressor cycling ratio which is equal to \emph{PLR\(_{actual}\)} / \emph{PLR\(_{min}\)} - -\emph{EvapCapAvail\(_{clg}\)} is the available full-load cooling capacity at current conditions (W) - -\emph{EvapCapFT\(_{clg}\)} is the cooling capacity function of temperature curve - -\emph{EIRFT\(_{clg}\)} is the electric input to cooling output factor for temperature function curve - -\emph{EIRFPLR\(_{clg}\)} is the electric input to cooling output factor for part-load function curve - -\({\dot m_{cw}}\) is the chilled water mass flow rate (kg/s) - -\({\dot m_{avail,cw}}\) is the chilled water maximum available mass flow rate (kg/s) - -\emph{PLR\(_{clg}\)} is the cooling part-load ratio which is equal to \emph{CoolingLoad} / \emph{EvapCapAvail\(_{clg}\)} - -\emph{PLR\(_{actual}\)} is the actual part-load ratio at current conditions - -\emph{PLR\(_{min}\)} is the minimum part-load ratio - -\(QCon{d_{clg}}\) is the total condenser heat transfer energy (J) - -\(\dot QCon{d_{clg}}\) is the condenser heat transfer rate (W) - -\(QEva{p_{clg}}\) is the total evaporator heat transfer energy (J) - -\(\dot QEva{p_{clg}}\) is the evaporator heat transfer rate (W) - -\({\dot Q_{falseloading,clg}}\) is the false loading rate (W) - -\emph{RefCOP\(_{clg}\)} is the reference coefficient of performance (W/W) - -\emph{RefEvapCap\(_{clg}\)} is the reference evaporator capacity (W) - -\emph{FullLoadPwr\(_{clg}\)} is the reference full load power which is equal to \emph{EvapCapAvail\(_{clg}\)} / \emph{RefCOP\(_{clg}\)} (W) - -\emph{T\(_{cond}\)} is either the entering or the leaving condenser water temperature depending on user input for the condenser water independent variable: \emph{T\(_{cond,l}\)} if ``LeavingCondenser'' is chosen or \emph{T\(_{cond,e}\)} if ``EnteringCondenser'' is chosen. - -\emph{T\(_{cond,e}\)} is the entering condenser water temperature (\(^{\circ}\)C) - -\emph{T\(_{cond,l}\)} is the leaving condenser water temperature (\(^{\circ}\)C) - -\emph{T\(_{cw,e}\)} is the entering chilled water~ temperature (W) - -\emph{T\(_{cw,l}\)} is the leaving chilled water~ temperature (W) - -\(\Delta {T_{cw}}\) is the chilled water inlet and outlet temperature difference (\(^{\circ}\)C) - -\(\Delta {T_{cw}}_{,\max }\) is the maximum chilled water inlet and outlet temperature difference (\(^{\circ}\)C). - -The model sequentially calls each chiller-heater module in the order defined in the Central Heat Pump System object. It then determines cooling load that each chiller-heater needs to meet and water flow rates delivered to each chiller-heater. Once each chiller-heater is assumed to operate, it determines cooling capacity and efficiency using user-supplied performance information. - -Three performance curves are used in the calculation of cooling capacity and efficiency as follows: - -\begin{enumerate} -\item Cooling mode cooling capacity function of temperature curve (\emph{EvapCapFT\(_{clg}\)}) -\item Cooling mode electric input to cooling output ratio function of temperature curve (\emph{EIRFT\(_{clg}\)}) -\item Cooling mode electric input to cooling output ratio function of part load ratio curve (\emph{EIRFPLR\(_{clg}\)}) -\end{enumerate} - -The Cooling Capacity Function of Temperature Curve (\emph{EvapCapFT\(_{clg}\)}) represents the fraction of the cooling capacity of the chiller-heater as it varies by temperature. The curve should have a value of 1.0 at the reference conditions. The output of a bi-quadratic curve with the input variables being the leaving chilled water temperature and either the entering or leaving condenser water temperature is given by: +The reference cooling evaporator capacity and compressor power are \begin{equation} -EvapCapF{T_{clg}} = a + b{T_{cw,l}} + c{T_{cw,l}}^2 + d{T_{Cond}} + e{T_{Cond}}^2 + f{T_{cw,l}}{T_{Cond}} +\dot Q_{evap,ref,c}=RefEvapCap_c \end{equation} -The Cooling Mode Electric Input to Cooling Output Ratio Function of Temperature (EIRFT\(_{clg}\)) curve represents the fraction of electricity to the chiller-heater at full load as it varies by temperature. The output of a bi-quadratic curve with the input variables being the leaving chilled water temperature and either the entering or leaving condenser water temperature is given by: - \begin{equation} -EIRF{T_{clg}} = a + b{T_{cw,l}} + c{T_{cw,l}}^2 + d{T_{Cond}} + e{T_{Cond}}^2 + f{T_{cw,l}}{T_{Cond}} +P_{ref,c}=\frac{RefEvapCap_c}{COP_{ref,c}}. \end{equation} -The Cooling Mode Electric Input to Cooling Output Ratio Function of Part Load Ratio (EIRFPLR\(_{clg}\)) curve represents the fraction of electricity to the chiller-heater as the load on the chiller varies at a given set of operating temperatures. The curve is normalized so that at full load the value of the curve should be 1.0. Note that the bi-cubic formulation below is generally only valid when LeavingCondenser variable is chosen for the field of Cooling Mode Condenser Water Temperature Curve Input Variable whereas the quadratic curve can be used for both choices, i.e., LeavingCondenser and EnteringCondenser. Bi-cubic may also be used when the chiller-heater uses a variable-speed compressor motor drive. The output of this curve can be determined by one of the following three performance curves: +The heating-mode inputs are ratios relative to the cooling reference point. They define the reference evaporator capacity, compressor power, and evaporator-based reference COP used by the heating curves: \begin{equation} -EIRFPL{R_{clg}} = a + b{T_{Cond,l}} + c{T_{Cond,l}}^2 + dPLR + ePL{R^2} + f{T_{Cond,l}}PLR + g \cdot 0 + hPL{R^3} + i \cdot 0 + j \cdot 0 +\dot Q_{evap,ref,h}=R_Q RefEvapCap_c \end{equation} \begin{equation} -EIRFPL{R_{clg}} = a + bPLR + cPL{R^2} +P_{ref,h}=R_P P_{ref,c} \end{equation} \begin{equation} -EIRFPL{R_{clg}} = a + bPLR + cPL{R^2} + dPL{R^3} +COP_{ref,h}=\frac{\dot Q_{evap,ref,h}}{P_{ref,h}}. \end{equation} -The full-load cooling capacity at specific temperature operating conditions (i.e., at temperatures different from the design temperatures) is then computed as follows: +For either curve set, denoted below by subscript \(m\), the capacity and EIR temperature modifiers are biquadratic functions of the evaporator leaving temperature and a selected condenser temperature: \begin{equation} -EvapCapAvai{l_{clg}} = RefEvapCa{p_{clg}} \cdot EvapCapF{T_{clg}} +F_{Q,m}=a+bT_{evap,l}+cT_{evap,l}^{2}+dT_{cond}+eT_{cond}^{2}+fT_{evap,l}T_{cond} \end{equation} -The model then determines current chiller-heater's evaporator heat transfer rate based on the total cooling load required a central heat pump system to meet and the maximum available chiller-heater cooling capacity. The maximum evaporator temperature difference between the entering chilled water temperature (\emph{T\(_{cw,e}\)}) and the leaving chilled water temperature (\emph{T\(_{cw,l}\)}) obtained from the plant loop setpoint temperature can also be determined. It then calculates mass flow rate for variable flow control chiller-heaters and the temperature difference for constant flow control chiller-heaters, setting the cooling load each chiller-heater needs to meet equal to the evaporator heat transfer rate. - -As for variable flow control chiller-heaters, the chilled water mass flow rate is computed as follows: - \begin{equation} -{\dot m_{cw}} = \frac{{\dot QEva{p_{clg}}}}{{{C_{p,cw}}\Delta {T_{cw,max}}}} +F_{EIR,T,m}=a+bT_{evap,l}+cT_{evap,l}^{2}+dT_{cond}+eT_{cond}^{2}+fT_{evap,l}T_{cond}. \end{equation} -The chilled water mass flow rate calculated is then compared to the maximum available mass flow rate for individual chiller-heaters. If the calculated one is bigger than the maximum, the model sets the chilled water mass flow rate equal to the maximum. It then adjusts the temperature difference based on the evaporator heat transfer rate and the maximum mass flow rate. If the adjusted temperature difference also exceeds the maximum, the model finally adjusts the evaporator heat transfer rate at the maximum temperature difference and mass flow rate as follows: +The independent variable \(T_{cond}\) is the actual entering condenser temperature when \emph{EnteringCondenser} is selected and the actual leaving condenser temperature when \emph{LeavingCondenser} is selected. The cooling and heating selections are independent. Reference normalization checks use the corresponding reference entering or leaving temperature; reference temperatures are rating conditions, not operating setpoints or equipment cutoffs. -\begin{equation} -\dot QEva{p_{clg}} = {\dot m_{avail,cw}}{C_{p,cw}}\Delta {T_{cw,max}} -\end{equation} - -As for constant flow control chiller-heaters, the model calculates chilled water temperature difference as follows: +The available full-load evaporator capacity at the current temperatures is \begin{equation} -\Delta {T_{cw}} = \frac{{\dot QEva{p_{clg}}}}{{{{\dot m}_{avail,cw}}{C_{p,cw}}}} +\dot Q_{evap,avail,m}=\dot Q_{evap,ref,m}F_{Q,m}. \end{equation} -The temperature difference calculated is then compared to the maximum temperature difference allowed. If the calculated one is bigger than the maximum, the model sets the chilled water temperature difference equal the maximum, and then adjusts the evaporator heat transfer rate at the given conditions as follows: +The EIR part-load modifier may be a quadratic or cubic function of part-load ratio, or a bicubic function of condenser temperature and part-load ratio. The bicubic form is -\begin{equation} -\dot QEva{p_{clg}} = {\dot m_{avail,cw}}{C_{p,cw}}\Delta {T_{cw,max}} -\end{equation} +\begin{align} +F_{EIR,PLR,m}={}&a+bT_{cond}+cT_{cond}^{2}+dPLR+ePLR^{2}+fT_{cond}PLR \\ +&+gT_{cond}^{3}+hPLR^{3}+iT_{cond}^{2}PLR+jT_{cond}PLR^{2}. +\end{align} -The model then calculates the part-load ratio as the ratio of the evaporator heat transfer rate to the available chiller-heater capacity as follows: +For a bicubic curve, condenser temperature is the first independent variable and PLR is the second. Therefore, the model obtains the minimum and maximum PLR from the second curve domain. For a univariate curve it uses the only domain. The domain must have a nonnegative minimum, must include PLR 1.0, and must contain the applicable optimum PLR. -\begin{equation} -PL{R_{c\lg }} = \frac{{\dot QEva{p_{c\lg }}}}{{\dot QEvapAvai{l_{c\lg }}}} -\end{equation} +\subsection{Part-Load Operation and Energy Balance} +\label{part-load-operation-and-energy-balance} -The part-load ratio calculated is set to be between the maximum of 1.0 and the minimum of 0.0 when it is out of the range. Once the part-load ratio is calculated the cycling ratio and false loading rate can be obtained as follows: +The model reports the final operating PLR used to evaluate the EIRFPLR curve, the cycling ratio, and any false load from the same accepted operating point. For a cooling or simultaneous evaporator target, an unconstrained load ratio is \begin{equation} -CyclingRatio = \frac{{PL{R_{{\rm{actual}}}}}}{{PL{R_{{\rm{min}}}}}} +r=\frac{\dot Q_{evap,target}}{\dot Q_{evap,avail,m}}. \end{equation} -\begin{equation} -{\dot Q_{falseloading,c\lg }} = EvapCapAvai{l_{{\rm{clg}}}} \cdot PL{R_{c\lg }} \cdot CyclingRatio - \dot QEva{p_{{\rm{clg}}}} -\end{equation} - -The compressor power demand is then computed by: +The operating PLR and cycling ratio are \begin{equation} -CompPowe{r_{c\lg }} = RefFullLoadPowe{r_{{\rm{clg}}}} \cdot {\rm{ }}EIRF{T_{c\lg }}{\rm{ }} \cdot EIRFPL{R_{c\lg }} \cdot CyclingRatio +PLR=\min\left(PLR_{max},\max\left(r,PLR_{min}\right)\right) \end{equation} -The heat transfer rate for the chiller-heater condenser can then be computed as follows: - \begin{equation} -\dot QCon{d_{clg}} = ComPowe{r_{clg}} \cdot CompMotorEffic + \dot QEva{p_{clg}} + {\dot Q_{falseloading,clg}} +CR=\begin{cases} +\min\left(1,r/PLR_{min}\right) & PLR_{min}>0 \\ +1 & PLR_{min}=0. +\end{cases} \end{equation} -The total heat transfer energy by the evaporator and condenser can be calculated as follows: +The false-load rate is the portion of the cycled, unloaded evaporator capacity that is not useful evaporator heat transfer: \begin{equation} -QEva{p_{clg}} = \dot QEva{p_{clg}} \cdot TimeStepSys \cdot 3600 +\dot Q_{false}=\max\left(0,\dot Q_{evap,avail,m}PLR\,CR-\dot Q_{evap}\right). \end{equation} +Compressor electric power at the accepted point is + \begin{equation} -QCon{d_{c\lg }} = \dot QCon{d_{c\lg }} \cdot TimeStepSys \cdot 3600 +P=\frac{\dot Q_{evap,avail,m}}{COP_{ref,m}}F_{EIR,T,m}F_{EIR,PLR,m}CR. \end{equation} -Water consumption for the evaporative-cooled condenser is calculated using the difference in air humidity level across the evaporative media and the condenser air mass flow rate: +Let \(\eta_{motor}\) be the compressor motor efficiency input. The fraction \(\eta_{motor}P\) enters the refrigerant cycle; the remaining \((1-\eta_{motor})P\) is reported as external motor loss and is not added to the condenser water. The refrigerant-cycle balance is \begin{equation} -{V_{water}} = \frac{{{{\dot{m}_{air}}\left( {{\omega_{evapcond,out}} - {\omega_{evapcond,in}}} \right)}}}{{{\rho_{water}}}}\left( {PLR} \right)\left( {TimeStepSys} \right)\left( {3600} \right) +\dot Q_{cond}=\dot Q_{evap}+\dot Q_{false}+\eta_{motor}P. \end{equation} -where: - -\({V_{water}}\) is the DX cooling coil evap condenser water consumption (m\(^{3}\)) - -\({\dot{m}_{air}}\) is the evaporative condenser air mass flow rate (kg/s) - -\({\omega_{evapcond,in}}\) is the humidity ratio of outdoor air entering the evap condenser (kg/kg) - -\({\omega_{evapcond,out}}\) is the humidity ratio of air leaving the evap condenser (kg/kg) - -\({\rho_{water}}\) is the density of water at the outdoor dry-bulb temperature (kg/m\(^{3}\)) - -\emph{TimeStepSys} is the HVAC system simulation time step (hr). - - -\subsubsection{Heating-only mode and Simultaneous cooling-heating mode}\label{heating-only-mode-and-simultaneous-cooling-heating-mode} - -The following nomenclature is used in the heating equations: +Rate variables are converted to energy using the HVAC system time step in seconds. No heat-transfer quantity is overwritten to force a water outlet temperature. Flow or temperature limits reduce delivered load and leave the difference as unmet load. -\emph{CompMotorEffic} is the compressor motor efficiency +\subsection{Cooling-Only Mode} +\label{cooling-only-mode} -\emph{CompPower\(_{htg}\)} is the compressor power demand (W) - -\emph{CompPower\(_{@PLRmin}\)} is the compressor power at the minimum part-load ratio (W) - -\({C_{p,evap}}\) is the evaporator water specific heat (J/kg-K) - -\({C_{p,hw}}\) is the hot water specific heat (J/kg-K) - -\emph{CyclingRatio} is the compressor cycling ratio which is equal to \emph{PLR\(_{actual}\)} / \emph{PLR\(_{min}\)} - -\emph{EvapCapAvail\(_{htg}\)} is the available full-load cooling capacity at current conditions (W) - -\emph{EvapCapFT\(_{htg}\)} is the heating mode cooling capacity function of temperature curve - -\emph{EIRFT\(_{htg}\)} is the electric input to cooling output factor for temperature function curve - -\emph{EIRFPLR\(_{htg}\)} is the electric input to cooling output factor for part-load function curve - -\({\dot m_{avail,evap}}\) is the evaporator water maximum available mass flow rate (kg/s) - -\({\dot m_{avail,hw}}\) is the condenser water maximum available mass flow rate (kg/s) - -\({\dot m_{hw}}\) is the hot water mass flow rate (kg/s) - -\emph{PLR\(_{htg}\)} is the cooling part-load ratio which is equal to \emph{RefCap} / \emph{EvapCapAvail\(_{htg}\)} - -\emph{PLR\(_{max}\)} is the maximum part-load ratio at current conditions - -\emph{PLR\(_{min}\)} is the minimum part-load ratio - -\(QCon{d_{htg}}\) is the total condenser heat transfer energy (J) - -\(\dot QCondAvai{l_{htg}}\) is the available full-load heating capacity at current conditions (W) - -\(\dot QCon{d_{htg}}\) is the condenser heat transfer rate (W) - -\(QEva{p_{htg}}\) is the total evaporator heat transfer energy (J) - -\(\dot QEva{p_{htg}}\) is the evaporator heat transfer rate (W) - -\({\dot Q_{falseloading,htg}}\) is the false loading rate (W) - -\emph{RefCOP\(_{htg}\)} is the reference coefficient of performance (W/W) - -\emph{RefEvapCap\(_{htg}\)} is the reference evaporator capacity (W) - -\emph{FullLoadPwr\(_{htg}\)} is the reference full load power which is equal to \emph{EvapCapAvail\(_{htg}\)}/ \emph{RefCOP\(_{htg}\)} (W) - -\emph{T\(_{cond}\)} is either entering or leaving condenser water temperature depending on user input for condenser water independent variable. \emph{T\(_{cond,l}\)} if ``LeavingCondenser'' is chosen or \emph{T\(_{cond,e}\)} if ``EnteringCondenser'' is chosen. - -\emph{T\(_{cond,e}\)} is the entering condenser water temperature (\(^{\circ}\)C) - -\emph{T\(_{cond,l}\)} is the leaving condenser water temperature (\(^{\circ}\)C) - -\emph{T\(_{cw,l}\)} is the leaving chilled water temperature (\(^{\circ}\)C) - -\emph{T\(_{hw,e}\)} is the entering hot water temperature (\(^{\circ}\)C) - -\emph{T\(_{hw,l}\)} is the leaving hot water temperature (\(^{\circ}\)C) - -\(\Delta {T_{evap}}\) is the evaporator inlet and outlet water temperature difference (\(^{\circ}\)C) - -\(\Delta {T_{hw}}\) is the hot water inlet and outlet temperature difference (\(^{\circ}\)C) - -\(\Delta {T_{hw,max}}\) is the maximum hot water inlet and outlet temperature difference (\(^{\circ}\)C). - -The calculations for the evaporator side are similar to the cooling-only mode calculations. The evaporator capacity and efficiency is determined by a different set of three performance curves read in the cooling-only mode, and the performance curve set is used for both heating-only mode and simultaneous cooling-heating mode. During these modes, the evaporator side is not connected to the chilled water loop, but source water loop. The model thus assumes that each chiller-heater does not meet the plant loop chilled water setpoint temperature while the evaporator operates at the full load capacity to produce heating at a constant water flow rate. - -The model sequentially calls each chiller-heater module in the order of the definition in the central heat pump system. It then determines heating load that each chiller-heater needs to meet and water flow rates delivered to each chiller-heater. Once each chiller-heater is assumed to operate, it determines heating capacity and efficiency using the following performance curves: - -\begin{enumerate} -\item Heating mode cooling capacity function of temperature curve (\emph{EvapCapFT\(_{htg}\)}) -\item Heating mode electric input to cooling output ratio function of temperature curve (\emph{EIRFT\(_{htg}\)}) -\item Heating mode electric input to cooling output ratio function of part load ratio curve (\emph{EIRFPLR\(_{htg}\)}) -\end{enumerate} - -The output of a Heating Mode Cooling Capacity Function of Temperature curve with the input variables being the leaving chilled water temperature and either the entering or leaving condenser water temperature is given by: +In cooling-only mode, the evaporator is connected to the chilled-water loop and the condenser is connected to the source loop. The model first establishes the chilled-water outlet target from the active plant setpoint, subject to the evaporator low-temperature limit. The load-side flow limit is \begin{equation} -EvapCapF{T_{htg}} = a + b{T_{cw,l}} + c{T_{cw,l}}^2 + d{T_{Cond}} + e{T_{Cond}}^2 + f{T_{cw,l}}{T_{Cond}} +\dot Q_{flow,c}=\dot m_{cw,max}c_{p,cw}\left(T_{cw,in}-T_{cw,target}\right). \end{equation} -The output of a Heating Mode Cooling Output Ratio Function of Temperature curve with the input variables being the leaving chilled water temperature and either the entering or leaving condenser water temperature is given by: +The useful evaporator load cannot exceed the requested cooling load, the maximum curve-domain capacity, or this flow limit: \begin{equation} -EIRF{T_{htg}} = a + b{T_{cw,l}} + c{T_{cw,l}}^2 + d{T_{Cond}} + e{T_{Cond}}^2 + f{T_{cw,l}}{T_{Cond}} +\dot Q_{evap}=\min\left(\dot Q_{cool,req},\dot Q_{evap,avail,c}PLR_{max},\dot Q_{flow,c}\right). \end{equation} -The output of Heating Mode Cooling Output Ratio Function of Part Load Ratio curve can be determined by one of the following three performance curves as follows: +A variable-flow bank requests the flow needed to meet the outlet target, limited by available module and wrapper flow. A constant-flow bank uses the allocated flow and calculates the resulting outlet temperature. Chilled-water and source specific heats are evaluated from their respective plant-loop fluids. -\begin{equation} -EIRFPL{R_{htg}} = a + b{T_{Cond,l}} + c{T_{Cond,l}}^2 + dPLR + ePL{R^2} + f{T_{Cond,l}}PLR + g \cdot 0 + hPL{R^3} + i \cdot 0 + j \cdot 0 -\end{equation} +Capacity, power, false load, evaporator outlet temperature, and condenser outlet temperature are iterated because a leaving-condenser curve depends on the heat rejection calculated from those same curves. All condenser heat is rejected to the source loop. The delivered cooling is \(\dot Q_{evap}\), and any flow- or capacity-limited remainder is reported as unmet cooling load. -\begin{equation} -EIRFPL{R_{htg}} = a + bPLR + cPL{R^2} -\end{equation} - -\begin{equation} -EIRFPL{R_{htg}} = a + bPLR + cPL{R^2} + dPL{R^3} -\end{equation} +\subsection{Heating-Only Mode} +\label{heating-only-mode} -The full-load evaporator capacity at specific temperature operating conditions is then given by: +In heating-only mode, the evaporator extracts heat from the source loop and the condenser supplies the hot-water loop. The source-side evaporator heat is limited by allocated source flow, the source fluid specific heat, and the heating-mode evaporator leaving-temperature low limit: \begin{equation} -EvapCapAvai{l_{htg}} = RefEvapCa{p_{htg}} \cdot EvapCapF{T_{htg}} +\dot Q_{source,max}=\dot m_{source}c_{p,source}\left(T_{source,in}-T_{source,out,min}\right). \end{equation} -The part-load ratio is set to be between zero and the maximum, and the evaporator heat transfer rate is computed by: +The hot-water condenser load is limited by allocated flow and by the plant heating setpoint when one is present. The reference heating leaving temperature is a rating condition and is not treated as an equipment operating limit. + +At each temperature iteration the model solves for the PLR whose condenser heat matches the attainable heating target. For a target below the condenser heat at minimum PLR, the compressor operates at minimum PLR and cycles. Otherwise, a bounded bisection solve finds PLR between the minimum and the flow-limited maximum. Each candidate reevaluates EIRFPLR, compressor power, and \begin{equation} -\dot QEva{p_{htg}} = EvapCapAvai{l_{htg}}PL{R_{htg}} +\dot Q_{cond}(PLR)=\dot Q_{evap,avail,h}PLR+\eta_{motor}P(PLR). \end{equation} -The evaporator inlet and outlet temperature difference is then given by: +The accepted evaporator extraction, power, condenser delivery, and outlet temperatures therefore belong to one operating point. A variable-flow bank may reduce hot-water flow to the flow needed at the active outlet limit; a constant-flow bank uses its allocation. The heating COP is useful condenser heat divided by compressor electric power. -\begin{equation} -\Delta {T_{evap}} = \frac{{\dot QEva{p_{htg}}}}{{{{\dot m}_{avail,evap}}{C_{p,evap}}}} -\end{equation} +\subsection{Simultaneous Cooling and Heating Modes} +\label{simultaneous-cooling-and-heating-modes} -Once the part-load ratio is calculated the cycling ratio and false loading rate are computed by: +When cooling and heating are requested together, each staged module is solved as one refrigerant cycle using the heating curve set. The useful chilled-water load and source extraction share the evaporator result; useful hot-water heating and source rejection share the condenser result. The routing equations are \begin{equation} -CyclingRatio = \frac{{PL{R_{{\rm{actual}}}}}}{{PL{R_{{\rm{min}}}}}} +\dot Q_{cool}=\min\left(\dot Q_{cool,target},\dot Q_{evap}\right) \end{equation} \begin{equation} -{\dot Q_{falseloading,htg}} = EvapCapAvai{l_{{\rm{htg}}}} \cdot PL{R_{htg}} \cdot CyclingRatio - \dot QEva{p_{{\rm{htg}}}} +\dot Q_{source,extract}=\max\left(0,\dot Q_{evap}-\dot Q_{cool}\right) \end{equation} -The compressor power demand is then computed by: - \begin{equation} -CompPowe{r_{htg}} = FullLoadPowe{r_{htg}} \cdot EIRF{T_{htg}} \cdot EIRFPL{R_{htg}} \cdot CyclingRatio +\dot Q_{heat}=\min\left(\dot Q_{heat,target},\dot Q_{cond}\right) \end{equation} -The heat transfer rate of the chiller-heater condenser is then computed as follows: - \begin{equation} -\dot QCondAvai{l_{htg}} = ComPowe{r_{htg}} \cdot CompMotorEffic + \dot QEva{p_{htg}} + {\dot Q_{falseloading,htg}} +\dot Q_{source,reject}=\max\left(0,\dot Q_{cond}-\dot Q_{heat}\right). \end{equation} -Once condenser available heating capacity is determined, the model calculates current chiller-heater's condenser heat transfer rate based on the total heating load required a central heat pump system to meet as well as available heating capacity of the chiller-heater. The maximum condenser temperature difference between the entering hot water temperature (\emph{T\(_{hw,e}\)}) and the leaving hot water temperature (\emph{T\(_{hw,l}\)}) obtained from the plant loop setpoint temperature can also be obtained. It then calculates condenser water mass flow rate for variable flow control chiller-heaters and the hot water temperature difference for constant flow control chiller-heaters, setting the cooling load that each chiller-heater needs to meet equal the evaporator heat transfer rate. - -As for variable flow control chiller-heaters, the condenser water mass flow rate is computed as follows: +Source heat transfer is positive for rejection and negative for extraction: \begin{equation} -{\dot m_{hw}} = \frac{{\dot QCon{d_{htg}}}}{{{C_{p,hw}}\Delta {T_{hw,max}}}} +\dot Q_{source}=\dot Q_{source,reject}-\dot Q_{source,extract}. \end{equation} -The condenser water mass flow rate calculated is then compared to the maximum available mass flow rate for individual chiller-heaters. If the calculated one is bigger than the maximum, the model sets the condenser water mass flow rate equal the maximum. It then adjusts the hot water temperature difference at the maximum mass flow rate. If the adjusted temperature difference also exceeds the maximum, the model finally adjusts the condenser heat transfer rate at the maximum allowable conditions as follows: +Combining this routing definition with the refrigerant balance gives the three-loop balance \begin{equation} -\dot QCon{d_{htg}} = {\dot m_{avail,hw}}{C_{p,hw}}\Delta {T_{hw,max}} +\dot Q_{heat}+\dot Q_{source}=\dot Q_{cool}+\dot Q_{false}+\eta_{motor}P. \end{equation} -As for constant flow control chiller-heaters, the model calculates condenser temperature difference as follows: +The simultaneous modes are selected from the final signed source transfer: -\begin{equation} -\Delta {T_{hw}} = \frac{{\dot QCon{d_{htg}}}}{{{{\dot m}_{avail,hw}}{C_{p,hw}}}} -\end{equation} +\begin{itemize} +\item Mode 3, balanced heat recovery: \(\dot Q_{source}\) is zero within numerical tolerance. All condenser heat is useful hot-water heating and no source flow is required. +\item Mode 4, cooling-dominant: \(\dot Q_{source}>0\). Hot water receives the useful heating target and all remaining condenser heat is rejected to the source. +\item Mode 5, heating-dominant: \(\dot Q_{source}<0\). Useful chilled-water cooling is preserved and the remaining evaporator heat is extracted from the source. +\end{itemize} -The temperature difference calculated is then compared to maximum hot water temperature difference. If the calculated one is bigger than the maximum, the model sets the hot water temperature difference equal the maximum, and then adjusts the condenser heat transfer rate at the given conditions as follows: +If cooling operation alone would not produce enough condenser heat, the cycle evaporator load is increased, subject to source-extraction capacity, until the heating target or an operating limit is reached. If cooling would produce excess condenser heat and no source rejection is available, the cycle is reduced to an attainable balanced point and the remaining cooling request is unmet. -\begin{equation} -\dot QCon{d_{htg}} = {\dot m_{avail,hw}}{C_{p,hw}}\Delta {T_{hw,max}} -\end{equation} +When useful load and source duty share a heat exchanger, the curve temperature is the flow-weighted final refrigerant-side connection temperature. Thus the simultaneous temperature iteration updates the blended evaporator leaving temperature and blended condenser entering and leaving temperatures before reevaluating the capacity and EIR curves. -Finally, the total heat transfer energy by the evaporator and condenser can then be calculated as follows: +\subsection{Iteration, Flow Allocation, and System Assembly} +\label{iteration-flow-allocation-and-system-assembly} -\begin{equation} -QEva{p_{htg}} = \dot QEva{p_{htg}} \cdot TimeStepSys \cdot 3600 -\end{equation} +Cooling-only, heating-only, and simultaneous calculations under-relax their temperature estimates and stop when the largest temperature change is no greater than \(10^{-8}\) C or after 100 iterations. Heating and simultaneous load matching use up to 80 bounded bisection iterations. At convergence, the reported curve inputs, modifiers, power, heat transfer, and outlet temperatures describe the accepted state. The single-mode solvers explicitly reevaluate their final operating point after the temperature iteration. -\begin{equation} -QCon{d_{htg}} = \dot QCon{d_{htg}} \cdot TimeStepSys \cdot 3600 -\end{equation} +The parent \emph{CentralHeatPumpSystem} stages modules sequentially in input order. Each module receives no more than the remaining chilled-water, hot-water, and source flow. Heterogeneous modules retain their own capacities, curves, and connection-specific design flows. Bypass flow is mixed with the actual module outlet flows at the wrapper outlet. If all performance objects in a wrapper specify variable flow, the bank is variable flow. A constant-flow object or a legacy mixture of constant- and variable-flow objects makes the complete bank constant flow; mixed input produces a warning. -\subsection{References}\label{references-008} +\subsection{References} +\label{references-008} Central Geothermal Systems, Applications Engineering Manual, Trane Company, April 2010, SYS-APM009-EN. diff --git a/doc/input-output-reference/src/overview/group-plant-equipment.tex b/doc/input-output-reference/src/overview/group-plant-equipment.tex index 38db854fadd..c17460282ec 100644 --- a/doc/input-output-reference/src/overview/group-plant-equipment.tex +++ b/doc/input-output-reference/src/overview/group-plant-equipment.tex @@ -7207,11 +7207,11 @@ \subsubsection{Outputs}\label{outputs-16-000} \subsection{CentralHeatPumpSystem}\label{centralheatpumpsystem} -This is a central geothermal application that contains one or more chiller-heaters centrally located in the building; the available chilled and/or hot water is then piped to the individual zones. Chiller-heaters used for this particular system can be of two types: 1) standard vapor-compression, non-reversible cycle chillers designed for heat recovery or 2) reversible-cycle, water-to-water heat pump chillers. Unlike a distributed ground source heat pump configuration where individual heat pumps are located in each zone, a centralized geothermal configuration has one or more chiller-heaters. Its function is to encapsulate the extra controls needed to turn individual chiller-heater modules on/off and whether they are to operate in cooling-only, heating-only or simultaneous cooling-heating mode and whether to connect the source water to the evaporator or condenser side. A variety of control schemes can be designed by setting schedules for both zone control types and individual chiller-heaters schedules. +This central plant application contains one or more electric chiller-heater modules that serve chilled-water and hot-water loops and exchange residual heat with a source loop. Each module may operate in cooling-only, heating-only, balanced heat-recovery, cooling-dominant simultaneous, or heating-dominant simultaneous mode. In simultaneous operation one module may connect to all three loops: useful evaporator heat serves chilled water, useful condenser heat serves hot water, and only the residual is rejected to or extracted from the source. -The fluid used in this central system is usually water, and there is no sharing of condenser or evaporator water between multiple machines. However, the control logic is such that the source water can be delivered to individual chiller-heaters depending on their operating mode, e.g., modules in simultaneous cooling-heating mode receive no source water, modules in heating-only mode can have source water directed to their evaporator, or modules in cooling-only mode can have source water directed to their condenser; the decision on which module(s) receives the source water dictated by the `smart' controls. The following figures illustrate node interconnections between this central geothermal application and plant and condenser loops in various situations. +The source loop may contain water or another plant fluid such as glycol. Cooling-only operation connects the module condenser to the source. Heating-only operation connects the module evaporator to the source. Simultaneous operation connects the source only when the useful chilled-water and hot-water loads do not balance the refrigerant cycle. Source heat transfer is positive when heat is rejected to the source and negative when heat is extracted from it. The following figures illustrate representative loop connections and staging arrangements. -The order of the multiple chiller-heaters' operation is assumed to be sequential. In other words, the very first chiller-heater will be called at first to see if it meets all loads that the central heat pump system should meet. If the chiller-heater meets all loads, the rest chiller-heaters are assumed to be turned off. If not, the following chiller-heater will be called to meet the remaining loads until all loads are met in the order as defined in the set of individual chiller-heater objects below. The order of individual chiller-heater modules needs to be carefully arranged in case users are intended to see the performance of various combinations of different sizes of chiller-heaters in a central heat pump system. +Modules operate sequentially in input order. Each available module receives the remaining cooling and heating requests and no more than the remaining chilled-water, hot-water, and source flow. Its delivered loads and actual flows are subtracted before the next module is called. This preserves the capacities, curves, schedules, and connection-specific design flows of heterogeneous module banks. \begin{figure}[hbtp] % fig 76 \centering @@ -7231,7 +7231,7 @@ \subsection{CentralHeatPumpSystem}\label{centralheatpumpsystem} \caption{Diagram of a central heat pump system with one chiller-heater in heat recovery mode and two chiller-heaters in cooling-only mode \protect \label{fig:diagram-of-a-central-heat-pump-system-with-002}} \end{figure} -In the above example, the cooling load needs 3 chiller-heaters and the heating load needs 1 chiller heater. Chiller 1 is in heat recovery mode and isolated from the ground source loop while chillers 2 and 3 are in cooling-only mode, their condensers rejecting heat to the ground source loop. +In the above staging example, the cooling load needs three modules and the heating load needs one. The first module operates in balanced heat recovery while the remaining modules reject their condenser heat to the source. The production model can also route useful cooling, useful heating, and residual source rejection within a single cooling-dominant module. \begin{figure}[hbtp] % fig 79 \centering @@ -7239,7 +7239,7 @@ \subsection{CentralHeatPumpSystem}\label{centralheatpumpsystem} \caption{Diagram of a central heat pump system with two chiller-heaters in heat recovery mode and one chiller-heater in heating-only mode \protect \label{fig:diagram-of-a-central-heat-pump-system-with-003}} \end{figure} -In the above example, the heating load needs 3 chiller-heaters and the cooling load needs two chiller-heaters. Chillers 1 and 2 are in heat recovery mode and isolated from the ground source loop while chiller 3 is in heating-only mode, its evaporator extracting heat from the ground source loop. +In the above staging example, the heating load needs three modules and the cooling load needs two. The first two modules operate in balanced heat recovery and the final module extracts evaporator heat from the source. The production model can also preserve useful chilled-water cooling and route the remaining evaporator heat to the source within a single heating-dominant module. Users are required to define three different nodes such as chilled water, hot water, and source water nodes. Only this central heat pump system will be metered, and individual chiller-heaters' energy will be available for reporting only, but not metered. @@ -7275,11 +7275,11 @@ \subsubsection{Inputs}\label{inputs-19-004} \paragraph{Field: Ancillary Power}\label{field-ancillary-power-000} -This numeric field contains the ancillary power of the central heat pump system in watts. +This numeric field contains the ancillary electric power of the central heat pump system in watts. It is added once while the system is active and is multiplied by the Ancillary Operation Schedule Name value. \paragraph{Field: Ancillary Operation Schedule Name}\label{field-ancillary-operation-schedule-name} -This alpha field contains the identifying name of the ancillary power schedule. +This optional alpha field contains the ancillary power schedule. A blank field means that ancillary power is continuously available. Heating-only and heating-dominant operation assign scheduled ancillary power to the heating electric end use; the remaining active modes assign it to cooling. An invalid nonblank schedule name is a fatal input error. \paragraph{Field Set: Individual Chiller-Heater Module Objects}\label{field-set-individual-chiller-heater-module-objects} @@ -7313,7 +7313,7 @@ \subsubsection{Inputs}\label{inputs-19-004} HWInlet, !- Heating Loop Inlet Node Name HWOutlet, !- Heating Loop Outlet Node Name 460, !- Ancillary Power {W} - , !- Ancillary Operation Schedule Name + , !- Ancillary Operation Schedule Name (blank = AlwaysOn) ChillerHeaterPerformance:Electric:EIR, !- Chiller Heater Modules Object Type 1 ChillerHeaterModule 1, !- Chiller Heater Modules Performance Component Name 1 ON, !- Chiller Heater Modules Control Schedule Name 1 @@ -7371,13 +7371,13 @@ \subsubsection{Outputs}\label{outputs-17-000} \paragraph{Chiller Heater System Cooling Electricity Energy {[}J{]}}\label{chiller-heater-system-cooling-electric-energy-j} -These outputs are the sums of the cooling electric power consumption of the central heat pump system containing one or more child objects. Cooling Energy is metered on Cooling:Electricity, Electricity:Plant, and Electricity:Facility. +These outputs are the sum of module cooling electric consumption and any scheduled ancillary power assigned to the cooling end use. Cooling Energy is metered on Cooling:Electricity, Electricity:Plant, and Electricity:Facility. \paragraph{Chiller Heater System Heating Electricity Rate {[}W{]}}\label{chiller-heater-system-heating-electric-power-w} \paragraph{Chiller Heater System Heating Electricity Energy {[}J{]}}\label{chiller-heater-system-heating-electric-energy-j} -These outputs are the sums of the heating electric power consumption of the central heat pump system. Heating Energy is metered on Heating:Electricity, Electricity:Plant, and Electricity:Facility. +These outputs are the sum of module heating electric consumption and any scheduled ancillary power assigned to the heating end use. Heating Energy is metered on Heating:Electricity, Electricity:Plant, and Electricity:Facility. \paragraph{Chiller Heater System Cooling Rate {[}W{]}}\label{chiller-heater-system-cooling-rate-w} @@ -7395,7 +7395,7 @@ \subsubsection{Outputs}\label{outputs-17-000} \paragraph{Chiller Heater System Source Heat Transfer Energy {[}J{]}}\label{chiller-heater-system-source-heat-transfer-energy-j} -These outputs are heat transfer which is the heat rejected to a ground source loop or extracted from the source loop, depending on the operating modes of the chiller-heater modules. This is the sums of evaporator heat transfer of the chiller-heater modules in heating-only mode or heating dominant simultaneous cooling-heating mode while the sums of condenser heat transfer of the chiller-heater modules in cooling-only mode or cooling dominant simultaneous cooling-heating mode. Source Heat Transfer Energy is metered on HeatRejection:EnergyTransfer, EnergyTransfer:Plant, and EnergyTransfer:Facility. +These outputs are the signed residual heat transfer at the source connection. A positive value is heat rejected to the source in cooling-only or cooling-dominant operation. A negative value is heat extracted from the source in heating-only or heating-dominant operation. Balanced heat recovery reports zero source transfer within numerical tolerance. Source Heat Transfer Energy is metered on HeatRejection:EnergyTransfer, EnergyTransfer:Plant, and EnergyTransfer:Facility. \paragraph{Chiller Heater System Cooling Inlet Temperature {[}C{]}}\label{chiller-heater-system-cooling-inlet-temperature-c} @@ -7423,7 +7423,7 @@ \subsubsection{Outputs}\label{outputs-17-000} \subsection{ChillerHeaterPerformance:Electric:EIR}\label{chillerheaterperformancelectriceir} -The performance of the chiller-heater will be defined by two sets of curves meant to describe unloading for: 1) cooling-only mode, and 2) heating-only mode or simultaneous cooling-heating mode. Reference conditions must be defined for both because each has its own set of three unloading curves based on their associated reference conditions. The cooling-mode curves are typically (but not always) based on condenser entering water temperature while the heating-only mode curves are typically based on condenser leaving water temperature. This chiller-heater object allows the user to specify whether to use condenser leaving or condenser entering as a dependent variable to differentiate, if necessary, the condenser temperature basis used to generate the cooling- and heating-only mode curves. +The chiller-heater uses two sets of curves: one for cooling-only mode and one for heating-only and simultaneous cooling-heating modes. Each set contains capacity, EIR-temperature, and EIR-part-load curves with its own reference conditions. The selected EnteringCondenser or LeavingCondenser convention is applied independently to each curve set at both reference and operating conditions. Reference temperatures are rating conditions; they are not operating setpoints or equipment temperature limits. \subsubsection{Field: Name}\label{field-name-19-002} @@ -7435,7 +7435,7 @@ \subsubsection{Field: Reference Cooling Mode Evaporator Capacity}\label{field-re \subsubsection{Field: Reference Cooling Mode COP}\label{field-reference-cooling-mode-cop} -This numeric field contains the reference coefficient of performance of the evaporator for cooling. It must be based on the values of Reference Cooling Mode Leaving Chilled Water Temperature and Reference Cooling Mode Entering Condenser Water Temperature defined in the next two fields. +This positive numeric field contains the reference evaporator cooling capacity divided by compressor electric power. It must be based on the Reference Cooling Mode Leaving Chilled Water Temperature and on either the reference entering or leaving condenser temperature selected by Cooling Mode Temperature Curve Condenser Water Independent Variable. \subsubsection{Field: Reference Cooling Mode Leaving Chilled Water Temperature}\label{field-reference-cooling-mode-leaving-chilled-water-temperature} @@ -7451,45 +7451,43 @@ \subsubsection{Field: Reference Cooling Mode Leaving Condenser Water Temperature \subsubsection{Field: Reference Heating Mode Cooling Capacity Ratio}\label{field-reference-heating-mode-cooling-capacity-ratio} -This numeric field contains the evaporator's capacity during the reference heating-only or simultaneous cooling-heating mode as a ratio of reference cooling capacity. For example, the ratio may be expressed as follows: +This positive numeric field is the reference evaporator capacity for heating-only and simultaneous modes divided by the Reference Cooling Mode Evaporator Capacity: \begin{equation} - Ratio = \frac{{EvapCapHt{g_{@Tchw,l = 6.67C,Tcond,l = 51.67C}}}}{{EvapCapC{{\lg }_{@Tchw,l = 6.67C,Tcond,l = 35.0C}}}} +R_Q=\frac{\dot Q_{evap,ref,h}}{\dot Q_{evap,ref,c}}. \end{equation} -where Tchw,l is the leaving chilled water temperature and Tcond,l is the leaving condenser water temperature representing the heating-mode and cooling-mode reference temperatures defined elsewhere in this object. +The default is 0.75. The derived heating-mode reference evaporator capacity is used with the heating capacity curve. In heating-only operation the evaporator extracts heat from the source. In simultaneous operation the evaporator result is divided between useful chilled-water cooling and any residual source extraction. -The default is 0.75. This field is used to determine evaporator capacity in heating-only mode or simultaneous cooling-heating mode, multiplying it by the value entered in the field of the Reference Cooling Mode Evaporator Capacity. Note that even when there is no cooling load, i.e., the machine is in heating-only mode, the evaporator must still run and extract heat from the source water. In heating-only mode, the leaving chilled water temperature (Tchw,l) may ~float depending on the evaporator water flow rate and the source water temperature. In simultaneous cooling and heating mode, the leaving chilled water temperature (Tchw,l) does not float but is determined by the chilled water setpoint controls. For both heating-only and simultaneous cooling and heating mode, the leaving condenser temperature (Tcond,l) is determined by the hot water supply setpoint controls. This value of Tchw,l, combined with Tcond,l is plugged into the CAPFT and EIRFT curves to determine the off-rated heating-mode evaporator capacity and associated compressor power. +\subsubsection{Field: Reference Heating Mode Cooling Power Input Ratio}\label{field-reference-heating-mode-cooling-power-ratio} -\subsubsection{Field: Reference Heating Mode Cooling Power Ratio}\label{field-reference-heating-mode-cooling-power-ratio} - -This numeric field contains the compressor power during simultaneous cooling-heating mode as a ratio of reference cooling compressor power. For example, the ratio may be expressed as follows: +This positive numeric field is the heating-mode reference compressor power divided by the cooling-mode reference compressor power: \begin{equation} - Ratio = \frac{{Powe{r_{@Tchw,l = 6.67C,Tcond,l = 51.67C}}}}{{Powe{r_{@Tchw,l = 6.67C,Tcond,l = 35.0C}}}} +R_P=\frac{P_{ref,h}}{P_{ref,c}}. \end{equation} -The default value is 1.5. This field is used to determine full load compressor power at the simultaneous cooling-heating mode's reference chilled water leaving and condenser water leaving temperatures. Note: In heating-only mode, the chilled water leaving temperature floats so the EIRFT curve is used to modify the simultaneous cooling-heating full load compressor power value. +The default value is 1.38. Together with the capacity ratio, it defines the heating curve set's evaporator-based reference COP. The heating EIR temperature and part-load curves modify this reference power at the final operating temperatures and PLR. \subsubsection{Field: Reference Heating Mode Leaving Chilled Water Temperature}\label{field-reference-heating-mode-leaving-chilled-water-temperature} -This numeric field contains the reference leaving chilled water temperature of the evaporator in simultaneous cooling-heating mode in Celsius. This is typically the same as the reference cooling mode value and is used to create the heating-only mode and simultaneous cooling-heating mode unloading curves. The leaving chilled water temperature for the chiller-heater is not allowed to fall below this value during the heating-only mode and simultaneous cooling-heating mode. The default value is 6.67$^\circ$C. +This numeric field contains the evaporator leaving temperature at which the heating and simultaneous curve set is normalized. It is typically the same as the cooling reference value. It is a rating temperature, not an operating low limit; the Heating Mode Entering Chilled Water Temperature Low Limit field provides the model's source-side low-temperature constraint. The default value is 6.67$^\circ$C. \subsubsection{Field: Reference Heating Mode Leaving Condenser Water Temperature}\label{field-reference-heating-mode-leaving-condenser-water-temperature} -This numeric field contains the reference leaving condenser water temperature of the condenser in heating mode in Celsius. This field is typically controlled to the design hot water supply temperature, and used to create the heating-only mode and simultaneous cooling-heating mode unloading curves. The default value is 60.0$^\circ$C. +This numeric field contains the condenser leaving temperature at which the heating and simultaneous curve set is normalized. It commonly corresponds to a manufacturer hot-water supply rating point. It does not impose an operating setpoint or maximum temperature. The default value is 60.0$^\circ$C. \subsubsection{Field: Reference Heating Mode Entering Condenser Water Temperature}\label{field-reference-heating-mode-entering-condenser-water-temperature} -This numeric field contains the reference entering condenser water temperature of the condenser in heating mode in Celsius. This field typically represents the upper limit of the source water temperature. The default value is 29.44$^\circ$C. +This numeric field contains the condenser entering temperature at which the heating and simultaneous curve set is normalized. In heating-only mode this is the hot-water return temperature. During simultaneous operation it is the flow-weighted refrigerant condenser entering temperature when hot-water flow and source-rejection flow share the condenser. The default value is 29.44$^\circ$C. \subsubsection{Field: Heating Mode Entering Chilled Water Temperature Low Limit}\label{field-heating-mode-entering-chilled-water-temperature-low-limit} -This numeric field contains the low limit of the entering chilled water temperature in heating-only mode or simultaneous cooling-heating mode in Celsius. This field typically represents the lower limit of the source water temperature. If necessary, auxiliary heat will be activated to prevent source water temperature falling below the chilled water entering minimum. The default value is 12.2$^\circ$C. +This numeric field supplies the source-side low-temperature constraint for heating-only and simultaneous operation. The model applies it as the minimum fluid temperature leaving the source-connected evaporator, despite the legacy field name. Source flow or load is limited so the outlet does not fall below this value; the object does not activate auxiliary heat. The default value is 12.2$^\circ$C. \subsubsection{Field: Chilled Water Flow Mode Type}\label{field-chilled-water-flow-mode-type} -This alpha field contains the chilled water flow control mode. Valid water flow mode includes constant flow and variable flow, and the default water flow mode is constant flow. +This alpha field selects ConstantFlow or VariableFlow for the module, with ConstantFlow as the default. Flow mode is resolved for the complete parent CentralHeatPumpSystem. A bank is variable flow only when every referenced performance object specifies VariableFlow. If the bank mixes constant- and variable-flow objects, EnergyPlus issues a warning and applies ConstantFlow consistently to every module in that wrapper. \subsubsection{Field: Design Chilled Water Flow Rate}\label{field-design-chilled-water-flow-rate-8} @@ -7501,11 +7499,11 @@ \subsubsection{Field: Design Condenser Water Flow Rate}\label{field-design-conde \subsubsection{Field: Design Hot Water Flow Rate}\label{field-design-hot-water-flow-rate-2} -This numeric field contains the design hot water flow rate in m\(^{3}\)/s. The minimum value and default value for this field are zero, and this field is autosizable. +This numeric field contains the explicit design hot-water flow rate in m\(^{3}\)/s. The minimum and default are zero. This field is not autosizable. It supplies the module's hot-water connection flow limit and should be entered consistently with the intended heating capacity and temperature rise. \subsubsection{Field: Compressor Motor Efficiency}\label{field-compressor-motor-efficiency} -This numeric field contains the fraction of efficiency of the compressor electrical energy consumption that must be rejected by the condenser. This value must be between 0.0 and 1.0, and the default value is 1.0. +This numeric field is the fraction of compressor electric power delivered to the refrigerant cycle. The refrigerant condenser balance includes this fraction; the remaining motor loss is external to the modeled water-side cycle. The value must be from 0.0 through 1.0. A hermetic compressor normally uses 1.0, which is also the default. \subsubsection{Field: Cooling Mode Temperature Curve Condenser Water Independent Variable}\label{field-cooling-mode-temperature-curve-condenser-water-independent-variable} @@ -7521,7 +7519,7 @@ \subsubsection{Field: Cooling Mode Electric Input to Cooling Output Ratio Functi \subsubsection{Field: Cooling Mode Electric Input to Cooling Output Ratio Function of Part Load Ratio Curve Name}\label{field-cooling-mode-electric-input-to-cooling-output-ratio-function-of-part-load-ratio-curve-name} -This alpha field contains the name of a performance curve (ref: Performance Curves) that parameterizes the variation of the energy input to cooling output ratio (EIR) as a function of the part-load ratio (EIRFPLR). The EIR is the inverse of the COP, and the part-load ratio is the actual cooling load divided by the available cooling capacity of the chiller-heater. This curve is generated by dividing the operating electric input power by the available full-load capacity at the specific operating temperatures. The curve output should decrease from 1 towards 0 as part-load ratio decreases from 1 to 0. Note that the bi-cubic formulation is generally only valid when LeavingCondenser variable is chosen for the field of Cooling Mode Condenser Water Temperature Curve Input Variable whereas the quadratic curve can be used both choices, i.e., LeavingCondenser and EnteringCondenser. The output of this curve is then multiplied by the reference full-load EIR and the EIRFT to give the EIR at the specific temperatures and part-load ratio at which the chiller-heater is operating. This curve should have a value of 1.0 when the part-load ratio equals 1.0. The curve should be valid for the range of part-load ratios anticipated for the simulation. +This alpha field names the cooling EIR part-load modifier curve. It may be univariate in PLR or bicubic in condenser temperature and PLR. For a bicubic curve, the first independent variable is the configured entering or leaving condenser temperature and the second is PLR. Its PLR domain must have a nonnegative minimum, include 1.0, and contain the Cooling Mode Cooling Capacity Optimum Part Load Ratio. The curve should equal 1.0 at PLR 1.0 and the cooling reference condenser temperature. The model evaluates it at the final operating PLR and condenser temperature. \subsubsection{Field: Cooling Mode Cooling Capacity Optimum Part Load Ratio}\label{field-cooling-mode-cooling-capacity-optimum-part-load-ratio} @@ -7529,7 +7527,7 @@ \subsubsection{Field: Cooling Mode Cooling Capacity Optimum Part Load Ratio}\lab \subsubsection{Field: Heating Mode Temperature Curve Condenser Water Independent Variable}\label{field-heating-mode-temperature-curve-condenser-water-independent-variable} -This alpha field determines whether the entering or leaving condenser water temperature is used in the heating mode unloading performance curves that follow. Valid variables include EnteringCondenser and LeavingCondenser, and the default variable is EnteringCondenser. The condenser temperature used for the cooling mode unloading performance curves will be dependent on this field input. For example, leaving condenser water temperature will be used when the LeavingCondenser is chosen, otherwise entering condenser water temperature. +This alpha field determines whether the actual entering or leaving refrigerant condenser temperature is used by the heating and simultaneous performance curves. Valid choices are EnteringCondenser and LeavingCondenser; the default is LeavingCondenser. During simultaneous operation, the selected temperature is the final flow-weighted condenser connection temperature when useful hot-water heating and source rejection share the condenser. \subsubsection{Field: Heating Mode Cooling Capacity Function of Temperature Curve Name}\label{field-heating-mode-cooling-capacity-function-of-temperature-curve-name} @@ -7541,7 +7539,7 @@ \subsubsection{Field: Heating Mode Electric Input to Cooling Output Ratio Functi \subsubsection{Field: Heating Mode Electric Input to Cooling Output Ratio Function of Part Load Ratio Curve Name}\label{field-heating-mode-electric-input-to-cooling-output-ratio-function-of-part-load-ratio-curve-name} -This alpha field contains the name of a performance curve (ref: Performance Curves) that parameterizes the variation of the energy input to cooling output ratio (EIR) as a function of the part-load ratio (EIRFPLR). The EIR is the inverse of the COP, and the part-load ratio is the actual evaporator load divided by the available evaporator capacity of the chiller-heater at the reference heating and simultaneous cooling-heating mode temperatures. This curve is generated by dividing the operating electric input power by the available full-load capacity (do not divide by load) at the specific operating temperatures. The curve output should decrease from 1 towards 0 as part-load ratio decreases from 1 to 0. Note that the bicubic formulation below can only be used when the chiller-heater uses a variable speed compressor motor drive. It is also generally valid only when LeavingCondenser variable is chosen for the field of Cooling Mode Condenser Water Temperature Curve Input Variable whereas the quadratic curve can be used both choices, i.e., LeavingCondenser and EnteringCondenser. The output of this curve is then multiplied by the reference full-load EIR (inverse of the reference COP) and the EIRFT to give the EIR at the specific temperatures and part-load ratio at which the chiller-heater is operating. This curve should have a value of 1.0 when the part-load ratio equals 1.0. The curve should be valid for the range of part-load ratios anticipated for the simulation. +This alpha field names the heating and simultaneous EIR part-load modifier curve. It may be univariate in PLR or bicubic in condenser temperature and PLR. For a bicubic curve, the first independent variable is the heating mode's configured entering or leaving condenser temperature and the second is PLR. Its PLR domain must have a nonnegative minimum, include 1.0, and contain the Heating Mode Cooling Capacity Optimum Part Load Ratio. The curve should equal 1.0 at PLR 1.0 and the corresponding heating reference condenser temperature. Heating-only and simultaneous solvers reevaluate it at the final operating PLR and condenser temperature. \subsubsection{Field: Heating Mode Cooling Capacity Optimum Part Load Ratio}\label{field-heating-mode-cooling-capacity-optimum-part-load-ratio} @@ -7549,7 +7547,7 @@ \subsubsection{Field: Heating Mode Cooling Capacity Optimum Part Load Ratio}\lab \subsubsection{Field: Sizing Factor}\label{field-sizing-factor-12} -This optional numeric field allows the user to specify a sizing factor for this component. The Sizing Factor is a multiplier on the design plant chilled water loop flow rate associated with the chiller-heaters parent \hyperref[centralheatpumpsystem]{CentralHeatPumpSystem} object. The chiller-heater's modified chilled water flow can then be used to autocalculate the Design Condenser Water Flow Rate and Reference Cooling Mode Evaporator Capacity values. In general, it is best to autosize all three fields or set fixed values for all three fields. +This optional positive numeric field multiplies the autosized Reference Cooling Mode Evaporator Capacity, Design Chilled Water Flow Rate, and Design Condenser Water Flow Rate for this performance object. It does not calculate or scale the explicit Design Hot Water Flow Rate. In general, use consistent hard-sized values or autosize all fields that support autosizing. An example of this statement in an IDF is: @@ -7663,13 +7661,13 @@ \subsubsection{Chiller Heater Operation Mode Unit \textless{}x\textgreater{} \pr heating dominant simultaneous cooling-heating mode. \end{enumerate} -The first mode 0 is reported when the chiller-heater is turned off. The next two modes 2 and 3 are reported when the chiller-heater provides only either cooling or heating, respectively. The last three modes 3 to 5 indicate when the chiller-heater is in a simultaneous cooling-heating mode. Mode 3 indicates the chiller-heaters provides simultaneous cooling and heating \textbf{\emph{without}} heat exchange with the ground source. Mode 4 denotes that at least one of the chiller-heater modules in the central heat pump system provides both cooling and heating, and the chiller-heater is meeting remaining cooling demand (see Figure~\ref{fig:diagram-of-a-central-heat-pump-system-with-002}. Diagram of a central heat pump system with one chiller-heater in heat recovery mode and two chiller-heaters in cooling-only mode) Similarly, mode 5 indicates that at least one of the chiller-heater modules in the central heat pump system is in the heat recovery mode, and the chiller-heater is meeting remaining heating demand (see Figure~\ref{fig:diagram-of-a-central-heat-pump-system-with-003}). +Mode 0 is off. Modes 1 and 2 are cooling-only and heating-only, respectively. Modes 3 through 5 are simultaneous. Mode 3 is balanced heat recovery and has no source heat transfer within numerical tolerance. Mode 4 is cooling-dominant: the module serves useful cooling and heating and rejects all residual condenser heat to the source. Mode 5 is heating-dominant: the module preserves useful chilled-water cooling and extracts the remaining evaporator heat from the source. Thus a mode 4 or 5 module can have nonzero flow on all three connections. Note that the decision to operate individual chiller-heater modules is solely dependent on the chiller-heater schedule and loads. A fraction may appear in case the chiller-heater mode varies within a zone time step. In this particular case, users may define a detailed reporting frequency for this output variable. \subsubsection{Chiller Heater Part Load Ratio Unit \textless{}x\textgreater{} \protect\hyperlink{section-1}{~}}\label{chiller-heater-part-load-ratio-unit-x} -This output is the ratio of the evaporator heat transfer rate plus the false load heat transfer rate (if applicable) to the available chiller-heater capacity. This value is used to determine ChillerEIRFPLR. +This output is the final operating part-load ratio used to evaluate the EIRFPLR curve. Below the curve's minimum PLR, the reported PLR remains at the minimum and Cycling Ratio decreases to meet the load. It therefore corresponds to the same accepted operating point as the reported curve modifiers, power, heat transfer, and false load. \subsubsection{Chiller Heater Cycling Ratio Unit \textless{}x\textgreater{} \protect\hyperlink{section-1}{~}}\label{chiller-heater-cycling-ratio-unit-x} @@ -7679,13 +7677,13 @@ \subsubsection{Chiller Heater Cooling Electricity Rate Unit \textless{}x\textgre \subsubsection{Chiller Heater Cooling Electricity Energy Unit \textless{}x\textgreater{} {[}J{]}}\label{chiller-heater-cooling-electric-energy-unit-x-j} -These outputs are the cooling electric power consumption of the chiller-heater. +These outputs report the cooling-mode electric power and energy consumption of the chiller-heater. \subsubsection{Chiller Heater Heating Electricity Rate Unit \textless{}x\textgreater{} {[}W{]}}\label{chiller-heater-heating-electric-power-unit-x-w} \subsubsection{Chiller Heater Heating Electricity Energy Unit \textless{}x\textgreater{} {[}J{]}}\label{chiller-heater-heating-electric-energy-unit-x-j} -These outputs are the heating electric power consumption of the chiller-heater. +These outputs report the heating-mode electric power and energy consumption of the chiller-heater. \subsubsection{Chiller Heater Cooling Rate Unit \textless{}x\textgreater{} {[}W{]}}\label{chiller-heater-cooling-rate-unit-x-w} @@ -7699,40 +7697,40 @@ \subsubsection{Chiller Heater False Load Heat Transfer Energy Unit \textless{}x\ These outputs are the heat transfer rate and total heat transfer due to false loading of the chiller-heater. When the chiller-heater part-load ratio is below the minimum unloading ratio, the chiller-heater false loads (e.g.~hot-gas bypass) to further reduce capacity. -\subsubsection{Chiller Heater Condenser Heat Transfer Rate Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-condenser-heat-transfer-rate-unit-x-c} +\subsubsection{Chiller Heater Condenser Heat Transfer Rate Unit \textless{}x\textgreater{} {[}W{]}}\label{chiller-heater-condenser-heat-transfer-rate-unit-x-w} -\subsubsection{Chiller Heater Condenser Heat Transfer Energy Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-condenser-heat-transfer-energy-unit-x-c} +\subsubsection{Chiller Heater Condenser Heat Transfer Energy Unit \textless{}x\textgreater{} {[}J{]}}\label{chiller-heater-condenser-heat-transfer-energy-unit-x-j} -These outputs are the heat transfer which is the heating delivered by the chiller-heater module in heating mode. +These outputs are total refrigerant condenser heat transfer. In cooling-only mode it is rejected to the source. In heating-only mode it is delivered to hot water. In simultaneous modes it is divided between useful hot-water heating and any residual source rejection. \subsubsection{Chiller Heater Evaporator Inlet Temperature Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-evaporator-inlet-temperature-unit-x-c} \subsubsection{Chiller Heater Evaporator Outlet Temperature Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-evaporator-outlet-temperature-unit-x-c} -\subsubsection{Chiller Heater Evaporator Mass Flow Rate Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-evaporator-mass-flow-rate-unit-x-c} +\subsubsection{Chiller Heater Evaporator Mass Flow Rate Unit \textless{}x\textgreater{} {[}kg/s{]}}\label{chiller-heater-evaporator-mass-flow-rate-unit-x-kgs} -These outputs are the evaporator water inlet and outlet temperatures and flow rate. Note that these represent the chilled water temperatures and flow rate in cooling mode or the source water temperature and flow rate in heating mode. +These outputs are the final refrigerant evaporator inlet and outlet temperatures and total flow. They represent chilled water in cooling-only mode and source fluid in heating-only mode. In simultaneous operation they may be flow-weighted values combining useful chilled-water cooling with source extraction. \subsubsection{Chiller Heater Condenser Inlet Temperature Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-condenser-inlet-temperature-unit-x-c} \subsubsection{Chiller Heater Condenser Outlet Temperature Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-condenser-outlet-temperature-unit-x-c} -\subsubsection{Chiller Heater Condenser Mass Flow Rate Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-condenser-mass-flow-rate-unit-x-c} +\subsubsection{Chiller Heater Condenser Mass Flow Rate Unit \textless{}x\textgreater{} {[}kg/s{]}}\label{chiller-heater-condenser-mass-flow-rate-unit-x-kgs} -These outputs are the condenser water inlet and outlet temperatures and flow rate. Note that these represent the hot water temperatures and flow rate in heating mode or the source water temperature and flow rate in cooling mode. +These outputs are the final refrigerant condenser inlet and outlet temperatures and total flow. They represent source fluid in cooling-only mode and hot water in heating-only mode. In simultaneous operation they may be flow-weighted values combining useful hot-water heating with source rejection. -\subsubsection{Chiller Heater COP Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-cop-unit-x-c} +\subsubsection{Chiller Heater COP Unit \textless{}x\textgreater{} {[]}}\label{chiller-heater-cop-unit-x} -This output is the coefficient of performance for the chiller-heater. It is calculated as the evaporator heat transfer rate divided by the chiller-heater electric power. +This output is the coefficient of performance at the accepted operating point. In cooling-only mode it is evaporator heat including false load divided by compressor electric power. In heating-only and simultaneous modes it is total condenser heat divided by compressor electric power. -\subsubsection{Chiller Heater Capacity Temperature Modifier Multiplier Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-capacity-temperature-modifier-multiplier-unit-x-c} +\subsubsection{Chiller Heater Capacity Temperature Modifier Multiplier Unit \textless{}x\textgreater{} {[]}}\label{chiller-heater-capacity-temperature-modifier-multiplier-unit-x} -This is the output of the curve object Cooling Capacity Function of Temperature Curve. +This is the dimensionless output of the active Capacity Function of Temperature Curve at the final evaporator and configured condenser temperatures. -\subsubsection{Chiller Heater EIR Temperature Modifier Multiplier Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-eir-temperature-modifier-multiplier-unit-x-c} +\subsubsection{Chiller Heater EIR Temperature Modifier Multiplier Unit \textless{}x\textgreater{} {[]}}\label{chiller-heater-eir-temperature-modifier-multiplier-unit-x} -This is the output of the curve object Electric Input to Cooling Output Ratio Function of Temperature Curve. +This is the dimensionless output of the active Electric Input Ratio Function of Temperature Curve at the final temperatures. -\subsubsection{Chiller Heater EIR Part Load Modifier Multiplier Unit \textless{}x\textgreater{} {[}C{]}}\label{chiller-heater-eir-part-load-modifier-multiplier-unit-x-c} +\subsubsection{Chiller Heater EIR Part Load Modifier Multiplier Unit \textless{}x\textgreater{} {[]}}\label{chiller-heater-eir-part-load-modifier-multiplier-unit-x} -This is the output of the curve object Electric Input to Cooling Output Ratio Function of Part Load Ratio Curve. +This is the dimensionless output of the active Electric Input Ratio Function of Part Load Ratio Curve at the final PLR and, for a bicubic curve, the final configured condenser temperature. diff --git a/src/EnergyPlus/CMakeLists.txt b/src/EnergyPlus/CMakeLists.txt index 5be739bed67..0ee0913fbb8 100644 --- a/src/EnergyPlus/CMakeLists.txt +++ b/src/EnergyPlus/CMakeLists.txt @@ -510,8 +510,8 @@ set(SRC Plant/ReportLoopData.hh Plant/SplitterData.hh Plant/Subcomponents.hh - PlantCentralGSHP.cc - PlantCentralGSHP.hh + PlantCentralHeatPumpSystem.cc + PlantCentralHeatPumpSystem.hh PlantChillers.cc PlantChillers.hh PlantComponent.hh diff --git a/src/EnergyPlus/Data/CommonIncludes.hh b/src/EnergyPlus/Data/CommonIncludes.hh index 9913d265b3e..ba5c80fe448 100644 --- a/src/EnergyPlus/Data/CommonIncludes.hh +++ b/src/EnergyPlus/Data/CommonIncludes.hh @@ -228,7 +228,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/EnergyPlus/Data/EnergyPlusData.cc b/src/EnergyPlus/Data/EnergyPlusData.cc index bfbea8ee03c..a1f1b33e13e 100644 --- a/src/EnergyPlus/Data/EnergyPlusData.cc +++ b/src/EnergyPlus/Data/EnergyPlusData.cc @@ -204,7 +204,7 @@ EnergyPlusData::EnergyPlusData() this->dataPhotovoltaicThermalCollector = std::make_unique(); this->dataPipeHT = std::make_unique(); this->dataPipes = std::make_unique(); - this->dataPlantCentralGSHP = std::make_unique(); + this->dataPlantCentralHeatPumpSystem = std::make_unique(); this->dataPlantChillers = std::make_unique(); this->dataPlantCompTempSrc = std::make_unique(); this->dataPlantCondLoopOp = std::make_unique(); @@ -464,7 +464,7 @@ void EnergyPlusData::clear_state() this->dataPhotovoltaicThermalCollector->clear_state(); this->dataPipeHT->clear_state(); this->dataPipes->clear_state(); - this->dataPlantCentralGSHP->clear_state(); + this->dataPlantCentralHeatPumpSystem->clear_state(); this->dataPlantChillers->clear_state(); this->dataPlantCompTempSrc->clear_state(); this->dataPlantCondLoopOp->clear_state(); @@ -740,7 +740,7 @@ void EnergyPlusData::init_constant_state(EnergyPlusData &state) this->dataPhotovoltaicThermalCollector->init_constant_state(state); this->dataPipeHT->init_constant_state(state); this->dataPipes->init_constant_state(state); - this->dataPlantCentralGSHP->init_constant_state(state); + this->dataPlantCentralHeatPumpSystem->init_constant_state(state); this->dataPlantChillers->init_constant_state(state); this->dataPlantCompTempSrc->init_constant_state(state); this->dataPlantCondLoopOp->init_constant_state(state); @@ -1005,7 +1005,7 @@ void EnergyPlusData::init_state(EnergyPlusData &state) this->dataPhotovoltaicThermalCollector->init_state(state); this->dataPipeHT->init_state(state); this->dataPipes->init_state(state); - this->dataPlantCentralGSHP->init_state(state); + this->dataPlantCentralHeatPumpSystem->init_state(state); this->dataPlantChillers->init_state(state); this->dataPlantCompTempSrc->init_state(state); this->dataPlantCondLoopOp->init_state(state); diff --git a/src/EnergyPlus/Data/EnergyPlusData.hh b/src/EnergyPlus/Data/EnergyPlusData.hh index bf73356fd7c..14eaa219c4b 100644 --- a/src/EnergyPlus/Data/EnergyPlusData.hh +++ b/src/EnergyPlus/Data/EnergyPlusData.hh @@ -216,7 +216,7 @@ struct PhotovoltaicThermalCollectorsData; struct PhotovoltaicsData; struct PipeHeatTransferData; struct PipesData; -struct PlantCentralGSHPData; +struct PlantCentralHeatPumpSystemData; struct PlantChillersData; struct PlantCompTempSrcData; struct PlantCondLoopOperationData; @@ -475,7 +475,7 @@ struct EnergyPlusData : BaseGlobalStruct std::unique_ptr dataPhotovoltaic; std::unique_ptr dataPipeHT; std::unique_ptr dataPipes; - std::unique_ptr dataPlantCentralGSHP; + std::unique_ptr dataPlantCentralHeatPumpSystem; std::unique_ptr dataPlantChillers; std::unique_ptr dataPlantCompTempSrc; std::unique_ptr dataPlantCondLoopOp; diff --git a/src/EnergyPlus/Plant/Enums.hh b/src/EnergyPlus/Plant/Enums.hh index 4e562674d97..3e23fd53517 100644 --- a/src/EnergyPlus/Plant/Enums.hh +++ b/src/EnergyPlus/Plant/Enums.hh @@ -199,7 +199,7 @@ enum class PlantEquipmentType GrndHtExchgHorizTrench, FluidToFluidPlantHtExchg, WaterSource, - CentralGroundSourceHeatPump, + CentralHeatPumpSystem, UnitarySysRecovery, PackagedTESCoolingCoil, CoolingTower_VarSpdMerkel, diff --git a/src/EnergyPlus/Plant/PlantManager.cc b/src/EnergyPlus/Plant/PlantManager.cc index e6313315bc5..348cfd17df0 100644 --- a/src/EnergyPlus/Plant/PlantManager.cc +++ b/src/EnergyPlus/Plant/PlantManager.cc @@ -102,7 +102,7 @@ #include #include #include -#include +#include #include #include #include @@ -1342,8 +1342,8 @@ void GetPlantInput(EnergyPlusData &state) this_comp.compPtr = PhotovoltaicThermalCollectors::PVTCollectorStruct::factory(state, CompNames(CompNum)); break; } - case PlantEquipmentType::CentralGroundSourceHeatPump: { - this_comp.compPtr = PlantCentralGSHP::WrapperSpecs::factory(state, CompNames(CompNum)); + case PlantEquipmentType::CentralHeatPumpSystem: { + this_comp.compPtr = PlantCentralHeatPumpSystem::CentralHeatPumpSystem::factory(state, CompNames(CompNum)); // now deal with demand components of the ZoneHVAC type served by ControlCompOutput break; } @@ -4401,7 +4401,7 @@ void SetupBranchControlTypes(EnergyPlusData &state) this_component.HowLoadServed = DataPlant::HowMet::PassiveCap; } } break; - case DataPlant::PlantEquipmentType::CentralGroundSourceHeatPump: { // 86 + case DataPlant::PlantEquipmentType::CentralHeatPumpSystem: { // 86 this_component.FlowCtrl = DataBranchAirLoopPlant::ControlType::Active; if (LoopSideCtr == LoopSideLocation::Demand) { this_component.FlowPriority = DataPlant::LoopFlowStatus::NeedyAndTurnsLoopOn; diff --git a/src/EnergyPlus/PlantCentralGSHP.cc b/src/EnergyPlus/PlantCentralGSHP.cc deleted file mode 100644 index 56fb2d87ddf..00000000000 --- a/src/EnergyPlus/PlantCentralGSHP.cc +++ /dev/null @@ -1,3344 +0,0 @@ -// EnergyPlus, Copyright (c) 1996-present, The Board of Trustees of the University of Illinois, -// The Regents of the University of California, through Lawrence Berkeley National Laboratory -// (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge -// National Laboratory, managed by UT-Battelle, Alliance for Energy Innovation, LLC, and other -// contributors. All rights reserved. -// -// NOTICE: This Software was developed under funding from the U.S. Department of Energy and the -// U.S. Government consequently retains certain rights. As such, the U.S. Government has been -// granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, -// worldwide license in the Software to reproduce, distribute copies to the public, prepare -// derivative works, and perform publicly and display publicly, and to permit others to do so. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// (1) Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// (2) Redistributions in binary form must reproduce the above copyright notice, this list of -// conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory, -// the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific prior -// written permission. -// -// (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form -// without changes from the version obtained under this License, or (ii) Licensee makes a -// reference solely to the software portion of its product, Licensee must refer to the -// software as "EnergyPlus version X" software, where "X" is the version number Licensee -// obtained under this License and may not use a different name for the software. Except as -// specifically required in this Section (4), Licensee shall not use in a company name, a -// product name, in advertising, publicity, or other promotional activities any name, trade -// name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly -// similar designation, without the U.S. Department of Energy's prior written consent. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -// C++ Headers -#include -#include -#include - -// ObjexxFCL Headers -#include -#include - -// EnergyPlus Headers -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace EnergyPlus::PlantCentralGSHP { - -// MODULE INFORMATION: -// AUTHOR PNNL -// DATE WRITTEN Feb 2013 -// MODIFIED na -// RE-ENGINEERED na -// PURPOSE OF THIS MODULE: -// This module simulates the performance of the Central Plant GSHP systems -// It currently includes one object: ChillerHeaterPerformance:Electric:EIR. -// The other object available for this central CGSHP system such as HeatPumpPerformance:WaterToWater:EIR -// will be implemented later. - -// METHODOLOGY EMPLOYED: -// Once the PlantLoopManager determines that the Central Plant GSHP -// is available to meet a loop cooling and heating demands, it calls simulate -// which in turn calls the electric PlantCentralGSHP model. The PlantCentralGSHP model is based on -// polynomial fits of chiller/heater or heat pump performance data. - -PlantComponent *WrapperSpecs::factory(EnergyPlusData &state, std::string const &objectName) -{ - // Process the input data - if (state.dataPlantCentralGSHP->getWrapperInputFlag) { - GetWrapperInput(state); - state.dataPlantCentralGSHP->getWrapperInputFlag = false; - } - - // Now look for this particular object - for (auto &thisWrapper : state.dataPlantCentralGSHP->Wrapper) { - if (thisWrapper.Name == objectName) { - return &thisWrapper; - } - } - // If we didn't find it, fatal - ShowFatalError(state, std::format("LocalPlantCentralGSHPFactory: Error getting inputs for object named: {}", objectName)); // LCOV_EXCL_LINE -} - -void WrapperSpecs::onInitLoopEquip(EnergyPlusData &state, const PlantLocation &calledFromLocation) -{ - this->initialize(state, 0.0, calledFromLocation.loopNum); - this->SizeWrapper(state); -} - -void WrapperSpecs::getDesignCapacities( - [[maybe_unused]] EnergyPlusData &state, const PlantLocation &calledFromLocation, Real64 &MaxLoad, Real64 &MinLoad, Real64 &OptLoad) -{ - MinLoad = 0.0; - MaxLoad = 0.0; - OptLoad = 0.0; - if (calledFromLocation.loopNum == this->CWPlantLoc.loopNum) { // Chilled water loop - for (int NumChillerHeater = 1; NumChillerHeater <= this->ChillerHeaterNums; ++NumChillerHeater) { - auto const &chillerHeater = this->ChillerHeater(NumChillerHeater); - MaxLoad += chillerHeater.RefCapCooling * chillerHeater.MaxPartLoadRatCooling; - OptLoad += chillerHeater.RefCapCooling * chillerHeater.OptPartLoadRatCooling; - MinLoad += chillerHeater.RefCapCooling * chillerHeater.MinPartLoadRatCooling; - } - } else if (calledFromLocation.loopNum == this->HWPlantLoc.loopNum) { // Hot water loop - for (int NumChillerHeater = 1; NumChillerHeater <= this->ChillerHeaterNums; ++NumChillerHeater) { - auto const &chillerHeater = this->ChillerHeater(NumChillerHeater); - MaxLoad += chillerHeater.RefCapClgHtg * chillerHeater.MaxPartLoadRatClgHtg; - OptLoad += chillerHeater.RefCapClgHtg * chillerHeater.OptPartLoadRatClgHtg; - MinLoad += chillerHeater.RefCapClgHtg * chillerHeater.MinPartLoadRatClgHtg; - } - } -} - -void WrapperSpecs::getSizingFactor(Real64 &SizFac) -{ - SizFac = 1.0; -} - -void WrapperSpecs::simulate( - EnergyPlusData &state, const PlantLocation &calledFromLocation, bool FirstHVACIteration, Real64 &CurLoad, [[maybe_unused]] bool RunFlag) -{ - if (calledFromLocation.loopNum != this->GLHEPlantLoc.loopNum) { - - this->initialize(state, CurLoad, calledFromLocation.loopNum); - this->CalcWrapperModel(state, CurLoad, calledFromLocation.loopNum); - - } else if (calledFromLocation.loopNum == this->GLHEPlantLoc.loopNum) { - PlantUtilities::UpdateChillerComponentCondenserSide(state, - calledFromLocation.loopNum, - this->GLHEPlantLoc.loopSideNum, - DataPlant::PlantEquipmentType::CentralGroundSourceHeatPump, - this->GLHEInletNodeNum, - this->GLHEOutletNodeNum, - this->Report.GLHERate, - this->Report.GLHEInletTemp, - this->Report.GLHEOutletTemp, - this->Report.GLHEmdot, - FirstHVACIteration); - - // Use the first chiller heater's evaporator capacity ratio to determine dominant load - this->SimulClgDominant = false; - this->SimulHtgDominant = false; - if (this->WrapperCoolingLoad > 0 && this->WrapperHeatingLoad > 0) { - Real64 SimulLoadRatio = this->WrapperCoolingLoad / this->WrapperHeatingLoad; - if (SimulLoadRatio > this->ChillerHeater(1).ClgHtgToCoolingCapRatio) { - this->SimulClgDominant = true; - this->SimulHtgDominant = false; - } else { - this->SimulHtgDominant = true; - this->SimulClgDominant = false; - } - } - } -} - -void WrapperSpecs::SizeWrapper(EnergyPlusData &state) -{ - // SUBROUTINE INFORMATION: - // AUTHOR Yunzhi Huang, PNNL - // DATE WRITTEN Feb 2013 - // MODIFIED November 2013 Daeho Kang, add component sizing table entries - // RE-ENGINEERED na - - // PURPOSE OF THIS SUBROUTINE: - // This subroutine is for sizing all the components under each 'CentralHeatPumpSystem' object, - // for which capacities and flow rates have not been specified in the input. - - // METHODOLOGY EMPLOYED: - // Obtains evaporator flow rate from the plant sizing array. Calculates reference capacity from - // the evaporator (or load side) flow rate and the chilled water loop design delta T. The condenser - // flow (or source side) rate is calculated from the reference capacity, the COP, and the condenser - // loop design delta T. - - static constexpr std::string_view RoutineName("SizeCGSHPChillerHeater"); - - // auto-size the chiller heater components - - for (int NumChillerHeater = 1; NumChillerHeater <= this->ChillerHeaterNums; ++NumChillerHeater) { - bool ErrorsFound = false; - - // find the appropriate Plant Sizing objects - int PltSizNum = this->CWPlantLoc.loop->PlantSizNum; - int PltSizCondNum = this->GLHEPlantLoc.loop->PlantSizNum; - - auto &chillerHeater = this->ChillerHeater(NumChillerHeater); - - Real64 tmpNomCap = chillerHeater.RefCapCooling; - Real64 tmpEvapVolFlowRate = chillerHeater.EvapVolFlowRate; - Real64 tmpCondVolFlowRate = chillerHeater.CondVolFlowRate; - - // auto-size the Evaporator Flow Rate - if (PltSizNum > 0) { - if (state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate >= HVAC::SmallWaterVolFlow) { - tmpEvapVolFlowRate = state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate * chillerHeater.SizFac; - chillerHeater.tmpEvapVolFlowRate = tmpEvapVolFlowRate; - if (!chillerHeater.EvapVolFlowRateWasAutoSized) { - tmpEvapVolFlowRate = chillerHeater.EvapVolFlowRate; - } - - } else { - if (chillerHeater.EvapVolFlowRateWasAutoSized) { - tmpEvapVolFlowRate = 0.0; - } - chillerHeater.tmpEvapVolFlowRate = tmpEvapVolFlowRate; - } - if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { - if (chillerHeater.EvapVolFlowRateWasAutoSized) { - chillerHeater.EvapVolFlowRate = tmpEvapVolFlowRate; - if (state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { - BaseSizer::reportSizerOutput(state, - "ChillerHeaterPerformance:Electric:EIR", - chillerHeater.Name, - "Design Size Reference Chilled Water Flow Rate [m3/s]", - tmpEvapVolFlowRate); - } - if (state.dataPlnt->PlantFirstSizesOkayToReport) { - BaseSizer::reportSizerOutput(state, - "ChillerHeaterPerformance:Electric:EIR", - chillerHeater.Name, - "Initial Design Size Reference Chilled Water Flow Rate [m3/s]", - tmpEvapVolFlowRate); - } - } else { - if (chillerHeater.EvapVolFlowRate > 0.0 && tmpEvapVolFlowRate > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport && - !this->mySizesReported) { - - // Hardsized evaporator design volume flow rate for reporting - Real64 EvapVolFlowRateUser = chillerHeater.EvapVolFlowRate; - BaseSizer::reportSizerOutput(state, - "ChillerHeaterPerformance:Electric:EIR", - chillerHeater.Name, - "Design Size Reference Chilled Water Flow Rate [m3/s]", - tmpEvapVolFlowRate, - "User-Specified Reference Chilled Water Flow Rate [m3/s]", - EvapVolFlowRateUser); - tmpEvapVolFlowRate = EvapVolFlowRateUser; - if (state.dataGlobal->DisplayExtraWarnings) { - if ((std::abs(tmpEvapVolFlowRate - EvapVolFlowRateUser) / EvapVolFlowRateUser) > - state.dataSize->AutoVsHardSizingThreshold) { - ShowMessage(state, - std::format("SizeChillerHeaterPerformanceElectricEIR: Potential issue with equipment sizing for {}", - chillerHeater.Name)); - ShowContinueError( - state, std::format("User-Specified Reference Chilled Water Flow Rate of {:.5f} [m3/s]", EvapVolFlowRateUser)); - ShowContinueError( - state, - std::format("differs from Design Size Reference Chilled Water Flow Rate of {:.5f} [m3/s]", tmpEvapVolFlowRate)); - ShowContinueError(state, "This may, or may not, indicate mismatched component sizes."); - ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components."); - } - } - } - } - } - } else { - if (chillerHeater.EvapVolFlowRateWasAutoSized) { - if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { - ShowSevereError(state, "Autosizing of CGSHP Chiller Heater evap flow rate requires a loop Sizing:Plant object"); - ShowContinueError(state, std::format("Occurs in CGSHP Chiller Heater Performance object={}", chillerHeater.Name)); - ErrorsFound = true; - } - } else { - if (chillerHeater.EvapVolFlowRate > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { - BaseSizer::reportSizerOutput(state, - "ChillerHeaterPerformance:Electric:EIR", - chillerHeater.Name, - "User-Specified Reference Chilled Water Flow Rate [m3/s]", - chillerHeater.EvapVolFlowRate); - } - } - } - - // auto-size the Reference Cooling Capacity - // each individual chiller heater module is sized to be capable of supporting the total load on the wrapper - if (PltSizNum > 0) { - if (state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate >= HVAC::SmallWaterVolFlow && tmpEvapVolFlowRate > 0.0) { - Real64 Cp = this->CWPlantLoc.loop->glycol->getSpecificHeat(state, Constant::CWInitConvTemp, RoutineName); - - Real64 rho = this->CWPlantLoc.loop->glycol->getDensity(state, Constant::CWInitConvTemp, RoutineName); - tmpNomCap = Cp * rho * state.dataSize->PlantSizData(PltSizNum).DeltaT * tmpEvapVolFlowRate; - if (!chillerHeater.RefCapCoolingWasAutoSized) { - tmpNomCap = chillerHeater.RefCapCooling; - } - } else { - if (chillerHeater.RefCapCoolingWasAutoSized) { - tmpNomCap = 0.0; - } - } - if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { - if (chillerHeater.RefCapCoolingWasAutoSized) { - chillerHeater.RefCapCooling = tmpNomCap; - - // Now that we have the Reference Cooling Capacity, we need to also initialize the Heating side - // given the ratios - chillerHeater.RefCapClgHtg = chillerHeater.RefCapCooling * chillerHeater.ClgHtgToCoolingCapRatio; - - chillerHeater.RefPowerClgHtg = (chillerHeater.RefCapCooling / chillerHeater.RefCOPCooling) * chillerHeater.ClgHtgtoCogPowerRatio; - - chillerHeater.RefCOPClgHtg = chillerHeater.RefCapClgHtg / chillerHeater.RefPowerClgHtg; - - if (state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { - BaseSizer::reportSizerOutput( - state, "ChillerHeaterPerformance:Electric:EIR", chillerHeater.Name, "Design Size Reference Capacity [W]", tmpNomCap); - } - if (state.dataPlnt->PlantFirstSizesOkayToReport) { - BaseSizer::reportSizerOutput(state, - "ChillerHeaterPerformance:Electric:EIR", - chillerHeater.Name, - "Initial Design Size Reference Capacity [W]", - tmpNomCap); - } - } else { - if (chillerHeater.RefCapCooling > 0.0 && tmpNomCap > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport && - !this->mySizesReported) { - - // Hardsized nominal capacity cooling power for reporting - Real64 NomCapUser = chillerHeater.RefCapCooling; - BaseSizer::reportSizerOutput(state, - "ChillerHeaterPerformance:Electric:EIR", - chillerHeater.Name, - "Design Size Reference Capacity [W]", - tmpNomCap, - "User-Specified Reference Capacity [W]", - NomCapUser); - tmpNomCap = NomCapUser; - if (state.dataGlobal->DisplayExtraWarnings) { - if ((std::abs(tmpNomCap - NomCapUser) / NomCapUser) > state.dataSize->AutoVsHardSizingThreshold) { - ShowMessage(state, - std::format("SizeChillerHeaterPerformanceElectricEIR: Potential issue with equipment sizing for {}", - chillerHeater.Name)); - ShowContinueError(state, std::format("User-Specified Reference Capacity of {:.2f} [W]", NomCapUser)); - ShowContinueError(state, std::format("differs from Design Size Reference Capacity of {:.2f} [W]", tmpNomCap)); - ShowContinueError(state, "This may, or may not, indicate mismatched component sizes."); - ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components."); - } - } - } - } - } - } else { - if (chillerHeater.RefCapCoolingWasAutoSized) { - if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { - ShowSevereError(state, std::format("Size ChillerHeaterPerformance:Electric:EIR=\"{}\", autosize error.", chillerHeater.Name)); - ShowContinueError(state, "Autosizing of CGSHP Chiller Heater reference capacity requires"); - ShowContinueError(state, "a cooling loop Sizing:Plant object."); - ErrorsFound = true; - } - } else { - if (chillerHeater.RefCapCooling > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { - BaseSizer::reportSizerOutput(state, - "ChillerHeaterPerformance:Electric:EIR", - chillerHeater.Name, - "User-Specified Reference Capacity [W]", - chillerHeater.RefCapCooling); - } - } - } - - // auto-size the condenser volume flow rate - // each individual chiller heater module is sized to be capable of supporting the total load on the wrapper - if (PltSizCondNum > 0) { - if (state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate >= HVAC::SmallWaterVolFlow) { - Real64 rho = this->GLHEPlantLoc.loop->glycol->getDensity(state, Constant::CWInitConvTemp, RoutineName); - // TODO: JM 2018-12-06 I wonder why Cp isn't calculated at the same temp as rho... - Real64 Cp = this->GLHEPlantLoc.loop->glycol->getSpecificHeat(state, chillerHeater.TempRefCondInCooling, RoutineName); - tmpCondVolFlowRate = tmpNomCap * (1.0 + (1.0 / chillerHeater.RefCOPCooling) * chillerHeater.OpenMotorEff) / - (state.dataSize->PlantSizData(PltSizCondNum).DeltaT * Cp * rho); - chillerHeater.tmpCondVolFlowRate = tmpCondVolFlowRate; - if (!chillerHeater.CondVolFlowRateWasAutoSized) { - tmpCondVolFlowRate = chillerHeater.CondVolFlowRate; - } - - } else { - if (chillerHeater.CondVolFlowRateWasAutoSized) { - tmpCondVolFlowRate = 0.0; - } - chillerHeater.tmpCondVolFlowRate = tmpCondVolFlowRate; - } - if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { - if (chillerHeater.CondVolFlowRateWasAutoSized) { - chillerHeater.CondVolFlowRate = tmpCondVolFlowRate; - if (state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { - BaseSizer::reportSizerOutput(state, - "ChillerHeaterPerformance:Electric:EIR", - chillerHeater.Name, - "Design Size Reference Condenser Water Flow Rate [m3/s]", - tmpCondVolFlowRate); - } - if (state.dataPlnt->PlantFirstSizesOkayToReport) { - BaseSizer::reportSizerOutput(state, - "ChillerHeaterPerformance:Electric:EIR", - chillerHeater.Name, - "Initial Design Size Reference Condenser Water Flow Rate [m3/s]", - tmpCondVolFlowRate); - } - } else { - if (chillerHeater.CondVolFlowRate > 0.0 && tmpCondVolFlowRate > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport && - !this->mySizesReported) { - - // Hardsized condenser design volume flow rate for reporting - Real64 CondVolFlowRateUser = chillerHeater.CondVolFlowRate; - BaseSizer::reportSizerOutput(state, - "ChillerHeaterPerformance:Electric:EIR", - chillerHeater.Name, - "Design Size Reference Condenser Water Flow Rate [m3/s]", - tmpCondVolFlowRate, - "User-Specified Reference Condenser Water Flow Rate [m3/s]", - CondVolFlowRateUser); - if (state.dataGlobal->DisplayExtraWarnings) { - if ((std::abs(tmpCondVolFlowRate - CondVolFlowRateUser) / CondVolFlowRateUser) > - state.dataSize->AutoVsHardSizingThreshold) { - ShowMessage(state, - std::format("SizeChillerHeaterPerformanceElectricEIR: Potential issue with equipment sizing for {}", - chillerHeater.Name)); - ShowContinueError( - state, std::format("User-Specified Reference Condenser Water Flow Rate of {:.5f} [m3/s]", CondVolFlowRateUser)); - ShowContinueError( - state, - std::format("differs from Design Size Reference Condenser Water Flow Rate of {:.5f} [m3/s]", tmpCondVolFlowRate)); - ShowContinueError(state, "This may, or may not, indicate mismatched component sizes."); - ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components."); - } - } - } - } - } - } else { - if (chillerHeater.CondVolFlowRateWasAutoSized) { - if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { - ShowSevereError(state, std::format("Size ChillerHeaterPerformance:Electric:EIR=\"{}\", autosize error.", chillerHeater.Name)); - ShowContinueError(state, "Autosizing of CGSHP Chiller Heater condenser flow rate requires"); - ShowContinueError(state, "a condenser loop Sizing:Plant object."); - ErrorsFound = true; - } - } else { - if (chillerHeater.CondVolFlowRate > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { - BaseSizer::reportSizerOutput(state, - "ChillerHeaterPerformance:Electric:EIR", - chillerHeater.Name, - "User-Specified Reference Condenser Water Flow Rate [m3/s]", - chillerHeater.CondVolFlowRate); - } - } - } - - if (state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { - // create predefined report - std::string equipName = chillerHeater.Name; - OutputReportPredefined::PreDefTableEntry( - state, state.dataOutRptPredefined->pdchMechType, equipName, "ChillerHeaterPerformance:Electric:EIR"); - OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchMechNomEff, equipName, chillerHeater.RefCOPCooling); - OutputReportPredefined::PreDefTableEntry(state, state.dataOutRptPredefined->pdchMechNomCap, equipName, chillerHeater.RefCapCooling); - } - - if (ErrorsFound) { - ShowFatalError(state, "Preceding sizing errors cause program termination"); - } - } - - // sum individual volume flows and register wrapper inlets - Real64 TotalEvapVolFlowRate = 0.0; - Real64 TotalCondVolFlowRate = 0.0; - Real64 TotalHotWaterVolFlowRate = 0.0; - for (int NumChillerHeater = 1; NumChillerHeater <= this->ChillerHeaterNums; ++NumChillerHeater) { - auto const &chillerHeater = this->ChillerHeater(NumChillerHeater); - TotalEvapVolFlowRate += chillerHeater.tmpEvapVolFlowRate; - TotalCondVolFlowRate += chillerHeater.tmpCondVolFlowRate; - TotalHotWaterVolFlowRate += chillerHeater.DesignHotWaterVolFlowRate; - } - - PlantUtilities::RegisterPlantCompDesignFlow(state, this->CHWInletNodeNum, TotalEvapVolFlowRate); - PlantUtilities::RegisterPlantCompDesignFlow(state, this->HWInletNodeNum, TotalHotWaterVolFlowRate); - // save the reference condenser water volumetric flow rate for use by the condenser water loop sizing algorithms - PlantUtilities::RegisterPlantCompDesignFlow(state, this->GLHEInletNodeNum, TotalCondVolFlowRate); - - if (state.dataPlnt->PlantFinalSizesOkayToReport) { - this->mySizesReported = true; - } - - return; -} - -void GetWrapperInput(EnergyPlusData &state) -{ - // SUBROUTINE INFORMATION: - // AUTHOR: Yunzhi Huang and Daeho Kang, PNNL - // DATE WRITTEN: Feb 2013 - - // PURPOSE OF THIS SUBROUTINE: - // This routine will get the input required by the Wrapper model. - - static constexpr std::string_view routineName = "GetWrapperInput"; - - bool ErrorsFound(false); // True when input errors are found - int NumAlphas; // Number of elements in the alpha array - int NumNums; // Number of elements in the numeric array - int IOStat; // IO Status when calling get input subroutine - - state.dataIPShortCut->cCurrentModuleObject = "CentralHeatPumpSystem"; - state.dataPlantCentralGSHP->numWrappers = - state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject); - - if (state.dataPlantCentralGSHP->numWrappers <= 0) { - ShowSevereError(state, std::format("No {} equipment specified in input file", state.dataIPShortCut->cCurrentModuleObject)); - } - - state.dataPlantCentralGSHP->Wrapper.allocate(state.dataPlantCentralGSHP->numWrappers); - - // Load arrays with electric EIR chiller data - for (int WrapperNum = 1; WrapperNum <= state.dataPlantCentralGSHP->numWrappers; ++WrapperNum) { - state.dataInputProcessing->inputProcessor->getObjectItem(state, - state.dataIPShortCut->cCurrentModuleObject, - WrapperNum, - state.dataIPShortCut->cAlphaArgs, - NumAlphas, - state.dataIPShortCut->rNumericArgs, - NumNums, - IOStat, - _, - state.dataIPShortCut->lAlphaFieldBlanks, - state.dataIPShortCut->cAlphaFieldNames, - state.dataIPShortCut->cNumericFieldNames); - - ErrorObjectHeader eoh{routineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)}; - - auto &wrapper = state.dataPlantCentralGSHP->Wrapper(WrapperNum); - - wrapper.Name = state.dataIPShortCut->cAlphaArgs(1); - - wrapper.CHWInletNodeNum = Node::GetOnlySingleNode(state, - state.dataIPShortCut->cAlphaArgs(2), - ErrorsFound, - Node::ConnectionObjectType::CentralHeatPumpSystem, - state.dataIPShortCut->cAlphaArgs(1), - Node::FluidType::Water, - Node::ConnectionType::Inlet, - Node::CompFluidStream::Primary, - Node::ObjectIsNotParent); // node name : connection should be careful! - wrapper.CHWOutletNodeNum = Node::GetOnlySingleNode(state, - state.dataIPShortCut->cAlphaArgs(3), - ErrorsFound, - Node::ConnectionObjectType::CentralHeatPumpSystem, - state.dataIPShortCut->cAlphaArgs(1), - Node::FluidType::Water, - Node::ConnectionType::Outlet, - Node::CompFluidStream::Primary, - Node::ObjectIsNotParent); - wrapper.CoolSetPointTempNode = wrapper.CHWOutletNodeNum; - Node::TestCompSet(state, - state.dataIPShortCut->cCurrentModuleObject, - state.dataIPShortCut->cAlphaArgs(1), - state.dataIPShortCut->cAlphaArgs(2), - state.dataIPShortCut->cAlphaArgs(3), - "Chilled Water Nodes"); - - wrapper.GLHEInletNodeNum = Node::GetOnlySingleNode(state, - state.dataIPShortCut->cAlphaArgs(4), - ErrorsFound, - Node::ConnectionObjectType::CentralHeatPumpSystem, - state.dataIPShortCut->cAlphaArgs(1), - Node::FluidType::Water, - Node::ConnectionType::Inlet, - Node::CompFluidStream::Secondary, - Node::ObjectIsNotParent); // node name : connection should be careful! - wrapper.GLHEOutletNodeNum = Node::GetOnlySingleNode(state, - state.dataIPShortCut->cAlphaArgs(5), - ErrorsFound, - Node::ConnectionObjectType::CentralHeatPumpSystem, - state.dataIPShortCut->cAlphaArgs(1), - Node::FluidType::Water, - Node::ConnectionType::Outlet, - Node::CompFluidStream::Secondary, - Node::ObjectIsNotParent); - Node::TestCompSet(state, - state.dataIPShortCut->cCurrentModuleObject, - state.dataIPShortCut->cAlphaArgs(1), - state.dataIPShortCut->cAlphaArgs(4), - state.dataIPShortCut->cAlphaArgs(5), - "GLHE Nodes"); - - wrapper.HWInletNodeNum = Node::GetOnlySingleNode(state, - state.dataIPShortCut->cAlphaArgs(6), - ErrorsFound, - Node::ConnectionObjectType::CentralHeatPumpSystem, - state.dataIPShortCut->cAlphaArgs(1), - Node::FluidType::Water, - Node::ConnectionType::Inlet, - Node::CompFluidStream::Tertiary, - Node::ObjectIsNotParent); // node name : connection should be careful! - wrapper.HWOutletNodeNum = Node::GetOnlySingleNode(state, - state.dataIPShortCut->cAlphaArgs(7), - ErrorsFound, - Node::ConnectionObjectType::CentralHeatPumpSystem, - state.dataIPShortCut->cAlphaArgs(1), - Node::FluidType::Water, - Node::ConnectionType::Outlet, - Node::CompFluidStream::Tertiary, - Node::ObjectIsNotParent); - wrapper.HeatSetPointTempNode = wrapper.HWOutletNodeNum; - Node::TestCompSet(state, - state.dataIPShortCut->cCurrentModuleObject, - state.dataIPShortCut->cAlphaArgs(1), - state.dataIPShortCut->cAlphaArgs(6), - state.dataIPShortCut->cAlphaArgs(7), - "Hot Water Nodes"); - - wrapper.AncillaryPower = state.dataIPShortCut->rNumericArgs(1); - if (state.dataIPShortCut->lAlphaFieldBlanks(8)) { - // Leave this as nullptr - } else if ((wrapper.ancillaryPowerSched = Sched::GetSchedule(state, state.dataIPShortCut->cAlphaArgs(8))) == nullptr) { - ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(8), state.dataIPShortCut->cAlphaArgs(8)); - } - - int NumberOfComp = (NumAlphas - 8) / 3; - wrapper.NumOfComp = NumberOfComp; - wrapper.WrapperComp.allocate(NumberOfComp); - - if (wrapper.NumOfComp == 0) { - ShowSevereError(state, - std::format("GetWrapperInput: No component names on {}={}", state.dataIPShortCut->cCurrentModuleObject, wrapper.Name)); - ErrorsFound = true; - } else { - int Comp = 0; - int NumChHtrPerWrapper = 0; - for (int loop = 9; loop <= NumAlphas; loop += 3) { - ++Comp; - wrapper.WrapperComp(Comp).WrapperPerformanceObjectType = state.dataIPShortCut->cAlphaArgs(loop); - wrapper.WrapperComp(Comp).WrapperComponentName = state.dataIPShortCut->cAlphaArgs(loop + 1); - - if (state.dataIPShortCut->lAlphaFieldBlanks(loop + 2)) { - wrapper.WrapperComp(Comp).chSched = - Sched::GetScheduleAlwaysOn(state); // Not an availability schedule, but defaults to constant-1.0 - } else if ((wrapper.WrapperComp(Comp).chSched = Sched::GetSchedule(state, state.dataIPShortCut->cAlphaArgs(loop + 2))) == nullptr) { - wrapper.WrapperComp(Comp).chSched = - Sched::GetScheduleAlwaysOn(state); // Not an availability schedule, but defaults to constant-1.0 - ShowWarningItemNotFound(state, - eoh, - state.dataIPShortCut->cAlphaFieldNames(loop + 2), - state.dataIPShortCut->cAlphaArgs(loop + 2), - "The Control Schedule is treated as AlwaysOn instead."); - } - - wrapper.WrapperComp(Comp).WrapperIdenticalObjectNum = state.dataIPShortCut->rNumericArgs(1 + Comp); - if (wrapper.WrapperComp(Comp).WrapperPerformanceObjectType == "CHILLERHEATERPERFORMANCE:ELECTRIC:EIR") { - - // count number of chiller heaters (including identical units) for current wrapper - if (wrapper.WrapperComp(Comp).WrapperIdenticalObjectNum > 1) { - NumChHtrPerWrapper += wrapper.WrapperComp(Comp).WrapperIdenticalObjectNum; - } else { - ++NumChHtrPerWrapper; - } - - // count total number of chiller heaters (not including identical units) for ALL wrappers - ++state.dataPlantCentralGSHP->numChillerHeaters; - } - } - - wrapper.ChillerHeaterNums = NumChHtrPerWrapper; - } - - if (ErrorsFound) { - ShowFatalError(state, - std::format("GetWrapperInput: Invalid {} Input, preceding condition(s) cause termination.", - state.dataIPShortCut->cCurrentModuleObject)); - } - - // ALLOCATE ARRAYS - if (state.dataPlantCentralGSHP->numChillerHeaters == 0) { - ShowFatalError(state, - std::format("{} : {} requires ChillerHeaterPerformance:Electric:EIR object(s).", - state.dataIPShortCut->cCurrentModuleObject, - wrapper.Name)); - } - } - - if (state.dataPlantCentralGSHP->numChillerHeaters > 0) { - - for (int WrapperNum = 1; WrapperNum <= state.dataPlantCentralGSHP->numWrappers; ++WrapperNum) { - auto &wrapper = state.dataPlantCentralGSHP->Wrapper(WrapperNum); - wrapper.ChillerHeater.allocate(wrapper.ChillerHeaterNums); - } - GetChillerHeaterInput(state); - } - - for (int WrapperNum = 1; WrapperNum <= state.dataPlantCentralGSHP->numWrappers; ++WrapperNum) { - auto &wrapper = state.dataPlantCentralGSHP->Wrapper(WrapperNum); - int ChillerHeaterNum = 0; // initialize nth chiller heater index (including identical units) for current wrapper - for (int Comp = 1; Comp <= wrapper.NumOfComp; ++Comp) { - if (wrapper.WrapperComp(Comp).WrapperPerformanceObjectType == "CHILLERHEATERPERFORMANCE:ELECTRIC:EIR") { - std::string CompName = wrapper.WrapperComp(Comp).WrapperComponentName; - int CompIndex = Util::FindItemInList(CompName, state.dataPlantCentralGSHP->ChillerHeater); - // User may enter invalid name rather than selecting one from the object list - if (CompIndex <= 0) { - ShowSevereError(state, std::format("GetWrapperInput: Invalid Chiller Heater Modules Performance Component Name ={}", CompName)); - ShowContinueError(state, "Select the name of ChillerHeaterPerformance:Electric:EIR object(s) from the object list."); - ShowFatalError(state, "Program terminates due to preceding condition."); - } - wrapper.WrapperComp(Comp).WrapperPerformanceObjectIndex = CompIndex; - if (state.dataPlantCentralGSHP->ChillerHeater(CompIndex).VariableFlow) { - wrapper.VariableFlowCH = true; - } - for (int i_CH = 1; i_CH <= wrapper.WrapperComp(Comp).WrapperIdenticalObjectNum; ++i_CH) { - // increment nth chiller heater index (including identical units) for current wrapper - ++ChillerHeaterNum; - wrapper.ChillerHeater(ChillerHeaterNum) = state.dataPlantCentralGSHP->ChillerHeater(CompIndex); - } - } - } - } - - // Release memory from temporary arrays; values now copied into their associated Wrapper in above loop - if (allocated(state.dataPlantCentralGSHP->ChillerHeater)) { - state.dataPlantCentralGSHP->ChillerHeater.deallocate(); - } - - // Set up output variables - for (int WrapperNum = 1; WrapperNum <= state.dataPlantCentralGSHP->numWrappers; ++WrapperNum) { - } // End of wrapper count -} - -void WrapperSpecs::setupOutputVars(EnergyPlusData &state) -{ - SetupOutputVariable(state, - "Chiller Heater System Cooling Electricity Energy", - Constant::Units::J, - this->Report.TotElecCooling, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Sum, - this->Name, - Constant::eResource::Electricity, - OutputProcessor::Group::Plant, - OutputProcessor::EndUseCat::Cooling); - - SetupOutputVariable(state, - "Chiller Heater System Heating Electricity Energy", - Constant::Units::J, - this->Report.TotElecHeating, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Sum, - this->Name, - Constant::eResource::Electricity, - OutputProcessor::Group::Plant, - OutputProcessor::EndUseCat::Heating); - - SetupOutputVariable(state, - "Chiller Heater System Cooling Electricity Rate", - Constant::Units::W, - this->Report.TotElecCoolingPwr, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Heating Electricity Rate", - Constant::Units::W, - this->Report.TotElecHeatingPwr, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Cooling Energy", - Constant::Units::J, - this->Report.CoolingEnergy, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Sum, - this->Name, - Constant::eResource::EnergyTransfer, - OutputProcessor::Group::Plant, - OutputProcessor::EndUseCat::Chillers); - - SetupOutputVariable(state, - "Chiller Heater System Heating Energy", - Constant::Units::J, - this->Report.HeatingEnergy, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Sum, - this->Name, - Constant::eResource::EnergyTransfer, - OutputProcessor::Group::Plant, - OutputProcessor::EndUseCat::Boilers); - - SetupOutputVariable(state, - "Chiller Heater System Source Heat Transfer Energy", - Constant::Units::J, - this->Report.GLHEEnergy, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Sum, - this->Name, - Constant::eResource::EnergyTransfer, - OutputProcessor::Group::Plant, - OutputProcessor::EndUseCat::HeatRejection); - - SetupOutputVariable(state, - "Chiller Heater System Cooling Rate", - Constant::Units::W, - this->Report.CoolingRate, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Heating Rate", - Constant::Units::W, - this->Report.HeatingRate, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Source Heat Transfer Rate", - Constant::Units::W, - this->Report.GLHERate, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Cooling Mass Flow Rate", - Constant::Units::kg_s, - this->Report.CHWmdot, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Heating Mass Flow Rate", - Constant::Units::kg_s, - this->Report.HWmdot, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Source Mass Flow Rate", - Constant::Units::kg_s, - this->Report.GLHEmdot, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Cooling Inlet Temperature", - Constant::Units::C, - this->Report.CHWInletTemp, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Heating Inlet Temperature", - Constant::Units::C, - this->Report.HWInletTemp, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Source Inlet Temperature", - Constant::Units::C, - this->Report.GLHEInletTemp, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Cooling Outlet Temperature", - Constant::Units::C, - this->Report.CHWOutletTemp, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Heating Outlet Temperature", - Constant::Units::C, - this->Report.HWOutletTemp, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - SetupOutputVariable(state, - "Chiller Heater System Source Outlet Temperature", - Constant::Units::C, - this->Report.GLHEOutletTemp, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - this->Name); - - if (this->ChillerHeaterNums > 0) { - - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - auto &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - SetupOutputVariable(state, - std::format("Chiller Heater Operation Mode Unit {}", ChillerHeaterNum), - Constant::Units::None, - chillerHeater.Report.currentMode, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Part Load Ratio Unit {}", ChillerHeaterNum), - Constant::Units::None, - chillerHeater.Report.ChillerPartLoadRatio, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Cycling Ratio Unit {}", ChillerHeaterNum), - Constant::Units::None, - chillerHeater.Report.ChillerCyclingRatio, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Cooling Electricity Rate Unit {}", ChillerHeaterNum), - Constant::Units::W, - chillerHeater.Report.CoolingPower, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Heating Electricity Rate Unit {}", ChillerHeaterNum), - Constant::Units::W, - chillerHeater.Report.HeatingPower, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Cooling Electricity Energy Unit {}", ChillerHeaterNum), - Constant::Units::J, - chillerHeater.Report.CoolingEnergy, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Sum, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Heating Electricity Energy Unit {}", ChillerHeaterNum), - Constant::Units::J, - chillerHeater.Report.HeatingEnergy, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Sum, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Cooling Rate Unit {}", ChillerHeaterNum), - Constant::Units::W, - chillerHeater.Report.QEvap, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Cooling Energy Unit {}", ChillerHeaterNum), - Constant::Units::J, - chillerHeater.Report.EvapEnergy, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Sum, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater False Load Heat Transfer Rate Unit {}", ChillerHeaterNum), - Constant::Units::W, - chillerHeater.Report.ChillerFalseLoadRate, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater False Load Heat Transfer Energy Unit {}", ChillerHeaterNum), - Constant::Units::J, - chillerHeater.Report.ChillerFalseLoad, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Sum, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Evaporator Inlet Temperature Unit {}", ChillerHeaterNum), - Constant::Units::C, - chillerHeater.Report.EvapInletTemp, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Evaporator Outlet Temperature Unit {}", ChillerHeaterNum), - Constant::Units::C, - chillerHeater.Report.EvapOutletTemp, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Evaporator Mass Flow Rate Unit {}", ChillerHeaterNum), - Constant::Units::kg_s, - chillerHeater.Report.Evapmdot, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Condenser Heat Transfer Rate Unit {}", ChillerHeaterNum), - Constant::Units::W, - chillerHeater.Report.QCond, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Condenser Heat Transfer Energy Unit {}", ChillerHeaterNum), - Constant::Units::J, - chillerHeater.Report.CondEnergy, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Sum, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater COP Unit {}", ChillerHeaterNum), - Constant::Units::W_W, - chillerHeater.Report.ActualCOP, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Capacity Temperature Modifier Multiplier Unit {}", ChillerHeaterNum), - Constant::Units::None, - chillerHeater.Report.ChillerCapFT, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater EIR Temperature Modifier Multiplier Unit {}", ChillerHeaterNum), - Constant::Units::None, - chillerHeater.Report.ChillerEIRFT, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater EIR Part Load Modifier Multiplier Unit {}", ChillerHeaterNum), - Constant::Units::None, - chillerHeater.Report.ChillerEIRFPLR, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Condenser Inlet Temperature Unit {}", ChillerHeaterNum), - Constant::Units::C, - chillerHeater.Report.CondInletTemp, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Condenser Outlet Temperature Unit {}", ChillerHeaterNum), - Constant::Units::C, - chillerHeater.Report.CondOutletTemp, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - - SetupOutputVariable(state, - std::format("Chiller Heater Condenser Mass Flow Rate Unit {}", ChillerHeaterNum), - Constant::Units::kg_s, - chillerHeater.Report.Condmdot, - OutputProcessor::TimeStepType::System, - OutputProcessor::StoreType::Average, - chillerHeater.Name); - } // End of individual chiller heater count for current wrapper - - } // End of individual chiller heater output -} - -void GetChillerHeaterInput(EnergyPlusData &state) -{ - // SUBROUTINE INFORMATION: - // AUTHOR: Kyung Tae Yun, Mississippi State University - // DATE WRITTEN: Feb 2013 - - // PURPOSE OF THIS SUBROUTINE: - // This routine will get the input required by the ChillerHeaterPerformance:Electric:EIR model. - - bool CHErrorsFound(false); // True when input errors are found - int NumAlphas; // Number of elements in the alpha array - int NumNums; // Number of elements in the numeric array - int IOStat; // IO Status when calling get input subroutine - Array1D CurveValArray(11); // Used to evaluate PLFFPLR curve objects - - state.dataIPShortCut->cCurrentModuleObject = "ChillerHeaterPerformance:Electric:EIR"; - state.dataPlantCentralGSHP->numChillerHeaters = - state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject); - - if (state.dataPlantCentralGSHP->numChillerHeaters <= 0) { - ShowSevereError(state, std::format("No {} equipment specified in input file", state.dataIPShortCut->cCurrentModuleObject)); - CHErrorsFound = true; - } - - // Allocate temporary ChillerHeater and ChillerHeaterReport arrays - if (allocated(state.dataPlantCentralGSHP->ChillerHeater)) { - state.dataPlantCentralGSHP->ChillerHeater.deallocate(); - } - state.dataPlantCentralGSHP->ChillerHeater.allocate(state.dataPlantCentralGSHP->numChillerHeaters); - - // Load arrays with electric EIR chiller data - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= state.dataPlantCentralGSHP->numChillerHeaters; ++ChillerHeaterNum) { - state.dataInputProcessing->inputProcessor->getObjectItem(state, - state.dataIPShortCut->cCurrentModuleObject, - ChillerHeaterNum, - state.dataIPShortCut->cAlphaArgs, - NumAlphas, - state.dataIPShortCut->rNumericArgs, - NumNums, - IOStat, - _, - state.dataIPShortCut->lAlphaFieldBlanks, - state.dataIPShortCut->cAlphaFieldNames, - state.dataIPShortCut->cNumericFieldNames); - - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).Name = state.dataIPShortCut->cAlphaArgs(1); - - if (Util::SameString(state.dataIPShortCut->cAlphaArgs(3), "LEAVINGCONDENSER")) { - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).CondModeCooling = CondenserModeTemperature::LeavingCondenser; - } else { // only other option and default value is EnteringCondenser - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).CondModeCooling = CondenserModeTemperature::EnteringCondenser; - } - - // Performance curves - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerCapFTCoolingIDX = - Curve::GetCurveIndex(state, state.dataIPShortCut->cAlphaArgs(4)); - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerCapFTCoolingIDX == 0) { - ShowSevereError(state, std::format("Invalid {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, std::format("Entered in {}={}", state.dataIPShortCut->cAlphaFieldNames(4), state.dataIPShortCut->cAlphaArgs(4))); - CHErrorsFound = true; - } - - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFTCoolingIDX = - Curve::GetCurveIndex(state, state.dataIPShortCut->cAlphaArgs(5)); - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFTCoolingIDX == 0) { - ShowSevereError(state, std::format("Invalid {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, std::format("Entered in {}={}", state.dataIPShortCut->cAlphaFieldNames(5), state.dataIPShortCut->cAlphaArgs(5))); - CHErrorsFound = true; - } - - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRCoolingIDX = - Curve::GetCurveIndex(state, state.dataIPShortCut->cAlphaArgs(6)); - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRCoolingIDX == 0) { - ShowSevereError(state, std::format("Invalid {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, std::format("Entered in {}={}", state.dataIPShortCut->cAlphaFieldNames(6), state.dataIPShortCut->cAlphaArgs(6))); - CHErrorsFound = true; - } - - if (Util::SameString(state.dataIPShortCut->cAlphaArgs(7), "LEAVINGCONDENSER")) { - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).CondModeHeating = CondenserModeTemperature::LeavingCondenser; - } else { // only other option and default value is EnteringCondenser - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).CondModeHeating = CondenserModeTemperature::EnteringCondenser; - } - - // Performance curves - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerCapFTHeatingIDX = - Curve::GetCurveIndex(state, state.dataIPShortCut->cAlphaArgs(8)); - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerCapFTHeatingIDX == 0) { - ShowSevereError(state, std::format("Invalid {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, std::format("Entered in {}={}", state.dataIPShortCut->cAlphaFieldNames(8), state.dataIPShortCut->cAlphaArgs(8))); - CHErrorsFound = true; - } - - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFTHeatingIDX = - Curve::GetCurveIndex(state, state.dataIPShortCut->cAlphaArgs(9)); - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFTHeatingIDX == 0) { - ShowSevereError(state, std::format("Invalid {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, std::format("Entered in {}={}", state.dataIPShortCut->cAlphaFieldNames(9), state.dataIPShortCut->cAlphaArgs(9))); - CHErrorsFound = true; - } - - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRHeatingIDX = - Curve::GetCurveIndex(state, state.dataIPShortCut->cAlphaArgs(10)); - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRHeatingIDX == 0) { - ShowSevereError(state, std::format("Invalid {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, - std::format("Entered in {}={}", state.dataIPShortCut->cAlphaFieldNames(10), state.dataIPShortCut->cAlphaArgs(10))); - CHErrorsFound = true; - } - - if (state.dataIPShortCut->cAlphaArgs(2) == "CONSTANTFLOW") { - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ConstantFlow = true; - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).VariableFlow = false; - } else if (state.dataIPShortCut->cAlphaArgs(2) == "VARIABLEFLOW") { - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ConstantFlow = false; - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).VariableFlow = true; - } - - if (ChillerHeaterNum > 1) { - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ConstantFlow != - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum - 1).ConstantFlow) { - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ConstantFlow = true; - ShowWarningError(state, - std::format("Water flow mode is different from the other chiller heater(s) {}={}", - state.dataIPShortCut->cCurrentModuleObject, - state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, - std::format("Entered in {}={}", state.dataIPShortCut->cAlphaFieldNames(2), state.dataIPShortCut->cAlphaArgs(2))); - ShowContinueError(state, "Simulation assumes CONSTANTFLOW and continues.."); - } - } - - // Chiller rated performance data - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefCapCooling = state.dataIPShortCut->rNumericArgs(1); - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefCapCooling == DataSizing::AutoSize) { - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefCapCoolingWasAutoSized = true; - } - if (state.dataIPShortCut->rNumericArgs(1) == 0.0) { - ShowSevereError(state, std::format("Invalid {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError( - state, std::format("Entered in {}={:.2f}", state.dataIPShortCut->cNumericFieldNames(1), state.dataIPShortCut->rNumericArgs(1))); - CHErrorsFound = true; - } - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefCOPCooling = state.dataIPShortCut->rNumericArgs(2); - if (state.dataIPShortCut->rNumericArgs(2) == 0.0) { - ShowSevereError(state, std::format("Invalid {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError( - state, std::format("Entered in {}={:.2f}", state.dataIPShortCut->cNumericFieldNames(2), state.dataIPShortCut->rNumericArgs(2))); - CHErrorsFound = true; - } - - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefEvapOutCooling = state.dataIPShortCut->rNumericArgs(3); - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefCondInCooling = state.dataIPShortCut->rNumericArgs(4); - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefCondOutCooling = state.dataIPShortCut->rNumericArgs(5); - - // Reference Heating Mode Ratios for Capacity and Power - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ClgHtgToCoolingCapRatio = state.dataIPShortCut->rNumericArgs(6); - if (state.dataIPShortCut->rNumericArgs(6) == 0.0) { - ShowSevereError(state, std::format("Invalid {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError( - state, std::format("Entered in {}={:.2f}", state.dataIPShortCut->cNumericFieldNames(6), state.dataIPShortCut->rNumericArgs(6))); - CHErrorsFound = true; - } - - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ClgHtgtoCogPowerRatio = state.dataIPShortCut->rNumericArgs(7); - if (state.dataIPShortCut->rNumericArgs(7) == 0.0) { - ShowSevereError(state, std::format("Invalid {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError( - state, std::format("Entered in {}={:.2f}", state.dataIPShortCut->cNumericFieldNames(7), state.dataIPShortCut->rNumericArgs(7))); - CHErrorsFound = true; - } - - if (!state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefCapCoolingWasAutoSized) { - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefCapClgHtg = - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ClgHtgToCoolingCapRatio * - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefCapCooling; - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefPowerClgHtg = - (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefCapCooling / - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefCOPCooling) * - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ClgHtgtoCogPowerRatio; - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefCOPClgHtg = - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefCapClgHtg / - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).RefPowerClgHtg; - } - - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefEvapOutClgHtg = state.dataIPShortCut->rNumericArgs(8); - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefCondOutClgHtg = state.dataIPShortCut->rNumericArgs(9); - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefCondInClgHtg = state.dataIPShortCut->rNumericArgs(10); - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempLowLimitEvapOut = state.dataIPShortCut->rNumericArgs(11); - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).EvapVolFlowRate = state.dataIPShortCut->rNumericArgs(12); - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).EvapVolFlowRate == DataSizing::AutoSize) { - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).EvapVolFlowRateWasAutoSized = true; - } - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).CondVolFlowRate = state.dataIPShortCut->rNumericArgs(13); - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).CondVolFlowRate == DataSizing::AutoSize) { - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).CondVolFlowRateWasAutoSized = true; - } - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).DesignHotWaterVolFlowRate = state.dataIPShortCut->rNumericArgs(14); - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).OpenMotorEff = state.dataIPShortCut->rNumericArgs(15); - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).OptPartLoadRatCooling = state.dataIPShortCut->rNumericArgs(16); - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).OptPartLoadRatClgHtg = state.dataIPShortCut->rNumericArgs(17); - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).SizFac = state.dataIPShortCut->rNumericArgs(18); - - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).SizFac <= 0.0) { - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).SizFac = 1.0; - } - - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).OpenMotorEff < 0.0 || - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).OpenMotorEff > 1.0) { - ShowSevereError( - state, std::format("GetCurveInput: For {}: {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, - std::format("{} = {:.3f}", state.dataIPShortCut->cNumericFieldNames(14), state.dataIPShortCut->rNumericArgs(14))); - ShowContinueError(state, std::format("{} must be greater than or equal to zero", state.dataIPShortCut->cNumericFieldNames(14))); - ShowContinueError(state, std::format("{} must be less than or equal to one", state.dataIPShortCut->cNumericFieldNames(14))); - CHErrorsFound = true; - } - - // Check the CAP-FT, EIR-FT, and PLR curves and warn user if different from 1.0 by more than +-10% - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerCapFTCoolingIDX > 0) { - Real64 CurveVal = Curve::CurveValue(state, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerCapFTCoolingIDX, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefEvapOutCooling, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefCondInCooling); - if (CurveVal > 1.10 || CurveVal < 0.90) { - ShowWarningError(state, "Capacity ratio as a function of temperature curve output is not equal to 1.0"); - ShowContinueError(state, - std::format("(+ or - 10%) at reference conditions for {}= {}", - state.dataIPShortCut->cCurrentModuleObject, - state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", CurveVal)); - } - } - - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFTCoolingIDX > 0) { - Real64 CurveVal = Curve::CurveValue(state, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFTCoolingIDX, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefEvapOutCooling, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefCondInCooling); - if (CurveVal > 1.10 || CurveVal < 0.90) { - ShowWarningError(state, "Energy input ratio as a function of temperature curve output is not equal to 1.0"); - ShowContinueError(state, - std::format("(+ or - 10%) at reference conditions for {}= {}", - state.dataIPShortCut->cCurrentModuleObject, - state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", CurveVal)); - } - } - - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRCoolingIDX > 0) { - Real64 CurveVal = Curve::CurveValue(state, state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRCoolingIDX, 1.0); - - if (CurveVal > 1.10 || CurveVal < 0.90) { - ShowWarningError(state, "Energy input ratio as a function of part-load ratio curve output is not equal to 1.0"); - ShowContinueError(state, - std::format("(+ or - 10%) at reference conditions for {}= {}", - state.dataIPShortCut->cCurrentModuleObject, - state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", CurveVal)); - } - } - - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRCoolingIDX > 0) { - bool FoundNegValue = false; - for (int CurveCheck = 0; CurveCheck <= 10; ++CurveCheck) { - Real64 CurveValTmp = Curve::CurveValue( - state, state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRCoolingIDX, double(CurveCheck / 10.0)); - if (CurveValTmp < 0.0) { - FoundNegValue = true; - } - CurveValArray(CurveCheck + 1) = int(CurveValTmp * 100.0) / 100.0; - } - if (FoundNegValue) { - ShowWarningError(state, "Energy input ratio as a function of part-load ratio curve shows negative values "); - ShowContinueError(state, std::format("for {}= {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, "EIR as a function of PLR curve output at various part-load ratios shown below:"); - ShowContinueError(state, "PLR = 0.00 0.10 0.20 0.30 0.40 0.50 0.60 0.70 0.80 0.90 1.00"); - - ShowContinueError(state, std::format("Curve Output = {:7.2F}", EnergyPlus::join(CurveValArray, ","))); - - CHErrorsFound = true; - } - } - - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerCapFTHeatingIDX > 0) { - Real64 CurveVal = Curve::CurveValue(state, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerCapFTHeatingIDX, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefEvapOutClgHtg, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefCondInClgHtg); - if (CurveVal > 1.10 || CurveVal < 0.90) { - ShowWarningError(state, "Capacity ratio as a function of temperature curve output is not equal to 1.0"); - ShowContinueError(state, - std::format("(+ or - 10%) at reference conditions for {}= {}", - state.dataIPShortCut->cCurrentModuleObject, - state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", CurveVal)); - } - } - - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFTHeatingIDX > 0) { - Real64 CurveVal = Curve::CurveValue(state, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFTHeatingIDX, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefEvapOutClgHtg, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).TempRefCondInClgHtg); - if (CurveVal > 1.10 || CurveVal < 0.90) { - ShowWarningError(state, "Energy input ratio as a function of temperature curve output is not equal to 1.0"); - ShowContinueError(state, - std::format("(+ or - 10%) at reference conditions for {}= {}", - state.dataIPShortCut->cCurrentModuleObject, - state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", CurveVal)); - } - } - - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRHeatingIDX > 0) { - Real64 CurveVal = Curve::CurveValue(state, state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRHeatingIDX, 1.0); - - if (CurveVal > 1.10 || CurveVal < 0.90) { - ShowWarningError(state, "Energy input ratio as a function of part-load ratio curve output is not equal to 1.0"); - ShowContinueError(state, - std::format("(+ or - 10%) at reference conditions for {}= {}", - state.dataIPShortCut->cCurrentModuleObject, - state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", CurveVal)); - } - } - - if (state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRHeatingIDX > 0) { - bool FoundNegValue = false; - for (int CurveCheck = 0; CurveCheck <= 10; ++CurveCheck) { - Real64 CurveValTmp = Curve::CurveValue( - state, state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRHeatingIDX, double(CurveCheck / 10.0)); - if (CurveValTmp < 0.0) { - FoundNegValue = true; - } - CurveValArray(CurveCheck + 1) = int(CurveValTmp * 100.0) / 100.0; - } - if (FoundNegValue) { - ShowWarningError(state, "Energy input ratio as a function of part-load ratio curve shows negative values "); - ShowContinueError(state, std::format("for {}= {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1))); - ShowContinueError(state, "EIR as a function of PLR curve output at various part-load ratios shown below:"); - ShowContinueError(state, "PLR = 0.00 0.10 0.20 0.30 0.40 0.50 0.60 0.70 0.80 0.90 1.00"); - - ShowContinueError(state, std::format("Curve Output = {:7.2F}", EnergyPlus::join(CurveValArray, ","))); - - CHErrorsFound = true; - } - } - - Curve::GetCurveMinMaxValues(state, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRHeatingIDX, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).MinPartLoadRatClgHtg, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).MaxPartLoadRatClgHtg); - - Curve::GetCurveMinMaxValues(state, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).ChillerEIRFPLRCoolingIDX, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).MinPartLoadRatCooling, - state.dataPlantCentralGSHP->ChillerHeater(ChillerHeaterNum).MaxPartLoadRatCooling); - } - - if (CHErrorsFound) { - ShowFatalError(state, std::format("Errors found in processing input for {}", state.dataIPShortCut->cCurrentModuleObject)); - } -} - -void WrapperSpecs::initialize(EnergyPlusData &state, - Real64 MyLoad, // Demand Load - int LoopNum // Loop Number Index -) -{ - // SUBROUTINE INFORMATION: - // AUTHOR Daeho Kang, PNNL - // DATE WRITTEN Feb 2013 - // MODIFIED na - // RE-ENGINEERED na - - // PURPOSE OF THIS SUBROUTINE: - // This subroutine is for initializations of the CentralHeatPumpSystem variables - - // METHODOLOGY EMPLOYED: - // Uses the status flags to trigger initializations. - - static constexpr std::string_view RoutineName("InitCGSHPHeatPump"); - - if (this->setupOutputVarsFlag) { - this->setupOutputVars(state); - this->setupOutputVarsFlag = false; - } - - if (this->MyWrapperFlag) { - // Locate the chillers on the plant loops for later usage - bool errFlag = false; - PlantUtilities::ScanPlantLoopsForObject(state, - this->Name, - DataPlant::PlantEquipmentType::CentralGroundSourceHeatPump, - this->CWPlantLoc, - errFlag, - _, - _, - _, - this->CHWInletNodeNum, - _); - - PlantUtilities::ScanPlantLoopsForObject(state, - this->Name, - DataPlant::PlantEquipmentType::CentralGroundSourceHeatPump, - this->HWPlantLoc, - errFlag, - _, - _, - _, - this->HWInletNodeNum, - _); - - PlantUtilities::ScanPlantLoopsForObject(state, - this->Name, - DataPlant::PlantEquipmentType::CentralGroundSourceHeatPump, - this->GLHEPlantLoc, - errFlag, - _, - _, - _, - this->GLHEInletNodeNum, - _); - - PlantUtilities::InterConnectTwoPlantLoopSides( - state, this->CWPlantLoc, this->GLHEPlantLoc, DataPlant::PlantEquipmentType::CentralGroundSourceHeatPump, true); - - PlantUtilities::InterConnectTwoPlantLoopSides( - state, this->HWPlantLoc, this->GLHEPlantLoc, DataPlant::PlantEquipmentType::CentralGroundSourceHeatPump, true); - - PlantUtilities::InterConnectTwoPlantLoopSides( - state, this->CWPlantLoc, this->HWPlantLoc, DataPlant::PlantEquipmentType::CentralGroundSourceHeatPump, true); - - if (this->VariableFlowCH) { // why do this only for VS chiller heaters? constant flow also uses set points. - // Reset flow priority - if (LoopNum == this->CWPlantLoc.loopNum) { - DataPlant::CompData::getPlantComponent(state, this->CWPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn; - } else if (LoopNum == this->HWPlantLoc.loopNum) { - DataPlant::CompData::getPlantComponent(state, this->HWPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn; - } - } // moved up from below next 2 set point checks for #5808 - - // check if setpoint on outlet node - chilled water loop - if (state.dataLoopNodes->Node(this->CHWOutletNodeNum).TempSetPoint == Node::SensedNodeFlagValue) { - if (!state.dataGlobal->AnyEnergyManagementSystemInModel) { - if (!this->CoolSetPointErrDone) { - ShowWarningError(state, - std::format("Missing temperature setpoint on cooling side for CentralHeatPumpSystem named {}", this->Name)); - ShowContinueError(state, - " A temperature setpoint is needed at the outlet node of a CentralHeatPumpSystem, use a SetpointManager"); - ShowContinueError(state, " The overall loop setpoint will be assumed for CentralHeatPumpSystem. The simulation continues ... "); - this->CoolSetPointErrDone = true; - } - } else { - // need call to EMS to check node - bool FatalError = false; // but not really fatal yet, but should be. - EMSManager::CheckIfNodeSetPointManagedByEMS(state, this->CHWOutletNodeNum, HVAC::CtrlVarType::Temp, FatalError); - state.dataLoopNodes->NodeSetpointCheck(this->CHWOutletNodeNum).needsSetpointChecking = false; - if (FatalError) { - if (!this->CoolSetPointErrDone) { - ShowWarningError(state, - std::format("Missing temperature setpoint on cooling side for CentralHeatPumpSystem named {}", this->Name)); - ShowContinueError(state, "A temperature setpoint is needed at the outlet node of a CentralHeatPumpSystem "); - ShowContinueError(state, "use a Setpoint Manager to establish a setpoint at the chiller side outlet node "); - ShowContinueError(state, "or use an EMS actuator to establish a setpoint at the outlet node "); - ShowContinueError(state, "The overall loop setpoint will be assumed for chiller side. The simulation continues ... "); - this->CoolSetPointErrDone = true; - } - } - } - this->CoolSetPointTempNode = this->CWPlantLoc.loop->TempSetPointNodeNum; - } - - if (state.dataLoopNodes->Node(this->HWOutletNodeNum).TempSetPoint == Node::SensedNodeFlagValue) { - if (!state.dataGlobal->AnyEnergyManagementSystemInModel) { - if (!this->HeatSetPointErrDone) { - ShowWarningError(state, - std::format("Missing temperature setpoint on heating side for CentralHeatPumpSystem named {}", this->Name)); - ShowContinueError(state, - " A temperature setpoint is needed at the outlet node of a CentralHeatPumpSystem, use a SetpointManager"); - ShowContinueError(state, " The overall loop setpoint will be assumed for CentralHeatPumpSystem. The simulation continues ... "); - this->HeatSetPointErrDone = true; - } - } else { - // need call to EMS to check node - bool FatalError = false; // but not really fatal yet, but should be. - EMSManager::CheckIfNodeSetPointManagedByEMS(state, this->HWOutletNodeNum, HVAC::CtrlVarType::Temp, FatalError); - state.dataLoopNodes->NodeSetpointCheck(this->HWOutletNodeNum).needsSetpointChecking = false; - if (FatalError) { - if (!this->HeatSetPointErrDone) { - ShowWarningError(state, - std::format("Missing temperature setpoint on heating side for CentralHeatPumpSystem named {}", this->Name)); - ShowContinueError(state, "A temperature setpoint is needed at the outlet node of a CentralHeatPumpSystem "); - ShowContinueError(state, "use a Setpoint Manager to establish a setpoint at the chiller side outlet node "); - ShowContinueError(state, "or use an EMS actuator to establish a setpoint at the outlet node "); - ShowContinueError(state, "The overall loop setpoint will be assumed for chiller side. The simulation continues ... "); - this->HeatSetPointErrDone = true; - } - } - } - this->HeatSetPointTempNode = this->HWPlantLoc.loop->TempSetPointNodeNum; - } - this->MyWrapperFlag = false; - } - - if (this->MyWrapperEnvrnFlag && state.dataGlobal->BeginEnvrnFlag && (state.dataPlnt->PlantFirstSizesOkayToFinalize)) { - - this->CHWVolFlowRate = 0.0; - this->HWVolFlowRate = 0.0; - this->GLHEVolFlowRate = 0.0; - - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - auto &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - this->CHWVolFlowRate += chillerHeater.EvapVolFlowRate; - this->HWVolFlowRate += chillerHeater.DesignHotWaterVolFlowRate; - this->GLHEVolFlowRate += chillerHeater.CondVolFlowRate; - } - - Real64 rho = this->CWPlantLoc.loop->glycol->getDensity(state, Constant::CWInitConvTemp, RoutineName); - - this->CHWMassFlowRateMax = this->CHWVolFlowRate * rho; - this->HWMassFlowRateMax = this->HWVolFlowRate * rho; - this->GLHEMassFlowRateMax = this->GLHEVolFlowRate * rho; - - PlantUtilities::InitComponentNodes(state, 0.0, this->CHWMassFlowRateMax, this->CHWInletNodeNum, this->CHWOutletNodeNum); - PlantUtilities::InitComponentNodes(state, 0.0, this->HWMassFlowRateMax, this->HWInletNodeNum, this->HWOutletNodeNum); - PlantUtilities::InitComponentNodes(state, 0.0, this->GLHEMassFlowRateMax, this->GLHEInletNodeNum, this->GLHEOutletNodeNum); - - // Initialize nodes for individual chiller heaters - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - auto &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - chillerHeater.EvapInletNode.MassFlowRateMin = 0.0; - chillerHeater.EvapInletNode.MassFlowRateMinAvail = 0.0; - chillerHeater.EvapInletNode.MassFlowRateMax = rho * chillerHeater.EvapVolFlowRate; - chillerHeater.EvapInletNode.MassFlowRateMaxAvail = rho * chillerHeater.EvapVolFlowRate; - chillerHeater.EvapInletNode.MassFlowRate = 0.0; - chillerHeater.CondInletNode.MassFlowRateMin = 0.0; - chillerHeater.CondInletNode.MassFlowRateMinAvail = 0.0; - chillerHeater.CondInletNode.MassFlowRateMax = rho * chillerHeater.EvapVolFlowRate; - chillerHeater.CondInletNode.MassFlowRateMaxAvail = rho * chillerHeater.EvapVolFlowRate; - chillerHeater.CondInletNode.MassFlowRate = 0.0; - chillerHeater.CondInletNode.MassFlowRateRequest = 0.0; - } - this->MyWrapperEnvrnFlag = false; - } - - if (!state.dataGlobal->BeginEnvrnFlag) { - this->MyWrapperEnvrnFlag = true; - } - - Real64 mdotCHW; // Chilled water mass flow rate - Real64 mdotHW; // Hot water mass flow rate - Real64 mdotGLHE; // Condenser water mass flow rate - - // Switch over the mass flow rate to the condenser loop, i.e., ground heat exchanger - if (LoopNum == this->CWPlantLoc.loopNum) { // called for on cooling loop - if (MyLoad < -1.0) { // calling for cooling - mdotCHW = state.dataLoopNodes->Node(this->CHWInletNodeNum).MassFlowRateMax; - } else { - mdotCHW = 0.0; - } - if (this->WrapperHeatingLoad > 1.0) { - mdotHW = state.dataLoopNodes->Node(this->HWInletNodeNum).MassFlowRateMax; - } else { - mdotHW = 0.0; - } - if ((MyLoad < -1.0) || (this->WrapperHeatingLoad > 1.0)) { - mdotGLHE = state.dataLoopNodes->Node(this->GLHEInletNodeNum).MassFlowRateMax; - } else { - mdotGLHE = 0.0; - } - - } else if (LoopNum == this->HWPlantLoc.loopNum) { - if (MyLoad > 1.0) { - mdotHW = state.dataLoopNodes->Node(this->HWInletNodeNum).MassFlowRateMax; - } else { - mdotHW = 0.0; - } - if (this->WrapperCoolingLoad > 1.0) { - mdotCHW = state.dataLoopNodes->Node(this->CHWInletNodeNum).MassFlowRateMax; - } else { - mdotCHW = 0.0; - } - if ((MyLoad > 1.0) || (this->WrapperCoolingLoad > 1.0)) { - mdotGLHE = state.dataLoopNodes->Node(this->GLHEInletNodeNum).MassFlowRateMax; - } else { - mdotGLHE = 0.0; - } - - } else if (LoopNum == this->GLHEPlantLoc.loopNum) { - if (this->WrapperCoolingLoad > 1.0) { - mdotCHW = state.dataLoopNodes->Node(this->CHWInletNodeNum).MassFlowRateMax; - } else { - mdotCHW = 0.0; - } - if (this->WrapperHeatingLoad > 1.0) { - mdotHW = state.dataLoopNodes->Node(this->HWInletNodeNum).MassFlowRateMax; - } else { - mdotHW = 0.0; - } - if ((this->WrapperHeatingLoad > 1.0) || (this->WrapperCoolingLoad > 1.0)) { - mdotGLHE = state.dataLoopNodes->Node(this->GLHEInletNodeNum).MassFlowRateMax; - } else { - mdotGLHE = 0.0; - } - } - - PlantUtilities::SetComponentFlowRate(state, mdotCHW, this->CHWInletNodeNum, this->CHWOutletNodeNum, this->CWPlantLoc); - - PlantUtilities::SetComponentFlowRate(state, mdotHW, this->HWInletNodeNum, this->HWOutletNodeNum, this->HWPlantLoc); - - PlantUtilities::SetComponentFlowRate(state, mdotGLHE, this->GLHEInletNodeNum, this->GLHEOutletNodeNum, this->GLHEPlantLoc); -} - -void WrapperSpecs::CalcChillerModel(EnergyPlusData &state) -{ - // SUBROUTINE INFORMATION: - // AUTHOR Daeho Kang, PNNL - // DATE WRITTEN Feb 2013 - // MODIFIED na - // RE-ENGINEERED na - - // PURPOSE OF THIS SUBROUTINE: - // Simulate a ChillerHeaterPerformance:Electric:EIR using curve fit - - // METHODOLOGY EMPLOYED: - // Use empirical curve fits to model performance at off-reference conditions - - // REFERENCES: - // 1. DOE-2 Engineers Manual, Version 2.1A, November 1982, LBL-11353 - - static constexpr std::string_view RoutineName("CalcChillerHeaterModel"); - static constexpr std::string_view RoutineNameElecEIRChiller("CalcElectricEIRChillerModel"); - - bool IsLoadCoolRemaining; - bool NextCompIndicator(false); // Component indicator when identical chiller heaters exist - int CompNum = 0; // Component number in the loop REAL(r64) :: FRAC - int IdenticalUnitCounter = 0; // Pointer to count number of identical unit passed - Real64 CurAvailCHWMassFlowRate(0.0); // Maximum available mass flow rate for current chiller heater - - // Cooling load evaporator should meet - Real64 EvaporatorLoad = this->WrapperCoolingLoad; - - // Chilled water inlet mass flow rate - Real64 CHWInletMassFlowRate = state.dataLoopNodes->Node(this->CHWInletNodeNum).MassFlowRate; - - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - - // Initialize local variables for each chiller heater - CurrentMode currentMode = CurrentMode::Off; - state.dataPlantCentralGSHP->ChillerCapFT = 0.0; - state.dataPlantCentralGSHP->ChillerEIRFT = 0.0; - state.dataPlantCentralGSHP->ChillerEIRFPLR = 0.0; - state.dataPlantCentralGSHP->ChillerPartLoadRatio = 0.0; - state.dataPlantCentralGSHP->ChillerCyclingRatio = 0.0; - state.dataPlantCentralGSHP->ChillerFalseLoadRate = 0.0; - - Real64 CHPower = 0.0; - Real64 QCondenser = 0.0; - Real64 QEvaporator = 0.0; - Real64 FRAC = 1.0; - Real64 ActualCOP = 0.0; - Real64 EvapInletTemp = state.dataLoopNodes->Node(this->CHWInletNodeNum).Temp; - Real64 CondInletTemp = state.dataLoopNodes->Node(this->GLHEInletNodeNum).Temp; - Real64 EvapOutletTemp = EvapInletTemp; - Real64 CondOutletTemp = CondInletTemp; - - auto &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - chillerHeater.Report.currentMode = CurrentMode::Off; - - // Find proper schedule values - if (this->NumOfComp != this->ChillerHeaterNums) { // Identical units exist - if (ChillerHeaterNum == 1) { - IdenticalUnitCounter = 0; - NextCompIndicator = false; - CompNum = ChillerHeaterNum; - } - if (NextCompIndicator) { - ++CompNum; - } - if (CompNum == 1) { - if (ChillerHeaterNum != this->WrapperComp(CompNum).WrapperIdenticalObjectNum) { - NextCompIndicator = false; - } else if (ChillerHeaterNum == this->WrapperComp(CompNum).WrapperIdenticalObjectNum) { - NextCompIndicator = true; - } - } else if (CompNum > 1) { - if ((ChillerHeaterNum - ((ChillerHeaterNum - 1) - IdenticalUnitCounter)) != this->WrapperComp(CompNum).WrapperIdenticalObjectNum) { - NextCompIndicator = false; - } else if ((ChillerHeaterNum - ((ChillerHeaterNum - 1) - IdenticalUnitCounter)) == - this->WrapperComp(CompNum).WrapperIdenticalObjectNum) { - NextCompIndicator = true; - } - } - ++IdenticalUnitCounter; - int IdenticalUnitRemaining = this->WrapperComp(CompNum).WrapperIdenticalObjectNum - IdenticalUnitCounter; - if (IdenticalUnitRemaining == 0) { - IdenticalUnitCounter = 0; - } - } else if (this->NumOfComp == this->ChillerHeaterNums) { - ++CompNum; - } - - if (CompNum > this->NumOfComp) { - ShowSevereError(state, std::format("CalcChillerModel: ChillerHeater=\"{}\", calculated component number too big.", this->Name)); - ShowContinueError(state, std::format("Max number of components=[{}], indicated component number=[{}].", this->NumOfComp, CompNum)); - ShowFatalError(state, "Program terminates due to preceding condition."); - } - - Real64 EvapMassFlowRate; // Actual evaporator mass flow rate - Real64 CondMassFlowRate; // Condenser mass flow rate - - // Check whether this chiller heater needs to run - if (EvaporatorLoad > 0.0 && (this->WrapperComp(CompNum).chSched->getCurrentVal() > 0.0)) { - IsLoadCoolRemaining = true; - - // Calculate density ratios to adjust mass flow rates from initialized ones - // Hot water temperature is known, but evaporator mass flow rates will be adjusted in the following "Do" loop - Real64 InitDensity = this->CWPlantLoc.loop->glycol->getDensity(state, Constant::CWInitConvTemp, RoutineName); - Real64 EvapDensity = this->CWPlantLoc.loop->glycol->getDensity(state, EvapInletTemp, RoutineName); - Real64 CondDensity = this->CWPlantLoc.loop->glycol->getDensity(state, CondInletTemp, RoutineName); - - // Calculate density ratios to adjust mass flow rates from initialized ones - - // Fraction between standardized density and local density in the chilled water side - Real64 CHWDensityRatio = EvapDensity / InitDensity; - - // Fraction between standardized density and local density in the condenser side - Real64 GLHEDensityRatio = CondDensity / InitDensity; - CondMassFlowRate = chillerHeater.CondInletNode.MassFlowRateMaxAvail; - EvapMassFlowRate = chillerHeater.EvapInletNode.MassFlowRateMaxAvail; - EvapMassFlowRate *= CHWDensityRatio; - CondMassFlowRate *= GLHEDensityRatio; - - // Check available flows from plant and then adjust as necessary - if (CurAvailCHWMassFlowRate == 0) { // The very first chiller heater to operate - CurAvailCHWMassFlowRate = CHWInletMassFlowRate; - } else if (ChillerHeaterNum > 1) { - CurAvailCHWMassFlowRate -= this->ChillerHeater(ChillerHeaterNum - 1).EvapOutletNode.MassFlowRate; - } - EvapMassFlowRate = min(CurAvailCHWMassFlowRate, EvapMassFlowRate); - } else { - IsLoadCoolRemaining = false; - EvapMassFlowRate = 0.0; - CondMassFlowRate = 0.0; - currentMode = CurrentMode::Off; - } - - // Chiller heater is on when cooling load for this chiller heater remains and chilled water available - if (IsLoadCoolRemaining && (EvapMassFlowRate > 0) && (this->WrapperComp(CompNum).chSched->getCurrentVal() > 0)) { - // Indicate current mode is cooling-only mode. Simultaneous clg/htg mode will be set later - currentMode = CurrentMode::CoolingOnly; - - // Assign proper performance curve information depending on the control mode - // Cooling curve is used only for cooling-only mode, and the others (Simultaneous and heating) read the heating curve - if (this->SimulClgDominant || this->SimulHtgDominant) { - chillerHeater.RefCap = chillerHeater.RefCapClgHtg; - chillerHeater.RefCOP = chillerHeater.RefCOPClgHtg; - chillerHeater.TempRefEvapOut = chillerHeater.TempRefEvapOutClgHtg; - chillerHeater.TempRefCondIn = chillerHeater.TempRefCondInClgHtg; - chillerHeater.TempRefCondOut = chillerHeater.TempRefCondOutClgHtg; - chillerHeater.OptPartLoadRat = chillerHeater.OptPartLoadRatClgHtg; - chillerHeater.CondMode = chillerHeater.CondModeHeating; - chillerHeater.ChillerCapFTIDX = chillerHeater.ChillerCapFTHeatingIDX; - chillerHeater.ChillerEIRFTIDX = chillerHeater.ChillerEIRFTHeatingIDX; - chillerHeater.ChillerEIRFPLRIDX = chillerHeater.ChillerEIRFPLRHeatingIDX; - } else { - chillerHeater.RefCap = chillerHeater.RefCapCooling; - chillerHeater.RefCOP = chillerHeater.RefCOPCooling; - chillerHeater.TempRefEvapOut = chillerHeater.TempRefEvapOutCooling; - chillerHeater.TempRefCondIn = chillerHeater.TempRefCondInCooling; - chillerHeater.TempRefCondOut = chillerHeater.TempRefCondOutCooling; - chillerHeater.OptPartLoadRat = chillerHeater.OptPartLoadRatCooling; - chillerHeater.CondMode = chillerHeater.CondModeCooling; - chillerHeater.ChillerCapFTIDX = chillerHeater.ChillerCapFTCoolingIDX; - chillerHeater.ChillerEIRFTIDX = chillerHeater.ChillerEIRFTCoolingIDX; - chillerHeater.ChillerEIRFPLRIDX = chillerHeater.ChillerEIRFPLRCoolingIDX; - } - - // Only used to read curve values - CondOutletTemp = chillerHeater.TempRefCondOutCooling; - Real64 CondTempforCurve = this->setChillerHeaterCondTemp(state, ChillerHeaterNum, CondInletTemp, CondOutletTemp); - - // Chiller reference capacity - Real64 ChillerRefCap = chillerHeater.RefCap; - Real64 ReferenceCOP = chillerHeater.RefCOP; - Real64 TempLowLimitEout = chillerHeater.TempLowLimitEvapOut; - Real64 EvapOutletTempSetPoint = chillerHeater.TempRefEvapOutCooling; - - // Calculate Chiller Capacity as a function of temperature and error check - state.dataPlantCentralGSHP->ChillerCapFT = this->calcChillerCapFT(state, ChillerHeaterNum, EvapOutletTempSetPoint, CondTempforCurve); - - // Calculate the specific heat of chilled water - Real64 Cp = this->CWPlantLoc.loop->glycol->getSpecificHeat(state, EvapInletTemp, RoutineName); - - // Calculate cooling load this chiller should meet and the other chillers are demanded - EvapOutletTempSetPoint = state.dataLoopNodes->Node(this->CoolSetPointTempNode).TempSetPoint; - - // Minimum capacity of the evaporator - Real64 EvaporatorCapMin = chillerHeater.MinPartLoadRatCooling * chillerHeater.RefCapCooling; - - // Remaining cooling load the other chiller heaters should meet - Real64 CoolingLoadToMeet = min(chillerHeater.RefCapCooling, max(std::abs(EvaporatorLoad), EvaporatorCapMin)); - - // Available chiller capacity as a function of temperature - // Chiller available capacity at current operating conditions [W] - Real64 AvailChillerCap = ChillerRefCap * state.dataPlantCentralGSHP->ChillerCapFT; - - Real64 PartLoadRat; // Operating part load ratio - Real64 MinPartLoadRat; // Min allowed operating fraction of full load - Real64 MaxPartLoadRat; // Max allowed operating fraction of full load - - Curve::GetCurveMinMaxValues(state, chillerHeater.ChillerEIRFPLRIDX, MinPartLoadRat, MaxPartLoadRat); - - // Set load this chiller heater should meet - QEvaporator = min(CoolingLoadToMeet, (AvailChillerCap * MaxPartLoadRat)); - EvapOutletTemp = EvapOutletTempSetPoint; - Real64 EvapDeltaTemp = EvapInletTemp - EvapOutletTemp; - - // Calculate temperatures for constant flow and mass flow rates for variable flow - if (EvapMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) { - if (this->SimulHtgDominant) { // Evaporator operates at full capacity for heating - PartLoadRat = max(0.0, min((ChillerRefCap / AvailChillerCap), MaxPartLoadRat)); - QEvaporator = AvailChillerCap * PartLoadRat; - EvapDeltaTemp = QEvaporator / EvapMassFlowRate / Cp; - EvapOutletTemp = EvapInletTemp - EvapDeltaTemp; - } else { // Cooling only mode or cooling dominant simultaneous htg/clg mode - if (this->VariableFlowCH) { // Variable flow - Real64 EvapMassFlowRateCalc = QEvaporator / EvapDeltaTemp / Cp; - if (EvapMassFlowRateCalc > EvapMassFlowRate) { - EvapMassFlowRateCalc = EvapMassFlowRate; - Real64 EvapDeltaTempCalc = QEvaporator / EvapMassFlowRate / Cp; - EvapOutletTemp = EvapInletTemp - EvapDeltaTempCalc; - if (EvapDeltaTempCalc > EvapDeltaTemp) { - QEvaporator = EvapMassFlowRate * Cp * EvapDeltaTemp; - } - } - EvapMassFlowRate = EvapMassFlowRateCalc; - } else { // Constant Flow - Real64 EvapOutletTempCalc = EvapInletTemp - EvapDeltaTemp; - if (EvapOutletTempCalc > EvapOutletTemp) { // Load to meet should be adjusted - EvapOutletTempCalc = EvapOutletTemp; - QEvaporator = EvapMassFlowRate * Cp * EvapDeltaTemp; - } - EvapOutletTemp = EvapOutletTempCalc; - } // End of flow control decision - } // End of operation mode - } else { - QEvaporator = 0.0; - EvapOutletTemp = EvapInletTemp; - } - - // Run evaporator checks and adjust outlet temp and QEvaporator if necessary - WrapperSpecs::checkEvapOutletTemp( - state, ChillerHeaterNum, EvapOutletTemp, TempLowLimitEout, EvapInletTemp, QEvaporator, EvapMassFlowRate, Cp, currentMode); - - // Calculate part load once more since evaporator capacity might be modified - WrapperSpecs::calcPLRAndCyclingRatio(state, AvailChillerCap, PartLoadRat, MinPartLoadRat, MaxPartLoadRat, QEvaporator, FRAC); - - // Determine chiller compressor power and transfer heat calculation - state.dataPlantCentralGSHP->ChillerEIRFT = - max(0.0, Curve::CurveValue(state, chillerHeater.ChillerEIRFTIDX, EvapOutletTemp, CondTempforCurve)); - state.dataPlantCentralGSHP->ChillerEIRFPLR = max(0.0, Curve::CurveValue(state, chillerHeater.ChillerEIRFPLRIDX, PartLoadRat)); - - if (ReferenceCOP <= 0.0) { - CHPower = 0.0; - } else { - CHPower = - (AvailChillerCap / ReferenceCOP) * state.dataPlantCentralGSHP->ChillerEIRFPLR * state.dataPlantCentralGSHP->ChillerEIRFT * FRAC; - } - - QCondenser = CHPower * chillerHeater.OpenMotorEff + QEvaporator + state.dataPlantCentralGSHP->ChillerFalseLoadRate; - - if (CHPower == 0.0) { - ActualCOP = 0.0; - } else { - ActualCOP = (QEvaporator + state.dataPlantCentralGSHP->ChillerFalseLoadRate) / CHPower; - } - - if (CondMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) { - Cp = this->GLHEPlantLoc.loop->glycol->getSpecificHeat(state, CondInletTemp, RoutineNameElecEIRChiller); - CondOutletTemp = QCondenser / CondMassFlowRate / Cp + CondInletTemp; - } else { - ShowSevereError(state, std::format("CalcChillerheaterModel: Condenser flow = 0, for Chillerheater={}", chillerHeater.Name)); - ShowContinueErrorTimeStamp(state, ""); - } - - // Determine load next chillers should meet - if (EvaporatorLoad < QEvaporator) { - EvaporatorLoad = 0.0; // No remaining load so the rest will be off - } else { - EvaporatorLoad -= QEvaporator; - } - - // Initialize reporting variable when this chiller doesn't need to operate - if (QEvaporator == 0.0) { - currentMode = CurrentMode::Off; - state.dataPlantCentralGSHP->ChillerPartLoadRatio = 0.0; - state.dataPlantCentralGSHP->ChillerCyclingRatio = 0.0; - state.dataPlantCentralGSHP->ChillerFalseLoadRate = 0.0; - EvapMassFlowRate = 0.0; - CondMassFlowRate = 0.0; - CHPower = 0.0; - QCondenser = 0.0; - EvapOutletTemp = EvapInletTemp; - CondOutletTemp = CondInletTemp; - EvaporatorLoad = 0.0; - } - - } // End of calculation for cooling - - // Set variables to the arrays - chillerHeater.EvapOutletNode.MassFlowRate = EvapMassFlowRate; - chillerHeater.CondOutletNode.MassFlowRate = CondMassFlowRate; - chillerHeater.EvapOutletNode.Temp = EvapOutletTemp; - chillerHeater.EvapInletNode.Temp = EvapInletTemp; - chillerHeater.CondOutletNode.Temp = CondOutletTemp; - chillerHeater.CondInletNode.Temp = CondInletTemp; - chillerHeater.Report.currentMode = currentMode; - chillerHeater.Report.ChillerPartLoadRatio = state.dataPlantCentralGSHP->ChillerPartLoadRatio; - chillerHeater.Report.ChillerCyclingRatio = state.dataPlantCentralGSHP->ChillerCyclingRatio; - chillerHeater.Report.ChillerFalseLoadRate = state.dataPlantCentralGSHP->ChillerFalseLoadRate; - chillerHeater.Report.ChillerCapFT = state.dataPlantCentralGSHP->ChillerCapFT; - chillerHeater.Report.ChillerEIRFT = state.dataPlantCentralGSHP->ChillerEIRFT; - chillerHeater.Report.ChillerEIRFPLR = state.dataPlantCentralGSHP->ChillerEIRFPLR; - chillerHeater.Report.CoolingPower = CHPower; - chillerHeater.Report.HeatingPower = 0.0; - chillerHeater.Report.QEvap = QEvaporator; - chillerHeater.Report.QCond = QCondenser; - chillerHeater.Report.EvapOutletTemp = EvapOutletTemp; - chillerHeater.Report.EvapInletTemp = EvapInletTemp; - chillerHeater.Report.CondOutletTemp = CondOutletTemp; - chillerHeater.Report.CondInletTemp = CondInletTemp; - chillerHeater.Report.Evapmdot = EvapMassFlowRate; - chillerHeater.Report.Condmdot = CondMassFlowRate; - chillerHeater.Report.ActualCOP = ActualCOP; - - if (this->SimulClgDominant || this->SimulHtgDominant) { // Store for using these cooling side data in the hot water loop - chillerHeater.Report.currentMode = currentMode; - chillerHeater.Report.ChillerPartLoadRatioSimul = state.dataPlantCentralGSHP->ChillerPartLoadRatio; - chillerHeater.Report.ChillerCyclingRatioSimul = state.dataPlantCentralGSHP->ChillerCyclingRatio; - chillerHeater.Report.ChillerFalseLoadRateSimul = state.dataPlantCentralGSHP->ChillerFalseLoadRate; - chillerHeater.Report.ChillerCapFTSimul = state.dataPlantCentralGSHP->ChillerCapFT; - chillerHeater.Report.ChillerEIRFTSimul = state.dataPlantCentralGSHP->ChillerEIRFT; - chillerHeater.Report.ChillerEIRFPLRSimul = state.dataPlantCentralGSHP->ChillerEIRFPLR; - chillerHeater.Report.CoolingPowerSimul = CHPower; - chillerHeater.Report.QEvapSimul = QEvaporator; - chillerHeater.Report.EvapOutletTempSimul = EvapOutletTemp; - chillerHeater.Report.EvapInletTempSimul = EvapInletTemp; - chillerHeater.Report.EvapmdotSimul = EvapMassFlowRate; - if (this->SimulClgDominant) { - chillerHeater.Report.QCondSimul = QCondenser; - chillerHeater.Report.CondOutletTempSimul = CondOutletTemp; - chillerHeater.Report.CondInletTempSimul = CondInletTemp; - chillerHeater.Report.CondmdotSimul = CondMassFlowRate; - } - } - } -} - -void WrapperSpecs::CalcChillerHeaterModel(EnergyPlusData &state) -{ - // SUBROUTINE INFORMATION: - // AUTHOR Daeho Kang, PNNL - // DATE WRITTEN Feb 2013 - // MODIFIED na - // RE-ENGINEERED na - - // PURPOSE OF THIS SUBROUTINE: - // Simulate a ChillerHeaterPerformance:Electric:EIR using curve fit - - // METHODOLOGY EMPLOYED: - // Use empirical curve fits to model performance at off-reference conditions - - // REFERENCES: - // 1. DOE-2 Engineers Manual, Version 2.1A, November 1982, LBL-11353 - - static constexpr std::string_view RoutineName("CalcChillerHeaterModel"); - - bool IsLoadHeatRemaining; // Ture if heating load remains for this chiller heater - bool NextCompIndicator(false); // Component indicator when identical chiller heaters exist - int CompNum(0); // Component number - int IdenticalUnitCounter = 0; // Pointer to count number of identical unit passed - int IdenticalUnitRemaining; // Pointer to count number of identical unit available for a component - Real64 CondenserLoad(0.0); // Remaining heating load that this wrapper should meet - Real64 CurAvailHWMassFlowRate(0.0); // Maximum available hot water mass within the wrapper bank - - CondenserLoad = this->WrapperHeatingLoad; - Real64 HWInletMassFlowRate = state.dataLoopNodes->Node(this->HWInletNodeNum).MassFlowRate; - - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - - auto &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - - // Set module level inlet and outlet nodes and initialize other local variables - CurrentMode currentMode = CurrentMode::Off; - state.dataPlantCentralGSHP->ChillerPartLoadRatio = 0.0; - state.dataPlantCentralGSHP->ChillerCyclingRatio = 0.0; - state.dataPlantCentralGSHP->ChillerFalseLoadRate = 0.0; - Real64 CHPower = 0.0; - Real64 QCondenser = 0.0; - Real64 QEvaporator = 0.0; - Real64 FRAC = 1.0; - Real64 CondDeltaTemp = 0.0; - Real64 CoolingPower = 0.0; - Real64 ActualCOP = 0.0; - Real64 EvapInletTemp = state.dataLoopNodes->Node(this->GLHEInletNodeNum).Temp; - Real64 CondInletTemp = state.dataLoopNodes->Node(this->HWInletNodeNum).Temp; - Real64 EvapOutletTemp = EvapInletTemp; - Real64 CondOutletTemp = CondInletTemp; - - // Find proper schedule values - if (this->NumOfComp != this->ChillerHeaterNums) { // Identical units exist - if (ChillerHeaterNum == 1) { - IdenticalUnitCounter = 0; - NextCompIndicator = false; - CompNum = ChillerHeaterNum; - } - if (NextCompIndicator) { - ++CompNum; - } - if (CompNum == 1) { - if (ChillerHeaterNum != this->WrapperComp(CompNum).WrapperIdenticalObjectNum) { - NextCompIndicator = false; - } else if (ChillerHeaterNum == this->WrapperComp(CompNum).WrapperIdenticalObjectNum) { - NextCompIndicator = true; - } - } else if (CompNum > 1) { - if ((ChillerHeaterNum - ((ChillerHeaterNum - 1) - IdenticalUnitCounter)) != this->WrapperComp(CompNum).WrapperIdenticalObjectNum) { - NextCompIndicator = false; - } else if ((ChillerHeaterNum - ((ChillerHeaterNum - 1) - IdenticalUnitCounter)) == - this->WrapperComp(CompNum).WrapperIdenticalObjectNum) { - NextCompIndicator = true; - } - } - ++IdenticalUnitCounter; - IdenticalUnitRemaining = this->WrapperComp(CompNum).WrapperIdenticalObjectNum - IdenticalUnitCounter; - if (IdenticalUnitRemaining == 0) { - IdenticalUnitCounter = 0; - } - } else if (this->NumOfComp == this->ChillerHeaterNums) { - ++CompNum; - } - - Real64 CondMassFlowRate; // Condenser mass flow rate through this chiller heater - Real64 EvapMassFlowRate; // Evaporator mass flow rate through this chiller heater - - // Check to see if this chiller heater needs to run - if (CondenserLoad > 0.0 && (this->WrapperComp(CompNum).chSched->getCurrentVal() > 0)) { - IsLoadHeatRemaining = true; - - // Calculate density ratios to adjust mass flow rates from initialized ones - // Hot water temperature is known, but condenser mass flow rates will be adjusted in the following "Do" loop - Real64 InitDensity = this->CWPlantLoc.loop->glycol->getDensity(state, Constant::CWInitConvTemp, RoutineName); - Real64 EvapDensity = this->CWPlantLoc.loop->glycol->getDensity(state, EvapInletTemp, RoutineName); - Real64 CondDensity = this->CWPlantLoc.loop->glycol->getDensity(state, CondInletTemp, RoutineName); - - // Calculate density ratios to adjust mass flow rates from initialized ones - Real64 HWDensityRatio = CondDensity / InitDensity; - Real64 GLHEDensityRatio = EvapDensity / InitDensity; - - EvapMassFlowRate = chillerHeater.EvapInletNode.MassFlowRateMaxAvail; - CondMassFlowRate = chillerHeater.CondInletNode.MassFlowRateMaxAvail; - EvapMassFlowRate *= GLHEDensityRatio; - CondMassFlowRate *= HWDensityRatio; - - // Check flows from plant to adjust as necessary - if (CurAvailHWMassFlowRate == 0) { // First chiller heater which is on - CurAvailHWMassFlowRate = HWInletMassFlowRate; - } else if (ChillerHeaterNum > 1) { - CurAvailHWMassFlowRate -= this->ChillerHeater(ChillerHeaterNum - 1).CondOutletNode.MassFlowRate; - } - CondMassFlowRate = min(CurAvailHWMassFlowRate, CondMassFlowRate); - - // It is not enforced to be the smaller of CH max temperature and plant temp setpoint. - // Hot water temperatures at the individual CHs' outlet may be greater than plant setpoint temp, - // but should be lower than the CHs max temp - CondOutletTemp = chillerHeater.TempRefCondOutClgHtg; - CondDeltaTemp = CondOutletTemp - CondInletTemp; - - if (CondDeltaTemp < 0.0) { // Hot water temperature is greater than the maximum - if (chillerHeater.ChillerEIRRefTempErrorIndex == 0) { - ShowSevereMessage( - state, - std::format("CalcChillerHeaterModel: ChillerHeaterPerformance:Electric:EIR=\"{}\", DeltaTemp < 0", chillerHeater.Name)); - ShowContinueError( - state, - std::format(" Reference Simultaneous Cooling-Heating Mode Leaving Condenser Water Temperature [{:.1f}]", CondOutletTemp)); - ShowContinueError(state, std::format("is below condenser inlet temperature of [{:.1f}].", CondInletTemp)); - ShowContinueErrorTimeStamp(state, ""); - ShowContinueError(state, " Reset reference temperature to one greater than the inlet temperature "); - } - ShowRecurringSevereErrorAtEnd(state, - "ChillerHeaterPerformance:Electric:EIR=\"" + chillerHeater.Name + - "\": Reference temperature problems continue.", - chillerHeater.ChillerEIRRefTempErrorIndex, - CondDeltaTemp, - CondDeltaTemp, - _, - "deltaC", - "deltaC"); - QCondenser = 0.0; - IsLoadHeatRemaining = false; - } - - if (ChillerHeaterNum > 1) { - // Operation mode needs to be set in a simultaneous clg/htg mode - // Always off even heating load remains if this CH is assumed to be off in the loop 1 - if (this->SimulClgDominant) { - if (chillerHeater.Report.QEvapSimul == 0.0) { - currentMode = CurrentMode::Off; - IsLoadHeatRemaining = false; - } else { // Heat recovery - currentMode = CurrentMode::HeatRecovery; - } - } - } // End of simultaneous clg/htg mode determination - - } else { // chiller heater is off - IsLoadHeatRemaining = false; - CondMassFlowRate = 0.0; - EvapMassFlowRate = 0.0; - currentMode = CurrentMode::Off; - if (this->SimulClgDominant) { - if (chillerHeater.Report.QEvapSimul > 0.0) { - currentMode = CurrentMode::CoolingDominant; - } - } // End of mode determination - } // End of system operation determinatoin - - if (IsLoadHeatRemaining && CondMassFlowRate > 0.0 && (this->WrapperComp(CompNum).chSched->getCurrentVal() > 0)) { // System is on - // Operation mode - if (this->SimulHtgDominant) { - if (chillerHeater.Report.QEvapSimul == 0.0) { - currentMode = CurrentMode::HeatingDominant; // No cooling necessary - } else { // Heat recovery mode. Both chilled water and hot water loops are connected. No condenser flow. - currentMode = CurrentMode::HeatRecovery; - } - } - - // Mode 3 (HeatRecovery) and 5 (HeatingDominant) use cooling side data stored from the chilled water loop - // Mode 4 (CoolingDominant) uses all data from the chilled water loop due to no heating demand - // Fix for Defect #10065: When the heating load is dominant and the Current Mode is 3 (HeatRecovery), - // simulation must go through the "heating" side to properly update the power consumption. - // Otherwise, the power consumption could come back zero for heating and cooling. - if (this->SimulClgDominant || (currentMode == CurrentMode::HeatRecovery && !this->SimulHtgDominant)) { - currentMode = CurrentMode::HeatRecovery; - QCondenser = chillerHeater.Report.QCondSimul; - this->adjustChillerHeaterCondFlowTemp(state, QCondenser, CondMassFlowRate, CondOutletTemp, CondInletTemp, CondDeltaTemp); - } else { // Either Mode 2 (HeatingOnly) or 3 (HeatRecovery) or 5 (HeatingDominant) - if (this->SimulHtgDominant) { - currentMode = CurrentMode::HeatingDominant; - } else { - currentMode = CurrentMode::HeatingOnly; - } - - state.dataPlantCentralGSHP->ChillerCapFT = 0.0; - state.dataPlantCentralGSHP->ChillerEIRFT = 0.0; - state.dataPlantCentralGSHP->ChillerEIRFPLR = 0.0; - - // Assign curve values to local data array - chillerHeater.RefCap = chillerHeater.RefCapClgHtg; - chillerHeater.RefCOP = chillerHeater.RefCOPClgHtg; - chillerHeater.TempRefEvapOut = chillerHeater.TempRefEvapOutClgHtg; - chillerHeater.TempRefCondOut = chillerHeater.TempRefCondOutClgHtg; - chillerHeater.OptPartLoadRat = chillerHeater.OptPartLoadRatClgHtg; - chillerHeater.CondMode = chillerHeater.CondModeHeating; - chillerHeater.ChillerCapFTIDX = chillerHeater.ChillerCapFTHeatingIDX; - chillerHeater.ChillerEIRFTIDX = chillerHeater.ChillerEIRFTHeatingIDX; - chillerHeater.ChillerEIRFPLRIDX = chillerHeater.ChillerEIRFPLRHeatingIDX; - - // Reference condenser temperature for the performance curve reading: set to entering or leaving condenser temperature based on user - // input - Real64 CondTempforCurve = this->setChillerHeaterCondTemp(state, ChillerHeaterNum, CondInletTemp, chillerHeater.TempRefCondOutClgHtg); - - Real64 ChillerRefCap = chillerHeater.RefCap; - Real64 ReferenceCOP = chillerHeater.RefCOP; - EvapOutletTemp = chillerHeater.TempRefEvapOutClgHtg; - Real64 TempLowLimitEout = chillerHeater.TempLowLimitEvapOut; - Real64 EvapOutletTempSetPoint = state.dataLoopNodes->Node(this->CoolSetPointTempNode).TempSetPoint; - - // Calculate Chiller Capacity as a function of temperature and error check - state.dataPlantCentralGSHP->ChillerCapFT = this->calcChillerCapFT(state, ChillerHeaterNum, EvapOutletTempSetPoint, CondTempforCurve); - - // Available chiller capacity as a function of temperature - Real64 AvailChillerCap = ChillerRefCap * state.dataPlantCentralGSHP->ChillerCapFT; - - Real64 PartLoadRat; // Operating part load ratio - Real64 MinPartLoadRat; // Min allowed operating fraction of full load - Real64 MaxPartLoadRat; // Max allowed operating fraction of full load - Curve::GetCurveMinMaxValues(state, chillerHeater.ChillerEIRFPLRIDX, MinPartLoadRat, MaxPartLoadRat); - - // Part load ratio based on reference capacity and available chiller capacity - if (AvailChillerCap > 0) { - PartLoadRat = max(0.0, min((ChillerRefCap / AvailChillerCap), MaxPartLoadRat)); - } else { - PartLoadRat = 0.0; - } - - Real64 Cp = this->HWPlantLoc.loop->glycol->getSpecificHeat(state, chillerHeater.EvapInletNode.Temp, RoutineName); - - // Calculate evaporator heat transfer - if (EvapMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) { - QEvaporator = AvailChillerCap * PartLoadRat; - Real64 EvapDeltaTemp = QEvaporator / EvapMassFlowRate / Cp; - EvapOutletTemp = EvapInletTemp - EvapDeltaTemp; - } - - // Run evaporator checks and adjust outlet temp and QEvaporator if necessary - WrapperSpecs::checkEvapOutletTemp(state, - ChillerHeaterNum, - EvapOutletTemp, - TempLowLimitEout, - chillerHeater.EvapInletNode.Temp, - QEvaporator, - EvapMassFlowRate, - Cp, - currentMode); - - WrapperSpecs::calcPLRAndCyclingRatio(state, AvailChillerCap, PartLoadRat, MinPartLoadRat, MaxPartLoadRat, QEvaporator, FRAC); - - state.dataPlantCentralGSHP->ChillerEIRFT = - max(0.0, Curve::CurveValue(state, chillerHeater.ChillerEIRFTIDX, EvapOutletTemp, CondTempforCurve)); - state.dataPlantCentralGSHP->ChillerEIRFPLR = max(0.0, Curve::CurveValue(state, chillerHeater.ChillerEIRFPLRIDX, PartLoadRat)); - CHPower = - (AvailChillerCap / ReferenceCOP) * state.dataPlantCentralGSHP->ChillerEIRFPLR * state.dataPlantCentralGSHP->ChillerEIRFT * FRAC; - - if (CHPower <= 0.0) { - ActualCOP = 0.0; - } else { - ActualCOP = (QEvaporator + state.dataPlantCentralGSHP->ChillerFalseLoadRate) / CHPower; - } - - QCondenser = CHPower * chillerHeater.OpenMotorEff + QEvaporator + state.dataPlantCentralGSHP->ChillerFalseLoadRate; - Real64 qCondenserFullLoad = QCondenser; - - // Determine heating load for this heater and pass the remaining load to the next chiller heater - Real64 CondenserCapMin = QCondenser * MinPartLoadRat; - Real64 HeatingLoadToMeet = min(QCondenser, max(std::abs(CondenserLoad), CondenserCapMin)); - - // Set load this chiller heater should meet and temperatures given - QCondenser = min(HeatingLoadToMeet, QCondenser); - - // Calculate outlet temperature for constant flow and mass flow rate for variable flow - // Limit mass flow rate for this chiller heater to the available mass at given temperature conditions - // when mass flow rate calculated to meet the load is greater than the maximum available - // then recalculate heating load this chiller heater can meet - if (currentMode == CurrentMode::HeatingOnly || this->SimulHtgDominant) { - if (CondMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance && CondDeltaTemp > 0.0) { - this->adjustChillerHeaterCondFlowTemp(state, QCondenser, CondMassFlowRate, CondOutletTemp, CondInletTemp, CondDeltaTemp); - if (qCondenserFullLoad > 0.0) { - Real64 constexpr diffTolerance = 0.0001; - if (((qCondenserFullLoad - QCondenser) / qCondenserFullLoad) > diffTolerance) { - // QCondenser was reduced, so reduce evaporator side quantities by a factor of the condenser based PLR - PartLoadRat = max(MinPartLoadRat, min((QCondenser / qCondenserFullLoad), MaxPartLoadRat)); - QCondenser = PartLoadRat * qCondenserFullLoad; - this->adjustChillerHeaterCondFlowTemp( - state, QCondenser, CondMassFlowRate, CondOutletTemp, CondInletTemp, CondDeltaTemp); - // In most situations here, QCondenser will not be reduced here, but it has to be taken into account. This will - // potentially violate the minPLR but this will keep the solution simple for now. - // So, basically multiply all terms in the energy balance by the same factor to maintain the energy balance. - Real64 modifiedPLR = QCondenser / qCondenserFullLoad; - QEvaporator *= modifiedPLR; - CHPower *= modifiedPLR; - PartLoadRat = modifiedPLR; - state.dataPlantCentralGSHP->ChillerFalseLoadRate *= modifiedPLR; - // Now re-adjust things on the evaporator side to get the correct flows/temperatures - this->adjustChillerHeaterEvapFlowTemp(state, QEvaporator, EvapMassFlowRate, EvapOutletTemp, EvapInletTemp); - } - } - } else { - QCondenser = 0.0; - CondOutletTemp = CondInletTemp; - } - state.dataPlantCentralGSHP->ChillerPartLoadRatio = PartLoadRat; - } - - } // End of calculation depending on the modes - - // Determine load next chiller heater meets - if (CondenserLoad < QCondenser) { // Heating load is met by this chiller heater - CondenserLoad = 0.0; - } else { - CondenserLoad -= QCondenser; - } - - if (QCondenser == 0.0) { - currentMode = CurrentMode::Off; - state.dataPlantCentralGSHP->ChillerPartLoadRatio = 0.0; - state.dataPlantCentralGSHP->ChillerCyclingRatio = 0.0; - state.dataPlantCentralGSHP->ChillerFalseLoadRate = 0.0; - EvapMassFlowRate = 0.0; - CondMassFlowRate = 0.0; - CHPower = 0.0; - QEvaporator = 0.0; - EvapOutletTemp = EvapInletTemp; - CondOutletTemp = CondInletTemp; - CondenserLoad = 0.0; - } - - // Heat recovery or cooling dominant modes need to use the evaporator side information - if (currentMode == CurrentMode::HeatRecovery || currentMode == CurrentMode::CoolingDominant) { - state.dataPlantCentralGSHP->ChillerPartLoadRatio = chillerHeater.Report.ChillerPartLoadRatioSimul; - state.dataPlantCentralGSHP->ChillerCyclingRatio = chillerHeater.Report.ChillerCyclingRatioSimul; - state.dataPlantCentralGSHP->ChillerFalseLoadRate = chillerHeater.Report.ChillerFalseLoadRateSimul; - state.dataPlantCentralGSHP->ChillerCapFT = chillerHeater.Report.ChillerCapFTSimul; - state.dataPlantCentralGSHP->ChillerEIRFT = chillerHeater.Report.ChillerEIRFTSimul; - state.dataPlantCentralGSHP->ChillerEIRFPLR = chillerHeater.Report.ChillerEIRFPLRSimul; - QEvaporator = chillerHeater.Report.QEvapSimul; - EvapOutletTemp = chillerHeater.Report.EvapOutletTempSimul; - EvapInletTemp = chillerHeater.Report.EvapInletTempSimul; - EvapMassFlowRate = chillerHeater.Report.EvapmdotSimul; - if (this->SimulClgDominant) { - CHPower = chillerHeater.Report.CoolingPowerSimul; - chillerHeater.Report.HeatingPower = 0.0; - } - } - } - - // Check if it is mode 4 (CoolingDominant), then skip binding local variables - if (currentMode == CurrentMode::CoolingDominant) { - chillerHeater.Report.currentMode = currentMode; - } else { - chillerHeater.EvapOutletNode.MassFlowRate = EvapMassFlowRate; - chillerHeater.CondOutletNode.MassFlowRate = CondMassFlowRate; - chillerHeater.EvapOutletNode.Temp = EvapOutletTemp; - chillerHeater.EvapInletNode.Temp = EvapInletTemp; - chillerHeater.CondOutletNode.Temp = CondOutletTemp; - chillerHeater.CondInletNode.Temp = CondInletTemp; - chillerHeater.Report.currentMode = currentMode; - chillerHeater.Report.ChillerPartLoadRatio = state.dataPlantCentralGSHP->ChillerPartLoadRatio; - chillerHeater.Report.ChillerCyclingRatio = state.dataPlantCentralGSHP->ChillerCyclingRatio; - chillerHeater.Report.ChillerFalseLoadRate = state.dataPlantCentralGSHP->ChillerFalseLoadRate; - chillerHeater.Report.ChillerCapFT = state.dataPlantCentralGSHP->ChillerCapFT; - chillerHeater.Report.ChillerEIRFT = state.dataPlantCentralGSHP->ChillerEIRFT; - chillerHeater.Report.ChillerEIRFPLR = state.dataPlantCentralGSHP->ChillerEIRFPLR; - chillerHeater.Report.CoolingPower = CoolingPower; - chillerHeater.Report.HeatingPower = CHPower; - chillerHeater.Report.QEvap = QEvaporator; - chillerHeater.Report.QCond = QCondenser; - chillerHeater.Report.EvapOutletTemp = EvapOutletTemp; - chillerHeater.Report.EvapInletTemp = EvapInletTemp; - chillerHeater.Report.CondOutletTemp = CondOutletTemp; - chillerHeater.Report.CondInletTemp = CondInletTemp; - chillerHeater.Report.Evapmdot = EvapMassFlowRate; - chillerHeater.Report.Condmdot = CondMassFlowRate; - chillerHeater.Report.ActualCOP = ActualCOP; - } - } -} - -void WrapperSpecs::adjustChillerHeaterCondFlowTemp(EnergyPlusData &state, - Real64 &QCondenser, - Real64 &CondMassFlowRate, - Real64 &CondOutletTemp, - Real64 const CondInletTemp, - Real64 const CondDeltaTemp) -{ - // Based on whether this is variable or constant flow, adjust either flow or outlet temperature and also the load - static constexpr std::string_view RoutineName("adjustChillerHeaterCondFlowTemp"); - Real64 Cp = this->HWPlantLoc.loop->glycol->getSpecificHeat(state, CondInletTemp, RoutineName); - - if (this->VariableFlowCH) { // Variable Flow (adjust flow and condenser load as needed) - Real64 CondMassFlowRateCalc = QCondenser / CondDeltaTemp / Cp; - if (CondMassFlowRateCalc > CondMassFlowRate) { - CondMassFlowRateCalc = CondMassFlowRate; - Real64 CondDeltaTempCalc = QCondenser / CondMassFlowRate / Cp; - if (CondDeltaTempCalc > CondDeltaTemp) { // Load to meet should be adjusted - QCondenser = CondMassFlowRate * Cp * CondDeltaTemp; - } - } - CondMassFlowRate = CondMassFlowRateCalc; - } else { // Constant Flow (adjust outlet temperature and condenser load as needed) - Real64 CondDeltaTempCalc = QCondenser / CondMassFlowRate / Cp; - Real64 CondOutletTempCalc = CondDeltaTempCalc + CondInletTemp; - if (CondOutletTempCalc > CondOutletTemp) { // Load to meet should be adjusted - CondOutletTempCalc = CondOutletTemp; - QCondenser = CondMassFlowRate * Cp * CondDeltaTemp; - } - CondOutletTemp = CondOutletTempCalc; - } -} - -void WrapperSpecs::adjustChillerHeaterEvapFlowTemp( - EnergyPlusData &state, Real64 const qEvaporator, Real64 &evapMassFlowRate, Real64 &evapOutletTemp, Real64 const evapInletTemp) -{ - // Adjust flow and outlet temperature for the evaporator side without modifying the heat transfer rate - Real64 constexpr lowLoad = 0.001; - static constexpr std::string_view routineName("adjustChillerHeaterEvapFlowTemp"); - Real64 Cp = this->HWPlantLoc.loop->glycol->getSpecificHeat(state, evapInletTemp, routineName); - Real64 evapDeltaTemp = evapInletTemp - evapOutletTemp; - - if ((qEvaporator < lowLoad) || (evapDeltaTemp <= 0.0)) { - evapMassFlowRate = 0.0; - evapOutletTemp = evapInletTemp; - } else { - if (this->VariableFlowCH) { // for variable flow, adjust flow if higher than max value passed in - Real64 evapMassFlowRateCalc = qEvaporator / evapDeltaTemp / Cp; - if (evapMassFlowRateCalc > evapMassFlowRate) { - evapMassFlowRateCalc = evapMassFlowRate; - } - evapMassFlowRate = evapMassFlowRateCalc; - } - // Adjust temperature for either flow type to maintain agreement with qEvaporator - evapDeltaTemp = qEvaporator / evapMassFlowRate / Cp; - evapOutletTemp = evapInletTemp - evapDeltaTemp; - } -} - -Real64 WrapperSpecs::setChillerHeaterCondTemp([[maybe_unused]] EnergyPlusData &state, - int const numChillerHeater, - Real64 const condEnteringTemp, - Real64 const condLeavingTemp) -{ - Real64 setChillerHeaterCondTemp; - if (this->ChillerHeater(numChillerHeater).CondMode == CondenserModeTemperature::EnteringCondenser) { - setChillerHeaterCondTemp = condEnteringTemp; - } else { // by default, if not EnteringCondenser, then this can only be LeavingCondenser - setChillerHeaterCondTemp = condLeavingTemp; - } - return setChillerHeaterCondTemp; -} - -Real64 WrapperSpecs::calcChillerCapFT(EnergyPlusData &state, int const numChillerHeater, Real64 const evapOutletTemp, Real64 const condTemp) -{ - // Calculate the chiller capacity as a function of temperature - Real64 chillCapFT = Curve::CurveValue(state, this->ChillerHeater(numChillerHeater).ChillerCapFTIDX, evapOutletTemp, condTemp); - - // Tracks errors for when the capacity is calculated as less than zero - if (chillCapFT < 0) { - if (this->ChillerHeater(numChillerHeater).ChillerCapFTError < 1 && !state.dataGlobal->WarmupFlag) { - ++this->ChillerHeater(numChillerHeater).ChillerCapFTError; - ShowWarningError(state, std::format("ChillerHeaterPerformance:Electric:EIR \"{}\":", this->ChillerHeater(numChillerHeater).Name)); - ShowContinueError(state, - std::format(" ChillerHeater Capacity as a Function of Temperature curve output is negative ({:.3f}).", chillCapFT)); - ShowContinueError(state, - std::format(" Negative value occurs using an Evaporator Outlet Temp of {:.1f} and a Condenser Inlet Temp of {:.1f}.", - evapOutletTemp, - condTemp)); - ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation."); - } else if (!state.dataGlobal->WarmupFlag) { - ++this->ChillerHeater(numChillerHeater).ChillerCapFTError; - ShowRecurringWarningErrorAtEnd( - state, - "ChillerHeaterPerformance:Electric:EIR \"" + this->ChillerHeater(numChillerHeater).Name + - "\": ChillerHeater Capacity as a Function of Temperature curve output is negative warning continues...", - this->ChillerHeater(numChillerHeater).ChillerCapFTErrorIndex, - chillCapFT, - chillCapFT); - } - chillCapFT = 0.0; - } - return chillCapFT; -} - -void WrapperSpecs::checkEvapOutletTemp([[maybe_unused]] EnergyPlusData &state, - int const numChillerHeater, - Real64 &evapOutletTemp, - Real64 const lowTempLimitEout, - Real64 const evapInletTemp, - Real64 &qEvaporator, - Real64 const evapMassFlowRate, - Real64 const Cp, - CurrentMode const mode) -{ - // Check evaporator temperature low limit and adjust capacity if needed - if (evapOutletTemp < lowTempLimitEout && mode != CurrentMode::CoolingOnly) { - if ((evapInletTemp - lowTempLimitEout) > DataPlant::DeltaTempTol) { - evapOutletTemp = lowTempLimitEout; - Real64 evapDeltaTemp = evapInletTemp - evapOutletTemp; - qEvaporator = evapMassFlowRate * Cp * evapDeltaTemp; - } else { - qEvaporator = 0.0; - evapOutletTemp = evapInletTemp; - } - } - - // Check if the outlet temperature exceeds the node minimum temperature and adjust capacity if needed - if (evapOutletTemp < this->ChillerHeater(numChillerHeater).EvapOutletNode.TempMin) { - if ((evapInletTemp - this->ChillerHeater(numChillerHeater).EvapOutletNode.TempMin) > DataPlant::DeltaTempTol) { - evapOutletTemp = this->ChillerHeater(numChillerHeater).EvapOutletNode.TempMin; - Real64 evapDeltaTemp = evapInletTemp - evapOutletTemp; - qEvaporator = evapMassFlowRate * Cp * evapDeltaTemp; - } else { - qEvaporator = 0.0; - evapOutletTemp = evapInletTemp; - } - } -} - -void WrapperSpecs::calcPLRAndCyclingRatio(EnergyPlusData &state, - Real64 const availChillerCap, - Real64 &actualPartLoadRatio, - Real64 const minPartLoadRatio, - Real64 const maxPartLoadRatio, - Real64 const qEvaporator, - Real64 &frac) -{ - // Calculate PLR (actualPartLoadRatio) based on evaporator load and available capacity, factoring in max PLR - if (availChillerCap <= 0.0) { - actualPartLoadRatio = 0; - frac = 1.0; - } else { - actualPartLoadRatio = max(0.0, min((qEvaporator / availChillerCap), maxPartLoadRatio)); - // If chiller cycles below minimum part load ratio, frac = amount of time chiller is ON during this time step - if (minPartLoadRatio > 0.0) { - frac = min(1.0, (actualPartLoadRatio / minPartLoadRatio)); - } else { - frac = 1.0; - } - actualPartLoadRatio = max(actualPartLoadRatio, minPartLoadRatio); - } - - state.dataPlantCentralGSHP->ChillerCyclingRatio = frac; - - // Evaporator part load ratio - state.dataPlantCentralGSHP->ChillerPartLoadRatio = actualPartLoadRatio; - - // Calculate the load due to false loading on chiller over and above water side load - state.dataPlantCentralGSHP->ChillerFalseLoadRate = (availChillerCap * actualPartLoadRatio * frac) - qEvaporator; - if (state.dataPlantCentralGSHP->ChillerFalseLoadRate < HVAC::SmallLoad) { - state.dataPlantCentralGSHP->ChillerFalseLoadRate = 0.0; - } -} - -void WrapperSpecs::CalcWrapperModel(EnergyPlusData &state, Real64 &MyLoad, int const LoopNum) -{ - // SUBROUTINE INFORMATION: - // AUTHOR Daeho Kang, PNNL - // DATE WRITTEN Feb 2013 - // MODIFIED na - // RE-ENGINEERED na - - // PURPOSE OF THIS SUBROUTINE: - // Calculate node information connected to plant & condenser loop - - // METHODOLOGY EMPLOYED: - // Use empirical curve fits to model performance at off-reference conditions - - Real64 CurHeatingLoad = 0.0; // Total heating load chiller heater bank (wrapper) meets - Real64 CHWOutletTemp; // Chiller heater bank chilled water outlet temperature - Real64 CHWOutletMassFlowRate; // Chiller heater bank chilled water outlet mass flow rate - Real64 HWOutletTemp; // Chiller heater bank hot water outlet temperature - Real64 GLHEOutletTemp; // Chiller heater bank condenser loop outlet temperature - Real64 GLHEOutletMassFlowRate; // Chiller heater bank condenser loop outlet mass flow rate - Real64 WrapperElecPowerCool(0.0); // Chiller heater bank total cooling electricity [W] - Real64 WrapperElecPowerHeat(0.0); // Chiller heater bank total heating electricity [W] - Real64 WrapperCoolRate(0.0); // Chiller heater bank total cooling rate [W] - Real64 WrapperHeatRate(0.0); // Chiller heater bank total heating rate [W] - Real64 WrapperGLHERate(0.0); // Chiller heater bank total condenser heat transfer rate [W] - Real64 WrapperElecEnergyCool(0.0); // Chiller heater bank total electric cooling energy [J] - Real64 WrapperElecEnergyHeat(0.0); // Chiller heater bank total electric heating energy [J] - Real64 WrapperCoolEnergy(0.0); // Chiller heater bank total cooling energy [J] - Real64 WrapperHeatEnergy(0.0); // Chiller heater bank total heating energy [J] - Real64 WrapperGLHEEnergy(0.0); // Chiller heater bank total condenser heat transfer energy [J] - - // Chiller heater bank chilled water inlet mass flow rate - Real64 CHWInletMassFlowRate = 0.0; - - Real64 HWInletMassFlowRate = 0.0; - Real64 GLHEInletMassFlowRate = 0.0; - Real64 CHWInletTemp = state.dataLoopNodes->Node(this->CHWInletNodeNum).Temp; - - // Chiller heater bank hot water inlet temperature - Real64 HWInletTemp = state.dataLoopNodes->Node(this->HWInletNodeNum).Temp; - - // Chiller heater bank condenser loop inlet temperature - Real64 GLHEInletTemp = state.dataLoopNodes->Node(this->GLHEInletNodeNum).Temp; - - Real64 CurCoolingLoad = 0.0; // Total cooling load chiller heater bank (wrapper) meets - - // Initiate loads and inlet temperatures each loop - if (LoopNum == this->CWPlantLoc.loopNum) { - CHWInletMassFlowRate = state.dataLoopNodes->Node(this->CHWInletNodeNum).MassFlowRateMaxAvail; - HWInletMassFlowRate = state.dataLoopNodes->Node(this->HWInletNodeNum).MassFlowRate; - GLHEInletMassFlowRate = state.dataLoopNodes->Node(this->GLHEInletNodeNum).MassFlowRateMaxAvail; - DataPlant::LoopSideLocation LoopSideNum = this->CWPlantLoc.loopSideNum; - this->WrapperCoolingLoad = 0.0; - CurCoolingLoad = std::abs(MyLoad); - this->WrapperCoolingLoad = CurCoolingLoad; - // Set actual mass flow rate at the nodes when it's locked - if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).FlowLock == DataPlant::FlowLock::Locked) { - CHWInletMassFlowRate = state.dataLoopNodes->Node(this->CHWInletNodeNum).MassFlowRate; - } - if (CHWInletMassFlowRate == 0.0) { - GLHEInletMassFlowRate = 0.0; - } - - } else if (LoopNum == this->HWPlantLoc.loopNum) { - CHWInletMassFlowRate = state.dataLoopNodes->Node(this->CHWInletNodeNum).MassFlowRate; - HWInletMassFlowRate = state.dataLoopNodes->Node(this->HWInletNodeNum).MassFlowRateMaxAvail; - GLHEInletMassFlowRate = state.dataLoopNodes->Node(this->GLHEInletNodeNum).MassFlowRateMaxAvail; - DataPlant::LoopSideLocation LoopSideNum = this->HWPlantLoc.loopSideNum; - this->WrapperHeatingLoad = 0.0; - CurHeatingLoad = MyLoad; - this->WrapperHeatingLoad = CurHeatingLoad; - // Set actual mass flow rate at the nodes when it's locked - if (state.dataPlnt->PlantLoop(LoopNum).LoopSide(LoopSideNum).FlowLock == DataPlant::FlowLock::Locked) { - HWInletMassFlowRate = state.dataLoopNodes->Node(this->HWInletNodeNum).MassFlowRate; - } - if (HWInletMassFlowRate == 0.0) { - GLHEInletMassFlowRate = 0.0; - } - } - - if (LoopNum == this->CWPlantLoc.loopNum) { - if (CurCoolingLoad > 0.0 && CHWInletMassFlowRate > 0.0 && GLHEInletMassFlowRate > 0) { - - this->CalcChillerModel(state); - this->UpdateChillerRecords(state); - - // Initialize local variables only for calculating mass-weighed temperatures - CHWOutletTemp = 0.0; - GLHEOutletTemp = 0.0; - CHWOutletMassFlowRate = 0.0; - GLHEOutletMassFlowRate = 0.0; - - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - auto const &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - - // Calculated mass flow rate used by individual chiller heater and bypasses - CHWOutletMassFlowRate += chillerHeater.Report.Evapmdot; - CHWOutletTemp += chillerHeater.Report.EvapOutletTemp * (chillerHeater.Report.Evapmdot / CHWInletMassFlowRate); - WrapperElecPowerCool += chillerHeater.Report.CoolingPower; - WrapperCoolRate += chillerHeater.Report.QEvap; - WrapperElecEnergyCool += chillerHeater.Report.CoolingEnergy; - WrapperCoolEnergy += chillerHeater.Report.EvapEnergy; - if (GLHEInletMassFlowRate > 0.0) { - GLHEOutletMassFlowRate += chillerHeater.Report.Condmdot; - if (GLHEOutletMassFlowRate > GLHEInletMassFlowRate) { - GLHEOutletMassFlowRate = GLHEInletMassFlowRate; - } - GLHEOutletTemp += chillerHeater.Report.CondOutletTemp * (chillerHeater.Report.Condmdot / GLHEInletMassFlowRate); - WrapperGLHERate += chillerHeater.Report.QCond; - WrapperGLHEEnergy += chillerHeater.Report.CondEnergy; - } else { - GLHEInletMassFlowRate = 0.0; - GLHEOutletMassFlowRate = 0.0; - GLHEOutletTemp = GLHEInletTemp; - WrapperGLHERate = 0.0; - WrapperGLHEEnergy = 0.0; - } - } // End of summation of mass flow rates and mass weighted temperatrue - - // Calculate temperatures for the mixed flows in the chiller bank - Real64 CHWBypassMassFlowRate = CHWInletMassFlowRate - CHWOutletMassFlowRate; - if (CHWBypassMassFlowRate > 0.0) { - CHWOutletTemp += CHWInletTemp * CHWBypassMassFlowRate / CHWInletMassFlowRate; - } else { - // CHWOutletTemp = CHWOutletTemp; // Self-assignment commented out - } - - if (GLHEInletMassFlowRate > 0.0) { - Real64 GLHEBypassMassFlowRate = GLHEInletMassFlowRate - GLHEOutletMassFlowRate; - if (GLHEBypassMassFlowRate > 0.0) { - GLHEOutletTemp += GLHEInletTemp * GLHEBypassMassFlowRate / GLHEInletMassFlowRate; - } else { - // GLHEOutletTemp = GLHEOutletTemp; // Self-assignment commented out - } - } else { - GLHEOutletTemp = GLHEInletTemp; - } - - HWOutletTemp = HWInletTemp; - - if (this->ancillaryPowerSched != nullptr) { - WrapperElecPowerCool += (this->AncillaryPower * this->ancillaryPowerSched->getCurrentVal()); - } - - state.dataLoopNodes->Node(this->CHWOutletNodeNum).Temp = CHWOutletTemp; - state.dataLoopNodes->Node(this->HWOutletNodeNum).Temp = HWOutletTemp; - state.dataLoopNodes->Node(this->GLHEOutletNodeNum).Temp = GLHEOutletTemp; - - } else { - - // Initialize local variables - CHWOutletTemp = CHWInletTemp; - HWOutletTemp = HWInletTemp; - GLHEOutletTemp = GLHEInletTemp; - - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - auto &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - chillerHeater.EvapOutletNode.MassFlowRate = 0.0; - chillerHeater.CondOutletNode.MassFlowRate = 0.0; - chillerHeater.EvapOutletNode.Temp = CHWInletTemp; - chillerHeater.EvapInletNode.Temp = CHWInletTemp; - chillerHeater.CondOutletNode.Temp = GLHEInletTemp; - chillerHeater.CondInletNode.Temp = GLHEInletTemp; - chillerHeater.Report.currentMode = CurrentMode::Off; - chillerHeater.Report.ChillerPartLoadRatio = 0.0; - chillerHeater.Report.ChillerCyclingRatio = 0.0; - chillerHeater.Report.ChillerFalseLoadRate = 0.0; - chillerHeater.Report.ChillerCapFT = 0.0; - chillerHeater.Report.ChillerEIRFT = 0.0; - chillerHeater.Report.ChillerEIRFPLR = 0.0; - chillerHeater.Report.CoolingPower = 0.0; - chillerHeater.Report.HeatingPower = 0.0; - chillerHeater.Report.QEvap = 0.0; - chillerHeater.Report.QCond = 0.0; - chillerHeater.Report.EvapOutletTemp = CHWOutletTemp; - chillerHeater.Report.EvapInletTemp = CHWInletTemp; - chillerHeater.Report.CondOutletTemp = GLHEOutletTemp; - chillerHeater.Report.CondInletTemp = GLHEInletTemp; - chillerHeater.Report.Evapmdot = 0.0; - chillerHeater.Report.Condmdot = 0.0; - chillerHeater.Report.ChillerFalseLoad = 0.0; - chillerHeater.Report.CoolingEnergy = 0.0; - chillerHeater.Report.HeatingEnergy = 0.0; - chillerHeater.Report.EvapEnergy = 0.0; - chillerHeater.Report.CondEnergy = 0.0; - chillerHeater.Report.ActualCOP = 0.0; - } - } - - if (this->SimulHtgDominant || this->SimulClgDominant) { - state.dataLoopNodes->Node(this->CHWOutletNodeNum).Temp = CHWOutletTemp; - this->Report.CHWInletTempSimul = CHWInletTemp; - this->Report.CHWOutletTempSimul = CHWOutletTemp; - this->Report.CHWmdotSimul = CHWInletMassFlowRate; - this->Report.GLHEInletTempSimul = GLHEInletTemp; - this->Report.GLHEOutletTempSimul = GLHEOutletTemp; - this->Report.GLHEmdotSimul = GLHEInletMassFlowRate; - this->Report.TotElecCoolingSimul = WrapperElecEnergyCool; - this->Report.CoolingEnergySimul = WrapperCoolEnergy; - this->Report.TotElecCoolingPwrSimul = WrapperElecPowerCool; - this->Report.CoolingRateSimul = WrapperCoolRate; - - } else { - - state.dataLoopNodes->Node(this->CHWOutletNodeNum).Temp = CHWOutletTemp; - state.dataLoopNodes->Node(this->HWOutletNodeNum).Temp = HWOutletTemp; - state.dataLoopNodes->Node(this->GLHEOutletNodeNum).Temp = GLHEOutletTemp; - this->Report.CHWInletTemp = CHWInletTemp; - this->Report.CHWOutletTemp = CHWOutletTemp; - this->Report.HWInletTemp = HWInletTemp; - this->Report.HWOutletTemp = HWOutletTemp; - this->Report.GLHEInletTemp = GLHEInletTemp; - this->Report.GLHEOutletTemp = GLHEOutletTemp; - this->Report.CHWmdot = CHWInletMassFlowRate; - this->Report.HWmdot = HWInletMassFlowRate; - this->Report.GLHEmdot = GLHEInletMassFlowRate; - this->Report.TotElecCooling = WrapperElecEnergyCool; - this->Report.TotElecHeating = WrapperElecEnergyHeat; - this->Report.CoolingEnergy = WrapperCoolEnergy; - this->Report.HeatingEnergy = WrapperHeatEnergy; - this->Report.GLHEEnergy = WrapperGLHEEnergy; - this->Report.TotElecCoolingPwr = WrapperElecPowerCool; - this->Report.TotElecHeatingPwr = WrapperElecPowerHeat; - this->Report.CoolingRate = WrapperCoolRate; - this->Report.HeatingRate = WrapperHeatRate; - this->Report.GLHERate = WrapperGLHERate; - } // // Cooling loop calculation - PlantUtilities::SetComponentFlowRate(state, CHWInletMassFlowRate, this->CHWInletNodeNum, this->CHWOutletNodeNum, this->CWPlantLoc); - - PlantUtilities::SetComponentFlowRate(state, HWInletMassFlowRate, this->HWInletNodeNum, this->HWOutletNodeNum, this->HWPlantLoc); - - PlantUtilities::SetComponentFlowRate(state, GLHEInletMassFlowRate, this->GLHEInletNodeNum, this->GLHEOutletNodeNum, this->GLHEPlantLoc); - - MyLoad = -WrapperCoolRate; - - } else if (LoopNum == this->HWPlantLoc.loopNum) { // Hot water loop - if (CurHeatingLoad > 0.0 && HWInletMassFlowRate > 0.0) { - - this->CalcChillerHeaterModel(state); - this->UpdateChillerHeaterRecords(state); - - // Calculate individual CH units's temperatures and mass flow rates - CHWOutletTemp = 0.0; - HWOutletTemp = 0.0; - GLHEOutletTemp = 0.0; - CHWOutletMassFlowRate = 0.0; - Real64 HWOutletMassFlowRate = 0.0; - GLHEOutletMassFlowRate = 0.0; - - if (this->SimulHtgDominant || this->SimulClgDominant) { - if (this->SimulClgDominant) { - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - auto const &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - CurrentMode currentMode = chillerHeater.Report.currentMode; - CHWInletTemp = this->Report.CHWInletTempSimul; - GLHEInletTemp = this->Report.GLHEInletTempSimul; - CHWInletMassFlowRate = this->Report.CHWmdotSimul; - GLHEInletMassFlowRate = this->Report.GLHEmdotSimul; - - if (currentMode != CurrentMode::Off) { // This chiller heater unit is on - if (currentMode == CurrentMode::HeatRecovery) { // Heat recovery mode. Both chilled water and hot water connections - CHWOutletMassFlowRate += chillerHeater.Report.EvapmdotSimul; // Wrapper evaporator side to plant chilled water loop - HWOutletMassFlowRate += chillerHeater.Report.Condmdot; // Wrapper condenser side to plant hot water loop - if (HWInletMassFlowRate > 0.0) { - HWOutletTemp += chillerHeater.Report.CondOutletTemp * - (chillerHeater.Report.Condmdot / HWInletMassFlowRate); // Only calculate in the heat recovery mode - } else { - HWOutletTemp = HWInletTemp; - } - } else { // Mode 4. Cooling-only mode with other heat recovery units. Condenser flows. - CHWOutletMassFlowRate += chillerHeater.Report.EvapmdotSimul; // Wrapper evaporator side to plant chilled water loop - // Sum condenser node mass flow rates and mass weighed temperatures - if (GLHEInletMassFlowRate > 0.0) { - GLHEOutletMassFlowRate += chillerHeater.Report.CondmdotSimul; - if (GLHEOutletMassFlowRate > GLHEInletMassFlowRate) { - GLHEOutletMassFlowRate = GLHEInletMassFlowRate; - } - GLHEOutletTemp += - chillerHeater.Report.CondOutletTempSimul * (chillerHeater.Report.CondmdotSimul / GLHEInletMassFlowRate); - WrapperGLHERate += chillerHeater.Report.QCondSimul; - WrapperGLHEEnergy += chillerHeater.Report.CondEnergySimul; - } else { - GLHEInletMassFlowRate = 0.0; - GLHEOutletMassFlowRate = 0.0; - GLHEOutletTemp = GLHEInletTemp; - WrapperGLHERate = 0.0; - WrapperGLHEEnergy = 0.0; - } - } - } else { // This chiller heater is off - // Check if any unit is cooling only mode - if (ChillerHeaterNum == this->ChillerHeaterNums) { // All units are heat revocery mode. No condenser flow - GLHEOutletMassFlowRate = 0.0; - GLHEInletMassFlowRate = 0.0; - GLHEOutletTemp = GLHEInletTemp; - } else { // At least, one of chiller heater units is cooling-only mode - // GLHEOutletMassFlowRate = GLHEOutletMassFlowRate; // Self-assignment commented out - // GLHEOutletTemp = GLHEOutletTemp; // Self-assignment commented out - } - } - // Calculate mass weighed chilled water temperatures - if (CHWInletMassFlowRate > 0.0) { - CHWOutletTemp += chillerHeater.Report.EvapOutletTempSimul * (chillerHeater.Report.EvapmdotSimul / CHWInletMassFlowRate); - } else { - CHWOutletTemp = CHWInletTemp; - } - - WrapperElecPowerCool += chillerHeater.Report.CoolingPowerSimul; // Cooling electricity - WrapperCoolRate += chillerHeater.Report.QEvapSimul; - WrapperElecEnergyCool += chillerHeater.Report.CoolingEnergySimul; - WrapperCoolEnergy += chillerHeater.Report.EvapEnergySimul; - // Avoid double counting wrapper energy use - WrapperElecPowerHeat = 0.0; - WrapperHeatRate = 0.0; - WrapperHeatEnergy = 0.0; - } - - // Calculate chilled water temperature - if (CHWInletMassFlowRate > 0.0) { - Real64 CHWBypassMassFlowRate = CHWInletMassFlowRate - CHWOutletMassFlowRate; - if (CHWBypassMassFlowRate > 0.0) { - CHWOutletTemp += CHWInletTemp * CHWBypassMassFlowRate / CHWInletMassFlowRate; - } else { // No bypass withnin a wrapper - // CHWOutletTemp = CHWOutletTemp; // Self-assignment commented out - } - } else { - CHWOutletTemp = CHWInletTemp; - } - // Calculate hot water outlet temperature - if (HWInletMassFlowRate > 0.0) { - Real64 HWBypassMassFlowRate = HWInletMassFlowRate - HWOutletMassFlowRate; - if (HWBypassMassFlowRate > 0.0) { - HWOutletTemp += HWInletTemp * HWBypassMassFlowRate / HWInletMassFlowRate; - } else { - // HWOutletTemp = HWOutletTemp; // Self-assignment commented out - } - } else { - HWOutletTemp = HWInletTemp; - } - // Calculate condenser outlet temperature - if (GLHEInletMassFlowRate > 0.0) { - Real64 GLHEBypassMassFlowRate = GLHEInletMassFlowRate - GLHEOutletMassFlowRate; - if (GLHEBypassMassFlowRate > 0.0) { - GLHEOutletTemp += GLHEInletTemp * GLHEBypassMassFlowRate / GLHEInletMassFlowRate; - } else { - // GLHEOutletTemp = GLHEOutletTemp; // Self-assignment commented out - } - } else { - GLHEOutletTemp = GLHEInletTemp; - } - - // Add ancillary power if scheduled - if (this->ancillaryPowerSched != nullptr) { - WrapperElecPowerCool += (this->AncillaryPower * this->ancillaryPowerSched->getCurrentVal()); - } - - // Electricity should be counted once for cooling in this mode - WrapperElecEnergyHeat = 0.0; - - } else if (this->SimulHtgDominant) { // Heating dominant simultaneous clg/htg mode - - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - auto &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - // Set temperatures and mass flow rates for the cooling side - CurrentMode currentMode = chillerHeater.Report.currentMode; - CHWInletTemp = this->Report.CHWInletTempSimul; - CHWInletMassFlowRate = this->Report.CHWmdotSimul; - - if (currentMode != CurrentMode::Off) { // This chiller heater unit is on - if (currentMode == CurrentMode::HeatRecovery) { // Heat recovery mode. Both chilled water and hot water connections - CHWOutletMassFlowRate += chillerHeater.Report.EvapmdotSimul; // Wrapper evaporator side to plant chilled water loop - HWOutletMassFlowRate += chillerHeater.Report.Condmdot; // Wrapper condenser side to plant hot water loop - if (CHWInletMassFlowRate > 0.0) { - CHWOutletTemp += chillerHeater.Report.EvapOutletTempSimul * - (chillerHeater.Report.EvapmdotSimul / - CHWInletMassFlowRate); // Only need to calculate in the heat recovery mode - } else { - CHWOutletTemp = CHWInletTemp; - } - } else { // Mode 5. Heating only mode with other heat recovery units - HWOutletMassFlowRate += chillerHeater.Report.Condmdot; // Wrapper condenser side to plant hot water loop - if (GLHEInletMassFlowRate > 0.0) { - GLHEOutletMassFlowRate += chillerHeater.Report.Evapmdot; // Wrapper evaporator side to plant condenser loop - if (GLHEOutletMassFlowRate > GLHEInletMassFlowRate) { - GLHEOutletMassFlowRate = GLHEInletMassFlowRate; - } - GLHEOutletTemp += chillerHeater.Report.EvapOutletTemp * (chillerHeater.Report.Evapmdot / GLHEInletMassFlowRate); - WrapperGLHERate += chillerHeater.Report.QEvap; - WrapperGLHEEnergy += chillerHeater.Report.EvapEnergy; - } else { - GLHEInletMassFlowRate = 0.0; - GLHEOutletMassFlowRate = 0.0; - GLHEOutletTemp = GLHEInletTemp; - WrapperGLHERate = 0.0; - WrapperGLHEEnergy = 0.0; - } - } // End of heat recovery mode - - } else { // This chiller heater is off - - // Check if any unit is heating only mode - if (ChillerHeaterNum == this->ChillerHeaterNums) { // All are heat revocery mode. No condenser flow - GLHEOutletMassFlowRate = 0.0; - GLHEInletMassFlowRate = 0.0; - GLHEOutletTemp = GLHEInletTemp; - } else { // At least, one of chiller heater units is heating only mode - // GLHEOutletMassFlowRate = GLHEOutletMassFlowRate; // Self-assignment commented out - // GLHEOutletTemp = GLHEOutletTemp; // Self-assignment commented out - } - } - - // Calculate mass weighed hot water temperatures - if (HWInletMassFlowRate > 0.0) { - HWOutletTemp += chillerHeater.Report.CondOutletTemp * - (chillerHeater.Report.Condmdot / HWInletMassFlowRate); // Always heating as long as heating load remains - } else { - HWOutletTemp = HWInletTemp; - } - - WrapperElecPowerHeat += chillerHeater.Report.HeatingPower; - WrapperHeatRate += chillerHeater.Report.QCond; - WrapperElecEnergyHeat += chillerHeater.Report.HeatingEnergy; - WrapperHeatEnergy += chillerHeater.Report.CondEnergy; - - // Avoid double counting wrapper energy use - WrapperElecPowerCool = 0.0; - WrapperCoolRate = 0.0; - } - // Calculate chilled water outlet temperature - if (CHWInletMassFlowRate > 0.0) { - Real64 CHWBypassMassFlowRate = CHWInletMassFlowRate - CHWOutletMassFlowRate; - if (CHWBypassMassFlowRate > 0.0) { - CHWOutletTemp += CHWInletTemp * CHWBypassMassFlowRate / CHWInletMassFlowRate; - } else { // No bypass withnin a wrapper - // CHWOutletTemp = CHWOutletTemp; // Self-assignment commented out - } - } else { - CHWOutletTemp = CHWInletTemp; - } - // Calculate hot water outlet temperature - if (HWInletMassFlowRate > 0.0) { - Real64 HWBypassMassFlowRate = HWInletMassFlowRate - HWOutletMassFlowRate; - if (HWBypassMassFlowRate > 0.0) { - HWOutletTemp += HWInletTemp * HWBypassMassFlowRate / HWInletMassFlowRate; - } else { - // HWOutletTemp = HWOutletTemp; // Self-assignment commented out - } - } else { - HWOutletTemp = HWInletTemp; - } - // Calculate condenser outlet temperature - if (GLHEInletMassFlowRate > 0.0) { - Real64 GLHEBypassMassFlowRate = GLHEInletMassFlowRate - GLHEOutletMassFlowRate; - if (GLHEBypassMassFlowRate > 0.0) { - GLHEOutletTemp += GLHEInletTemp * GLHEBypassMassFlowRate / GLHEInletMassFlowRate; - } else { - // GLHEOutletTemp = GLHEOutletTemp; // Self-assignment commented out - } - } else { - GLHEOutletTemp = GLHEInletTemp; - } - - // Check if ancillary power is used - if (this->ancillaryPowerSched != nullptr) { - WrapperElecPowerHeat += (this->AncillaryPower * this->ancillaryPowerSched->getCurrentVal()); - } - - // Electricity should be counted once - WrapperElecEnergyCool = 0.0; - - } // End of simultaneous clg/htg mode calculations - - } else { // Heating only mode (mode 2) - - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - auto const &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - HWOutletMassFlowRate += chillerHeater.Report.Condmdot; - HWOutletTemp += chillerHeater.Report.CondOutletTemp * chillerHeater.Report.Condmdot / HWInletMassFlowRate; - WrapperElecPowerHeat += chillerHeater.Report.HeatingPower; - WrapperHeatRate += chillerHeater.Report.QCond; - WrapperElecEnergyHeat += chillerHeater.Report.HeatingEnergy; - WrapperHeatEnergy += chillerHeater.Report.CondEnergy; - - if (GLHEInletMassFlowRate > 0.0) { - GLHEOutletMassFlowRate += chillerHeater.Report.Evapmdot; - if (GLHEOutletMassFlowRate > GLHEInletMassFlowRate) { - GLHEOutletMassFlowRate = GLHEInletMassFlowRate; - } - GLHEOutletTemp += chillerHeater.Report.EvapOutletTemp * (chillerHeater.Report.Evapmdot / GLHEInletMassFlowRate); - WrapperGLHERate += chillerHeater.Report.QEvap; - WrapperGLHEEnergy += chillerHeater.Report.EvapEnergy; - } else { // No source water flow - GLHEOutletMassFlowRate = 0.0; - GLHEInletMassFlowRate = 0.0; - GLHEOutletTemp = GLHEInletTemp; - WrapperGLHERate = 0.0; - WrapperGLHEEnergy = 0.0; - } - } - - // Calculate hot water outlet temperature - if (HWInletMassFlowRate > 0.0) { - Real64 HWBypassMassFlowRate = HWInletMassFlowRate - HWOutletMassFlowRate; - if (HWBypassMassFlowRate > 0.0) { - HWOutletTemp += HWInletTemp * HWBypassMassFlowRate / HWInletMassFlowRate; - } else { - // HWOutletTemp = HWOutletTemp; // Self-assignment commented out - if (HWOutletTemp > HWInletTemp) { - HWOutletTemp = HWInletTemp; - } - } - } else { - HWOutletTemp = HWInletTemp; - } - - // Calculate condenser outlet temperature - if (GLHEInletMassFlowRate > 0.0) { - Real64 GLHEBypassMassFlowRate = GLHEInletMassFlowRate - GLHEOutletMassFlowRate; - if (GLHEBypassMassFlowRate > 0.0) { - GLHEOutletTemp += GLHEInletTemp * GLHEBypassMassFlowRate / GLHEInletMassFlowRate; - } else { - // GLHEOutletTemp = GLHEOutletTemp; // Self-assignment commented out - } - } else { - GLHEOutletTemp = GLHEInletTemp; - } - - CHWOutletTemp = CHWInletTemp; - - // Add ancillary power if necessary - if (this->ancillaryPowerSched != nullptr) { - WrapperElecPowerHeat += (this->AncillaryPower * this->ancillaryPowerSched->getCurrentVal()); - } - - } // End of calculations - - PlantUtilities::SetComponentFlowRate(state, CHWInletMassFlowRate, this->CHWInletNodeNum, this->CHWOutletNodeNum, this->CWPlantLoc); - - PlantUtilities::SetComponentFlowRate(state, HWInletMassFlowRate, this->HWInletNodeNum, this->HWOutletNodeNum, this->HWPlantLoc); - - PlantUtilities::SetComponentFlowRate(state, GLHEInletMassFlowRate, this->GLHEInletNodeNum, this->GLHEOutletNodeNum, this->GLHEPlantLoc); - - // Local variables - this->Report.CHWInletTemp = CHWInletTemp; - this->Report.CHWOutletTemp = CHWOutletTemp; - this->Report.HWInletTemp = HWInletTemp; - this->Report.HWOutletTemp = HWOutletTemp; - this->Report.GLHEInletTemp = GLHEInletTemp; - this->Report.GLHEOutletTemp = GLHEOutletTemp; - this->Report.CHWmdot = CHWInletMassFlowRate; - this->Report.HWmdot = HWInletMassFlowRate; - this->Report.GLHEmdot = GLHEInletMassFlowRate; - this->Report.TotElecCooling = WrapperElecEnergyCool; - this->Report.TotElecHeating = WrapperElecEnergyHeat; - this->Report.CoolingEnergy = WrapperCoolEnergy; - this->Report.HeatingEnergy = WrapperHeatEnergy; - this->Report.GLHEEnergy = WrapperGLHEEnergy; - this->Report.TotElecCoolingPwr = WrapperElecPowerCool; - this->Report.TotElecHeatingPwr = WrapperElecPowerHeat; - this->Report.CoolingRate = WrapperCoolRate; - this->Report.HeatingRate = WrapperHeatRate; - this->Report.GLHERate = WrapperGLHERate; - - state.dataLoopNodes->Node(this->CHWOutletNodeNum).Temp = CHWOutletTemp; - state.dataLoopNodes->Node(this->HWOutletNodeNum).Temp = HWOutletTemp; - state.dataLoopNodes->Node(this->GLHEOutletNodeNum).Temp = GLHEOutletTemp; - - } else { // Central chiller heater system is off - - CHWOutletTemp = CHWInletTemp; - HWOutletTemp = HWInletTemp; - GLHEOutletTemp = GLHEInletTemp; - state.dataLoopNodes->Node(this->CHWOutletNodeNum).Temp = CHWOutletTemp; - state.dataLoopNodes->Node(this->HWOutletNodeNum).Temp = HWOutletTemp; - state.dataLoopNodes->Node(this->GLHEOutletNodeNum).Temp = GLHEOutletTemp; - - if (this->WrapperCoolingLoad == 0.0 && !this->SimulHtgDominant) { - - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - auto &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - chillerHeater.EvapOutletNode.MassFlowRate = 0.0; - chillerHeater.CondOutletNode.MassFlowRate = 0.0; - chillerHeater.EvapOutletNode.Temp = CHWInletTemp; - chillerHeater.EvapInletNode.Temp = CHWInletTemp; - chillerHeater.CondOutletNode.Temp = GLHEInletTemp; - chillerHeater.CondInletNode.Temp = GLHEInletTemp; - chillerHeater.Report.currentMode = CurrentMode::Off; - chillerHeater.Report.ChillerPartLoadRatio = 0.0; - chillerHeater.Report.ChillerCyclingRatio = 0.0; - chillerHeater.Report.ChillerFalseLoadRate = 0.0; - chillerHeater.Report.ChillerCapFT = 0.0; - chillerHeater.Report.ChillerEIRFT = 0.0; - chillerHeater.Report.ChillerEIRFPLR = 0.0; - chillerHeater.Report.CoolingPower = 0.0; - chillerHeater.Report.HeatingPower = 0.0; - chillerHeater.Report.QEvap = 0.0; - chillerHeater.Report.QCond = 0.0; - chillerHeater.Report.EvapOutletTemp = CHWOutletTemp; - chillerHeater.Report.EvapInletTemp = CHWInletTemp; - chillerHeater.Report.CondOutletTemp = GLHEOutletTemp; - chillerHeater.Report.CondInletTemp = GLHEInletTemp; - chillerHeater.Report.Evapmdot = 0.0; - chillerHeater.Report.Condmdot = 0.0; - chillerHeater.Report.ChillerFalseLoad = 0.0; - chillerHeater.Report.CoolingEnergy = 0.0; - chillerHeater.Report.HeatingEnergy = 0.0; - chillerHeater.Report.EvapEnergy = 0.0; - chillerHeater.Report.CondEnergy = 0.0; - chillerHeater.Report.ActualCOP = 0.0; - } - - this->Report.CHWInletTemp = CHWInletTemp; - this->Report.CHWOutletTemp = CHWOutletTemp; - this->Report.HWInletTemp = HWInletTemp; - this->Report.HWOutletTemp = HWOutletTemp; - this->Report.GLHEInletTemp = GLHEInletTemp; - this->Report.GLHEOutletTemp = GLHEOutletTemp; - this->Report.CHWmdot = CHWInletMassFlowRate; - this->Report.HWmdot = HWInletMassFlowRate; - this->Report.GLHEmdot = GLHEInletMassFlowRate; - this->Report.TotElecCooling = WrapperElecEnergyCool; - this->Report.TotElecHeating = WrapperElecEnergyHeat; - this->Report.CoolingEnergy = WrapperCoolEnergy; - this->Report.HeatingEnergy = WrapperHeatEnergy; - this->Report.GLHEEnergy = WrapperGLHEEnergy; - this->Report.TotElecCoolingPwr = WrapperElecPowerCool; - this->Report.TotElecHeatingPwr = WrapperElecPowerHeat; - this->Report.CoolingRate = WrapperCoolRate; - this->Report.HeatingRate = WrapperHeatRate; - this->Report.GLHERate = WrapperGLHERate; - - PlantUtilities::SetComponentFlowRate(state, CHWInletMassFlowRate, this->CHWInletNodeNum, this->CHWOutletNodeNum, this->CWPlantLoc); - - PlantUtilities::SetComponentFlowRate(state, HWInletMassFlowRate, this->HWInletNodeNum, this->HWOutletNodeNum, this->HWPlantLoc); - - PlantUtilities::SetComponentFlowRate( - state, GLHEInletMassFlowRate, this->GLHEInletNodeNum, this->GLHEOutletNodeNum, this->GLHEPlantLoc); - } - - } // Heating loop calculation - MyLoad = WrapperHeatRate; - } -} - -void WrapperSpecs::UpdateChillerRecords(EnergyPlusData &state) // Wrapper number -{ - - // SUBROUTINE INFORMATION: - // AUTHOR: Daeho Kang, PNNL - // DATE WRITTEN: Feb 2013 - - // PURPOSE OF THIS SUBROUTINE: - // Update chiller heater variables - - Real64 SecInTimeStep; // Number of seconds per HVAC system time step, to convert from W (J/s) to J - int ChillerHeaterNum; // Chiller heater number - - SecInTimeStep = state.dataHVACGlobal->TimeStepSysSec; - - for (ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - auto &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - chillerHeater.Report.ChillerFalseLoad = chillerHeater.Report.ChillerFalseLoadRate * SecInTimeStep; - chillerHeater.Report.CoolingEnergy = chillerHeater.Report.CoolingPower * SecInTimeStep; - chillerHeater.Report.HeatingEnergy = chillerHeater.Report.HeatingPower * SecInTimeStep; - chillerHeater.Report.EvapEnergy = chillerHeater.Report.QEvap * SecInTimeStep; - chillerHeater.Report.CondEnergy = chillerHeater.Report.QCond * SecInTimeStep; - if (this->SimulClgDominant || this->SimulHtgDominant) { - chillerHeater.Report.ChillerFalseLoadSimul = chillerHeater.Report.ChillerFalseLoad; - chillerHeater.Report.CoolingEnergySimul = chillerHeater.Report.CoolingEnergy; - chillerHeater.Report.EvapEnergySimul = chillerHeater.Report.EvapEnergy; - chillerHeater.Report.CondEnergySimul = chillerHeater.Report.CondEnergy; - } - } -} - -void WrapperSpecs::UpdateChillerHeaterRecords(EnergyPlusData &state) // Wrapper number -{ - - // SUBROUTINE INFORMATION: - // AUTHOR: Daeho Kang, PNNL - // DATE WRITTEN: Feb 2013 - - // Number of seconds per HVAC system time step, to convert from W (J/s) to J - Real64 SecInTimeStep = state.dataHVACGlobal->TimeStepSysSec; - - for (int ChillerHeaterNum = 1; ChillerHeaterNum <= this->ChillerHeaterNums; ++ChillerHeaterNum) { - auto &chillerHeater = this->ChillerHeater(ChillerHeaterNum); - chillerHeater.Report.ChillerFalseLoad = chillerHeater.Report.ChillerFalseLoadRate * SecInTimeStep; - chillerHeater.Report.CoolingEnergy = chillerHeater.Report.CoolingPower * SecInTimeStep; - chillerHeater.Report.HeatingEnergy = chillerHeater.Report.HeatingPower * SecInTimeStep; - chillerHeater.Report.EvapEnergy = chillerHeater.Report.QEvap * SecInTimeStep; - chillerHeater.Report.CondEnergy = chillerHeater.Report.QCond * SecInTimeStep; - } -} -void WrapperSpecs::oneTimeInit_new([[maybe_unused]] EnergyPlusData &state) -{ -} - -void WrapperSpecs::oneTimeInit([[maybe_unused]] EnergyPlusData &state) -{ -} - -} // namespace EnergyPlus::PlantCentralGSHP diff --git a/src/EnergyPlus/PlantCentralGSHP.hh b/src/EnergyPlus/PlantCentralGSHP.hh deleted file mode 100644 index 446ffbdde66..00000000000 --- a/src/EnergyPlus/PlantCentralGSHP.hh +++ /dev/null @@ -1,439 +0,0 @@ -// EnergyPlus, Copyright (c) 1996-present, The Board of Trustees of the University of Illinois, -// The Regents of the University of California, through Lawrence Berkeley National Laboratory -// (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge -// National Laboratory, managed by UT-Battelle, Alliance for Energy Innovation, LLC, and other -// contributors. All rights reserved. -// -// NOTICE: This Software was developed under funding from the U.S. Department of Energy and the -// U.S. Government consequently retains certain rights. As such, the U.S. Government has been -// granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, -// worldwide license in the Software to reproduce, distribute copies to the public, prepare -// derivative works, and perform publicly and display publicly, and to permit others to do so. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// (1) Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// (2) Redistributions in binary form must reproduce the above copyright notice, this list of -// conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory, -// the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific prior -// written permission. -// -// (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form -// without changes from the version obtained under this License, or (ii) Licensee makes a -// reference solely to the software portion of its product, Licensee must refer to the -// software as "EnergyPlus version X" software, where "X" is the version number Licensee -// obtained under this License and may not use a different name for the software. Except as -// specifically required in this Section (4), Licensee shall not use in a company name, a -// product name, in advertising, publicity, or other promotional activities any name, trade -// name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly -// similar designation, without the U.S. Department of Energy's prior written consent. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -#ifndef PlantCentralGSHP_hh_INCLUDED -#define PlantCentralGSHP_hh_INCLUDED - -// ObjexxFCL Headers -#include - -// EnergyPlus Headers -#include -#include -#include -#include - -namespace EnergyPlus { - -// Forward declarations -struct EnergyPlusData; - -namespace PlantCentralGSHP { - - enum class CurrentMode - { - Invalid = -1, - Off, - CoolingOnly, - HeatingOnly, - HeatRecovery, - CoolingDominant, - HeatingDominant, - Num - }; - - enum class CondenserModeTemperature - { - Invalid = -1, - EnteringCondenser, - LeavingCondenser, - Num - }; - - struct CGSHPNodeData - { - // Members - Real64 Temp = 0.0; // {C} - Real64 TempMin = 0.0; // {C} - Real64 TempSetPoint = 0.0; // SensedNodeFlagValue ! {C} - Real64 MassFlowRate = 0.0; // {kg/s} - Real64 MassFlowRateMin = 0.0; // {kg/s} - Real64 MassFlowRateMax = 0.0; // SensedNodeFlagValue ! {kg/s} - Real64 MassFlowRateMinAvail = 0.0; // {kg/s} - Real64 MassFlowRateMaxAvail = 0.0; // {kg/s} - Real64 MassFlowRateSetPoint = 0.0; // {kg/s} - Real64 MassFlowRateRequest = 0.0; // {kg/s} - }; - - struct WrapperComponentSpecs - { - std::string WrapperPerformanceObjectType; // Component type - std::string WrapperComponentName; // Component name - int WrapperPerformanceObjectIndex = 0; // Component index in the input array - int WrapperIdenticalObjectNum = 0; // Number of identical objects - Sched::Schedule *chSched = nullptr; // schedule - }; - - struct CHReportVars - { - CurrentMode currentMode = CurrentMode::Invalid; // 0-off; 1-CoolingOnly; 2-HeatingOnly; 3-HeatRecovery; 4-CoolingDominant; 5-HeatingDominant - Real64 ChillerPartLoadRatio = 0.0; // Chiller PLR (Load/Capacity) - Real64 ChillerCyclingRatio = 0.0; // Chiller cycling ratio (time on/time step) - Real64 ChillerFalseLoad = 0.0; // Chiller false load over and above water side load [J] - Real64 ChillerFalseLoadRate = 0.0; // Chiller false load rate over and above water side load [W] - Real64 CoolingPower = 0.0; // Chiller power, W - Real64 HeatingPower = 0.0; // Chiller power, W - Real64 QEvap = 0.0; // Evaporator heat transfer rate [W] - Real64 QCond = 0.0; // Condenser heat transfer rate [W] - Real64 CoolingEnergy = 0.0; // Chiller electric consumption [J] - Real64 HeatingEnergy = 0.0; // Chiller electric consumption [J] - Real64 EvapEnergy = 0.0; // Evaporator heat transfer energy [J] - Real64 CondEnergy = 0.0; // Condenser heat transfer energy [J] - Real64 CondInletTemp = 0.0; // Condenser inlet temperature [C] - Real64 EvapInletTemp = 0.0; // Evaporator inlet temperature [C] - Real64 CondOutletTemp = 0.0; // Condenser outlet temperature [C] - Real64 EvapOutletTemp = 0.0; // Evaporator outlet temperature [C] - Real64 Evapmdot = 0.0; // Evaporator mass flow rate [kg/s] - Real64 Condmdot = 0.0; // Condenser mass flow rate [kg/s] - Real64 ActualCOP = 0.0; // Coefficient of performance - Real64 ChillerCapFT = 0.0; // Chiller capacity curve output value - Real64 ChillerEIRFT = 0.0; // Chiller EIRFT curve output value - Real64 ChillerEIRFPLR = 0.0; // Chiller EIRFPLR curve output value - Real64 CondenserFanPowerUse = 0.0; // Air-cooled condenser fan power [W] - Real64 CondenserFanEnergy = 0.0; // Air-cooled condenser fan energy [J] - Real64 ChillerPartLoadRatioSimul = 0.0; // Chiller PLR (Load/Capacity) for simul clg/htg mode - Real64 ChillerCyclingRatioSimul = 0.0; // Chiller cycling ratio (time on/time step) for simul clg/htg mode - Real64 ChillerFalseLoadSimul = 0.0; // Chiller false load for simul clg/htg mode [J] - Real64 ChillerFalseLoadRateSimul = 0.0; // Chiller false load rate for simul clg/htg mode [W] - Real64 CoolingPowerSimul = 0.0; // Chiller power for simul clg/htg mode [W] - Real64 QEvapSimul = 0.0; // Evaporator heat transfer rate for simul clg/htg mode [W] - Real64 QCondSimul = 0.0; // Evaporator heat transfer rate for simul clg/htg mode [W] - Real64 CoolingEnergySimul = 0.0; // Chiller electric consumption for simul clg/htg mode [J] - Real64 EvapEnergySimul = 0.0; // Evaporator heat transfer energy for simul clg/htg mode [J] - Real64 CondEnergySimul = 0.0; // Condenser heat transfer energy for simul clg/htg mode [J] - Real64 EvapInletTempSimul = 0.0; // Evaporator inlet temperature for simul clg/htg mode [C] - Real64 EvapOutletTempSimul = 0.0; // Evaporator outlet temperature for simul clg/htg mode [C] - Real64 EvapmdotSimul = 0.0; // Evaporator mass flow rate for simul clg/htg mode [kg/s] - Real64 CondInletTempSimul = 0.0; // Condenser inlet temperature for simul clg/htg mode [C] - Real64 CondOutletTempSimul = 0.0; // Condenser outlet temperature for simul clg/htg mode [C] - Real64 CondmdotSimul = 0.0; // Condenser mass flow rate for simul clg/htg mode [kg/s] - Real64 ChillerCapFTSimul = 0.0; // Chiller capacity curve output value for simul clg/htg mode - Real64 ChillerEIRFTSimul = 0.0; // Chiller EIRFT curve output value for simul clg/htg mode - Real64 ChillerEIRFPLRSimul = 0.0; // Chiller EIRFPLR curve output value for simul clg/htg mode - }; - - struct ChillerHeaterSpecs - { - std::string Name; // Name of the Chiller Heater object - CondenserModeTemperature CondModeCooling = CondenserModeTemperature::Invalid; // Cooling mode temperature curve input variable - CondenserModeTemperature CondModeHeating = CondenserModeTemperature::Invalid; // Clg/Htg mode temperature curve input variable - CondenserModeTemperature CondMode = CondenserModeTemperature::Invalid; // Current mode temperature curve input variable - bool ConstantFlow = false; // True if this is a Constant Flow Chiller - bool VariableFlow = false; // True if this is a Variable Flow Chiller - bool CoolSetPointSetToLoop = false; // True if the setpoint is missing at the outlet node - bool HeatSetPointSetToLoop = false; // True if the setpoint is missing at the outlet node - bool CoolSetPointErrDone = false; // true if setpoint warning issued - bool HeatSetPointErrDone = false; // true if setpoint warning issued - bool PossibleSubcooling = false; // flag to indicate chiller is doing less cooling that requested - int ChillerHeaterNum = 1; // Chiller heater number - int ChillerCapFTCoolingIDX = 0; // Cooling capacity function of temperature curve index - int ChillerEIRFTCoolingIDX = 0; // Elec Input to Cooling Output ratio function of temperature curve index - int ChillerEIRFPLRCoolingIDX = 0; // Elec Input to cooling output ratio function of PLR curve index - int ChillerCapFTHeatingIDX = 0; // Clg/Htg capacity function of temperature curve index - int ChillerEIRFTHeatingIDX = 0; // Elec Input to Clg/Htg Output ratio function of temperature curve index - int ChillerEIRFPLRHeatingIDX = 0; // Elec Input to Clg/Htg output ratio function of PLR curve index - int ChillerCapFTIDX = 0; // Capacity function of temperature curve index - int ChillerEIRFTIDX = 0; // Elec Input to demand output ratio function of temperature curve index - int ChillerEIRFPLRIDX = 0; // Elec Input to demand output ratio function of PLR curve index - int EvapInletNodeNum = 0; // Node number on the inlet side of the plant (evaporator side) - int EvapOutletNodeNum = 0; // Node number on the outlet side of the plant (evaporator side) - int CondInletNodeNum = 0; // Node number on the inlet side of the condenser - int CondOutletNodeNum = 0; // Node number on the outlet side of the condenser - int ChillerCapFTError = 0; // Used for negative capacity as a function of temp warnings - int ChillerCapFTErrorIndex = 0; // Used for negative capacity as a function of temp warnings - int ChillerEIRFTError = 0; // Used for negative EIR as a function of temp warnings - int ChillerEIRFTErrorIndex = 0; // Used for negative EIR as a function of temp warnings - int ChillerEIRFPLRError = 0; // Used for negative EIR as a function of PLR warnings - int ChillerEIRFPLRErrorIndex = 0; // Used for negative EIR as a function of PLR warnings - int ChillerEIRRefTempErrorIndex = 0; // Used for reference temperature problems - int DeltaTErrCount = 0; // Evaporator delta T equals 0 for variable flow chiller warning messages - int DeltaTErrCountIndex = 0; // Index to evaporator delta T = 0 for variable flow chiller warning messages - int CondMassFlowIndex = 0; // Index to condenser mass flow rate - Real64 RefCapCooling = 0.0; // Reference cooling-mode evaporator capacity [W] - bool RefCapCoolingWasAutoSized = false; // true if reference cooling capacity was autosize on input - Real64 RefCOPCooling = 0.0; // Reference cooling-mode COP - Real64 TempRefEvapOutCooling = 0.0; // Reference cooling-mode evaporator leaving temperature [C] - Real64 TempRefCondInCooling = 0.0; // Reference cooling-mode condenser entering temperature [C] - Real64 TempRefCondOutCooling = 0.0; // Reference cooling-mode condenser leaving temperature [C] - Real64 MaxPartLoadRatCooling = 0.0; // Maximum Part load ratio in cooling mode - Real64 OptPartLoadRatCooling = 0.0; // Optimum Part load ratio in cooling mode - Real64 MinPartLoadRatCooling = 0.0; // minimum Part load ratio in cooling mode - Real64 ClgHtgToCoolingCapRatio = 0.0; // ratio of clg/htg-mode evaporator capacity to cooling-mode evap. cap - Real64 ClgHtgtoCogPowerRatio = 0.0; // ratio of clg/htg-mode evaporator power to cooling-mode evap. power - Real64 RefCapClgHtg = 0.0; // Reference clg/htg-mode evaporator capacity [W] - Real64 RefCOPClgHtg = 0.0; // Reference clg/htg-mode COP - Real64 RefPowerClgHtg = 0.0; // Reference clg/htg-mode evaporator power [W] - Real64 TempRefEvapOutClgHtg = 0.0; // Reference clg/htg-mode evaporator leaving temperature [C] - Real64 TempRefCondInClgHtg = 0.0; // Reference clg/htg-mode condenser entering temperature [C] - Real64 TempRefCondOutClgHtg = 0.0; // Reference clg/htg-mode condenser leaving temperature [C] - Real64 TempLowLimitEvapOut = 0.0; // Low temperature shut off [C] - Real64 MaxPartLoadRatClgHtg = 0.0; // Maximum Part load ratio in simultaneous heating/cooling mode - Real64 OptPartLoadRatClgHtg = 0.0; // Optimum Part load ratio in simultaneous heating/cooling mode - Real64 MinPartLoadRatClgHtg = 0.0; // minimum Part load ratio in simultaneous heating/cooling mode - CGSHPNodeData EvapInletNode = {}; // Chiller heater evaperator inlet node - CGSHPNodeData EvapOutletNode = {}; // Chiller heater evaperator outlet node - CGSHPNodeData CondInletNode = {}; // Chiller heater condenser inlet node - CGSHPNodeData CondOutletNode = {}; // Chiller heater condenser outlet node - Real64 EvapVolFlowRate = 0.0; // Reference water volumetric flow rate through the evaporator [m3/s] - bool EvapVolFlowRateWasAutoSized = false; // true if evaporator flow rate was autosize on input - Real64 tmpEvapVolFlowRate = 0.0; // temporary ref water vol flow rate for intermediate sizing [m3/s] - Real64 CondVolFlowRate = 0.0; // Reference water volumetric flow rate through the condenser [m3/s] - bool CondVolFlowRateWasAutoSized = false; // true if condenser flow rate was autosize on input - Real64 tmpCondVolFlowRate = 0.0; // temporary ref water vol flow rate for intermediate sizing [m3/s] - Real64 CondMassFlowRateMax = 0.0; // Reference water mass flow rate through condenser [kg/s] - Real64 EvapMassFlowRateMax = 0.0; // Reference water mass flow rate through evaporator [kg/s] - Real64 Evapmdot = 0.0; // Evaporator mass flow rate [kg/s] - Real64 Condmdot = 0.0; // Condenser mass flow rate [kg/s] - Real64 DesignHotWaterVolFlowRate = 0.0; // Design hot water volumetric flow rate through the condenser [m3/s] - Real64 OpenMotorEff = 0.0; // Open chiller motor efficiency [fraction, 0 to 1] - Real64 SizFac = 0.0; // sizing factor - Real64 RefCap = 0.0; // Reference evaporator capacity [W] - Real64 RefCOP = 0.0; // Reference COP - Real64 TempRefEvapOut = 0.0; // Reference evaporator leaving temperature [C] - Real64 TempRefCondIn = 0.0; // Reference condenser entering temperature [C] - Real64 TempRefCondOut = 0.0; // Reference condenser leaving temperature [C] - Real64 OptPartLoadRat = 0.0; // Optimal operating fraction of full load - Real64 ChillerEIRFPLRMin = 0.0; // Minimum value of PLR from EIRFPLR curve - Real64 ChillerEIRFPLRMax = 0.0; // Maximum value of PLR from EIRFPLR curve - CHReportVars Report; - }; - - struct WrapperReportVars - { - Real64 Power = 0.0; // Wrapper power, W - Real64 QCHW = 0.0; // Chilled water heat transfer rate [W] - Real64 QHW = 0.0; // Hot Water heat transfer rate [W] - Real64 QGLHE = 0.0; // Geo-field heat transfer rate [W] - Real64 TotElecCooling = 0.0; // Wrapper cooling electric consumption [J] - Real64 TotElecHeating = 0.0; // Wrapper heating electric consumption [J] - Real64 CoolingEnergy = 0.0; // Chilled water heat transfer energy [J] - Real64 HeatingEnergy = 0.0; // Hot Water heat transfer energy [J] - Real64 GLHEEnergy = 0.0; // Geo-field heat transfer energy [J] - Real64 TotElecCoolingPwr = 0.0; // Wrapper cooling electric consumption rate [W] - Real64 TotElecHeatingPwr = 0.0; // Wrapper heating electric consumption rate [W] - Real64 CoolingRate = 0.0; // Chilled water heat transfer rate [W] - Real64 HeatingRate = 0.0; // Hot Water heat transfer rate [W] - Real64 GLHERate = 0.0; // Geo-field heat transfer rate [W] - Real64 CHWInletTemp = 0.0; // Chilled water inlet temperature [C] - Real64 HWInletTemp = 0.0; // Hot water inlet temperature [C] - Real64 GLHEInletTemp = 0.0; // Geo-field inlet temperature [C] - Real64 CHWOutletTemp = 0.0; // Chilled water Outlet temperature [C] - Real64 HWOutletTemp = 0.0; // Hot water Outlet temperature [C] - Real64 GLHEOutletTemp = 0.0; // Geo-field Outlet temperature [C] - Real64 CHWmdot = 0.0; // Chilled water mass flow rate [kg/s] - Real64 HWmdot = 0.0; // Hot water mass flow rate [kg/s] - Real64 GLHEmdot = 0.0; // Geo-field mass flow rate [kg/s] - Real64 TotElecCoolingSimul = 0.0; // Wrapper cooling electric consumption [J] - Real64 CoolingEnergySimul = 0.0; // Chilled water heat transfer energy [J] - Real64 TotElecCoolingPwrSimul = 0.0; // Wrapper cooling electric consumption rate [W] - Real64 CoolingRateSimul = 0.0; // Chilled water heat transfer rate [W] - Real64 CHWInletTempSimul = 0.0; // Chilled water inlet temperature [C] - Real64 GLHEInletTempSimul = 0.0; // Geo-field inlet temperature [C] - Real64 CHWOutletTempSimul = 0.0; // Chilled water Outlet temperature [C] - Real64 GLHEOutletTempSimul = 0.0; // Geo-field Outlet temperature [C] - Real64 CHWmdotSimul = 0.0; // Chilled water mass flow rate [kg/s] - Real64 GLHEmdotSimul = 0.0; // Geo-field mass flow rate [kg/s] - }; - - struct WrapperSpecs : PlantComponent - { - std::string Name; // User identifier - bool VariableFlowCH = false; // True if all chiller heaters are variable flow control - Sched::Schedule *ancillaryPowerSched = nullptr; // Schedule value for ancillary power control - Sched::Schedule *chSched = nullptr; // Schedule value for individual chiller heater control - int CHWInletNodeNum = 0; // Node number on the inlet side of the plant (Chilled Water side) - int CHWOutletNodeNum = 0; // Node number on the outlet side of the plant (Chilled Water side) - int HWInletNodeNum = 0; // Node number on the inlet side of the plant (Hot Water side) - int HWOutletNodeNum = 0; // Node number on the outlet side of the plant (Hot Water side) - int GLHEInletNodeNum = 0; // Node number on the inlet side of the plant (GLHE Water side) - int GLHEOutletNodeNum = 0; // Node number on the outlet side of the plant (GLHE Water side) - int CoolSetPointTempNode = 0; // Node number of the cooling setpoint temperature node - int HeatSetPointTempNode = 0; // Node number of the heating setpoint temperature node - bool EvapOutletMinTempCheck = true; // True if minimum evaporator outlet temperature warning is enabled - int NumOfComp = 0; // Number of Components under the wrapper - Real64 CHWMassFlowRate = 0.0; // Chilled water mass flow rate - Real64 HWMassFlowRate = 0.0; // Hot water mass flow rate - Real64 GLHEMassFlowRate = 0.0; // Condenser water mass flow rate - Real64 CHWMassFlowRateMax = 0.0; // Maximum chilled water mass flow rate - Real64 HWMassFlowRateMax = 0.0; // Maximum hot water mass flow rate - Real64 GLHEMassFlowRateMax = 0.0; // Maximum condenser water mass flow rate - Real64 WrapperCoolingLoad = 0.0; // Cooling demand for the central heat pump system - Real64 WrapperHeatingLoad = 0.0; // Heating demand for the central heat pump system - Real64 AncillaryPower = 0.0; // Wrapper Ancillary Power - Array1D WrapperComp; - Array1D ChillerHeater; // Dimension to number of machines - bool CoolSetPointErrDone = false; // true if setpoint warning issued - bool HeatSetPointErrDone = false; // true if setpoint warning issued - int ChillerHeaterNums = 0; // Total number of chiller heater units - PlantLocation CWPlantLoc = {}; // Chilled water plant loop component index - PlantLocation HWPlantLoc = {}; // Hot water plant loop component index - PlantLocation GLHEPlantLoc = {}; // Geo-field water plant loop component index - int CHWMassFlowIndex = 0; // Chilled water flow index - int HWMassFlowIndex = 0; // Hot water flow index - int GLHEMassFlowIndex = 0; // Condenser side flow index - Real64 SizingFactor = 1.0; // Sizing factor to adjust the capacity - Real64 CHWVolFlowRate = 0.0; // Chilled water volume flow rate [kg/s] - Real64 HWVolFlowRate = 0.0; // Hot water volume flow rate [kg/s] - Real64 GLHEVolFlowRate = 0.0; // Geo-field volume flow rate [kg/s] - bool MyWrapperFlag = true; - bool MyWrapperEnvrnFlag = true; - bool SimulClgDominant = false; - bool SimulHtgDominant = false; - WrapperReportVars Report; - bool setupOutputVarsFlag = true; - bool mySizesReported = false; - - static PlantComponent *factory(EnergyPlusData &state, std::string const &objectName); - - void getSizingFactor(Real64 &SizFac) override; - - void getDesignCapacities( - EnergyPlusData &state, const PlantLocation &calledFromLocation, Real64 &MaxLoad, Real64 &MinLoad, Real64 &OptLoad) override; - - void setupOutputVars(EnergyPlusData &state); - - void initialize(EnergyPlusData &state, - Real64 MyLoad, // Demand Load - int LoopNum // Loop Number Index - ); - - void simulate([[maybe_unused]] EnergyPlusData &state, - const PlantLocation &calledFromLocation, - bool FirstHVACIteration, - Real64 &CurLoad, - bool RunFlag) override; - - void SizeWrapper(EnergyPlusData &state); - - void CalcWrapperModel(EnergyPlusData &state, Real64 &MyLoad, int LoopNum); - - void CalcChillerModel(EnergyPlusData &state); - - void CalcChillerHeaterModel(EnergyPlusData &state); - - void adjustChillerHeaterCondFlowTemp(EnergyPlusData &state, - Real64 &QCondenser, - Real64 &CondMassFlowRate, - Real64 &CondOutletTemp, - Real64 const CondInletTemp, - Real64 const CondDeltaTemp); - - void adjustChillerHeaterEvapFlowTemp( - EnergyPlusData &state, Real64 const qEvaporator, Real64 &evapMassFlowRate, Real64 &evapOutletTemp, Real64 const evapInletTemp); - - Real64 - setChillerHeaterCondTemp(EnergyPlusData &state, int const numChillerHeater, Real64 const condEnteringTemp, Real64 const condLeavingTemp); - - Real64 calcChillerCapFT(EnergyPlusData &state, int const numChillerHeater, Real64 const evapOutletTemp, Real64 const condTemp); - - void checkEvapOutletTemp(EnergyPlusData &state, - int const numChillerHeater, - Real64 &evapOutletTemp, - Real64 const lowTempLimitEout, - Real64 evapInletTemp, - Real64 &qEvaporator, - Real64 const evapMassFlowRate, - Real64 const Cp, - CurrentMode const mode); - - void calcPLRAndCyclingRatio(EnergyPlusData &state, - Real64 const availChillerCap, - Real64 &actualPartLoadRatio, - Real64 const minPartLoadRatio, - Real64 const maxPartLoadRatio, - Real64 const qEvaporator, - Real64 &frac); - - void UpdateChillerHeaterRecords(EnergyPlusData &state); - - void UpdateChillerRecords(EnergyPlusData &state); - - void onInitLoopEquip([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation) override; - - void oneTimeInit_new(EnergyPlusData &state) override; - - void oneTimeInit(EnergyPlusData &state) override; - }; - - void GetWrapperInput(EnergyPlusData &state); - - void GetChillerHeaterInput(EnergyPlusData &state); - -} // namespace PlantCentralGSHP - -struct PlantCentralGSHPData : BaseGlobalStruct -{ - - bool getWrapperInputFlag = true; // When TRUE, calls subroutine to read input file. - int numWrappers = 0; // Number of Wrappers specified in input - int numChillerHeaters = 0; // Number of Chiller/heaters specified in input - Real64 ChillerCapFT = 0.0; // Chiller/heater capacity fraction (evaluated as a function of temperature) - Real64 ChillerEIRFT = 0.0; // Chiller/heater electric input ratio (EIR = 1 / COP) as a function of temperature - Real64 ChillerEIRFPLR = 0.0; // Chiller/heater EIR as a function of part-load ratio (PLR) - Real64 ChillerPartLoadRatio = 0.0; // Chiller/heater part-load ratio (PLR) - Real64 ChillerCyclingRatio = 0.0; // Chiller/heater cycling ratio - Real64 ChillerFalseLoadRate = 0.0; // Chiller/heater false load over and above the water-side load [W] - EPVector Wrapper; - EPVector ChillerHeater; - - void init_constant_state([[maybe_unused]] EnergyPlusData &state) override - { - } - - void init_state([[maybe_unused]] EnergyPlusData &state) override - { - } - - void clear_state() override - { - new (this) PlantCentralGSHPData(); - } -}; - -} // namespace EnergyPlus - -#endif diff --git a/src/EnergyPlus/PlantCentralHeatPumpSystem.cc b/src/EnergyPlus/PlantCentralHeatPumpSystem.cc new file mode 100644 index 00000000000..f4ae2a49e4e --- /dev/null +++ b/src/EnergyPlus/PlantCentralHeatPumpSystem.cc @@ -0,0 +1,3643 @@ +// EnergyPlus, Copyright (c) 1996-present, The Board of Trustees of the University of Illinois, +// The Regents of the University of California, through Lawrence Berkeley National Laboratory +// (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge +// National Laboratory, managed by UT-Battelle, Alliance for Energy Innovation, LLC, and other +// contributors. All rights reserved. +// +// NOTICE: This Software was developed under funding from the U.S. Department of Energy and the +// U.S. Government consequently retains certain rights. As such, the U.S. Government has been +// granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, +// worldwide license in the Software to reproduce, distribute copies to the public, prepare +// derivative works, and perform publicly and display publicly, and to permit others to do so. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// (1) Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// (2) Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory, +// the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific prior +// written permission. +// +// (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form +// without changes from the version obtained under this License, or (ii) Licensee makes a +// reference solely to the software portion of its product, Licensee must refer to the +// software as "EnergyPlus version X" software, where "X" is the version number Licensee +// obtained under this License and may not use a different name for the software. Except as +// specifically required in this Section (4), Licensee shall not use in a company name, a +// product name, in advertising, publicity, or other promotional activities any name, trade +// name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly +// similar designation, without the U.S. Department of Energy's prior written consent. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +// C++ Headers +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ObjexxFCL Headers +#include + +// EnergyPlus Headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace EnergyPlus::PlantCentralHeatPumpSystem { + +// MODULE INFORMATION: +// AUTHOR PNNL +// DATE WRITTEN Feb 2013 +// MODIFIED na +// RE-ENGINEERED na +// PURPOSE OF THIS MODULE: +// This module simulates the performance of the CentralHeatPumpSystem objects +// It currently includes one object: ChillerHeaterPerformance:Electric:EIR. +// The other object available for this CentralHeatPumpSystem object such as +// HeatPumpPerformance:WaterToWater:EIR will be implemented later. + +// METHODOLOGY EMPLOYED: +// Once the PlantLoopManager determines that the CentralHeatPumpSystem +// is available to meet a loop cooling and heating demands, it calls simulate +// which in turn calls the electric PlantCentralHeatPumpSystem model. The +// PlantCentralHeatPumpSystem model is based on polynomial fits of chiller/heater or heat +// pump performance data. + +namespace { + + void getPartLoadCurveMinMax(EnergyPlusData &state, int const curveIndex, Real64 &minimumPartLoadRatio, Real64 &maximumPartLoadRatio) + { + auto const *curve = state.dataCurveManager->curves(curveIndex); + if (curve->numDims == 1) { + Curve::GetCurveMinMaxValues(state, curveIndex, minimumPartLoadRatio, maximumPartLoadRatio); + } else { + Real64 condenserTempMinimum = 0.0; + Real64 condenserTempMaximum = 0.0; + Curve::GetCurveMinMaxValues(state, curveIndex, condenserTempMinimum, condenserTempMaximum, minimumPartLoadRatio, maximumPartLoadRatio); + } + } + + Real64 evaluatePartLoadCurve(EnergyPlusData &state, int const curveIndex, Real64 const condenserTemp, Real64 const partLoadRatio) + { + auto const *curve = state.dataCurveManager->curves(curveIndex); + return curve->numDims == 1 ? Curve::CurveValue(state, curveIndex, partLoadRatio) + : Curve::CurveValue(state, curveIndex, condenserTemp, partLoadRatio); + } + + struct PartLoadOperatingPoint + { + Real64 evaporatorLoad = 0.0; + Real64 condenserLoad = 0.0; + Real64 compressorPower = 0.0; + Real64 falseLoadRate = 0.0; + Real64 partLoadRatio = 0.0; + Real64 cyclingRatio = 0.0; + Real64 eirPartLoadModifier = 0.0; + }; + + // A unit-width PLR bracket reaches the 1.0e-12 tolerance in 40 subdivisions; 50 leaves conservative headroom. + constexpr int maxOuterSolverIterations = 100; + constexpr int maxPartLoadSolverIterations = 50; + constexpr Real64 temperatureConvergenceTolerance = 1.0e-8; + constexpr Real64 partLoadAbsoluteTolerance = 1.0e-12; + constexpr Real64 loadAbsoluteTolerance = 1.0e-7; + constexpr Real64 loadRelativeTolerance = 1.0e-12; + + struct BisectionResult + { + Real64 value = 0.0; + Real64 bracketWidth = 0.0; + Real64 loadResidual = 0.0; + int iterations = 0; + SolverConvergenceStatus status = SolverConvergenceStatus::NotRequired; + }; + + template + BisectionResult solveBisection( + Real64 lowerValue, Real64 upperValue, Real64 const targetLoad, Real64 const valueTolerance, Real64 const loadScale, LoadAtValue loadAtValue) + { + BisectionResult result; + Real64 previousValue = std::numeric_limits::quiet_NaN(); + Real64 const loadTolerance = max(loadAbsoluteTolerance, loadRelativeTolerance * loadScale); + + for (int iteration = 0; iteration < maxPartLoadSolverIterations; ++iteration) { + Real64 const candidateValue = 0.5 * (lowerValue + upperValue); + Real64 const candidateLoad = loadAtValue(candidateValue); + result.value = candidateValue; + result.loadResidual = std::abs(candidateLoad - targetLoad); + result.iterations = iteration + 1; + + if (!std::isfinite(candidateValue) || !std::isfinite(candidateLoad) || !std::isfinite(result.loadResidual)) { + result.status = SolverConvergenceStatus::Invalid; + break; + } + + bool const stagnated = candidateValue == lowerValue || candidateValue == upperValue || candidateValue == previousValue; + if (candidateLoad < targetLoad) { + lowerValue = candidateValue; + } else { + upperValue = candidateValue; + } + result.bracketWidth = upperValue - lowerValue; + + if (result.bracketWidth <= valueTolerance && result.loadResidual <= loadTolerance) { + result.status = SolverConvergenceStatus::Converged; + break; + } + if (stagnated) { + result.status = SolverConvergenceStatus::Stagnated; + break; + } + previousValue = candidateValue; + } + + if (result.status == SolverConvergenceStatus::NotRequired) { + result.status = SolverConvergenceStatus::IterationLimit; + } + return result; + } + + std::string_view solverStatusName(SolverConvergenceStatus const status) + { + switch (status) { + case SolverConvergenceStatus::NotRequired: + return "not required"; + case SolverConvergenceStatus::Converged: + return "converged"; + case SolverConvergenceStatus::Stagnated: + return "floating-point stagnation"; + case SolverConvergenceStatus::IterationLimit: + return "iteration limit"; + case SolverConvergenceStatus::Invalid: + return "invalid numerical value"; + default: + assert(false); + return "unknown"; + } + } + + bool solverFailed(SolverConvergenceStatus const status) + { + return status != SolverConvergenceStatus::NotRequired && status != SolverConvergenceStatus::Converged; + } + + void reportSolverFailure(EnergyPlusData &state, + std::string const &systemName, + std::string const &moduleName, + int const moduleNum, + std::string_view const operatingMode, + std::string_view const iterationType, + SolverConvergenceStatus const status, + std::string const &requestedLoads, + int const iterations, + Real64 const bracketOrTemperatureResidual, + Real64 const loadResidual, + Real64 const finalOperatingPoint, + std::string_view const finalOperatingPointUnits, + SolverWarningData &warning) + { + ++warning.count; + if (warning.count == 1) { + ShowWarningError(state, + std::format(R"(CentralHeatPumpSystem "{}" module {} ("{}") {} {} failed to converge ({}).)", + systemName, + moduleNum, + moduleName, + operatingMode, + iterationType, + solverStatusName(status))); + ShowContinueError(state, + std::format("Requested {}; iterations={}; bracket/temperature residual={:.6g}; load residual={:.6g} W; " + "final operating point={:.6g} {}.", + requestedLoads, + iterations, + bracketOrTemperatureResidual, + loadResidual, + finalOperatingPoint, + finalOperatingPointUnits)); + return; + } + + ShowRecurringWarningErrorAtEnd(state, + std::format(R"(CentralHeatPumpSystem "{}" module {} ("{}") {} {} convergence failure continues.)", + systemName, + moduleNum, + moduleName, + operatingMode, + iterationType), + warning.recurringIndex, + bracketOrTemperatureResidual, + std::string(finalOperatingPointUnits)); + } + +} // namespace + +void Module::initialize(PerformanceData const &performanceData, Sched::Schedule *const schedule) +{ + this->performance = &performanceData; + this->availabilitySchedule = schedule; + this->variableFlow = performanceData.variableFlow; + this->sizing = ModuleSizingData(); + this->sizing.referenceCoolingCapacity = performanceData.referenceCoolingCapacity; + this->sizing.referenceHeatingCapacity = performanceData.referenceHeatingCapacity; + this->sizing.referenceHeatingCOP = performanceData.referenceHeatingCOP; + this->sizing.referenceHeatingPower = performanceData.referenceHeatingPower; + this->sizing.designEvaporatorVolFlowRate = performanceData.designEvaporatorVolFlowRate; + this->sizing.designCondenserVolFlowRate = performanceData.designCondenserVolFlowRate; + this->minimumEvaporatorOutletTemp = 0.0; + this->capacityCurveErrorCount = 0; + this->capacityCurveErrorIndex = 0; + this->coolingSolverWarning = SolverWarningData(); + this->heatingSolverWarning = SolverWarningData(); + this->heatingPartLoadSolverWarning = SolverWarningData(); + this->simultaneousSolverWarning = SolverWarningData(); + this->simultaneousPartLoadSolverWarning = SolverWarningData(); + this->result = ModuleResult(); +} + +PerformanceData const &Module::performanceData() const +{ + assert(this->performance != nullptr); + return *this->performance; +} + +std::string const &Module::name() const +{ + return this->performanceData().Name; +} + +bool Module::isAvailable() const +{ + return this->availabilitySchedule == nullptr || this->availabilitySchedule->getCurrentVal() > 0.0; +} + +ModePerformanceData Module::coolingModePerformance() const +{ + auto const &definition = this->performanceData(); + ModePerformanceData mode; + mode.condenserMode = definition.coolingCondenserTemperatureMode; + mode.capacityTemperatureCurveIndex = definition.coolingCapacityTemperatureCurveIndex; + mode.eirTemperatureCurveIndex = definition.coolingEIRTemperatureCurveIndex; + mode.eirPartLoadCurveIndex = definition.coolingEIRPartLoadCurveIndex; + mode.referenceEvaporatorCapacity = this->sizing.referenceCoolingCapacity; + mode.referenceCOP = definition.referenceCoolingCOP; + mode.referenceEvaporatorLeavingTemp = definition.coolingReferenceEvaporatorOutletTemp; + mode.referenceCondenserEnteringTemp = definition.coolingReferenceCondenserInletTemp; + mode.referenceCondenserLeavingTemp = definition.coolingReferenceCondenserOutletTemp; + mode.minimumPartLoadRatio = definition.coolingMinimumPartLoadRatio; + mode.maximumPartLoadRatio = definition.coolingMaximumPartLoadRatio; + mode.optimumPartLoadRatio = definition.coolingOptimumPartLoadRatio; + return mode; +} + +ModePerformanceData Module::heatingModePerformance() const +{ + auto const &definition = this->performanceData(); + ModePerformanceData mode; + mode.condenserMode = definition.heatingCondenserTemperatureMode; + mode.capacityTemperatureCurveIndex = definition.heatingCapacityTemperatureCurveIndex; + mode.eirTemperatureCurveIndex = definition.heatingEIRTemperatureCurveIndex; + mode.eirPartLoadCurveIndex = definition.heatingEIRPartLoadCurveIndex; + mode.referenceEvaporatorCapacity = this->sizing.referenceHeatingCapacity; + mode.referenceCOP = this->sizing.referenceHeatingCOP; + mode.referenceEvaporatorLeavingTemp = definition.heatingReferenceEvaporatorOutletTemp; + mode.referenceCondenserEnteringTemp = definition.heatingReferenceCondenserInletTemp; + mode.referenceCondenserLeavingTemp = definition.heatingReferenceCondenserOutletTemp; + mode.minimumPartLoadRatio = definition.heatingMinimumPartLoadRatio; + mode.maximumPartLoadRatio = definition.heatingMaximumPartLoadRatio; + mode.optimumPartLoadRatio = definition.heatingOptimumPartLoadRatio; + return mode; +} + +void Module::mapResultToPlantConnections() +{ + auto &moduleResult = this->result; + + moduleResult.coolingInletTemp = 0.0; + moduleResult.coolingOutletTemp = 0.0; + moduleResult.coolingMassFlowRate = 0.0; + moduleResult.heatingInletTemp = 0.0; + moduleResult.heatingOutletTemp = 0.0; + moduleResult.heatingMassFlowRate = 0.0; + moduleResult.sourceInletTemp = 0.0; + moduleResult.sourceOutletTemp = 0.0; + moduleResult.sourceMassFlowRate = 0.0; + moduleResult.coolingDelivered = 0.0; + moduleResult.heatingDelivered = 0.0; + moduleResult.heatRecovered = 0.0; + moduleResult.sourceHeatTransfer = 0.0; + + switch (moduleResult.currentMode) { + case CurrentMode::CoolingOnly: + case CurrentMode::CoolingDominant: + moduleResult.coolingInletTemp = moduleResult.evaporatorInletTemp; + moduleResult.coolingOutletTemp = moduleResult.evaporatorOutletTemp; + moduleResult.coolingMassFlowRate = moduleResult.evaporatorMassFlowRate; + moduleResult.sourceInletTemp = moduleResult.condenserInletTemp; + moduleResult.sourceOutletTemp = moduleResult.condenserOutletTemp; + moduleResult.sourceMassFlowRate = moduleResult.condenserMassFlowRate; + moduleResult.coolingDelivered = moduleResult.qEvaporator; + moduleResult.sourceHeatTransfer = moduleResult.qCondenser; + break; + case CurrentMode::HeatingOnly: + case CurrentMode::HeatingDominant: + moduleResult.heatingInletTemp = moduleResult.condenserInletTemp; + moduleResult.heatingOutletTemp = moduleResult.condenserOutletTemp; + moduleResult.heatingMassFlowRate = moduleResult.condenserMassFlowRate; + moduleResult.sourceInletTemp = moduleResult.evaporatorInletTemp; + moduleResult.sourceOutletTemp = moduleResult.evaporatorOutletTemp; + moduleResult.sourceMassFlowRate = moduleResult.evaporatorMassFlowRate; + moduleResult.heatingDelivered = moduleResult.qCondenser; + moduleResult.sourceHeatTransfer = -moduleResult.qEvaporator; + break; + case CurrentMode::HeatRecovery: + moduleResult.coolingInletTemp = moduleResult.evaporatorInletTemp; + moduleResult.coolingOutletTemp = moduleResult.evaporatorOutletTemp; + moduleResult.coolingMassFlowRate = moduleResult.evaporatorMassFlowRate; + moduleResult.heatingInletTemp = moduleResult.condenserInletTemp; + moduleResult.heatingOutletTemp = moduleResult.condenserOutletTemp; + moduleResult.heatingMassFlowRate = moduleResult.condenserMassFlowRate; + moduleResult.coolingDelivered = moduleResult.qEvaporator; + moduleResult.heatingDelivered = moduleResult.qCondenser; + moduleResult.heatRecovered = moduleResult.qCondenser; + break; + case CurrentMode::Off: + case CurrentMode::Invalid: + case CurrentMode::Num: + break; + } + + moduleResult.updatePowerAccounting(this->performanceData().compressorMotorEfficiency); +} + +void Module::updateResultEnergies(Real64 const secondsInTimeStep) +{ + this->result.updateEnergies(secondsInTimeStep); +} + +void Module::resetResult(Real64 const evaporatorInletTemp, Real64 const condenserInletTemp) +{ + this->result = ModuleResult(); + this->result.evaporatorInletTemp = evaporatorInletTemp; + this->result.evaporatorOutletTemp = evaporatorInletTemp; + this->result.condenserInletTemp = condenserInletTemp; + this->result.condenserOutletTemp = condenserInletTemp; + this->mapResultToPlantConnections(); +} + +PlantComponent *CentralHeatPumpSystem::factory(EnergyPlusData &state, std::string const &objectName) +{ + // Process the input data + if (state.dataPlantCentralHeatPumpSystem->getSystemInputFlag) { + getCentralHeatPumpSystemInput(state); + state.dataPlantCentralHeatPumpSystem->getSystemInputFlag = false; + } + + auto &systems = state.dataPlantCentralHeatPumpSystem->systems; + if (auto const system = std::ranges::find_if(systems, [&objectName](auto const &candidate) { return candidate.Name == objectName; }); + system != systems.end()) { + return &*system; + } + + ShowFatalError(state, + std::format("CentralHeatPumpSystem::factory: Error " + "getting inputs for object named: {}", + objectName)); // LCOV_EXCL_LINE +} + +void CentralHeatPumpSystem::onInitLoopEquip(EnergyPlusData &state, const PlantLocation &calledFromLocation) +{ + this->initialize(state, 0.0, calledFromLocation.loopNum, false); + this->size(state); +} + +void CentralHeatPumpSystem::getDesignCapacities( + [[maybe_unused]] EnergyPlusData &state, const PlantLocation &calledFromLocation, Real64 &maxLoad, Real64 &minLoad, Real64 &optimalLoad) +{ + minLoad = 0.0; + maxLoad = 0.0; + optimalLoad = 0.0; + Real64 minimumStageLoad = std::numeric_limits::max(); + auto accumulateCapacity = [&minimumStageLoad](ModePerformanceData const &mode, Real64 &maximumLoad, Real64 &optimumLoad) { + if (mode.referenceEvaporatorCapacity <= 0.0) { + return; + } + maximumLoad += mode.referenceEvaporatorCapacity * max(0.0, mode.maximumPartLoadRatio); + optimumLoad += mode.referenceEvaporatorCapacity * max(0.0, mode.optimumPartLoadRatio); + minimumStageLoad = min(minimumStageLoad, mode.referenceEvaporatorCapacity * max(0.0, mode.minimumPartLoadRatio)); + }; + + if (calledFromLocation.loopNum == this->coolingPlantLoc.loopNum) { + for (auto const &module : this->modules) { + accumulateCapacity(module.coolingModePerformance(), maxLoad, optimalLoad); + } + } else if (calledFromLocation.loopNum == this->heatingPlantLoc.loopNum) { + for (auto const &module : this->modules) { + auto heatingMode = module.heatingModePerformance(); + heatingMode.referenceEvaporatorCapacity += + max(0.0, module.sizing.referenceHeatingPower) * module.performanceData().compressorMotorEfficiency; + accumulateCapacity(heatingMode, maxLoad, optimalLoad); + } + } else if (calledFromLocation.loopNum == this->sourcePlantLoc.loopNum) { + Real64 maximumSourceRejection = 0.0; + Real64 optimumSourceRejection = 0.0; + Real64 maximumSourceExtraction = 0.0; + Real64 optimumSourceExtraction = 0.0; + for (auto const &module : this->modules) { + auto coolingMode = module.coolingModePerformance(); + Real64 const referenceCoolingPower = + coolingMode.referenceCOP > 0.0 ? coolingMode.referenceEvaporatorCapacity / coolingMode.referenceCOP : 0.0; + coolingMode.referenceEvaporatorCapacity += referenceCoolingPower * module.performanceData().compressorMotorEfficiency; + accumulateCapacity(coolingMode, maximumSourceRejection, optimumSourceRejection); + accumulateCapacity(module.heatingModePerformance(), maximumSourceExtraction, optimumSourceExtraction); + } + maxLoad = max(maximumSourceRejection, maximumSourceExtraction); + optimalLoad = max(optimumSourceRejection, optimumSourceExtraction); + } + + if (minimumStageLoad < std::numeric_limits::max()) { + minLoad = minimumStageLoad; + } +} + +void CentralHeatPumpSystem::getSizingFactor(Real64 &sizingFactor) +{ + sizingFactor = 1.0; +} + +void CentralHeatPumpSystem::simulate( + EnergyPlusData &state, const PlantLocation &calledFromLocation, bool firstHVACIteration, Real64 ¤tLoad, bool const runFlag) +{ + if (calledFromLocation.loopNum != this->sourcePlantLoc.loopNum) { + + this->initialize(state, currentLoad, calledFromLocation.loopNum, runFlag); + if (!runFlag) { + currentLoad = 0.0; + if (calledFromLocation.loopNum == this->coolingPlantLoc.loopNum) { + this->requestedCoolingLoad = 0.0; + } else if (calledFromLocation.loopNum == this->heatingPlantLoc.loopNum) { + this->requestedHeatingLoad = 0.0; + } + if (this->requestedCoolingLoad <= HVAC::SmallLoad && this->requestedHeatingLoad <= HVAC::SmallLoad) { + this->resetOffState(state); + } + return; + } + this->calculate(state, currentLoad, calledFromLocation.loopNum); + + } else if (calledFromLocation.loopNum == this->sourcePlantLoc.loopNum) { + // Useful-load callbacks own the system run state. The source callback only + // publishes the authoritative load-side result and must not erase an active + // cooling or heating request. + PlantUtilities::UpdateChillerComponentCondenserSide(state, + calledFromLocation.loopNum, + this->sourcePlantLoc.loopSideNum, + DataPlant::PlantEquipmentType::CentralHeatPumpSystem, + this->sourceInletNodeNum, + this->sourceOutletNodeNum, + this->report.sourceHeatTransferRate, + this->report.sourceInletTemp, + this->report.sourceOutletTemp, + this->report.sourceMassFlowRate, + firstHVACIteration); + } +} + +void CentralHeatPumpSystem::size(EnergyPlusData &state) +{ + // SUBROUTINE INFORMATION: + // AUTHOR Yunzhi Huang, PNNL + // DATE WRITTEN Feb 2013 + // MODIFIED November 2013 Daeho Kang, add component sizing table + // entries RE-ENGINEERED na + + // PURPOSE OF THIS SUBROUTINE: + // This subroutine is for sizing all the components under each + // 'CentralHeatPumpSystem' object, for which capacities and flow rates have + // not been specified in the input. + + // METHODOLOGY EMPLOYED: + // Obtains evaporator flow rate from the plant sizing array. Calculates + // reference capacity from the evaporator (or load side) flow rate and the + // chilled water loop design delta T. The condenser flow (or source side) + // rate is calculated from the reference capacity, the COP, and the condenser + // loop design delta T. + + static constexpr std::string_view routineName("CentralHeatPumpSystem::size"); + + // auto-size the chiller heater components + + for (auto &module : this->modules) { + bool errorsFound = false; + + // find the appropriate Plant Sizing objects + int coolingPlantSizingIndex = this->coolingPlantLoc.loop->PlantSizNum; + int sourcePlantSizingIndex = this->sourcePlantLoc.loop->PlantSizNum; + + auto const &performance = module.performanceData(); + auto &sizing = module.sizing; + + Real64 sizingNominalCapacity = sizing.referenceCoolingCapacity; + Real64 sizingEvaporatorVolFlowRate = sizing.designEvaporatorVolFlowRate; + Real64 sizingCondenserVolFlowRate = sizing.designCondenserVolFlowRate; + + // auto-size the Evaporator Flow Rate + if (coolingPlantSizingIndex > 0) { + Real64 designEvaporatorVolFlowRate = 0.0; + if (state.dataSize->PlantSizData(coolingPlantSizingIndex).DesVolFlowRate >= HVAC::SmallWaterVolFlow) { + designEvaporatorVolFlowRate = state.dataSize->PlantSizData(coolingPlantSizingIndex).DesVolFlowRate * performance.sizingFactor; + } + sizingEvaporatorVolFlowRate = + performance.designEvaporatorVolFlowRateWasAutoSized ? designEvaporatorVolFlowRate : sizing.designEvaporatorVolFlowRate; + if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { + if (performance.designEvaporatorVolFlowRateWasAutoSized) { + sizing.designEvaporatorVolFlowRate = designEvaporatorVolFlowRate; + if (state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "Design Size Reference Chilled Water Flow Rate [m3/s]", + designEvaporatorVolFlowRate); + } + if (state.dataPlnt->PlantFirstSizesOkayToReport) { + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "Initial Design Size Reference Chilled Water Flow Rate [m3/s]", + designEvaporatorVolFlowRate); + } + } else if (sizing.designEvaporatorVolFlowRate > 0.0 && designEvaporatorVolFlowRate > 0.0 && + state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { + Real64 const userEvaporatorVolFlowRate = sizing.designEvaporatorVolFlowRate; + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "Design Size Reference Chilled Water Flow Rate [m3/s]", + designEvaporatorVolFlowRate, + "User-Specified Reference Chilled Water Flow Rate [m3/s]", + userEvaporatorVolFlowRate); + if (state.dataGlobal->DisplayExtraWarnings && (std::abs(designEvaporatorVolFlowRate - userEvaporatorVolFlowRate) / + userEvaporatorVolFlowRate) > state.dataSize->AutoVsHardSizingThreshold) { + ShowMessage(state, + std::format("CentralHeatPumpSystem::size: " + "Potential issue with equipment sizing for {}", + module.name())); + ShowContinueError(state, + std::format("User-Specified Reference Chilled " + "Water Flow Rate of {:.5f} [m3/s]", + userEvaporatorVolFlowRate)); + ShowContinueError(state, + std::format("differs from Design Size Reference Chilled " + "Water Flow Rate of {:.5f} [m3/s]", + designEvaporatorVolFlowRate)); + ShowContinueError(state, "This may, or may not, indicate mismatched component sizes."); + ShowContinueError(state, + "Verify that the value entered is intended and " + "is consistent with other components."); + } + } + } + } else { + if (performance.designEvaporatorVolFlowRateWasAutoSized) { + if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { + ShowSevereError(state, + "Autosizing of ChillerHeaterPerformance:Electric:EIR evaporator flow " + "rate requires a loop Sizing:Plant object"); + ShowContinueError(state, std::format("Occurs in ChillerHeaterPerformance:Electric:EIR object={}", module.name())); + errorsFound = true; + } + } else { + if (sizing.designEvaporatorVolFlowRate > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "User-Specified Reference Chilled Water Flow Rate [m3/s]", + sizing.designEvaporatorVolFlowRate); + } + } + } + sizing.temporaryEvaporatorVolFlowRate = max(0.0, sizingEvaporatorVolFlowRate); + + // auto-size the Reference Cooling Capacity + // each individual chiller heater module is sized to be capable of + // supporting the total load on the system + if (coolingPlantSizingIndex > 0) { + Real64 designNominalCapacity = 0.0; + if (state.dataSize->PlantSizData(coolingPlantSizingIndex).DesVolFlowRate >= HVAC::SmallWaterVolFlow && + sizingEvaporatorVolFlowRate > 0.0) { + Real64 const cp = this->coolingPlantLoc.loop->glycol->getSpecificHeat(state, Constant::CWInitConvTemp, routineName); + Real64 const rho = this->coolingPlantLoc.loop->glycol->getDensity(state, Constant::CWInitConvTemp, routineName); + designNominalCapacity = cp * rho * state.dataSize->PlantSizData(coolingPlantSizingIndex).DeltaT * sizingEvaporatorVolFlowRate; + } + sizingNominalCapacity = performance.referenceCoolingCapacityWasAutoSized ? designNominalCapacity : sizing.referenceCoolingCapacity; + if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { + if (performance.referenceCoolingCapacityWasAutoSized) { + sizing.referenceCoolingCapacity = designNominalCapacity; + + // Now that we have the Reference Cooling Capacity, we need to also + // initialize the Heating side given the ratios + sizing.referenceHeatingCapacity = sizing.referenceCoolingCapacity * performance.heatingToCoolingCapacityRatio; + + sizing.referenceHeatingPower = + (sizing.referenceCoolingCapacity / performance.referenceCoolingCOP) * performance.heatingToCoolingPowerRatio; + + sizing.referenceHeatingCOP = sizing.referenceHeatingCapacity / sizing.referenceHeatingPower; + + if (state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "Design Size Reference Capacity [W]", + designNominalCapacity); + } + if (state.dataPlnt->PlantFirstSizesOkayToReport) { + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "Initial Design Size Reference Capacity [W]", + designNominalCapacity); + } + } else if (sizing.referenceCoolingCapacity > 0.0 && designNominalCapacity > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport && + !this->mySizesReported) { + Real64 const userNominalCapacity = sizing.referenceCoolingCapacity; + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "Design Size Reference Capacity [W]", + designNominalCapacity, + "User-Specified Reference Capacity [W]", + userNominalCapacity); + if (state.dataGlobal->DisplayExtraWarnings && + (std::abs(designNominalCapacity - userNominalCapacity) / userNominalCapacity) > state.dataSize->AutoVsHardSizingThreshold) { + ShowMessage(state, + std::format("CentralHeatPumpSystem::size: " + "Potential issue with equipment sizing for {}", + module.name())); + ShowContinueError(state, std::format("User-Specified Reference Capacity of {:.2f} [W]", userNominalCapacity)); + ShowContinueError(state, std::format("differs from Design Size Reference Capacity of {:.2f} [W]", designNominalCapacity)); + ShowContinueError(state, "This may, or may not, indicate mismatched component sizes."); + ShowContinueError(state, + "Verify that the value entered is intended and " + "is consistent with other components."); + } + } + } + } else { + if (performance.referenceCoolingCapacityWasAutoSized) { + if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { + ShowSevereError(state, + std::format("Size ChillerHeaterPerformance:Electric:EIR=\"{}\", " + "autosize error.", + module.name())); + ShowContinueError(state, "Autosizing of ChillerHeaterPerformance:Electric:EIR reference capacity requires"); + ShowContinueError(state, "a cooling loop Sizing:Plant object."); + errorsFound = true; + } + } else { + if (sizing.referenceCoolingCapacity > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "User-Specified Reference Capacity [W]", + sizing.referenceCoolingCapacity); + } + } + } + + // auto-size the condenser volume flow rate + // each individual chiller heater module is sized to be capable of + // supporting the total load on the system + if (sourcePlantSizingIndex > 0) { + auto const &sourceSizing = state.dataSize->PlantSizData(sourcePlantSizingIndex); + Real64 designCondenserVolFlowRate = 0.0; + if (sourceSizing.DesVolFlowRate >= HVAC::SmallWaterVolFlow && sizingNominalCapacity > 0.0 && sourceSizing.DeltaT > 0.0 && + performance.referenceCoolingCOP > 0.0) { + Real64 const rho = this->sourcePlantLoc.loop->glycol->getDensity(state, performance.coolingReferenceCondenserInletTemp, routineName); + Real64 const cp = + this->sourcePlantLoc.loop->glycol->getSpecificHeat(state, performance.coolingReferenceCondenserInletTemp, routineName); + designCondenserVolFlowRate = sizingNominalCapacity * (1.0 + performance.compressorMotorEfficiency / performance.referenceCoolingCOP) / + (sourceSizing.DeltaT * cp * rho); + } + sizingCondenserVolFlowRate = + performance.designCondenserVolFlowRateWasAutoSized ? designCondenserVolFlowRate : sizing.designCondenserVolFlowRate; + if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { + if (performance.designCondenserVolFlowRateWasAutoSized) { + sizing.designCondenserVolFlowRate = designCondenserVolFlowRate; + if (state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "Design Size Reference Condenser Water Flow Rate [m3/s]", + designCondenserVolFlowRate); + } + if (state.dataPlnt->PlantFirstSizesOkayToReport) { + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "Initial Design Size Reference Condenser Water Flow Rate " + "[m3/s]", + designCondenserVolFlowRate); + } + } else if (sizing.designCondenserVolFlowRate > 0.0 && designCondenserVolFlowRate > 0.0 && + state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { + Real64 const userCondenserVolFlowRate = sizing.designCondenserVolFlowRate; + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "Design Size Reference Condenser Water Flow Rate [m3/s]", + designCondenserVolFlowRate, + "User-Specified Reference Condenser Water Flow Rate [m3/s]", + userCondenserVolFlowRate); + if (state.dataGlobal->DisplayExtraWarnings && (std::abs(designCondenserVolFlowRate - userCondenserVolFlowRate) / + userCondenserVolFlowRate) > state.dataSize->AutoVsHardSizingThreshold) { + ShowMessage(state, + std::format("CentralHeatPumpSystem::size: " + "Potential issue with equipment sizing for {}", + module.name())); + ShowContinueError(state, + std::format("User-Specified Reference Condenser " + "Water Flow Rate of {:.5f} [m3/s]", + userCondenserVolFlowRate)); + ShowContinueError(state, + std::format("differs from Design Size Reference " + "Condenser Water Flow Rate of {:.5f} [m3/s]", + designCondenserVolFlowRate)); + ShowContinueError(state, "This may, or may not, indicate mismatched component sizes."); + ShowContinueError(state, + "Verify that the value entered is intended and " + "is consistent with other components."); + } + } + } + } else { + if (performance.designCondenserVolFlowRateWasAutoSized) { + if (state.dataPlnt->PlantFirstSizesOkayToFinalize) { + ShowSevereError(state, + std::format("Size ChillerHeaterPerformance:Electric:EIR=\"{}\", " + "autosize error.", + module.name())); + ShowContinueError(state, + "Autosizing of ChillerHeaterPerformance:Electric:EIR " + "condenser flow rate requires"); + ShowContinueError(state, "a condenser loop Sizing:Plant object."); + errorsFound = true; + } + } else { + if (sizing.designCondenserVolFlowRate > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { + BaseSizer::reportSizerOutput(state, + "ChillerHeaterPerformance:Electric:EIR", + module.name(), + "User-Specified Reference Condenser Water Flow Rate [m3/s]", + sizing.designCondenserVolFlowRate); + } + } + } + sizing.temporaryCondenserVolFlowRate = max(0.0, sizingCondenserVolFlowRate); + + if (state.dataPlnt->PlantFinalSizesOkayToReport && !this->mySizesReported) { + // create predefined report + const std::string &equipmentName = module.name(); + OutputReportPredefined::PreDefTableEntry( + state, state.dataOutRptPredefined->pdchMechType, equipmentName, "ChillerHeaterPerformance:Electric:EIR"); + OutputReportPredefined::PreDefTableEntry( + state, state.dataOutRptPredefined->pdchMechNomEff, equipmentName, performance.referenceCoolingCOP); + OutputReportPredefined::PreDefTableEntry( + state, state.dataOutRptPredefined->pdchMechNomCap, equipmentName, sizing.referenceCoolingCapacity); + } + + if (errorsFound) { + ShowFatalError(state, "Preceding sizing errors cause program termination"); + } + } + + // sum individual volume flows and register system inlets + Real64 totalEvaporatorVolFlowRate = 0.0; + Real64 totalSourceVolFlowRate = 0.0; + Real64 totalHeatingVolFlowRate = 0.0; + for (auto const &module : this->modules) { + auto const &performance = module.performanceData(); + auto const &sizing = module.sizing; + totalEvaporatorVolFlowRate += sizing.temporaryEvaporatorVolFlowRate; + totalSourceVolFlowRate += max(sizing.temporaryEvaporatorVolFlowRate, sizing.temporaryCondenserVolFlowRate); + totalHeatingVolFlowRate += performance.designHeatingVolFlowRate; + } + + PlantUtilities::RegisterPlantCompDesignFlow(state, this->coolingInletNodeNum, totalEvaporatorVolFlowRate); + PlantUtilities::RegisterPlantCompDesignFlow(state, this->heatingInletNodeNum, totalHeatingVolFlowRate); + // Source extraction uses evaporator flow and source rejection uses condenser + // flow; only one direction is active per module. + PlantUtilities::RegisterPlantCompDesignFlow(state, this->sourceInletNodeNum, totalSourceVolFlowRate); + + if (state.dataPlnt->PlantFinalSizesOkayToReport) { + this->mySizesReported = true; + } +} + +void CentralHeatPumpSystem::resolveFlowMode(EnergyPlusData &state) +{ + bool hasConstantFlow = false; + bool hasVariableFlow = false; + for (auto const &module : this->modules) { + auto const &performance = module.performanceData(); + hasConstantFlow = hasConstantFlow || performance.constantFlow || !performance.variableFlow; + hasVariableFlow = hasVariableFlow || performance.variableFlow; + } + + bool const mixedFlowModes = hasConstantFlow && hasVariableFlow; + this->allModulesVariableFlow = hasVariableFlow && !hasConstantFlow; + if (mixedFlowModes) { + ShowWarningError(state, + std::format("CentralHeatPumpSystem={} contains both " + "constant-flow and variable-flow performance objects.", + this->Name)); + ShowContinueError(state, "All modules in this CentralHeatPumpSystem will use constant-flow control."); + } + + for (auto &module : this->modules) { + module.variableFlow = !mixedFlowModes && this->allModulesVariableFlow; + } +} + +void getCentralHeatPumpSystemInput(EnergyPlusData &state) +{ + // SUBROUTINE INFORMATION: + // AUTHOR: Yunzhi Huang and Daeho Kang, PNNL + // DATE WRITTEN: Feb 2013 + + // PURPOSE OF THIS SUBROUTINE: + // This routine will get the input required by the CentralHeatPumpSystem model. + + static constexpr std::string_view routineName = "getCentralHeatPumpSystemInput"; + static constexpr char objectType[] = "CentralHeatPumpSystem"; + static constexpr char performanceObjectType[] = "ChillerHeaterPerformance:Electric:EIR"; + + static constexpr char coolingInletNodeKey[] = "cooling_loop_inlet_node_name"; + static constexpr char coolingOutletNodeKey[] = "cooling_loop_outlet_node_name"; + static constexpr char sourceInletNodeKey[] = "source_loop_inlet_node_name"; + static constexpr char sourceOutletNodeKey[] = "source_loop_outlet_node_name"; + static constexpr char heatingInletNodeKey[] = "heating_loop_inlet_node_name"; + static constexpr char heatingOutletNodeKey[] = "heating_loop_outlet_node_name"; + static constexpr char ancillaryPowerKey[] = "ancillary_power"; + static constexpr char ancillaryScheduleKey[] = "ancillary_operation_schedule_name"; + + struct ResolvedModuleGroup + { + std::size_t performanceIndex = 0; + int moduleCount = 0; + Sched::Schedule *availabilitySchedule = nullptr; + }; + + struct ModuleGroupFieldKeys + { + std::string performanceObjectType; + std::string performanceName; + std::string controlScheduleName; + std::string numberOfModules; + }; + + auto moduleGroupFieldKeys = [](int const groupNumber) { + std::string const suffix = std::to_string(groupNumber); + ModuleGroupFieldKeys keys{ + "chiller_heater_modules_performance_component_object_type_" + suffix, + "chiller_heater_modules_performance_component_name_" + suffix, + "chiller_heater_modules_control_schedule_name_" + suffix, + "number_of_chiller_heater_modules_" + suffix, + }; + + // These schema keys reflect the original IDD field spellings. + if (groupNumber == 3) { + keys.performanceObjectType = "chiller_heater_performance_component_object_type_3"; + keys.performanceName = "chiller_heater_performance_component_name_3"; + } else if (groupNumber == 5) { + keys.performanceName = "chiller_heater_models_performance_component_name_5"; + } else if (groupNumber == 11) { + keys.controlScheduleName = "chiller_heater_module_control_schedule_name_11"; + } else if (groupNumber == 18) { + keys.controlScheduleName = "chiller_heater_modules_control_control_schedule_name_18"; + } + return keys; + }; + + bool errorsFound = false; + auto &inputProcessor = state.dataInputProcessing->inputProcessor; + auto &systems = state.dataPlantCentralHeatPumpSystem->systems; + state.dataIPShortCut->cCurrentModuleObject = objectType; + state.dataPlantCentralHeatPumpSystem->numPerformanceReferences = 0; + + auto const instances = inputProcessor->epJSON.find(objectType); + if (instances == inputProcessor->epJSON.end() || instances->empty()) { + ShowSevereError(state, std::format("No {} equipment specified in input file", objectType)); + return; + } + + auto const &instancesValue = instances.value(); + systems.clear(); + systems.resize(instancesValue.size()); + + // Performance definitions are independent named objects. Parse and retain them before resolving system references. + getPerformanceInput(state); + + auto const &objectSchemaProps = inputProcessor->getObjectSchemaProps(state, objectType); + assert(instancesValue.size() == systems.size()); + + std::unordered_set systemNames; + std::size_t systemIndex = 0; + for (auto const &systemObject : instancesValue.items()) { + auto const &key = systemObject.key(); + auto const &objectFields = systemObject.value(); + inputProcessor->markObjectAsUsed(objectType, key); + + ErrorObjectHeader const eoh{routineName, objectType, key}; + auto &system = systems.at(systemIndex++); + system.Name = Util::makeUPPER(key); + if (!systemNames.emplace(system.Name).second) { + ShowSevereDuplicateName(state, eoh); + errorsFound = true; + continue; + } + + std::string const coolingInletNodeName = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, coolingInletNodeKey); + std::string const coolingOutletNodeName = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, coolingOutletNodeKey); + std::string const sourceInletNodeName = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, sourceInletNodeKey); + std::string const sourceOutletNodeName = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, sourceOutletNodeKey); + std::string const heatingInletNodeName = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, heatingInletNodeKey); + std::string const heatingOutletNodeName = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, heatingOutletNodeKey); + + system.coolingInletNodeNum = Node::GetOnlySingleNode(state, + coolingInletNodeName, + errorsFound, + Node::ConnectionObjectType::CentralHeatPumpSystem, + system.Name, + Node::FluidType::Water, + Node::ConnectionType::Inlet, + Node::CompFluidStream::Primary, + Node::ObjectIsNotParent); + system.coolingOutletNodeNum = Node::GetOnlySingleNode(state, + coolingOutletNodeName, + errorsFound, + Node::ConnectionObjectType::CentralHeatPumpSystem, + system.Name, + Node::FluidType::Water, + Node::ConnectionType::Outlet, + Node::CompFluidStream::Primary, + Node::ObjectIsNotParent); + system.coolingSetpointNodeNum = system.coolingOutletNodeNum; + Node::TestCompSet(state, objectType, system.Name, coolingInletNodeName, coolingOutletNodeName, "Chilled Water Nodes"); + + system.sourceInletNodeNum = Node::GetOnlySingleNode(state, + sourceInletNodeName, + errorsFound, + Node::ConnectionObjectType::CentralHeatPumpSystem, + system.Name, + Node::FluidType::Water, + Node::ConnectionType::Inlet, + Node::CompFluidStream::Secondary, + Node::ObjectIsNotParent); + system.sourceOutletNodeNum = Node::GetOnlySingleNode(state, + sourceOutletNodeName, + errorsFound, + Node::ConnectionObjectType::CentralHeatPumpSystem, + system.Name, + Node::FluidType::Water, + Node::ConnectionType::Outlet, + Node::CompFluidStream::Secondary, + Node::ObjectIsNotParent); + Node::TestCompSet(state, objectType, system.Name, sourceInletNodeName, sourceOutletNodeName, "Source Nodes"); + + system.heatingInletNodeNum = Node::GetOnlySingleNode(state, + heatingInletNodeName, + errorsFound, + Node::ConnectionObjectType::CentralHeatPumpSystem, + system.Name, + Node::FluidType::Water, + Node::ConnectionType::Inlet, + Node::CompFluidStream::Tertiary, + Node::ObjectIsNotParent); + system.heatingOutletNodeNum = Node::GetOnlySingleNode(state, + heatingOutletNodeName, + errorsFound, + Node::ConnectionObjectType::CentralHeatPumpSystem, + system.Name, + Node::FluidType::Water, + Node::ConnectionType::Outlet, + Node::CompFluidStream::Tertiary, + Node::ObjectIsNotParent); + system.heatingSetpointNodeNum = system.heatingOutletNodeNum; + Node::TestCompSet(state, objectType, system.Name, heatingInletNodeName, heatingOutletNodeName, "Hot water Nodes"); + + system.ancillaryPower = inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, ancillaryPowerKey); + std::string const ancillaryScheduleName = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, ancillaryScheduleKey); + if (ancillaryScheduleName.empty()) { + system.ancillaryPowerSched = Sched::GetScheduleAlwaysOn(state); + } else if ((system.ancillaryPowerSched = Sched::GetSchedule(state, ancillaryScheduleName)) == nullptr) { + ShowSevereItemNotFound(state, eoh, ancillaryScheduleKey, ancillaryScheduleName); + errorsFound = true; + } + + std::vector resolvedGroups; + resolvedGroups.reserve(20); + std::size_t totalModuleCount = 0; + for (int groupNumber = 1; groupNumber <= 20; ++groupNumber) { + auto const keys = moduleGroupFieldKeys(groupNumber); + if (!objectFields.contains(keys.performanceObjectType)) { + continue; + } + + std::string const enteredPerformanceObjectType = + inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, keys.performanceObjectType); + std::string const performanceName = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, keys.performanceName); + if (!Util::SameString(enteredPerformanceObjectType, performanceObjectType)) { + ShowSevereError(state, + std::format("{}: {}={} is not a supported performance object type for {}={}", + routineName, + keys.performanceObjectType, + enteredPerformanceObjectType, + objectType, + system.Name)); + errorsFound = true; + continue; + } + + auto const &performanceDefinitions = state.dataPlantCentralHeatPumpSystem->performanceDefinitions; + auto const performance = std::ranges::find_if( + performanceDefinitions, [&performanceName](auto const &candidate) { return Util::SameString(performanceName, candidate.Name); }); + if (performance == performanceDefinitions.end()) { + ShowSevereItemNotFound(state, eoh, keys.performanceName, performanceName); + ShowContinueError(state, "Select the name of a ChillerHeaterPerformance:Electric:EIR object."); + errorsFound = true; + continue; + } + auto const performanceIndex = static_cast(std::distance(performanceDefinitions.begin(), performance)); + + int const moduleCount = inputProcessor->getIntFieldValue(objectFields, objectSchemaProps, keys.numberOfModules); + if (moduleCount < 1) { + ShowSevereError(state, + std::format("{}: {} must be at least 1 for {}={}", routineName, keys.numberOfModules, objectType, system.Name)); + errorsFound = true; + continue; + } + + std::string const scheduleName = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, keys.controlScheduleName); + Sched::Schedule *availabilitySchedule = Sched::GetScheduleAlwaysOn(state); + if (!scheduleName.empty() && (availabilitySchedule = Sched::GetSchedule(state, scheduleName)) == nullptr) { + availabilitySchedule = Sched::GetScheduleAlwaysOn(state); + ShowWarningItemNotFound(state, eoh, keys.controlScheduleName, scheduleName, "the AlwaysOn schedule"); + } + + resolvedGroups.push_back({performanceIndex, moduleCount, availabilitySchedule}); + totalModuleCount += static_cast(moduleCount); + ++state.dataPlantCentralHeatPumpSystem->numPerformanceReferences; + } + + if (resolvedGroups.empty()) { + ShowSevereError(state, std::format("{}: No valid module groups specified for {}={}", routineName, objectType, system.Name)); + errorsFound = true; + continue; + } + + system.modules.resize(totalModuleCount); + std::size_t moduleIndex = 0; + for (auto const &group : resolvedGroups) { + for (int identicalModuleNum = 1; identicalModuleNum <= group.moduleCount; ++identicalModuleNum) { + system.modules.at(moduleIndex++) + .initialize(state.dataPlantCentralHeatPumpSystem->performanceDefinitions.at(group.performanceIndex), group.availabilitySchedule); + } + } + assert(moduleIndex == system.modules.size()); + system.resolveFlowMode(state); + } + + if (errorsFound) { + ShowFatalError(state, std::format("getCentralHeatPumpSystemInput: Invalid {} Input, preceding condition(s) cause termination.", objectType)); + } +} + +void CentralHeatPumpSystem::setupOutputVars(EnergyPlusData &state) +{ + SetupOutputVariable(state, + "Chiller Heater System Cooling Electricity Energy", + Constant::Units::J, + this->report.coolingElectricEnergy, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Sum, + this->Name, + Constant::eResource::Electricity, + OutputProcessor::Group::Plant, + OutputProcessor::EndUseCat::Cooling); + + SetupOutputVariable(state, + "Chiller Heater System Heating Electricity Energy", + Constant::Units::J, + this->report.heatingElectricEnergy, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Sum, + this->Name, + Constant::eResource::Electricity, + OutputProcessor::Group::Plant, + OutputProcessor::EndUseCat::Heating); + + SetupOutputVariable(state, + "Chiller Heater System Cooling Electricity Rate", + Constant::Units::W, + this->report.coolingElectricPower, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Heating Electricity Rate", + Constant::Units::W, + this->report.heatingElectricPower, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Cooling Energy", + Constant::Units::J, + this->report.coolingHeatTransferEnergy, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Sum, + this->Name, + Constant::eResource::EnergyTransfer, + OutputProcessor::Group::Plant, + OutputProcessor::EndUseCat::Chillers); + + SetupOutputVariable(state, + "Chiller Heater System Heating Energy", + Constant::Units::J, + this->report.heatingHeatTransferEnergy, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Sum, + this->Name, + Constant::eResource::EnergyTransfer, + OutputProcessor::Group::Plant, + OutputProcessor::EndUseCat::Boilers); + + SetupOutputVariable(state, + "Chiller Heater System Source Heat Transfer Energy", + Constant::Units::J, + this->report.sourceHeatTransferEnergy, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Sum, + this->Name, + Constant::eResource::EnergyTransfer, + OutputProcessor::Group::Plant, + OutputProcessor::EndUseCat::HeatRejection); + + SetupOutputVariable(state, + "Chiller Heater System Cooling Rate", + Constant::Units::W, + this->report.coolingHeatTransferRate, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Heating Rate", + Constant::Units::W, + this->report.heatingHeatTransferRate, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Source Heat Transfer Rate", + Constant::Units::W, + this->report.sourceHeatTransferRate, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Cooling Mass Flow Rate", + Constant::Units::kg_s, + this->report.coolingMassFlowRate, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Heating Mass Flow Rate", + Constant::Units::kg_s, + this->report.heatingMassFlowRate, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Source Mass Flow Rate", + Constant::Units::kg_s, + this->report.sourceMassFlowRate, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Cooling Inlet Temperature", + Constant::Units::C, + this->report.coolingInletTemp, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Heating Inlet Temperature", + Constant::Units::C, + this->report.heatingInletTemp, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Source Inlet Temperature", + Constant::Units::C, + this->report.sourceInletTemp, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Cooling Outlet Temperature", + Constant::Units::C, + this->report.coolingOutletTemp, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Heating Outlet Temperature", + Constant::Units::C, + this->report.heatingOutletTemp, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + SetupOutputVariable(state, + "Chiller Heater System Source Outlet Temperature", + Constant::Units::C, + this->report.sourceOutletTemp, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + this->Name); + + if (!this->modules.empty()) { + for (std::size_t moduleIndex = 0; moduleIndex < this->modules.size(); ++moduleIndex) { + int const moduleNum = static_cast(moduleIndex) + 1; + auto &module = this->modules[moduleIndex]; + SetupOutputVariable(state, + std::format("Chiller Heater Operation Mode Unit {}", moduleNum), + Constant::Units::None, + module.result.currentMode, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Part Load Ratio Unit {}", moduleNum), + Constant::Units::None, + module.result.partLoadRatio, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Cycling Ratio Unit {}", moduleNum), + Constant::Units::None, + module.result.cyclingRatio, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Cooling Electricity Rate Unit {}", moduleNum), + Constant::Units::W, + module.result.coolingPower, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Heating Electricity Rate Unit {}", moduleNum), + Constant::Units::W, + module.result.heatingPower, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Cooling Electricity Energy Unit {}", moduleNum), + Constant::Units::J, + module.result.coolingEnergy, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Sum, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Heating Electricity Energy Unit {}", moduleNum), + Constant::Units::J, + module.result.heatingEnergy, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Sum, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Cooling Rate Unit {}", moduleNum), + Constant::Units::W, + module.result.qEvaporator, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Cooling Energy Unit {}", moduleNum), + Constant::Units::J, + module.result.evaporatorEnergy, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Sum, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater False Load Heat Transfer Rate Unit {}", moduleNum), + Constant::Units::W, + module.result.falseLoadRate, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater False Load Heat Transfer Energy Unit {}", moduleNum), + Constant::Units::J, + module.result.falseLoadEnergy, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Sum, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Evaporator Inlet Temperature Unit {}", moduleNum), + Constant::Units::C, + module.result.evaporatorInletTemp, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Evaporator Outlet Temperature Unit {}", moduleNum), + Constant::Units::C, + module.result.evaporatorOutletTemp, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Evaporator Mass Flow Rate Unit {}", moduleNum), + Constant::Units::kg_s, + module.result.evaporatorMassFlowRate, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Condenser Heat Transfer Rate Unit {}", moduleNum), + Constant::Units::W, + module.result.qCondenser, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Condenser Heat Transfer Energy Unit {}", moduleNum), + Constant::Units::J, + module.result.condenserEnergy, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Sum, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater COP Unit {}", moduleNum), + Constant::Units::W_W, + module.result.actualCOP, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Capacity Temperature Modifier Multiplier Unit {}", moduleNum), + Constant::Units::None, + module.result.capacityTemperatureModifier, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater EIR Temperature Modifier Multiplier Unit {}", moduleNum), + Constant::Units::None, + module.result.eirTemperatureModifier, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater EIR Part Load Modifier Multiplier Unit {}", moduleNum), + Constant::Units::None, + module.result.eirPartLoadModifier, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Condenser Inlet Temperature Unit {}", moduleNum), + Constant::Units::C, + module.result.condenserInletTemp, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Condenser Outlet Temperature Unit {}", moduleNum), + Constant::Units::C, + module.result.condenserOutletTemp, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + + SetupOutputVariable(state, + std::format("Chiller Heater Condenser Mass Flow Rate Unit {}", moduleNum), + Constant::Units::kg_s, + module.result.condenserMassFlowRate, + OutputProcessor::TimeStepType::System, + OutputProcessor::StoreType::Average, + module.name()); + } // End of individual chiller heater count for current system + + } // End of individual chiller heater output +} + +void getPerformanceInput(EnergyPlusData &state) +{ + // SUBROUTINE INFORMATION: + // AUTHOR: Kyung Tae Yun, Mississippi State University + // DATE WRITTEN: Feb 2013 + + // PURPOSE OF THIS SUBROUTINE: + // This routine will get the input required by the + // ChillerHeaterPerformance:Electric:EIR model. + + static constexpr std::string_view routineName = "getPerformanceInput"; + static constexpr char objectType[] = "ChillerHeaterPerformance:Electric:EIR"; + + static constexpr char referenceCoolingCapacityKey[] = "reference_cooling_mode_evaporator_capacity"; + static constexpr char referenceCoolingCOPKey[] = "reference_cooling_mode_cop"; + static constexpr char referenceCoolingLeavingCoolingTempKey[] = "reference_cooling_mode_leaving_chilled_water_temperature"; + static constexpr char referenceCoolingEnteringCondenserTempKey[] = "reference_cooling_mode_entering_condenser_fluid_temperature"; + static constexpr char referenceCoolingLeavingCondenserTempKey[] = "reference_cooling_mode_leaving_condenser_water_temperature"; + static constexpr char referenceHeatingCapacityRatioKey[] = "reference_heating_mode_cooling_capacity_ratio"; + static constexpr char referenceHeatingPowerRatioKey[] = "reference_heating_mode_cooling_power_input_ratio"; + static constexpr char referenceHeatingLeavingCoolingTempKey[] = "reference_heating_mode_leaving_chilled_water_temperature"; + static constexpr char referenceHeatingLeavingCondenserTempKey[] = "reference_heating_mode_leaving_condenser_water_temperature"; + static constexpr char referenceHeatingEnteringCondenserTempKey[] = "reference_heating_mode_entering_condenser_fluid_temperature"; + static constexpr char heatingEnteringCoolingLowLimitKey[] = "heating_mode_entering_chilled_water_temperature_low_limit"; + static constexpr char coolingFlowModeKey[] = "chilled_water_flow_mode_type"; + static constexpr char designCoolingFlowRateKey[] = "design_chilled_water_flow_rate"; + static constexpr char designCondenserWaterFlowRateKey[] = "design_condenser_water_flow_rate"; + static constexpr char designHeatingFlowRateKey[] = "design_hot_water_flow_rate"; + static constexpr char compressorMotorEfficiencyKey[] = "compressor_motor_efficiency"; + static constexpr char coolingCondenserVariableKey[] = "cooling_mode_temperature_curve_condenser_water_independent_variable"; + static constexpr char coolingCapacityCurveKey[] = "cooling_mode_cooling_capacity_function_of_temperature_curve_name"; + static constexpr char coolingEIRTemperatureCurveKey[] = "cooling_mode_electric_input_to_cooling_output_ratio_function_of_" + "temperature_curve_name"; + static constexpr char coolingEIRPartLoadCurveKey[] = "cooling_mode_electric_input_to_cooling_output_ratio_function_of_part_" + "load_ratio_curve_name"; + static constexpr char coolingOptimumPartLoadRatioKey[] = "cooling_mode_cooling_capacity_optimum_part_load_ratio"; + static constexpr char heatingCondenserVariableKey[] = "heating_mode_temperature_curve_condenser_water_independent_variable"; + static constexpr char heatingCapacityCurveKey[] = "heating_mode_cooling_capacity_function_of_temperature_curve_name"; + static constexpr char heatingEIRTemperatureCurveKey[] = "heating_mode_electric_input_to_cooling_output_ratio_function_of_" + "temperature_curve_name"; + static constexpr char heatingEIRPartLoadCurveKey[] = "heating_mode_electric_input_to_cooling_output_ratio_function_of_part_" + "load_ratio_curve_name"; + static constexpr char heatingOptimumPartLoadRatioKey[] = "heating_mode_cooling_capacity_optimum_part_load_ratio"; + static constexpr char sizingFactorKey[] = "sizing_factor"; + + static constexpr char referenceCoolingCapacityField[] = "Reference Cooling Mode Evaporator Capacity"; + static constexpr char referenceCoolingCOPField[] = "Reference Cooling Mode COP"; + static constexpr char referenceHeatingCapacityRatioField[] = "Reference Heating Mode Cooling Capacity Ratio"; + static constexpr char referenceHeatingPowerRatioField[] = "Reference Heating Mode Cooling Power Input Ratio"; + static constexpr char compressorMotorEfficiencyField[] = "Compressor Motor Efficiency"; + static constexpr char coolingCapacityCurveField[] = "Cooling Mode Cooling Capacity Function of Temperature Curve Name"; + static constexpr char coolingEIRTemperatureCurveField[] = "Cooling Mode Electric Input to Cooling Output Ratio Function of " + "Temperature Curve Name"; + static constexpr char coolingEIRPartLoadCurveField[] = "Cooling Mode Electric Input to Cooling Output Ratio Function of Part " + "Load Ratio Curve Name"; + static constexpr char coolingOptimumPartLoadRatioField[] = "Cooling Mode Cooling Capacity Optimum Part Load Ratio"; + static constexpr char heatingCapacityCurveField[] = "Heating Mode Cooling Capacity Function of Temperature Curve Name"; + static constexpr char heatingEIRTemperatureCurveField[] = "Heating Mode Electric Input to Cooling Output Ratio Function of " + "Temperature Curve Name"; + static constexpr char heatingEIRPartLoadCurveField[] = "Heating Mode Electric Input to Cooling Output Ratio Function of Part " + "Load Ratio Curve Name"; + static constexpr char heatingOptimumPartLoadRatioField[] = "Heating Mode Cooling Capacity Optimum Part Load Ratio"; + + bool errorsFound = false; // True when input errors are found + + auto &inputProcessor = state.dataInputProcessing->inputProcessor; + auto &performanceDefinitions = state.dataPlantCentralHeatPumpSystem->performanceDefinitions; + state.dataIPShortCut->cCurrentModuleObject = objectType; + auto const instances = inputProcessor->epJSON.find(objectType); + + performanceDefinitions.clear(); + if (instances == inputProcessor->epJSON.end() || instances->empty()) { + ShowSevereError(state, std::format("No {} equipment specified in input file", objectType)); + errorsFound = true; + } else { + performanceDefinitions.resize(instances->size()); + } + + if (instances != inputProcessor->epJSON.end()) { + auto const &objectSchemaProps = inputProcessor->getObjectSchemaProps(state, objectType); + auto const &instancesValue = instances.value(); + assert(instancesValue.size() == performanceDefinitions.size()); + + std::unordered_set performanceNames; + std::size_t performanceIndex = 0; + for (auto const &performanceObject : instancesValue.items()) { + auto const &key = performanceObject.key(); + auto const &objectFields = performanceObject.value(); + + inputProcessor->markObjectAsUsed(objectType, key); + ErrorObjectHeader const eoh{routineName, objectType, key}; + + auto &performanceDefinition = performanceDefinitions.at(performanceIndex++); + performanceDefinition.Name = Util::makeUPPER(key); + if (!performanceNames.emplace(performanceDefinition.Name).second) { + ShowSevereDuplicateName(state, eoh); + errorsFound = true; + continue; + } + + std::string const coolingCondenserVariable = + inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, coolingCondenserVariableKey); + performanceDefinition.coolingCondenserTemperatureMode = Util::SameString(coolingCondenserVariable, "LEAVINGCONDENSER") + ? CondenserTemperatureMode::LeavingCondenser + : CondenserTemperatureMode::EnteringCondenser; + + std::string const coolingCapacityCurveName = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, coolingCapacityCurveKey); + performanceDefinition.coolingCapacityTemperatureCurveIndex = Curve::GetCurveIndex(state, coolingCapacityCurveName); + if (performanceDefinition.coolingCapacityTemperatureCurveIndex == 0) { + ShowSevereError(state, std::format("Invalid {}={}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Entered in {}={}", coolingCapacityCurveField, coolingCapacityCurveName)); + errorsFound = true; + } + + std::string const coolingEIRTemperatureCurveName = + inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, coolingEIRTemperatureCurveKey); + performanceDefinition.coolingEIRTemperatureCurveIndex = Curve::GetCurveIndex(state, coolingEIRTemperatureCurveName); + if (performanceDefinition.coolingEIRTemperatureCurveIndex == 0) { + ShowSevereError(state, std::format("Invalid {}={}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Entered in {}={}", coolingEIRTemperatureCurveField, coolingEIRTemperatureCurveName)); + errorsFound = true; + } + + std::string const coolingEIRPartLoadCurveName = + inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, coolingEIRPartLoadCurveKey); + performanceDefinition.coolingEIRPartLoadCurveIndex = Curve::GetCurveIndex(state, coolingEIRPartLoadCurveName); + if (performanceDefinition.coolingEIRPartLoadCurveIndex == 0) { + ShowSevereError(state, std::format("Invalid {}={}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Entered in {}={}", coolingEIRPartLoadCurveField, coolingEIRPartLoadCurveName)); + errorsFound = true; + } + + std::string const heatingCondenserVariable = + inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, heatingCondenserVariableKey); + performanceDefinition.heatingCondenserTemperatureMode = Util::SameString(heatingCondenserVariable, "LEAVINGCONDENSER") + ? CondenserTemperatureMode::LeavingCondenser + : CondenserTemperatureMode::EnteringCondenser; + + std::string const heatingCapacityCurveName = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, heatingCapacityCurveKey); + performanceDefinition.heatingCapacityTemperatureCurveIndex = Curve::GetCurveIndex(state, heatingCapacityCurveName); + if (performanceDefinition.heatingCapacityTemperatureCurveIndex == 0) { + ShowSevereError(state, std::format("Invalid {}={}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Entered in {}={}", heatingCapacityCurveField, heatingCapacityCurveName)); + errorsFound = true; + } + + std::string const heatingEIRTemperatureCurveName = + inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, heatingEIRTemperatureCurveKey); + performanceDefinition.heatingEIRTemperatureCurveIndex = Curve::GetCurveIndex(state, heatingEIRTemperatureCurveName); + if (performanceDefinition.heatingEIRTemperatureCurveIndex == 0) { + ShowSevereError(state, std::format("Invalid {}={}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Entered in {}={}", heatingEIRTemperatureCurveField, heatingEIRTemperatureCurveName)); + errorsFound = true; + } + + std::string const heatingEIRPartLoadCurveName = + inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, heatingEIRPartLoadCurveKey); + performanceDefinition.heatingEIRPartLoadCurveIndex = Curve::GetCurveIndex(state, heatingEIRPartLoadCurveName); + if (performanceDefinition.heatingEIRPartLoadCurveIndex == 0) { + ShowSevereError(state, std::format("Invalid {}={}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Entered in {}={}", heatingEIRPartLoadCurveField, heatingEIRPartLoadCurveName)); + errorsFound = true; + } + + std::string const coolingFlowMode = inputProcessor->getAlphaFieldValue(objectFields, objectSchemaProps, coolingFlowModeKey); + performanceDefinition.constantFlow = coolingFlowMode == "CONSTANTFLOW"; + performanceDefinition.variableFlow = coolingFlowMode == "VARIABLEFLOW"; + + // Chiller rated performance data + performanceDefinition.referenceCoolingCapacity = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, referenceCoolingCapacityKey); + performanceDefinition.referenceCoolingCapacityWasAutoSized = performanceDefinition.referenceCoolingCapacity == DataSizing::AutoSize; + if (!performanceDefinition.referenceCoolingCapacityWasAutoSized && performanceDefinition.referenceCoolingCapacity <= 0.0) { + ShowSevereError(state, std::format("Invalid {}={}", objectType, performanceDefinition.Name)); + ShowContinueError(state, + std::format("Entered in {}={:.2f}", referenceCoolingCapacityField, performanceDefinition.referenceCoolingCapacity)); + errorsFound = true; + } + + performanceDefinition.referenceCoolingCOP = inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, referenceCoolingCOPKey); + if (performanceDefinition.referenceCoolingCOP <= 0.0) { + ShowSevereError(state, std::format("Invalid {}={}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Entered in {}={:.2f}", referenceCoolingCOPField, performanceDefinition.referenceCoolingCOP)); + errorsFound = true; + } + + performanceDefinition.coolingReferenceEvaporatorOutletTemp = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, referenceCoolingLeavingCoolingTempKey); + performanceDefinition.coolingReferenceCondenserInletTemp = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, referenceCoolingEnteringCondenserTempKey); + performanceDefinition.coolingReferenceCondenserOutletTemp = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, referenceCoolingLeavingCondenserTempKey); + + performanceDefinition.heatingToCoolingCapacityRatio = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, referenceHeatingCapacityRatioKey); + if (performanceDefinition.heatingToCoolingCapacityRatio <= 0.0) { + ShowSevereError(state, std::format("Invalid {}={}", objectType, performanceDefinition.Name)); + ShowContinueError( + state, + std::format("Entered in {}={:.2f}", referenceHeatingCapacityRatioField, performanceDefinition.heatingToCoolingCapacityRatio)); + errorsFound = true; + } + + performanceDefinition.heatingToCoolingPowerRatio = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, referenceHeatingPowerRatioKey); + if (performanceDefinition.heatingToCoolingPowerRatio <= 0.0) { + ShowSevereError(state, std::format("Invalid {}={}", objectType, performanceDefinition.Name)); + ShowContinueError( + state, std::format("Entered in {}={:.2f}", referenceHeatingPowerRatioField, performanceDefinition.heatingToCoolingPowerRatio)); + errorsFound = true; + } + + if (!performanceDefinition.referenceCoolingCapacityWasAutoSized && performanceDefinition.referenceCoolingCapacity > 0.0 && + performanceDefinition.referenceCoolingCOP > 0.0 && performanceDefinition.heatingToCoolingCapacityRatio > 0.0 && + performanceDefinition.heatingToCoolingPowerRatio > 0.0) { + performanceDefinition.referenceHeatingCapacity = + performanceDefinition.heatingToCoolingCapacityRatio * performanceDefinition.referenceCoolingCapacity; + performanceDefinition.referenceHeatingPower = + (performanceDefinition.referenceCoolingCapacity / performanceDefinition.referenceCoolingCOP) * + performanceDefinition.heatingToCoolingPowerRatio; + performanceDefinition.referenceHeatingCOP = + performanceDefinition.referenceHeatingCapacity / performanceDefinition.referenceHeatingPower; + } + + performanceDefinition.heatingReferenceEvaporatorOutletTemp = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, referenceHeatingLeavingCoolingTempKey); + performanceDefinition.heatingReferenceCondenserOutletTemp = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, referenceHeatingLeavingCondenserTempKey); + performanceDefinition.heatingReferenceCondenserInletTemp = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, referenceHeatingEnteringCondenserTempKey); + performanceDefinition.minimumEvaporatorOutletTemp = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, heatingEnteringCoolingLowLimitKey); + + performanceDefinition.designEvaporatorVolFlowRate = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, designCoolingFlowRateKey); + performanceDefinition.designEvaporatorVolFlowRateWasAutoSized = performanceDefinition.designEvaporatorVolFlowRate == DataSizing::AutoSize; + performanceDefinition.designCondenserVolFlowRate = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, designCondenserWaterFlowRateKey); + performanceDefinition.designCondenserVolFlowRateWasAutoSized = performanceDefinition.designCondenserVolFlowRate == DataSizing::AutoSize; + performanceDefinition.designHeatingVolFlowRate = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, designHeatingFlowRateKey); + performanceDefinition.compressorMotorEfficiency = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, compressorMotorEfficiencyKey); + performanceDefinition.coolingOptimumPartLoadRatio = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, coolingOptimumPartLoadRatioKey); + performanceDefinition.heatingOptimumPartLoadRatio = + inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, heatingOptimumPartLoadRatioKey); + performanceDefinition.sizingFactor = inputProcessor->getRealFieldValue(objectFields, objectSchemaProps, sizingFactorKey); + + if (performanceDefinition.sizingFactor <= 0.0) { + performanceDefinition.sizingFactor = 1.0; + } + + if (performanceDefinition.compressorMotorEfficiency < 0.0 || performanceDefinition.compressorMotorEfficiency > 1.0) { + ShowSevereError(state, std::format("getPerformanceInput: For {}: {}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("{} = {:.3f}", compressorMotorEfficiencyField, performanceDefinition.compressorMotorEfficiency)); + ShowContinueError(state, std::format("{} must be greater than or equal to zero", compressorMotorEfficiencyField)); + ShowContinueError(state, std::format("{} must be less than or equal to one", compressorMotorEfficiencyField)); + errorsFound = true; + } + + Real64 const coolingReferenceCondenserTemp = + performanceDefinition.coolingCondenserTemperatureMode == CondenserTemperatureMode::LeavingCondenser + ? performanceDefinition.coolingReferenceCondenserOutletTemp + : performanceDefinition.coolingReferenceCondenserInletTemp; + Real64 const heatingReferenceCondenserTemp = + performanceDefinition.heatingCondenserTemperatureMode == CondenserTemperatureMode::LeavingCondenser + ? performanceDefinition.heatingReferenceCondenserOutletTemp + : performanceDefinition.heatingReferenceCondenserInletTemp; + + // Check the CAP-FT, EIR-FT, and PLR curves and warn user if different + // from 1.0 by more than +-10% + if (performanceDefinition.coolingCapacityTemperatureCurveIndex > 0) { + Real64 curveValue = Curve::CurveValue(state, + performanceDefinition.coolingCapacityTemperatureCurveIndex, + performanceDefinition.coolingReferenceEvaporatorOutletTemp, + coolingReferenceCondenserTemp); + if (curveValue > 1.10 || curveValue < 0.90) { + ShowWarningError(state, + "Capacity ratio as a function of temperature " + "curve output is not equal to 1.0"); + ShowContinueError(state, std::format("(+ or - 10%) at reference conditions for {}= {}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", curveValue)); + } + } + + if (performanceDefinition.coolingEIRTemperatureCurveIndex > 0) { + Real64 curveValue = Curve::CurveValue(state, + performanceDefinition.coolingEIRTemperatureCurveIndex, + performanceDefinition.coolingReferenceEvaporatorOutletTemp, + coolingReferenceCondenserTemp); + if (curveValue > 1.10 || curveValue < 0.90) { + ShowWarningError(state, + "Energy input ratio as a function of temperature " + "curve output is not equal to 1.0"); + ShowContinueError(state, std::format("(+ or - 10%) at reference conditions for {}= {}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", curveValue)); + } + } + + if (performanceDefinition.coolingEIRPartLoadCurveIndex > 0) { + Real64 curveValue = + evaluatePartLoadCurve(state, performanceDefinition.coolingEIRPartLoadCurveIndex, coolingReferenceCondenserTemp, 1.0); + + if (curveValue > 1.10 || curveValue < 0.90) { + ShowWarningError(state, + "Energy input ratio as a function of part-load " + "ratio curve output is not equal to 1.0"); + ShowContinueError(state, std::format("(+ or - 10%) at reference conditions for {}= {}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", curveValue)); + } + } + + if (performanceDefinition.coolingEIRPartLoadCurveIndex > 0) { + bool foundNegativeValue = false; + std::array curveValues{}; // Used to evaluate PLFFPLR curve objects + for (int curvePointIndex = 0; curvePointIndex <= 10; ++curvePointIndex) { + Real64 curveValue = evaluatePartLoadCurve( + state, performanceDefinition.coolingEIRPartLoadCurveIndex, coolingReferenceCondenserTemp, (curvePointIndex / 10.0)); + if (curveValue < 0.0) { + foundNegativeValue = true; + } + curveValues[static_cast(curvePointIndex)] = int(curveValue * 100.0 / 100.0); + } + if (foundNegativeValue) { + ShowWarningError(state, + "Energy input ratio as a function of part-load " + "ratio curve shows negative values "); + ShowContinueError(state, std::format("for {}= {}", objectType, performanceDefinition.Name)); + ShowContinueError(state, + "EIR as a function of PLR curve output at " + "various part-load ratios shown below:"); + ShowContinueError(state, + "PLR = 0.00 0.10 0.20 0.30 0.40 " + " 0.50 0.60 0.70 0.80 0.90 1.00"); + + ShowContinueError(state, std::format("Curve Output = {:7.2f}", EnergyPlus::join(curveValues, ","))); + + errorsFound = true; + } + } + + if (performanceDefinition.heatingCapacityTemperatureCurveIndex > 0) { + Real64 curveValue = Curve::CurveValue(state, + performanceDefinition.heatingCapacityTemperatureCurveIndex, + performanceDefinition.heatingReferenceEvaporatorOutletTemp, + heatingReferenceCondenserTemp); + if (curveValue > 1.10 || curveValue < 0.90) { + ShowWarningError(state, + "Capacity ratio as a function of temperature " + "curve output is not equal to 1.0"); + ShowContinueError(state, std::format("(+ or - 10%) at reference conditions for {}= {}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", curveValue)); + } + } + + if (performanceDefinition.heatingEIRTemperatureCurveIndex > 0) { + Real64 curveValue = Curve::CurveValue(state, + performanceDefinition.heatingEIRTemperatureCurveIndex, + performanceDefinition.heatingReferenceEvaporatorOutletTemp, + heatingReferenceCondenserTemp); + if (curveValue > 1.10 || curveValue < 0.90) { + ShowWarningError(state, + "Energy input ratio as a function of temperature " + "curve output is not equal to 1.0"); + ShowContinueError(state, std::format("(+ or - 10%) at reference conditions for {}= {}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", curveValue)); + } + } + + if (performanceDefinition.heatingEIRPartLoadCurveIndex > 0) { + Real64 curveValue = + evaluatePartLoadCurve(state, performanceDefinition.heatingEIRPartLoadCurveIndex, heatingReferenceCondenserTemp, 1.0); + + if (curveValue > 1.10 || curveValue < 0.90) { + ShowWarningError(state, + "Energy input ratio as a function of part-load " + "ratio curve output is not equal to 1.0"); + ShowContinueError(state, std::format("(+ or - 10%) at reference conditions for {}= {}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Curve output at reference conditions = {:.3f}", curveValue)); + } + } + + if (performanceDefinition.heatingEIRPartLoadCurveIndex > 0) { + bool foundNegativeValue = false; + std::array curveValues{}; // Used to evaluate PLFFPLR curve objects + for (int curvePointIndex = 0; curvePointIndex <= 10; ++curvePointIndex) { + Real64 curveValue = evaluatePartLoadCurve( + state, performanceDefinition.heatingEIRPartLoadCurveIndex, heatingReferenceCondenserTemp, (curvePointIndex / 10.0)); + if (curveValue < 0.0) { + foundNegativeValue = true; + } + curveValues[static_cast(curvePointIndex)] = int(curveValue * 100.0) / 100.0; + } + if (foundNegativeValue) { + ShowWarningError(state, + "Energy input ratio as a function of part-load " + "ratio curve shows negative values "); + ShowContinueError(state, std::format("for {}= {}", objectType, performanceDefinition.Name)); + ShowContinueError(state, + "EIR as a function of PLR curve output at " + "various part-load ratios shown below:"); + ShowContinueError(state, + "PLR = 0.00 0.10 0.20 0.30 " + "0.40 0.50 0.60 0.70 0.80 0.90 1.00"); + + ShowContinueError(state, std::format("Curve Output = {:7.2f}", EnergyPlus::join(curveValues, ","))); + + errorsFound = true; + } + } + + auto validatePartLoadDomain = [&](int const curveIndex, + Real64 const optimumPartLoadRatio, + Real64 &minimumPartLoadRatio, + Real64 &maximumPartLoadRatio, + std::string_view const optimumFieldName, + std::string_view const curveFieldName, + std::string const &curveName, + std::string_view const mode) { + if (curveIndex <= 0) { + return; + } + getPartLoadCurveMinMax(state, curveIndex, minimumPartLoadRatio, maximumPartLoadRatio); + if (minimumPartLoadRatio < 0.0 || minimumPartLoadRatio > 1.0 || maximumPartLoadRatio < 1.0 || + maximumPartLoadRatio < minimumPartLoadRatio) { + ShowSevereError(state, std::format("Invalid {} part-load curve domain for {}={}", mode, objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Entered in {}={}", curveFieldName, curveName)); + ShowContinueError(state, + std::format("Part-load ratio limits [{:.3f}, {:.3f}] must " + "include 1.0 and have a minimum no less than zero.", + minimumPartLoadRatio, + maximumPartLoadRatio)); + errorsFound = true; + } + if (optimumPartLoadRatio < minimumPartLoadRatio || optimumPartLoadRatio > maximumPartLoadRatio) { + ShowSevereError(state, std::format("Invalid {}={}", objectType, performanceDefinition.Name)); + ShowContinueError(state, std::format("Entered in {}={:.3f}", optimumFieldName, optimumPartLoadRatio)); + ShowContinueError(state, + std::format("{} must be within the associated part-load " + "curve limits [{:.3f}, {:.3f}].", + optimumFieldName, + minimumPartLoadRatio, + maximumPartLoadRatio)); + errorsFound = true; + } + }; + + validatePartLoadDomain(performanceDefinition.coolingEIRPartLoadCurveIndex, + performanceDefinition.coolingOptimumPartLoadRatio, + performanceDefinition.coolingMinimumPartLoadRatio, + performanceDefinition.coolingMaximumPartLoadRatio, + coolingOptimumPartLoadRatioField, + coolingEIRPartLoadCurveField, + coolingEIRPartLoadCurveName, + "cooling"); + validatePartLoadDomain(performanceDefinition.heatingEIRPartLoadCurveIndex, + performanceDefinition.heatingOptimumPartLoadRatio, + performanceDefinition.heatingMinimumPartLoadRatio, + performanceDefinition.heatingMaximumPartLoadRatio, + heatingOptimumPartLoadRatioField, + heatingEIRPartLoadCurveField, + heatingEIRPartLoadCurveName, + "heating"); + } + assert(performanceIndex == performanceDefinitions.size()); + } + + if (errorsFound) { + ShowFatalError(state, std::format("Errors found in processing input for {}", objectType)); + } +} + +void CentralHeatPumpSystem::initializeDesignFlowLimits(EnergyPlusData &state) +{ + static constexpr std::string_view routineName("CentralHeatPumpSystem::initialize"); + + this->coolingVolFlowRate = 0.0; + this->heatingVolFlowRate = 0.0; + this->sourceVolFlowRate = 0.0; + + for (auto const &module : this->modules) { + auto const &performance = module.performanceData(); + auto const &sizing = module.sizing; + this->coolingVolFlowRate += sizing.designEvaporatorVolFlowRate; + this->heatingVolFlowRate += performance.designHeatingVolFlowRate; + this->sourceVolFlowRate += max(sizing.designEvaporatorVolFlowRate, sizing.designCondenserVolFlowRate); + } + + Real64 const coolingDensity = this->coolingPlantLoc.loop->glycol->getDensity(state, Constant::CWInitConvTemp, routineName); + Real64 const heatingDensity = this->heatingPlantLoc.loop->glycol->getDensity(state, Constant::HWInitConvTemp, routineName); + Real64 const sourceDensity = this->sourcePlantLoc.loop->glycol->getDensity(state, Constant::CWInitConvTemp, routineName); + + this->coolingMassFlowRateMax = this->coolingVolFlowRate * coolingDensity; + this->heatingMassFlowRateMax = this->heatingVolFlowRate * heatingDensity; + this->sourceMassFlowRateMax = this->sourceVolFlowRate * sourceDensity; + + PlantUtilities::InitComponentNodes(state, 0.0, this->coolingMassFlowRateMax, this->coolingInletNodeNum, this->coolingOutletNodeNum); + PlantUtilities::InitComponentNodes(state, 0.0, this->heatingMassFlowRateMax, this->heatingInletNodeNum, this->heatingOutletNodeNum); + PlantUtilities::InitComponentNodes(state, 0.0, this->sourceMassFlowRateMax, this->sourceInletNodeNum, this->sourceOutletNodeNum); + + for (auto &module : this->modules) { + auto const &performance = module.performanceData(); + auto &sizing = module.sizing; + sizing.maximumCoolingMassFlowRate = coolingDensity * sizing.designEvaporatorVolFlowRate; + sizing.maximumHeatingMassFlowRate = heatingDensity * performance.designHeatingVolFlowRate; + sizing.maximumSourceEvaporatorMassFlowRate = sourceDensity * sizing.designEvaporatorVolFlowRate; + sizing.maximumSourceCondenserMassFlowRate = sourceDensity * sizing.designCondenserVolFlowRate; + sizing.maximumEvaporatorMassFlowRate = max(sizing.maximumCoolingMassFlowRate, sizing.maximumSourceEvaporatorMassFlowRate); + sizing.maximumCondenserMassFlowRate = max(sizing.maximumHeatingMassFlowRate, sizing.maximumSourceCondenserMassFlowRate); + } +} + +void CentralHeatPumpSystem::resetOffState(EnergyPlusData &state, bool const releasePlantFlows) +{ + Real64 const coolingInletTemp = state.dataLoopNodes->Node(this->coolingInletNodeNum).Temp; + Real64 const heatingInletTemp = state.dataLoopNodes->Node(this->heatingInletNodeNum).Temp; + Real64 const sourceInletTemp = state.dataLoopNodes->Node(this->sourceInletNodeNum).Temp; + + this->requestedCoolingLoad = 0.0; + this->requestedHeatingLoad = 0.0; + this->isCoolingDominant = false; + this->isHeatingDominant = false; + + for (auto &module : this->modules) { + module.resetResult(coolingInletTemp, sourceInletTemp); + } + + this->report = SystemReportData(); + this->report.coolingInletTemp = coolingInletTemp; + this->report.coolingOutletTemp = coolingInletTemp; + this->report.heatingInletTemp = heatingInletTemp; + this->report.heatingOutletTemp = heatingInletTemp; + this->report.sourceInletTemp = sourceInletTemp; + this->report.sourceOutletTemp = sourceInletTemp; + + state.dataLoopNodes->Node(this->coolingOutletNodeNum).Temp = coolingInletTemp; + state.dataLoopNodes->Node(this->heatingOutletNodeNum).Temp = heatingInletTemp; + state.dataLoopNodes->Node(this->sourceOutletNodeNum).Temp = sourceInletTemp; + + auto releaseFlow = [&](PlantLocation const &plantLoc, int const inletNodeNum, int const outletNodeNum) { + state.dataLoopNodes->Node(inletNodeNum).MassFlowRateRequest = 0.0; + if (releasePlantFlows && plantLoc.comp != nullptr) { + Real64 requestedMassFlowRate = 0.0; + PlantUtilities::SetComponentFlowRate(state, requestedMassFlowRate, inletNodeNum, outletNodeNum, plantLoc); + } + }; + releaseFlow(this->coolingPlantLoc, this->coolingInletNodeNum, this->coolingOutletNodeNum); + releaseFlow(this->heatingPlantLoc, this->heatingInletNodeNum, this->heatingOutletNodeNum); + releaseFlow(this->sourcePlantLoc, this->sourceInletNodeNum, this->sourceOutletNodeNum); +} + +void CentralHeatPumpSystem::initialize(EnergyPlusData &state, + Real64 load, // Demand Load + int loopNum, // Loop Number Index + bool const runFlag) +{ + // SUBROUTINE INFORMATION: + // AUTHOR Daeho Kang, PNNL + // DATE WRITTEN Feb 2013 + // MODIFIED na + // RE-ENGINEERED na + + // PURPOSE OF THIS SUBROUTINE: + // This subroutine is for initializations of the CentralHeatPumpSystem + // variables + + // METHODOLOGY EMPLOYED: + // Uses the status flags to trigger initializations. + + if (this->setupOutputVarsFlag) { + this->setupOutputVars(state); + this->setupOutputVarsFlag = false; + } + + if (this->plantScanPending) { + // Locate the chillers on the plant loops for later usage + bool errFlag = false; + PlantUtilities::ScanPlantLoopsForObject(state, + this->Name, + DataPlant::PlantEquipmentType::CentralHeatPumpSystem, + this->coolingPlantLoc, + errFlag, + _, + _, + _, + this->coolingInletNodeNum, + _); + + PlantUtilities::ScanPlantLoopsForObject(state, + this->Name, + DataPlant::PlantEquipmentType::CentralHeatPumpSystem, + this->heatingPlantLoc, + errFlag, + _, + _, + _, + this->heatingInletNodeNum, + _); + + PlantUtilities::ScanPlantLoopsForObject(state, + this->Name, + DataPlant::PlantEquipmentType::CentralHeatPumpSystem, + this->sourcePlantLoc, + errFlag, + _, + _, + _, + this->sourceInletNodeNum, + _); + + if (errFlag) { + ShowFatalError(state, + std::format("CentralHeatPumpSystem::initialize: CentralHeatPumpSystem={} could not " + "be located on all three connected plant loops.", + this->Name)); + } + + PlantUtilities::InterConnectTwoPlantLoopSides( + state, this->coolingPlantLoc, this->sourcePlantLoc, DataPlant::PlantEquipmentType::CentralHeatPumpSystem, true); + + PlantUtilities::InterConnectTwoPlantLoopSides( + state, this->heatingPlantLoc, this->sourcePlantLoc, DataPlant::PlantEquipmentType::CentralHeatPumpSystem, true); + + PlantUtilities::InterConnectTwoPlantLoopSides( + state, this->coolingPlantLoc, this->heatingPlantLoc, DataPlant::PlantEquipmentType::CentralHeatPumpSystem, true); + + if (this->allModulesVariableFlow) { // why do this only for VS chiller heaters? + // constant flow also uses set points. + // Reset flow priority + if (loopNum == this->coolingPlantLoc.loopNum) { + DataPlant::CompData::getPlantComponent(state, this->coolingPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn; + } else if (loopNum == this->heatingPlantLoc.loopNum) { + DataPlant::CompData::getPlantComponent(state, this->heatingPlantLoc).FlowPriority = DataPlant::LoopFlowStatus::NeedyIfLoopOn; + } + } // moved up from below next 2 set point checks for #5808 + + // check if setpoint on outlet node - chilled water loop + if (state.dataLoopNodes->Node(this->coolingOutletNodeNum).TempSetPoint == Node::SensedNodeFlagValue) { + if (!state.dataGlobal->AnyEnergyManagementSystemInModel) { + if (!this->coolingSetpointErrorIssued) { + ShowWarningError(state, + std::format("Missing temperature setpoint on cooling side " + "for CentralHeatPumpSystem named {}", + this->Name)); + ShowContinueError(state, + " A temperature setpoint is needed at the outlet node of " + "a CentralHeatPumpSystem, use a SetpointManager"); + ShowContinueError(state, + " The overall loop setpoint will be assumed for " + "CentralHeatPumpSystem. The simulation continues ... "); + this->coolingSetpointErrorIssued = true; + } + } else { + // need call to EMS to check node + bool FatalError = false; // but not really fatal yet, but should be. + EMSManager::CheckIfNodeSetPointManagedByEMS(state, this->coolingOutletNodeNum, HVAC::CtrlVarType::Temp, FatalError); + state.dataLoopNodes->NodeSetpointCheck(this->coolingOutletNodeNum).needsSetpointChecking = false; + if (FatalError) { + if (!this->coolingSetpointErrorIssued) { + ShowWarningError(state, + std::format("Missing temperature setpoint on cooling " + "side for CentralHeatPumpSystem named {}", + this->Name)); + ShowContinueError(state, + "A temperature setpoint is needed at the " + "outlet node of a CentralHeatPumpSystem "); + ShowContinueError(state, + "use a Setpoint Manager to establish a setpoint " + "at the chiller side outlet node "); + ShowContinueError(state, + "or use an EMS actuator to establish a " + "setpoint at the outlet node "); + ShowContinueError(state, + "The overall loop setpoint will be assumed for " + "chiller side. The simulation continues ... "); + this->coolingSetpointErrorIssued = true; + } + } + } + this->coolingSetpointNodeNum = this->coolingPlantLoc.loop->TempSetPointNodeNum; + } + + if (state.dataLoopNodes->Node(this->heatingOutletNodeNum).TempSetPoint == Node::SensedNodeFlagValue) { + if (!state.dataGlobal->AnyEnergyManagementSystemInModel) { + if (!this->heatingSetpointErrorIssued) { + ShowWarningError(state, + std::format("Missing temperature setpoint on heating side " + "for CentralHeatPumpSystem named {}", + this->Name)); + ShowContinueError(state, + " A temperature setpoint is needed at the outlet node of " + "a CentralHeatPumpSystem, use a SetpointManager"); + ShowContinueError(state, + " The overall loop setpoint will be assumed for " + "CentralHeatPumpSystem. The simulation continues ... "); + this->heatingSetpointErrorIssued = true; + } + } else { + // need call to EMS to check node + bool FatalError = false; // but not really fatal yet, but should be. + EMSManager::CheckIfNodeSetPointManagedByEMS(state, this->heatingOutletNodeNum, HVAC::CtrlVarType::Temp, FatalError); + state.dataLoopNodes->NodeSetpointCheck(this->heatingOutletNodeNum).needsSetpointChecking = false; + if (FatalError) { + if (!this->heatingSetpointErrorIssued) { + ShowWarningError(state, + std::format("Missing temperature setpoint on heating " + "side for CentralHeatPumpSystem named {}", + this->Name)); + ShowContinueError(state, + "A temperature setpoint is needed at the " + "outlet node of a CentralHeatPumpSystem "); + ShowContinueError(state, + "use a Setpoint Manager to establish a setpoint " + "at the chiller side outlet node "); + ShowContinueError(state, + "or use an EMS actuator to establish a " + "setpoint at the outlet node "); + ShowContinueError(state, + "The overall loop setpoint will be assumed for " + "chiller side. The simulation continues ... "); + this->heatingSetpointErrorIssued = true; + } + } + } + this->heatingSetpointNodeNum = this->heatingPlantLoc.loop->TempSetPointNodeNum; + } + this->plantScanPending = false; + } + + if (this->environmentInitPending && state.dataGlobal->BeginEnvrnFlag && (state.dataPlnt->PlantFirstSizesOkayToFinalize)) { + this->initializeDesignFlowLimits(state); + this->resetOffState(state, false); + this->environmentInitPending = false; + } + + if (!state.dataGlobal->BeginEnvrnFlag) { + this->environmentInitPending = true; + } + + Real64 coolingMassFlowRate; // Chilled water mass flow rate + Real64 heatingMassFlowRate; // Hot water mass flow rate + Real64 sourceMassFlowRate; // Condenser water mass flow rate + + if (!runFlag) { + if (loopNum == this->coolingPlantLoc.loopNum) { + coolingMassFlowRate = 0.0; + PlantUtilities::SetComponentFlowRate( + state, coolingMassFlowRate, this->coolingInletNodeNum, this->coolingOutletNodeNum, this->coolingPlantLoc); + } else if (loopNum == this->heatingPlantLoc.loopNum) { + heatingMassFlowRate = 0.0; + PlantUtilities::SetComponentFlowRate( + state, heatingMassFlowRate, this->heatingInletNodeNum, this->heatingOutletNodeNum, this->heatingPlantLoc); + } else if (loopNum == this->sourcePlantLoc.loopNum) { + sourceMassFlowRate = 0.0; + PlantUtilities::SetComponentFlowRate( + state, sourceMassFlowRate, this->sourceInletNodeNum, this->sourceOutletNodeNum, this->sourcePlantLoc); + } + return; + } + + // Switch over the mass flow rate to the condenser loop, i.e., ground heat + // exchanger + if (loopNum == this->coolingPlantLoc.loopNum) { // called for on cooling loop + if (load < -1.0) { // calling for cooling + coolingMassFlowRate = state.dataLoopNodes->Node(this->coolingInletNodeNum).MassFlowRateMax; + } else { + coolingMassFlowRate = 0.0; + } + if (this->requestedHeatingLoad > 1.0) { + heatingMassFlowRate = state.dataLoopNodes->Node(this->heatingInletNodeNum).MassFlowRateMax; + } else { + heatingMassFlowRate = 0.0; + } + if ((load < -1.0) || (this->requestedHeatingLoad > 1.0)) { + sourceMassFlowRate = state.dataLoopNodes->Node(this->sourceInletNodeNum).MassFlowRateMax; + } else { + sourceMassFlowRate = 0.0; + } + + } else if (loopNum == this->heatingPlantLoc.loopNum) { + if (load > 1.0) { + heatingMassFlowRate = state.dataLoopNodes->Node(this->heatingInletNodeNum).MassFlowRateMax; + } else { + heatingMassFlowRate = 0.0; + } + if (this->requestedCoolingLoad > 1.0) { + coolingMassFlowRate = state.dataLoopNodes->Node(this->coolingInletNodeNum).MassFlowRateMax; + } else { + coolingMassFlowRate = 0.0; + } + if ((load > 1.0) || (this->requestedCoolingLoad > 1.0)) { + sourceMassFlowRate = state.dataLoopNodes->Node(this->sourceInletNodeNum).MassFlowRateMax; + } else { + sourceMassFlowRate = 0.0; + } + + } else if (loopNum == this->sourcePlantLoc.loopNum) { + if (this->requestedCoolingLoad > 1.0) { + coolingMassFlowRate = state.dataLoopNodes->Node(this->coolingInletNodeNum).MassFlowRateMax; + } else { + coolingMassFlowRate = 0.0; + } + if (this->requestedHeatingLoad > 1.0) { + heatingMassFlowRate = state.dataLoopNodes->Node(this->heatingInletNodeNum).MassFlowRateMax; + } else { + heatingMassFlowRate = 0.0; + } + if ((this->requestedHeatingLoad > 1.0) || (this->requestedCoolingLoad > 1.0)) { + sourceMassFlowRate = state.dataLoopNodes->Node(this->sourceInletNodeNum).MassFlowRateMax; + } else { + sourceMassFlowRate = 0.0; + } + } + + PlantUtilities::SetComponentFlowRate(state, coolingMassFlowRate, this->coolingInletNodeNum, this->coolingOutletNodeNum, this->coolingPlantLoc); + + PlantUtilities::SetComponentFlowRate(state, heatingMassFlowRate, this->heatingInletNodeNum, this->heatingOutletNodeNum, this->heatingPlantLoc); + + PlantUtilities::SetComponentFlowRate(state, sourceMassFlowRate, this->sourceInletNodeNum, this->sourceOutletNodeNum, this->sourcePlantLoc); +} + +ModuleResult CentralHeatPumpSystem::solveCoolingOnly(EnergyPlusData &state, + std::size_t const moduleIndex, + Real64 const coolingLoad, + Real64 const evaporatorMassFlowRateMax, + Real64 const condenserMassFlowRate, + Real64 const evaporatorInletTemp, + Real64 const condenserInletTemp) +{ + static constexpr std::string_view routineName("CentralHeatPumpSystem cooling-only solver"); + + auto &module = this->modules[moduleIndex]; + int const moduleNum = static_cast(moduleIndex) + 1; + auto const &performance = module.performanceData(); + ModePerformanceData const modePerformance = module.coolingModePerformance(); + ModuleResult result; + result.requestedCoolingLoad = max(0.0, coolingLoad); + result.evaporatorInletTemp = evaporatorInletTemp; + result.evaporatorOutletTemp = evaporatorInletTemp; + result.condenserInletTemp = condenserInletTemp; + result.condenserOutletTemp = condenserInletTemp; + result.unmetCoolingLoad = result.requestedCoolingLoad; + + if (result.requestedCoolingLoad <= HVAC::SmallLoad || evaporatorMassFlowRateMax <= DataBranchAirLoopPlant::MassFlowTolerance || + condenserMassFlowRate <= DataBranchAirLoopPlant::MassFlowTolerance || modePerformance.referenceEvaporatorCapacity <= 0.0 || + modePerformance.referenceCOP <= 0.0) { + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; + } + + Real64 const minPartLoadRatio = modePerformance.minimumPartLoadRatio; + Real64 const maxPartLoadRatio = modePerformance.maximumPartLoadRatio; + + Real64 const evaporatorCp = this->coolingPlantLoc.loop->glycol->getSpecificHeat(state, evaporatorInletTemp, routineName); + Real64 const condenserCp = this->sourcePlantLoc.loop->glycol->getSpecificHeat(state, condenserInletTemp, routineName); + Real64 evaporatorOutletTarget = state.dataLoopNodes->Node(this->coolingSetpointNodeNum).TempSetPoint; + if (evaporatorOutletTarget == Node::SensedNodeFlagValue) { + evaporatorOutletTarget = module.minimumEvaporatorOutletTemp; + } + evaporatorOutletTarget = max(evaporatorOutletTarget, module.minimumEvaporatorOutletTemp); + Real64 const evaporatorDeltaTempTarget = max(0.0, evaporatorInletTemp - evaporatorOutletTarget); + Real64 const flowLimitedCooling = evaporatorMassFlowRateMax * evaporatorCp * evaporatorDeltaTempTarget; + + if (flowLimitedCooling <= HVAC::SmallLoad) { + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; + } + + Real64 evaporatorOutletGuess = evaporatorOutletTarget; + Real64 condenserOutletGuess = condenserInletTemp; + Real64 availableEvaporatorCapacity = 0.0; + Real64 qEvaporator = 0.0; + Real64 qCondenser = 0.0; + Real64 compressorPower = 0.0; + Real64 falseLoadRate = 0.0; + Real64 partLoadRatio = 0.0; + Real64 cyclingRatio = 0.0; + Real64 evaporatorMassFlowRate = evaporatorMassFlowRateMax; + Real64 evaporatorOutletTemp = evaporatorInletTemp; + Real64 condenserOutletTemp = condenserInletTemp; + Real64 capacityModifier = 0.0; + Real64 eirTemperatureModifier = 0.0; + Real64 eirPartLoadModifier = 0.0; + + for (int iteration = 0; iteration < maxOuterSolverIterations; ++iteration) { + result.solver.outerIterations = iteration + 1; + Real64 const condenserCurveTemp = selectCondenserCurveTemperature(modePerformance, condenserInletTemp, condenserOutletGuess); + ++result.solver.curveEvaluations; + capacityModifier = evaluateCapacityTemperatureModifier(state, module, modePerformance, evaporatorOutletGuess, condenserCurveTemp); + availableEvaporatorCapacity = modePerformance.referenceEvaporatorCapacity * capacityModifier; + qEvaporator = std::min({result.requestedCoolingLoad, availableEvaporatorCapacity * maxPartLoadRatio, flowLimitedCooling}); + + if (qEvaporator <= HVAC::SmallLoad || availableEvaporatorCapacity <= 0.0) { + qEvaporator = 0.0; + break; + } + + if (this->allModulesVariableFlow) { + evaporatorMassFlowRate = min(evaporatorMassFlowRateMax, qEvaporator / (evaporatorCp * evaporatorDeltaTempTarget)); + } else { + evaporatorMassFlowRate = evaporatorMassFlowRateMax; + } + evaporatorOutletTemp = evaporatorInletTemp - qEvaporator / (evaporatorMassFlowRate * evaporatorCp); + + Real64 const requestedPartLoadRatio = qEvaporator / availableEvaporatorCapacity; + partLoadRatio = min(maxPartLoadRatio, max(requestedPartLoadRatio, minPartLoadRatio)); + cyclingRatio = minPartLoadRatio > 0.0 ? min(1.0, requestedPartLoadRatio / minPartLoadRatio) : 1.0; + falseLoadRate = max(0.0, availableEvaporatorCapacity * partLoadRatio * cyclingRatio - qEvaporator); + + ++result.solver.curveEvaluations; + eirTemperatureModifier = + max(0.0, Curve::CurveValue(state, modePerformance.eirTemperatureCurveIndex, evaporatorOutletTemp, condenserCurveTemp)); + ++result.solver.curveEvaluations; + eirPartLoadModifier = max(0.0, evaluatePartLoadCurve(state, modePerformance.eirPartLoadCurveIndex, condenserCurveTemp, partLoadRatio)); + compressorPower = (availableEvaporatorCapacity / modePerformance.referenceCOP) * eirTemperatureModifier * eirPartLoadModifier * cyclingRatio; + qCondenser = qEvaporator + falseLoadRate + compressorPower * performance.compressorMotorEfficiency; + condenserOutletTemp = condenserInletTemp + qCondenser / (condenserMassFlowRate * condenserCp); + + Real64 const residual = max(std::abs(evaporatorOutletTemp - evaporatorOutletGuess), std::abs(condenserOutletTemp - condenserOutletGuess)); + result.solver.temperatureResidual = residual; + if (!std::isfinite(residual)) { + result.solver.outerStatus = SolverConvergenceStatus::Invalid; + break; + } + if (residual <= temperatureConvergenceTolerance) { + result.solver.outerStatus = SolverConvergenceStatus::Converged; + break; + } + Real64 const nextEvaporatorOutletGuess = 0.5 * (evaporatorOutletGuess + evaporatorOutletTemp); + Real64 const nextCondenserOutletGuess = 0.5 * (condenserOutletGuess + condenserOutletTemp); + if (nextEvaporatorOutletGuess == evaporatorOutletGuess && nextCondenserOutletGuess == condenserOutletGuess) { + result.solver.outerStatus = SolverConvergenceStatus::Stagnated; + break; + } + evaporatorOutletGuess = nextEvaporatorOutletGuess; + condenserOutletGuess = nextCondenserOutletGuess; + } + + if (result.solver.outerStatus == SolverConvergenceStatus::NotRequired && result.solver.outerIterations == maxOuterSolverIterations) { + result.solver.outerStatus = SolverConvergenceStatus::IterationLimit; + } + if (!state.dataGlobal->WarmupFlag && solverFailed(result.solver.outerStatus)) { + reportSolverFailure(state, + this->Name, + module.name(), + moduleNum, + "cooling-only", + "temperature iteration", + result.solver.outerStatus, + std::format("cooling load={:.6g} W", result.requestedCoolingLoad), + result.solver.outerIterations, + result.solver.temperatureResidual, + 0.0, + condenserOutletTemp, + "C", + module.coolingSolverWarning); + } + + if (qEvaporator <= HVAC::SmallLoad) { + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; + } + + Real64 const condenserCurveTemp = selectCondenserCurveTemperature(modePerformance, condenserInletTemp, condenserOutletTemp); + ++result.solver.curveEvaluations; + capacityModifier = evaluateCapacityTemperatureModifier(state, module, modePerformance, evaporatorOutletTemp, condenserCurveTemp); + availableEvaporatorCapacity = modePerformance.referenceEvaporatorCapacity * capacityModifier; + ++result.solver.curveEvaluations; + eirTemperatureModifier = max(0.0, Curve::CurveValue(state, modePerformance.eirTemperatureCurveIndex, evaporatorOutletTemp, condenserCurveTemp)); + ++result.solver.curveEvaluations; + eirPartLoadModifier = max(0.0, evaluatePartLoadCurve(state, modePerformance.eirPartLoadCurveIndex, condenserCurveTemp, partLoadRatio)); + compressorPower = (availableEvaporatorCapacity / modePerformance.referenceCOP) * eirTemperatureModifier * eirPartLoadModifier * cyclingRatio; + falseLoadRate = max(0.0, availableEvaporatorCapacity * partLoadRatio * cyclingRatio - qEvaporator); + qCondenser = qEvaporator + falseLoadRate + compressorPower * performance.compressorMotorEfficiency; + condenserOutletTemp = condenserInletTemp + qCondenser / (condenserMassFlowRate * condenserCp); + + ++result.solver.curveEvaluations; + Real64 const availableEIRPartLoadModifier = + max(0.0, evaluatePartLoadCurve(state, modePerformance.eirPartLoadCurveIndex, condenserCurveTemp, maxPartLoadRatio)); + Real64 const availablePower = + (availableEvaporatorCapacity / modePerformance.referenceCOP) * eirTemperatureModifier * availableEIRPartLoadModifier; + + result.currentMode = CurrentMode::CoolingOnly; + result.availableEvaporatorCapacity = availableEvaporatorCapacity; + result.availableCondenserCapacity = availableEvaporatorCapacity * maxPartLoadRatio + availablePower * performance.compressorMotorEfficiency; + result.qEvaporator = qEvaporator; + result.qCondenser = qCondenser; + result.coolingPower = compressorPower; + result.falseLoadRate = falseLoadRate; + result.partLoadRatio = partLoadRatio; + result.cyclingRatio = cyclingRatio; + result.unloadingRatio = partLoadRatio; + result.capacityTemperatureModifier = capacityModifier; + result.eirTemperatureModifier = eirTemperatureModifier; + result.eirPartLoadModifier = eirPartLoadModifier; + result.capacityCurveEvaporatorTemp = evaporatorOutletTemp; + result.capacityCurveCondenserTemp = condenserCurveTemp; + result.eirCurveEvaporatorTemp = evaporatorOutletTemp; + result.eirCurveCondenserTemp = condenserCurveTemp; + result.eirPartLoadCurvePLR = partLoadRatio; + result.eirPartLoadCurveCondenserTemp = condenserCurveTemp; + result.actualCOP = compressorPower > 0.0 ? (qEvaporator + falseLoadRate) / compressorPower : 0.0; + result.evaporatorOutletTemp = evaporatorOutletTemp; + result.evaporatorMassFlowRate = evaporatorMassFlowRate; + result.condenserOutletTemp = condenserOutletTemp; + result.condenserMassFlowRate = condenserMassFlowRate; + result.unmetCoolingLoad = max(0.0, result.requestedCoolingLoad - qEvaporator); + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; +} + +ModuleResult CentralHeatPumpSystem::solveHeatingOnly(EnergyPlusData &state, + std::size_t const moduleIndex, + Real64 const heatingLoad, + Real64 const evaporatorMassFlowRate, + Real64 const condenserMassFlowRateMax, + Real64 const evaporatorInletTemp, + Real64 const condenserInletTemp) +{ + static constexpr std::string_view routineName("CentralHeatPumpSystem heating-only solver"); + + auto &module = this->modules[moduleIndex]; + int const moduleNum = static_cast(moduleIndex) + 1; + auto const &performance = module.performanceData(); + ModePerformanceData const modePerformance = module.heatingModePerformance(); + ModuleResult result; + result.requestedHeatingLoad = max(0.0, heatingLoad); + result.evaporatorInletTemp = evaporatorInletTemp; + result.evaporatorOutletTemp = evaporatorInletTemp; + result.condenserInletTemp = condenserInletTemp; + result.condenserOutletTemp = condenserInletTemp; + result.unmetHeatingLoad = result.requestedHeatingLoad; + + if (result.requestedHeatingLoad <= HVAC::SmallLoad || evaporatorMassFlowRate <= DataBranchAirLoopPlant::MassFlowTolerance || + condenserMassFlowRateMax <= DataBranchAirLoopPlant::MassFlowTolerance || modePerformance.referenceEvaporatorCapacity <= 0.0 || + modePerformance.referenceCOP <= 0.0) { + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; + } + + Real64 const minPartLoadRatio = modePerformance.minimumPartLoadRatio; + Real64 const maxPartLoadRatio = modePerformance.maximumPartLoadRatio; + + Real64 const evaporatorCp = this->sourcePlantLoc.loop->glycol->getSpecificHeat(state, evaporatorInletTemp, routineName); + Real64 const condenserCp = this->heatingPlantLoc.loop->glycol->getSpecificHeat(state, condenserInletTemp, routineName); + Real64 const evaporatorOutletLowLimit = max(performance.minimumEvaporatorOutletTemp, module.minimumEvaporatorOutletTemp); + Real64 const sourceLimitedEvaporatorHeat = max(0.0, evaporatorMassFlowRate * evaporatorCp * (evaporatorInletTemp - evaporatorOutletLowLimit)); + + bool hasCondenserOutletLimit = false; + Real64 condenserOutletLimit = 0.0; + Real64 const plantHeatingSetPoint = state.dataLoopNodes->Node(this->heatingSetpointNodeNum).TempSetPoint; + if (plantHeatingSetPoint != Node::SensedNodeFlagValue) { + condenserOutletLimit = plantHeatingSetPoint; + hasCondenserOutletLimit = true; + } + Real64 const heatingLimitedCondenserHeat = hasCondenserOutletLimit + ? max(0.0, condenserMassFlowRateMax * condenserCp * (condenserOutletLimit - condenserInletTemp)) + : std::numeric_limits::max(); + + if (sourceLimitedEvaporatorHeat <= HVAC::SmallLoad || heatingLimitedCondenserHeat <= HVAC::SmallLoad) { + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; + } + + Real64 evaporatorOutletGuess = + max(evaporatorOutletLowLimit, evaporatorInletTemp - sourceLimitedEvaporatorHeat / (evaporatorMassFlowRate * evaporatorCp)); + Real64 condenserOutletGuess = + hasCondenserOutletLimit + ? min(condenserOutletLimit, condenserInletTemp + result.requestedHeatingLoad / (condenserMassFlowRateMax * condenserCp)) + : condenserInletTemp; + Real64 availableEvaporatorCapacity = 0.0; + Real64 availableCondenserCapacity = 0.0; + Real64 qEvaporator = 0.0; + Real64 qCondenser = 0.0; + Real64 compressorPower = 0.0; + Real64 partLoadRatio = 0.0; + Real64 cyclingRatio = 0.0; + Real64 condenserMassFlowRate = condenserMassFlowRateMax; + Real64 evaporatorOutletTemp = evaporatorInletTemp; + Real64 condenserOutletTemp = condenserInletTemp; + Real64 capacityModifier = 0.0; + Real64 eirTemperatureModifier = 0.0; + Real64 eirPartLoadModifier = 0.0; + + for (int iteration = 0; iteration < maxOuterSolverIterations; ++iteration) { + result.solver.outerIterations = iteration + 1; + result.solver.partLoadStatus = SolverConvergenceStatus::NotRequired; + result.solver.partLoadBracketWidth = 0.0; + result.solver.loadResidual = 0.0; + Real64 const condenserCurveTemp = selectCondenserCurveTemperature(modePerformance, condenserInletTemp, condenserOutletGuess); + ++result.solver.curveEvaluations; + capacityModifier = evaluateCapacityTemperatureModifier(state, module, modePerformance, evaporatorOutletGuess, condenserCurveTemp); + availableEvaporatorCapacity = modePerformance.referenceEvaporatorCapacity * capacityModifier; + ++result.solver.curveEvaluations; + eirTemperatureModifier = + max(0.0, Curve::CurveValue(state, modePerformance.eirTemperatureCurveIndex, evaporatorOutletGuess, condenserCurveTemp)); + + if (availableEvaporatorCapacity <= 0.0) { + break; + } + + auto operatingPointAtPLR = [&](Real64 const plr) { + PartLoadOperatingPoint point; + point.partLoadRatio = plr; + ++result.solver.curveEvaluations; + point.eirPartLoadModifier = max(0.0, evaluatePartLoadCurve(state, modePerformance.eirPartLoadCurveIndex, condenserCurveTemp, plr)); + point.evaporatorLoad = availableEvaporatorCapacity * plr; + point.compressorPower = (availableEvaporatorCapacity / modePerformance.referenceCOP) * eirTemperatureModifier * point.eirPartLoadModifier; + point.condenserLoad = point.evaporatorLoad + point.compressorPower * performance.compressorMotorEfficiency; + return point; + }; + + Real64 maximumAllowedPLR = min(maxPartLoadRatio, sourceLimitedEvaporatorHeat / availableEvaporatorCapacity); + Real64 maximumCyclingRatio = 1.0; + if (maximumAllowedPLR < minPartLoadRatio) { + maximumCyclingRatio = minPartLoadRatio > 0.0 ? max(0.0, maximumAllowedPLR / minPartLoadRatio) : 0.0; + maximumAllowedPLR = minPartLoadRatio; + } + auto const maximumPoint = operatingPointAtPLR(maximumAllowedPLR); + availableCondenserCapacity = maximumPoint.condenserLoad * maximumCyclingRatio; + availableCondenserCapacity = min(availableCondenserCapacity, heatingLimitedCondenserHeat); + Real64 const targetCondenserHeat = min(result.requestedHeatingLoad, availableCondenserCapacity); + if (targetCondenserHeat <= HVAC::SmallLoad) { + break; + } + + auto const minimumPoint = operatingPointAtPLR(minPartLoadRatio); + if (targetCondenserHeat < minimumPoint.condenserLoad) { + partLoadRatio = minPartLoadRatio; + cyclingRatio = min(maximumCyclingRatio, targetCondenserHeat / minimumPoint.condenserLoad); + } else { + cyclingRatio = 1.0; + Real64 const loadScale = max({targetCondenserHeat, availableCondenserCapacity, 1.0}); + const auto [value, bracketWidth, loadResidual, iterations, status] = + solveBisection(minPartLoadRatio, maximumAllowedPLR, targetCondenserHeat, partLoadAbsoluteTolerance, loadScale, [&](Real64 const plr) { + return operatingPointAtPLR(plr).condenserLoad; + }); + result.solver.partLoadIterations += iterations; + result.solver.partLoadStatus = status; + result.solver.partLoadBracketWidth = bracketWidth; + result.solver.loadResidual = loadResidual; + partLoadRatio = value; + } + + auto const operatingPoint = operatingPointAtPLR(partLoadRatio); + qEvaporator = availableEvaporatorCapacity * partLoadRatio * cyclingRatio; + compressorPower = operatingPoint.compressorPower * cyclingRatio; + eirPartLoadModifier = operatingPoint.eirPartLoadModifier; + qCondenser = qEvaporator + compressorPower * performance.compressorMotorEfficiency; + + if (this->allModulesVariableFlow && hasCondenserOutletLimit && condenserOutletLimit > condenserInletTemp) { + condenserMassFlowRate = min(condenserMassFlowRateMax, qCondenser / (condenserCp * (condenserOutletLimit - condenserInletTemp))); + } else { + condenserMassFlowRate = condenserMassFlowRateMax; + } + evaporatorOutletTemp = evaporatorInletTemp - qEvaporator / (evaporatorMassFlowRate * evaporatorCp); + condenserOutletTemp = condenserInletTemp + qCondenser / (condenserMassFlowRate * condenserCp); + + Real64 const residual = max(std::abs(evaporatorOutletTemp - evaporatorOutletGuess), std::abs(condenserOutletTemp - condenserOutletGuess)); + result.solver.temperatureResidual = residual; + if (!std::isfinite(residual)) { + result.solver.outerStatus = SolverConvergenceStatus::Invalid; + break; + } + if (residual <= temperatureConvergenceTolerance) { + result.solver.outerStatus = SolverConvergenceStatus::Converged; + break; + } + Real64 const nextEvaporatorOutletGuess = 0.5 * (evaporatorOutletGuess + evaporatorOutletTemp); + Real64 const nextCondenserOutletGuess = 0.5 * (condenserOutletGuess + condenserOutletTemp); + if (nextEvaporatorOutletGuess == evaporatorOutletGuess && nextCondenserOutletGuess == condenserOutletGuess) { + result.solver.outerStatus = SolverConvergenceStatus::Stagnated; + break; + } + evaporatorOutletGuess = nextEvaporatorOutletGuess; + condenserOutletGuess = nextCondenserOutletGuess; + } + + if (result.solver.outerStatus == SolverConvergenceStatus::NotRequired && result.solver.outerIterations == maxOuterSolverIterations) { + result.solver.outerStatus = SolverConvergenceStatus::IterationLimit; + } + if (!state.dataGlobal->WarmupFlag && solverFailed(result.solver.partLoadStatus)) { + reportSolverFailure(state, + this->Name, + module.name(), + moduleNum, + "heating-only", + "part-load iteration", + result.solver.partLoadStatus, + std::format("heating load={:.6g} W", result.requestedHeatingLoad), + result.solver.partLoadIterations, + result.solver.partLoadBracketWidth, + result.solver.loadResidual, + partLoadRatio, + "PLR", + module.heatingPartLoadSolverWarning); + } + if (!state.dataGlobal->WarmupFlag && solverFailed(result.solver.outerStatus)) { + reportSolverFailure(state, + this->Name, + module.name(), + moduleNum, + "heating-only", + "temperature iteration", + result.solver.outerStatus, + std::format("heating load={:.6g} W", result.requestedHeatingLoad), + result.solver.outerIterations, + result.solver.temperatureResidual, + result.solver.loadResidual, + condenserOutletTemp, + "C", + module.heatingSolverWarning); + } + + if (qCondenser <= HVAC::SmallLoad) { + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; + } + + Real64 const condenserCurveTemp = selectCondenserCurveTemperature(modePerformance, condenserInletTemp, condenserOutletTemp); + ++result.solver.curveEvaluations; + capacityModifier = evaluateCapacityTemperatureModifier(state, module, modePerformance, evaporatorOutletTemp, condenserCurveTemp); + availableEvaporatorCapacity = modePerformance.referenceEvaporatorCapacity * capacityModifier; + ++result.solver.curveEvaluations; + eirTemperatureModifier = max(0.0, Curve::CurveValue(state, modePerformance.eirTemperatureCurveIndex, evaporatorOutletTemp, condenserCurveTemp)); + ++result.solver.curveEvaluations; + eirPartLoadModifier = max(0.0, evaluatePartLoadCurve(state, modePerformance.eirPartLoadCurveIndex, condenserCurveTemp, partLoadRatio)); + compressorPower = (availableEvaporatorCapacity / modePerformance.referenceCOP) * eirTemperatureModifier * eirPartLoadModifier * cyclingRatio; + qEvaporator = availableEvaporatorCapacity * partLoadRatio * cyclingRatio; + qCondenser = qEvaporator + compressorPower * performance.compressorMotorEfficiency; + evaporatorOutletTemp = evaporatorInletTemp - qEvaporator / (evaporatorMassFlowRate * evaporatorCp); + condenserOutletTemp = condenserInletTemp + qCondenser / (condenserMassFlowRate * condenserCp); + + result.currentMode = CurrentMode::HeatingOnly; + result.availableEvaporatorCapacity = availableEvaporatorCapacity; + result.availableCondenserCapacity = availableCondenserCapacity; + result.qEvaporator = qEvaporator; + result.qCondenser = qCondenser; + result.heatingPower = compressorPower; + result.partLoadRatio = partLoadRatio; + result.cyclingRatio = cyclingRatio; + result.unloadingRatio = partLoadRatio; + result.capacityTemperatureModifier = capacityModifier; + result.eirTemperatureModifier = eirTemperatureModifier; + result.eirPartLoadModifier = eirPartLoadModifier; + result.capacityCurveEvaporatorTemp = evaporatorOutletTemp; + result.capacityCurveCondenserTemp = condenserCurveTemp; + result.eirCurveEvaporatorTemp = evaporatorOutletTemp; + result.eirCurveCondenserTemp = condenserCurveTemp; + result.eirPartLoadCurvePLR = partLoadRatio; + result.eirPartLoadCurveCondenserTemp = condenserCurveTemp; + result.actualCOP = compressorPower > 0.0 ? qCondenser / compressorPower : 0.0; + result.evaporatorOutletTemp = evaporatorOutletTemp; + result.evaporatorMassFlowRate = evaporatorMassFlowRate; + result.condenserOutletTemp = condenserOutletTemp; + result.condenserMassFlowRate = condenserMassFlowRate; + result.unmetHeatingLoad = max(0.0, result.requestedHeatingLoad - qCondenser); + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; +} + +ModuleResult CentralHeatPumpSystem::solveSimultaneous(EnergyPlusData &state, + std::size_t const moduleIndex, + Real64 const coolingLoad, + Real64 const heatingLoad, + Real64 const maximumCoolingMassFlowRate, + Real64 const maximumHeatingMassFlowRate, + Real64 const maximumSourceMassFlowRate, + Real64 const coolingInletTemp, + Real64 const heatingInletTemp, + Real64 const sourceInletTemp) +{ + static constexpr std::string_view routineName("CentralHeatPumpSystem simultaneous solver"); + + auto &module = this->modules[moduleIndex]; + int const moduleNum = static_cast(moduleIndex) + 1; + auto const &performance = module.performanceData(); + auto const &sizing = module.sizing; + ModePerformanceData const modePerformance = module.heatingModePerformance(); + ModuleResult result; + result.requestedCoolingLoad = max(0.0, coolingLoad); + result.requestedHeatingLoad = max(0.0, heatingLoad); + result.unmetCoolingLoad = result.requestedCoolingLoad; + result.unmetHeatingLoad = result.requestedHeatingLoad; + result.coolingInletTemp = coolingInletTemp; + result.coolingOutletTemp = coolingInletTemp; + result.heatingInletTemp = heatingInletTemp; + result.heatingOutletTemp = heatingInletTemp; + result.sourceInletTemp = sourceInletTemp; + result.sourceOutletTemp = sourceInletTemp; + + if (result.requestedCoolingLoad <= HVAC::SmallLoad || result.requestedHeatingLoad <= HVAC::SmallLoad || + maximumCoolingMassFlowRate <= DataBranchAirLoopPlant::MassFlowTolerance || + maximumHeatingMassFlowRate <= DataBranchAirLoopPlant::MassFlowTolerance || modePerformance.referenceEvaporatorCapacity <= 0.0 || + modePerformance.referenceCOP <= 0.0) { + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; + } + + Real64 const minPartLoadRatio = modePerformance.minimumPartLoadRatio; + Real64 const maxPartLoadRatio = modePerformance.maximumPartLoadRatio; + + Real64 const coolingCp = this->coolingPlantLoc.loop->glycol->getSpecificHeat(state, coolingInletTemp, routineName); + Real64 const heatingCp = this->heatingPlantLoc.loop->glycol->getSpecificHeat(state, heatingInletTemp, routineName); + Real64 const sourceCp = this->sourcePlantLoc.loop->glycol->getSpecificHeat(state, sourceInletTemp, routineName); + + Real64 coolingOutletTarget = state.dataLoopNodes->Node(this->coolingSetpointNodeNum).TempSetPoint; + if (coolingOutletTarget == Node::SensedNodeFlagValue) { + coolingOutletTarget = module.minimumEvaporatorOutletTemp; + } + coolingOutletTarget = max(coolingOutletTarget, module.minimumEvaporatorOutletTemp); + Real64 const coolingDeltaTempTarget = max(0.0, coolingInletTemp - coolingOutletTarget); + Real64 const coolingTarget = min(result.requestedCoolingLoad, maximumCoolingMassFlowRate * coolingCp * coolingDeltaTempTarget); + + bool hasHeatingOutletLimit = false; + Real64 heatingOutletLimit = 0.0; + Real64 const plantHeatingSetPoint = state.dataLoopNodes->Node(this->heatingSetpointNodeNum).TempSetPoint; + if (plantHeatingSetPoint != Node::SensedNodeFlagValue) { + heatingOutletLimit = plantHeatingSetPoint; + hasHeatingOutletLimit = true; + } + Real64 const heatingDeltaTempTarget = hasHeatingOutletLimit ? max(0.0, heatingOutletLimit - heatingInletTemp) : 0.0; + Real64 const heatingLimitedHeating = + hasHeatingOutletLimit ? maximumHeatingMassFlowRate * heatingCp * heatingDeltaTempTarget : std::numeric_limits::max(); + Real64 const heatingTarget = min(result.requestedHeatingLoad, heatingLimitedHeating); + + auto sourceFlowLimit = [maximumSourceMassFlowRate](Real64 const nodeFlowLimit, Real64 const designFlowLimit) { + Real64 flowLimit = nodeFlowLimit; + if (flowLimit <= DataBranchAirLoopPlant::MassFlowTolerance) { + flowLimit = designFlowLimit; + } + if (flowLimit <= DataBranchAirLoopPlant::MassFlowTolerance) { + flowLimit = maximumSourceMassFlowRate; + } + return min(maximumSourceMassFlowRate, max(0.0, flowLimit)); + }; + Real64 const maximumSourceEvaporatorMassFlowRate = + sourceFlowLimit(sizing.maximumSourceEvaporatorMassFlowRate, sizing.maximumEvaporatorMassFlowRate); + Real64 const maximumSourceCondenserMassFlowRate = sourceFlowLimit(sizing.maximumSourceCondenserMassFlowRate, sizing.maximumCondenserMassFlowRate); + Real64 const sourceOutletLowLimit = max(performance.minimumEvaporatorOutletTemp, module.minimumEvaporatorOutletTemp); + Real64 const sourceExtractionCapacity = maximumSourceEvaporatorMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance + ? max(0.0, maximumSourceEvaporatorMassFlowRate * sourceCp * (sourceInletTemp - sourceOutletLowLimit)) + : 0.0; + bool const canRejectToSource = maximumSourceCondenserMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance; + + if (coolingTarget <= HVAC::SmallLoad || heatingTarget <= HVAC::SmallLoad) { + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; + } + + Real64 evaporatorCurveTempGuess = coolingOutletTarget; + Real64 condenserEnteringTempGuess = heatingInletTemp; + Real64 condenserLeavingTempGuess = hasHeatingOutletLimit + ? min(heatingOutletLimit, heatingInletTemp + heatingTarget / (maximumHeatingMassFlowRate * heatingCp)) + : heatingInletTemp; + + Real64 availableEvaporatorCapacity = 0.0; + Real64 availableCondenserCapacity = 0.0; + Real64 qEvaporator = 0.0; + Real64 qCondenser = 0.0; + Real64 compressorPower = 0.0; + Real64 falseLoadRate = 0.0; + Real64 partLoadRatio = 0.0; + Real64 cyclingRatio = 0.0; + Real64 capacityModifier = 0.0; + Real64 eirTemperatureModifier = 0.0; + Real64 eirPartLoadModifier = 0.0; + + Real64 coolingDelivered = 0.0; + Real64 heatingDelivered = 0.0; + Real64 sourceExtraction = 0.0; + Real64 sourceRejection = 0.0; + Real64 coolingMassFlowRate = 0.0; + Real64 heatingMassFlowRate = 0.0; + Real64 sourceMassFlowRate = 0.0; + Real64 coolingOutletTemp = coolingInletTemp; + Real64 heatingOutletTemp = heatingInletTemp; + Real64 sourceOutletTemp = sourceInletTemp; + Real64 evaporatorInletTemp = coolingInletTemp; + Real64 evaporatorOutletTemp = coolingInletTemp; + Real64 evaporatorMassFlowRate = 0.0; + Real64 condenserInletTemp = heatingInletTemp; + Real64 condenserOutletTemp = heatingInletTemp; + Real64 condenserMassFlowRate = 0.0; + + for (int iteration = 0; iteration < maxOuterSolverIterations; ++iteration) { + result.solver.outerIterations = iteration + 1; + result.solver.partLoadStatus = SolverConvergenceStatus::NotRequired; + result.solver.partLoadBracketWidth = 0.0; + result.solver.loadResidual = 0.0; + Real64 const condenserCurveTemp = selectCondenserCurveTemperature(modePerformance, condenserEnteringTempGuess, condenserLeavingTempGuess); + ++result.solver.curveEvaluations; + capacityModifier = evaluateCapacityTemperatureModifier(state, module, modePerformance, evaporatorCurveTempGuess, condenserCurveTemp); + availableEvaporatorCapacity = modePerformance.referenceEvaporatorCapacity * capacityModifier; + ++result.solver.curveEvaluations; + eirTemperatureModifier = + max(0.0, Curve::CurveValue(state, modePerformance.eirTemperatureCurveIndex, evaporatorCurveTempGuess, condenserCurveTemp)); + if (availableEvaporatorCapacity <= 0.0) { + break; + } + + auto operatingPointAtEvaporatorLoad = [&](Real64 const evaporatorLoad) { + PartLoadOperatingPoint point; + if (evaporatorLoad <= HVAC::SmallLoad) { + return point; + } + Real64 const requestedPartLoadRatio = evaporatorLoad / availableEvaporatorCapacity; + point.partLoadRatio = min(maxPartLoadRatio, max(requestedPartLoadRatio, minPartLoadRatio)); + point.cyclingRatio = + requestedPartLoadRatio < minPartLoadRatio && minPartLoadRatio > 0.0 ? requestedPartLoadRatio / minPartLoadRatio : 1.0; + ++result.solver.curveEvaluations; + point.eirPartLoadModifier = + max(0.0, evaluatePartLoadCurve(state, modePerformance.eirPartLoadCurveIndex, condenserCurveTemp, point.partLoadRatio)); + point.evaporatorLoad = evaporatorLoad; + point.compressorPower = (availableEvaporatorCapacity / modePerformance.referenceCOP) * eirTemperatureModifier * + point.eirPartLoadModifier * point.cyclingRatio; + point.falseLoadRate = max(0.0, availableEvaporatorCapacity * point.partLoadRatio * point.cyclingRatio - point.evaporatorLoad); + point.condenserLoad = point.evaporatorLoad + point.falseLoadRate + point.compressorPower * performance.compressorMotorEfficiency; + return point; + }; + + Real64 const maximumEvaporatorLoad = min(availableEvaporatorCapacity * maxPartLoadRatio, coolingTarget + sourceExtractionCapacity); + auto coolingPoint = operatingPointAtEvaporatorLoad(min(coolingTarget, maximumEvaporatorLoad)); + auto maximumPoint = operatingPointAtEvaporatorLoad(maximumEvaporatorLoad); + availableCondenserCapacity = maximumPoint.condenserLoad; + auto selectedPoint = coolingPoint; + + bool const heatingDrivesCycle = coolingPoint.condenserLoad + HVAC::SmallLoad < heatingTarget; + bool const excessHeatCannotBeRejected = !canRejectToSource && coolingPoint.condenserLoad > heatingTarget + HVAC::SmallLoad; + if (heatingDrivesCycle || excessHeatCannotBeRejected) { + Real64 const lowerEvaporatorLoad = heatingDrivesCycle ? coolingPoint.evaporatorLoad : 0.0; + Real64 const upperEvaporatorLoad = heatingDrivesCycle ? maximumEvaporatorLoad : coolingPoint.evaporatorLoad; + Real64 const boundedHeatingTarget = min(heatingTarget, operatingPointAtEvaporatorLoad(upperEvaporatorLoad).condenserLoad); + Real64 const evaporatorCapacityScale = max(availableEvaporatorCapacity, 1.0); + Real64 const loadScale = max({boundedHeatingTarget, availableCondenserCapacity, 1.0}); + auto const bisection = + solveBisection(lowerEvaporatorLoad, + upperEvaporatorLoad, + boundedHeatingTarget, + partLoadAbsoluteTolerance * evaporatorCapacityScale, + loadScale, + [&](Real64 const evaporatorLoad) { return operatingPointAtEvaporatorLoad(evaporatorLoad).condenserLoad; }); + result.solver.partLoadIterations += bisection.iterations; + result.solver.partLoadStatus = bisection.status; + result.solver.partLoadBracketWidth = bisection.bracketWidth / evaporatorCapacityScale; + result.solver.loadResidual = bisection.loadResidual; + selectedPoint = operatingPointAtEvaporatorLoad(bisection.value); + } + + qEvaporator = selectedPoint.evaporatorLoad; + qCondenser = selectedPoint.condenserLoad; + compressorPower = selectedPoint.compressorPower; + falseLoadRate = selectedPoint.falseLoadRate; + partLoadRatio = selectedPoint.partLoadRatio; + cyclingRatio = selectedPoint.cyclingRatio; + eirPartLoadModifier = selectedPoint.eirPartLoadModifier; + + coolingDelivered = min(coolingTarget, qEvaporator); + sourceExtraction = max(0.0, qEvaporator - coolingDelivered); + heatingDelivered = min(heatingTarget, qCondenser); + sourceRejection = max(0.0, qCondenser - heatingDelivered); + + if (this->allModulesVariableFlow) { + coolingMassFlowRate = coolingDelivered > HVAC::SmallLoad && coolingDeltaTempTarget > 0.0 + ? min(maximumCoolingMassFlowRate, coolingDelivered / (coolingCp * coolingDeltaTempTarget)) + : 0.0; + heatingMassFlowRate = heatingDelivered > HVAC::SmallLoad && heatingDeltaTempTarget > 0.0 + ? min(maximumHeatingMassFlowRate, heatingDelivered / (heatingCp * heatingDeltaTempTarget)) + : maximumHeatingMassFlowRate; + } else { + coolingMassFlowRate = coolingDelivered > HVAC::SmallLoad ? maximumCoolingMassFlowRate : 0.0; + heatingMassFlowRate = heatingDelivered > HVAC::SmallLoad ? maximumHeatingMassFlowRate : 0.0; + } + if (sourceExtraction > HVAC::SmallLoad) { + sourceMassFlowRate = maximumSourceEvaporatorMassFlowRate; + } else if (sourceRejection > HVAC::SmallLoad) { + sourceMassFlowRate = maximumSourceCondenserMassFlowRate; + } else { + sourceMassFlowRate = 0.0; + } + + coolingOutletTemp = coolingMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance + ? coolingInletTemp - coolingDelivered / (coolingMassFlowRate * coolingCp) + : coolingInletTemp; + heatingOutletTemp = heatingMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance + ? heatingInletTemp + heatingDelivered / (heatingMassFlowRate * heatingCp) + : heatingInletTemp; + sourceOutletTemp = sourceMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance + ? sourceInletTemp + (sourceRejection - sourceExtraction) / (sourceMassFlowRate * sourceCp) + : sourceInletTemp; + + Real64 const evaporatorSourceMassFlowRate = sourceExtraction > HVAC::SmallLoad ? sourceMassFlowRate : 0.0; + evaporatorMassFlowRate = coolingMassFlowRate + evaporatorSourceMassFlowRate; + if (evaporatorMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) { + evaporatorInletTemp = (coolingMassFlowRate * coolingInletTemp + evaporatorSourceMassFlowRate * sourceInletTemp) / evaporatorMassFlowRate; + evaporatorOutletTemp = + (coolingMassFlowRate * coolingOutletTemp + evaporatorSourceMassFlowRate * sourceOutletTemp) / evaporatorMassFlowRate; + } + + Real64 const condenserSourceMassFlowRate = sourceRejection > HVAC::SmallLoad ? sourceMassFlowRate : 0.0; + condenserMassFlowRate = heatingMassFlowRate + condenserSourceMassFlowRate; + if (condenserMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) { + condenserInletTemp = (heatingMassFlowRate * heatingInletTemp + condenserSourceMassFlowRate * sourceInletTemp) / condenserMassFlowRate; + condenserOutletTemp = (heatingMassFlowRate * heatingOutletTemp + condenserSourceMassFlowRate * sourceOutletTemp) / condenserMassFlowRate; + } + + Real64 const temperatureResidual = max({std::abs(evaporatorOutletTemp - evaporatorCurveTempGuess), + std::abs(condenserInletTemp - condenserEnteringTempGuess), + std::abs(condenserOutletTemp - condenserLeavingTempGuess)}); + result.solver.temperatureResidual = temperatureResidual; + if (!std::isfinite(temperatureResidual)) { + result.solver.outerStatus = SolverConvergenceStatus::Invalid; + break; + } + if (temperatureResidual <= temperatureConvergenceTolerance) { + result.solver.outerStatus = SolverConvergenceStatus::Converged; + break; + } + Real64 const nextEvaporatorCurveTempGuess = 0.5 * (evaporatorCurveTempGuess + evaporatorOutletTemp); + Real64 const nextCondenserEnteringTempGuess = 0.5 * (condenserEnteringTempGuess + condenserInletTemp); + Real64 const nextCondenserLeavingTempGuess = 0.5 * (condenserLeavingTempGuess + condenserOutletTemp); + if (nextEvaporatorCurveTempGuess == evaporatorCurveTempGuess && nextCondenserEnteringTempGuess == condenserEnteringTempGuess && + nextCondenserLeavingTempGuess == condenserLeavingTempGuess) { + result.solver.outerStatus = SolverConvergenceStatus::Stagnated; + break; + } + evaporatorCurveTempGuess = nextEvaporatorCurveTempGuess; + condenserEnteringTempGuess = nextCondenserEnteringTempGuess; + condenserLeavingTempGuess = nextCondenserLeavingTempGuess; + } + + if (result.solver.outerStatus == SolverConvergenceStatus::NotRequired && result.solver.outerIterations == maxOuterSolverIterations) { + result.solver.outerStatus = SolverConvergenceStatus::IterationLimit; + } + if (!state.dataGlobal->WarmupFlag && solverFailed(result.solver.partLoadStatus)) { + reportSolverFailure(state, + this->Name, + module.name(), + moduleNum, + "simultaneous", + "part-load iteration", + result.solver.partLoadStatus, + std::format("cooling/heating loads={:.6g}/{:.6g} W", result.requestedCoolingLoad, result.requestedHeatingLoad), + result.solver.partLoadIterations, + result.solver.partLoadBracketWidth, + result.solver.loadResidual, + partLoadRatio, + "PLR", + module.simultaneousPartLoadSolverWarning); + } + if (!state.dataGlobal->WarmupFlag && solverFailed(result.solver.outerStatus)) { + reportSolverFailure(state, + this->Name, + module.name(), + moduleNum, + "simultaneous", + "temperature iteration", + result.solver.outerStatus, + std::format("cooling/heating loads={:.6g}/{:.6g} W", result.requestedCoolingLoad, result.requestedHeatingLoad), + result.solver.outerIterations, + result.solver.temperatureResidual, + result.solver.loadResidual, + condenserOutletTemp, + "C", + module.simultaneousSolverWarning); + } + + if (qEvaporator <= HVAC::SmallLoad || qCondenser <= HVAC::SmallLoad) { + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; + } + + Real64 const sourceHeatTransfer = sourceRejection - sourceExtraction; + Real64 const modeTolerance = max(HVAC::SmallLoad, 1.0e-8 * max(qEvaporator, qCondenser)); + if (sourceHeatTransfer > modeTolerance) { + result.currentMode = CurrentMode::CoolingDominant; + result.coolingPower = compressorPower; + } else if (sourceHeatTransfer < -modeTolerance) { + result.currentMode = CurrentMode::HeatingDominant; + result.heatingPower = compressorPower; + } else { + result.currentMode = CurrentMode::HeatRecovery; + result.coolingPower = compressorPower; + } + + result.availableEvaporatorCapacity = availableEvaporatorCapacity; + result.availableCondenserCapacity = availableCondenserCapacity; + result.qEvaporator = qEvaporator; + result.qCondenser = qCondenser; + result.falseLoadRate = falseLoadRate; + result.partLoadRatio = partLoadRatio; + result.cyclingRatio = cyclingRatio; + result.unloadingRatio = partLoadRatio; + result.capacityTemperatureModifier = capacityModifier; + result.eirTemperatureModifier = eirTemperatureModifier; + result.eirPartLoadModifier = eirPartLoadModifier; + result.capacityCurveEvaporatorTemp = evaporatorOutletTemp; + result.capacityCurveCondenserTemp = selectCondenserCurveTemperature(modePerformance, condenserInletTemp, condenserOutletTemp); + result.eirCurveEvaporatorTemp = evaporatorOutletTemp; + result.eirCurveCondenserTemp = result.capacityCurveCondenserTemp; + result.eirPartLoadCurvePLR = partLoadRatio; + result.eirPartLoadCurveCondenserTemp = result.capacityCurveCondenserTemp; + result.actualCOP = compressorPower > 0.0 ? qCondenser / compressorPower : 0.0; + result.evaporatorInletTemp = evaporatorInletTemp; + result.evaporatorOutletTemp = evaporatorOutletTemp; + result.evaporatorMassFlowRate = evaporatorMassFlowRate; + result.condenserInletTemp = condenserInletTemp; + result.condenserOutletTemp = condenserOutletTemp; + result.condenserMassFlowRate = condenserMassFlowRate; + result.coolingOutletTemp = coolingOutletTemp; + result.coolingMassFlowRate = coolingMassFlowRate; + result.heatingOutletTemp = heatingOutletTemp; + result.heatingMassFlowRate = heatingMassFlowRate; + result.sourceOutletTemp = sourceOutletTemp; + result.sourceMassFlowRate = sourceMassFlowRate; + result.coolingDelivered = coolingDelivered; + result.heatingDelivered = heatingDelivered; + result.heatRecovered = heatingDelivered; + result.sourceHeatTransfer = sourceHeatTransfer; + result.unmetCoolingLoad = max(0.0, result.requestedCoolingLoad - coolingDelivered); + result.unmetHeatingLoad = max(0.0, result.requestedHeatingLoad - heatingDelivered); + result.updatePowerAccounting(performance.compressorMotorEfficiency); + return result; +} + +Real64 CentralHeatPumpSystem::selectCondenserCurveTemperature(ModePerformanceData const &modePerformance, + Real64 const condenserEnteringTemp, + Real64 const condenserLeavingTemp) +{ + return modePerformance.condenserMode == CondenserTemperatureMode::EnteringCondenser ? condenserEnteringTemp : condenserLeavingTemp; +} + +Real64 CentralHeatPumpSystem::evaluateCapacityTemperatureModifier( + EnergyPlusData &state, Module &module, ModePerformanceData const &modePerformance, Real64 const evaporatorOutletTemp, Real64 const condenserTemp) +{ + Real64 capacityTemperatureModifier = Curve::CurveValue(state, modePerformance.capacityTemperatureCurveIndex, evaporatorOutletTemp, condenserTemp); + + if (capacityTemperatureModifier < 0.0) { + if (module.capacityCurveErrorCount < 1 && !state.dataGlobal->WarmupFlag) { + ++module.capacityCurveErrorCount; + ShowWarningError(state, std::format("ChillerHeaterPerformance:Electric:EIR \"{}\":", module.name())); + ShowContinueError(state, + std::format(" modules Capacity as a Function of " + "Temperature curve output is negative ({:.3f}).", + capacityTemperatureModifier)); + ShowContinueError(state, + std::format(" Negative value occurs using an Evaporator Outlet Temp " + "of {:.1f} and a Condenser Inlet Temp of {:.1f}.", + evaporatorOutletTemp, + condenserTemp)); + ShowContinueErrorTimeStamp(state, " Resetting curve output to zero and continuing simulation."); + } else if (!state.dataGlobal->WarmupFlag) { + ++module.capacityCurveErrorCount; + ShowRecurringWarningErrorAtEnd(state, + "ChillerHeaterPerformance:Electric:EIR \"" + module.name() + + "\": modules Capacity as a Function of Temperature curve " + "output is negative warning continues...", + module.capacityCurveErrorIndex, + capacityTemperatureModifier, + capacityTemperatureModifier); + } + capacityTemperatureModifier = 0.0; + } + return capacityTemperatureModifier; +} + +void CentralHeatPumpSystem::calculateCoolingOnly(EnergyPlusData &state, + Real64 const coolingMassFlowRate, + Real64 const sourceMassFlowRate, + Real64 const coolingInletTemp, + Real64 const sourceInletTemp) +{ + Real64 remainingCoolingLoad = this->requestedCoolingLoad; + Real64 remainingCoolingMassFlowRate = max(0.0, coolingMassFlowRate); + Real64 remainingSourceMassFlowRate = max(0.0, sourceMassFlowRate); + + auto allocateConnectionFlow = [](Real64 const remainingFlow, Real64 const connectionDesignFlow, Real64 const legacyDesignFlow) { + Real64 designFlow = connectionDesignFlow; + if (designFlow <= DataBranchAirLoopPlant::MassFlowTolerance) { + designFlow = legacyDesignFlow; + } + if (designFlow <= DataBranchAirLoopPlant::MassFlowTolerance) { + designFlow = remainingFlow; + } + return min(max(0.0, remainingFlow), max(0.0, designFlow)); + }; + + for (std::size_t moduleIndex = 0; moduleIndex < this->modules.size(); ++moduleIndex) { + auto &module = this->modules[moduleIndex]; + auto const &sizing = module.sizing; + bool const moduleIsAvailable = module.isAvailable(); + + Real64 const moduleCoolingMassFlowRate = + allocateConnectionFlow(remainingCoolingMassFlowRate, sizing.maximumCoolingMassFlowRate, sizing.maximumEvaporatorMassFlowRate); + Real64 const moduleSourceMassFlowRate = + allocateConnectionFlow(remainingSourceMassFlowRate, sizing.maximumSourceCondenserMassFlowRate, sizing.maximumCondenserMassFlowRate); + ModuleResult result; + if (moduleIsAvailable && remainingCoolingLoad > HVAC::SmallLoad && moduleCoolingMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance && + moduleSourceMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) { + result = this->solveCoolingOnly( + state, moduleIndex, remainingCoolingLoad, moduleCoolingMassFlowRate, moduleSourceMassFlowRate, coolingInletTemp, sourceInletTemp); + module.result = result; + module.mapResultToPlantConnections(); + result = module.result; + } else { + result.requestedCoolingLoad = remainingCoolingLoad; + result.unmetCoolingLoad = remainingCoolingLoad; + result.evaporatorInletTemp = coolingInletTemp; + result.evaporatorOutletTemp = coolingInletTemp; + result.condenserInletTemp = sourceInletTemp; + result.condenserOutletTemp = sourceInletTemp; + result.coolingInletTemp = coolingInletTemp; + result.coolingOutletTemp = coolingInletTemp; + result.sourceInletTemp = sourceInletTemp; + result.sourceOutletTemp = sourceInletTemp; + } + + result.isAvailable = moduleIsAvailable; + remainingCoolingLoad = max(0.0, remainingCoolingLoad - result.coolingDelivered); + result.unmetCoolingLoad = remainingCoolingLoad; + remainingCoolingMassFlowRate = max(0.0, remainingCoolingMassFlowRate - result.coolingMassFlowRate); + remainingSourceMassFlowRate = max(0.0, remainingSourceMassFlowRate - result.sourceMassFlowRate); + module.result = result; + } + + this->isCoolingDominant = false; + this->isHeatingDominant = false; + this->updateReportingAndNodes(state, + coolingMassFlowRate, + 0.0, + sourceMassFlowRate, + coolingInletTemp, + state.dataLoopNodes->Node(this->heatingInletNodeNum).Temp, + sourceInletTemp); +} + +void CentralHeatPumpSystem::calculateHeatingOnly(EnergyPlusData &state, + Real64 const heatingMassFlowRate, + Real64 const sourceMassFlowRate, + Real64 const heatingInletTemp, + Real64 const sourceInletTemp) +{ + Real64 remainingHeatingLoad = this->requestedHeatingLoad; + Real64 remainingHeatingMassFlowRate = max(0.0, heatingMassFlowRate); + Real64 remainingSourceMassFlowRate = max(0.0, sourceMassFlowRate); + + auto allocateConnectionFlow = [](Real64 const remainingFlow, Real64 const connectionDesignFlow, Real64 const legacyDesignFlow) { + Real64 designFlow = connectionDesignFlow; + if (designFlow <= DataBranchAirLoopPlant::MassFlowTolerance) { + designFlow = legacyDesignFlow; + } + if (designFlow <= DataBranchAirLoopPlant::MassFlowTolerance) { + designFlow = remainingFlow; + } + return min(max(0.0, remainingFlow), max(0.0, designFlow)); + }; + + for (std::size_t moduleIndex = 0; moduleIndex < this->modules.size(); ++moduleIndex) { + auto &module = this->modules[moduleIndex]; + auto const &sizing = module.sizing; + bool const moduleIsAvailable = module.isAvailable(); + + Real64 const moduleHeatingMassFlowRate = + allocateConnectionFlow(remainingHeatingMassFlowRate, sizing.maximumHeatingMassFlowRate, sizing.maximumCondenserMassFlowRate); + Real64 const moduleSourceMassFlowRate = + allocateConnectionFlow(remainingSourceMassFlowRate, sizing.maximumSourceEvaporatorMassFlowRate, sizing.maximumEvaporatorMassFlowRate); + ModuleResult result; + if (moduleIsAvailable && remainingHeatingLoad > HVAC::SmallLoad && moduleHeatingMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance && + moduleSourceMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) { + result = this->solveHeatingOnly( + state, moduleIndex, remainingHeatingLoad, moduleSourceMassFlowRate, moduleHeatingMassFlowRate, sourceInletTemp, heatingInletTemp); + module.result = result; + module.mapResultToPlantConnections(); + result = module.result; + } else { + result.requestedHeatingLoad = remainingHeatingLoad; + result.unmetHeatingLoad = remainingHeatingLoad; + result.evaporatorInletTemp = sourceInletTemp; + result.evaporatorOutletTemp = sourceInletTemp; + result.condenserInletTemp = heatingInletTemp; + result.condenserOutletTemp = heatingInletTemp; + result.heatingInletTemp = heatingInletTemp; + result.heatingOutletTemp = heatingInletTemp; + result.sourceInletTemp = sourceInletTemp; + result.sourceOutletTemp = sourceInletTemp; + } + + result.isAvailable = moduleIsAvailable; + remainingHeatingLoad = max(0.0, remainingHeatingLoad - result.heatingDelivered); + result.unmetHeatingLoad = remainingHeatingLoad; + remainingHeatingMassFlowRate = max(0.0, remainingHeatingMassFlowRate - result.heatingMassFlowRate); + remainingSourceMassFlowRate = max(0.0, remainingSourceMassFlowRate - result.sourceMassFlowRate); + module.result = result; + } + + this->isCoolingDominant = false; + this->isHeatingDominant = false; + this->updateReportingAndNodes(state, + 0.0, + heatingMassFlowRate, + sourceMassFlowRate, + state.dataLoopNodes->Node(this->coolingInletNodeNum).Temp, + heatingInletTemp, + sourceInletTemp); +} + +void CentralHeatPumpSystem::updateReportingAndNodes(EnergyPlusData const &state, + Real64 const coolingMassFlowRate, + Real64 const heatingMassFlowRate, + Real64 const sourceMassFlowRate, + Real64 const coolingInletTemp, + Real64 const heatingInletTemp, + Real64 const sourceInletTemp) +{ + Real64 const secondsInTimeStep = state.dataHVACGlobal->TimeStepSysSec; + Real64 totalCoolingHeatTransferRate = 0.0; + Real64 totalHeatingHeatTransferRate = 0.0; + Real64 totalSourceHeatTransfer = 0.0; + Real64 totalCoolingPower = 0.0; + Real64 totalHeatingPower = 0.0; + Real64 usedCoolingMassFlowRate = 0.0; + Real64 usedHeatingMassFlowRate = 0.0; + Real64 usedSourceMassFlowRate = 0.0; + Real64 coolingOutletTemperatureSum = 0.0; + Real64 heatingOutletTemperatureSum = 0.0; + Real64 sourceOutletTemperatureSum = 0.0; + + for (auto &module : this->modules) { + module.updateResultEnergies(secondsInTimeStep); + auto const &result = module.result; + totalCoolingHeatTransferRate += result.coolingDelivered; + totalHeatingHeatTransferRate += result.heatingDelivered; + totalSourceHeatTransfer += result.sourceHeatTransfer; + totalCoolingPower += result.coolingPower; + totalHeatingPower += result.heatingPower; + usedCoolingMassFlowRate += result.coolingMassFlowRate; + usedHeatingMassFlowRate += result.heatingMassFlowRate; + usedSourceMassFlowRate += result.sourceMassFlowRate; + coolingOutletTemperatureSum += result.coolingOutletTemp * result.coolingMassFlowRate; + heatingOutletTemperatureSum += result.heatingOutletTemp * result.heatingMassFlowRate; + sourceOutletTemperatureSum += result.sourceOutletTemp * result.sourceMassFlowRate; + } + + auto mixConnection = + [](Real64 const totalMassFlowRate, Real64 const usedMassFlowRate, Real64 const outletTemperatureSum, Real64 const inletTemp) { + if (totalMassFlowRate <= DataBranchAirLoopPlant::MassFlowTolerance || usedMassFlowRate <= DataBranchAirLoopPlant::MassFlowTolerance) { + return inletTemp; + } + if (usedMassFlowRate > totalMassFlowRate) { + return outletTemperatureSum / usedMassFlowRate; + } + Real64 const bypassMassFlowRate = totalMassFlowRate - usedMassFlowRate; + return (outletTemperatureSum + bypassMassFlowRate * inletTemp) / totalMassFlowRate; + }; + + Real64 const coolingOutletTemp = mixConnection(coolingMassFlowRate, usedCoolingMassFlowRate, coolingOutletTemperatureSum, coolingInletTemp); + Real64 const heatingOutletTemp = mixConnection(heatingMassFlowRate, usedHeatingMassFlowRate, heatingOutletTemperatureSum, heatingInletTemp); + Real64 const sourceOutletTemp = mixConnection(sourceMassFlowRate, usedSourceMassFlowRate, sourceOutletTemperatureSum, sourceInletTemp); + + if (this->ancillaryPower > 0.0) { + Real64 const scheduleValue = this->ancillaryPowerSched != nullptr ? this->ancillaryPowerSched->getCurrentVal() : 1.0; + Real64 const availableAncillaryPower = this->ancillaryPower * scheduleValue; + if ((totalHeatingHeatTransferRate > HVAC::SmallLoad && totalCoolingHeatTransferRate <= HVAC::SmallLoad) || this->isHeatingDominant) { + totalHeatingPower += availableAncillaryPower; + } else { + totalCoolingPower += availableAncillaryPower; + } + } + + this->report.coolingInletTemp = coolingInletTemp; + this->report.coolingOutletTemp = coolingOutletTemp; + this->report.heatingInletTemp = heatingInletTemp; + this->report.heatingOutletTemp = heatingOutletTemp; + this->report.sourceInletTemp = sourceInletTemp; + this->report.sourceOutletTemp = sourceOutletTemp; + this->report.coolingMassFlowRate = coolingMassFlowRate; + this->report.heatingMassFlowRate = heatingMassFlowRate; + this->report.sourceMassFlowRate = sourceMassFlowRate; + this->report.coolingElectricPower = totalCoolingPower; + this->report.heatingElectricPower = totalHeatingPower; + this->report.coolingHeatTransferRate = totalCoolingHeatTransferRate; + this->report.heatingHeatTransferRate = totalHeatingHeatTransferRate; + this->report.sourceHeatTransferRate = totalSourceHeatTransfer; + this->report.coolingElectricEnergy = totalCoolingPower * secondsInTimeStep; + this->report.heatingElectricEnergy = totalHeatingPower * secondsInTimeStep; + this->report.coolingHeatTransferEnergy = totalCoolingHeatTransferRate * secondsInTimeStep; + this->report.heatingHeatTransferEnergy = totalHeatingHeatTransferRate * secondsInTimeStep; + this->report.sourceHeatTransferEnergy = totalSourceHeatTransfer * secondsInTimeStep; + + state.dataLoopNodes->Node(this->coolingOutletNodeNum).Temp = coolingOutletTemp; + state.dataLoopNodes->Node(this->heatingOutletNodeNum).Temp = heatingOutletTemp; + state.dataLoopNodes->Node(this->sourceOutletNodeNum).Temp = sourceOutletTemp; +} + +void CentralHeatPumpSystem::calculateSimultaneous(EnergyPlusData &state, + Real64 const coolingMassFlowRate, + Real64 const heatingMassFlowRate, + Real64 const sourceMassFlowRate, + Real64 const coolingInletTemp, + Real64 const heatingInletTemp, + Real64 const sourceInletTemp) +{ + Real64 remainingCoolingLoad = this->requestedCoolingLoad; + Real64 remainingHeatingLoad = this->requestedHeatingLoad; + Real64 remainingCoolingMassFlowRate = max(0.0, coolingMassFlowRate); + Real64 remainingHeatingMassFlowRate = max(0.0, heatingMassFlowRate); + Real64 remainingSourceMassFlowRate = max(0.0, sourceMassFlowRate); + + for (std::size_t moduleIndex = 0; moduleIndex < this->modules.size(); ++moduleIndex) { + auto &module = this->modules[moduleIndex]; + auto const &sizing = module.sizing; + bool const moduleIsAvailable = module.isAvailable(); + + auto moduleFlowLimit = [](Real64 const remainingFlow, Real64 const connectionDesignFlow, Real64 const legacyDesignFlow) { + Real64 flowLimit = connectionDesignFlow; + if (flowLimit <= DataBranchAirLoopPlant::MassFlowTolerance) { + flowLimit = legacyDesignFlow; + } + if (flowLimit <= DataBranchAirLoopPlant::MassFlowTolerance) { + flowLimit = remainingFlow; + } + return min(remainingFlow, max(0.0, flowLimit)); + }; + + Real64 const moduleCoolingMassFlowRate = + moduleFlowLimit(remainingCoolingMassFlowRate, sizing.maximumCoolingMassFlowRate, sizing.maximumEvaporatorMassFlowRate); + Real64 const moduleHeatingMassFlowRate = + moduleFlowLimit(remainingHeatingMassFlowRate, sizing.maximumHeatingMassFlowRate, sizing.maximumCondenserMassFlowRate); + Real64 moduleSourceMassFlowRate = 0.0; + ModuleResult result; + + if (moduleIsAvailable && remainingCoolingLoad > HVAC::SmallLoad && remainingHeatingLoad > HVAC::SmallLoad && + moduleCoolingMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance && + moduleHeatingMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) { + moduleSourceMassFlowRate = moduleFlowLimit(remainingSourceMassFlowRate, + max(sizing.maximumSourceEvaporatorMassFlowRate, sizing.maximumSourceCondenserMassFlowRate), + max(sizing.maximumEvaporatorMassFlowRate, sizing.maximumCondenserMassFlowRate)); + result = this->solveSimultaneous(state, + moduleIndex, + remainingCoolingLoad, + remainingHeatingLoad, + moduleCoolingMassFlowRate, + moduleHeatingMassFlowRate, + moduleSourceMassFlowRate, + coolingInletTemp, + heatingInletTemp, + sourceInletTemp); + } else if (moduleIsAvailable && remainingCoolingLoad > HVAC::SmallLoad && + moduleCoolingMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) { + moduleSourceMassFlowRate = + moduleFlowLimit(remainingSourceMassFlowRate, sizing.maximumSourceCondenserMassFlowRate, sizing.maximumCondenserMassFlowRate); + result = this->solveCoolingOnly( + state, moduleIndex, remainingCoolingLoad, moduleCoolingMassFlowRate, moduleSourceMassFlowRate, coolingInletTemp, sourceInletTemp); + module.result = result; + module.mapResultToPlantConnections(); + result = module.result; + if (result.currentMode == CurrentMode::CoolingOnly) { + result.currentMode = CurrentMode::CoolingDominant; + } + result.requestedHeatingLoad = remainingHeatingLoad; + result.unmetHeatingLoad = remainingHeatingLoad; + } else if (moduleIsAvailable && remainingHeatingLoad > HVAC::SmallLoad && + moduleHeatingMassFlowRate > DataBranchAirLoopPlant::MassFlowTolerance) { + moduleSourceMassFlowRate = + moduleFlowLimit(remainingSourceMassFlowRate, sizing.maximumSourceEvaporatorMassFlowRate, sizing.maximumEvaporatorMassFlowRate); + result = this->solveHeatingOnly( + state, moduleIndex, remainingHeatingLoad, moduleSourceMassFlowRate, moduleHeatingMassFlowRate, sourceInletTemp, heatingInletTemp); + module.result = result; + module.mapResultToPlantConnections(); + result = module.result; + if (result.currentMode == CurrentMode::HeatingOnly) { + result.currentMode = CurrentMode::HeatingDominant; + } + result.requestedCoolingLoad = remainingCoolingLoad; + result.unmetCoolingLoad = remainingCoolingLoad; + } else { + result.requestedCoolingLoad = remainingCoolingLoad; + result.requestedHeatingLoad = remainingHeatingLoad; + result.unmetCoolingLoad = remainingCoolingLoad; + result.unmetHeatingLoad = remainingHeatingLoad; + result.coolingInletTemp = coolingInletTemp; + result.coolingOutletTemp = coolingInletTemp; + result.heatingInletTemp = heatingInletTemp; + result.heatingOutletTemp = heatingInletTemp; + result.sourceInletTemp = sourceInletTemp; + result.sourceOutletTemp = sourceInletTemp; + } + + result.isAvailable = moduleIsAvailable; + remainingCoolingLoad = max(0.0, remainingCoolingLoad - result.coolingDelivered); + remainingHeatingLoad = max(0.0, remainingHeatingLoad - result.heatingDelivered); + result.unmetCoolingLoad = remainingCoolingLoad; + result.unmetHeatingLoad = remainingHeatingLoad; + remainingCoolingMassFlowRate = max(0.0, remainingCoolingMassFlowRate - result.coolingMassFlowRate); + remainingHeatingMassFlowRate = max(0.0, remainingHeatingMassFlowRate - result.heatingMassFlowRate); + remainingSourceMassFlowRate = max(0.0, remainingSourceMassFlowRate - result.sourceMassFlowRate); + + module.result = result; + } + + Real64 totalCoolingHeatTransferRate = 0.0; + Real64 totalHeatingHeatTransferRate = 0.0; + Real64 totalSourceHeatTransfer = 0.0; + for (auto const &module : this->modules) { + totalCoolingHeatTransferRate += module.result.coolingDelivered; + totalHeatingHeatTransferRate += module.result.heatingDelivered; + totalSourceHeatTransfer += module.result.sourceHeatTransfer; + } + Real64 const sourceModeTolerance = + max(HVAC::SmallLoad, 1.0e-8 * max({totalCoolingHeatTransferRate, totalHeatingHeatTransferRate, std::abs(totalSourceHeatTransfer)})); + this->isCoolingDominant = totalSourceHeatTransfer > sourceModeTolerance; + this->isHeatingDominant = totalSourceHeatTransfer < -sourceModeTolerance; + this->updateReportingAndNodes( + state, coolingMassFlowRate, heatingMassFlowRate, sourceMassFlowRate, coolingInletTemp, heatingInletTemp, sourceInletTemp); +} + +void CentralHeatPumpSystem::calculate(EnergyPlusData &state, Real64 &load, int const loopNum) +{ + // SUBROUTINE INFORMATION: + // AUTHOR Daeho Kang, PNNL + // DATE WRITTEN Feb 2013 + // MODIFIED na + // RE-ENGINEERED na + + // PURPOSE OF THIS SUBROUTINE: + // Calculate node information connected to plant & condenser loop + + // METHODOLOGY EMPLOYED: + // Use empirical curve fits to model performance at off-reference conditions + + Real64 currentHeatingLoad = 0.0; // Total heating load chiller heater bank (system) meets + + // Chiller heater bank chilled water inlet mass flow rate + Real64 coolingInletMassFlowRate = 0.0; + + Real64 heatingInletMassFlowRate = 0.0; + Real64 sourceInletMassFlowRate = 0.0; + Real64 coolingInletTemp = state.dataLoopNodes->Node(this->coolingInletNodeNum).Temp; + + // Chiller heater bank hot water inlet temperature + Real64 heatingInletTemp = state.dataLoopNodes->Node(this->heatingInletNodeNum).Temp; + + // Chiller heater bank condenser loop inlet temperature + Real64 sourceInletTemp = state.dataLoopNodes->Node(this->sourceInletNodeNum).Temp; + + Real64 currentCoolingLoad = 0.0; // Total cooling load chiller heater bank (system) meets + + // Initiate loads and inlet temperatures each loop + if (loopNum == this->coolingPlantLoc.loopNum) { + coolingInletMassFlowRate = state.dataLoopNodes->Node(this->coolingInletNodeNum).MassFlowRate; + heatingInletMassFlowRate = state.dataLoopNodes->Node(this->heatingInletNodeNum).MassFlowRate; + sourceInletMassFlowRate = state.dataLoopNodes->Node(this->sourceInletNodeNum).MassFlowRate; + this->requestedCoolingLoad = 0.0; + currentCoolingLoad = std::abs(load); + this->requestedCoolingLoad = currentCoolingLoad; + if (coolingInletMassFlowRate == 0.0) { + sourceInletMassFlowRate = 0.0; + } + + } else if (loopNum == this->heatingPlantLoc.loopNum) { + coolingInletMassFlowRate = state.dataLoopNodes->Node(this->coolingInletNodeNum).MassFlowRate; + heatingInletMassFlowRate = state.dataLoopNodes->Node(this->heatingInletNodeNum).MassFlowRate; + sourceInletMassFlowRate = state.dataLoopNodes->Node(this->sourceInletNodeNum).MassFlowRate; + this->requestedHeatingLoad = 0.0; + currentHeatingLoad = load; + this->requestedHeatingLoad = currentHeatingLoad; + if (heatingInletMassFlowRate == 0.0) { + sourceInletMassFlowRate = 0.0; + } + } + + if (this->requestedCoolingLoad > HVAC::SmallLoad && this->requestedHeatingLoad > HVAC::SmallLoad) { + coolingInletMassFlowRate = state.dataLoopNodes->Node(this->coolingInletNodeNum).MassFlowRate; + heatingInletMassFlowRate = state.dataLoopNodes->Node(this->heatingInletNodeNum).MassFlowRate; + sourceInletMassFlowRate = state.dataLoopNodes->Node(this->sourceInletNodeNum).MassFlowRate; + + this->calculateSimultaneous( + state, coolingInletMassFlowRate, heatingInletMassFlowRate, sourceInletMassFlowRate, coolingInletTemp, heatingInletTemp, sourceInletTemp); + + PlantUtilities::SetComponentFlowRate( + state, coolingInletMassFlowRate, this->coolingInletNodeNum, this->coolingOutletNodeNum, this->coolingPlantLoc); + PlantUtilities::SetComponentFlowRate( + state, heatingInletMassFlowRate, this->heatingInletNodeNum, this->heatingOutletNodeNum, this->heatingPlantLoc); + PlantUtilities::SetComponentFlowRate( + state, sourceInletMassFlowRate, this->sourceInletNodeNum, this->sourceOutletNodeNum, this->sourcePlantLoc); + + load = loopNum == this->coolingPlantLoc.loopNum ? -this->report.coolingHeatTransferRate : this->report.heatingHeatTransferRate; + return; + } + + this->isCoolingDominant = false; + this->isHeatingDominant = false; + + if (loopNum == this->coolingPlantLoc.loopNum && currentCoolingLoad > HVAC::SmallLoad) { + this->calculateCoolingOnly(state, coolingInletMassFlowRate, sourceInletMassFlowRate, coolingInletTemp, sourceInletTemp); + PlantUtilities::SetComponentFlowRate( + state, coolingInletMassFlowRate, this->coolingInletNodeNum, this->coolingOutletNodeNum, this->coolingPlantLoc); + PlantUtilities::SetComponentFlowRate( + state, heatingInletMassFlowRate, this->heatingInletNodeNum, this->heatingOutletNodeNum, this->heatingPlantLoc); + PlantUtilities::SetComponentFlowRate( + state, sourceInletMassFlowRate, this->sourceInletNodeNum, this->sourceOutletNodeNum, this->sourcePlantLoc); + load = -this->report.coolingHeatTransferRate; + return; + } + + if (loopNum == this->heatingPlantLoc.loopNum && currentHeatingLoad > HVAC::SmallLoad) { + this->calculateHeatingOnly(state, heatingInletMassFlowRate, sourceInletMassFlowRate, heatingInletTemp, sourceInletTemp); + PlantUtilities::SetComponentFlowRate( + state, coolingInletMassFlowRate, this->coolingInletNodeNum, this->coolingOutletNodeNum, this->coolingPlantLoc); + PlantUtilities::SetComponentFlowRate( + state, heatingInletMassFlowRate, this->heatingInletNodeNum, this->heatingOutletNodeNum, this->heatingPlantLoc); + PlantUtilities::SetComponentFlowRate( + state, sourceInletMassFlowRate, this->sourceInletNodeNum, this->sourceOutletNodeNum, this->sourcePlantLoc); + load = this->report.heatingHeatTransferRate; + return; + } + + this->resetOffState(state); + load = 0.0; +} + +void CentralHeatPumpSystem::oneTimeInit([[maybe_unused]] EnergyPlusData &state) +{ +} + +} // namespace EnergyPlus::PlantCentralHeatPumpSystem diff --git a/src/EnergyPlus/PlantCentralHeatPumpSystem.hh b/src/EnergyPlus/PlantCentralHeatPumpSystem.hh new file mode 100644 index 00000000000..11634036aa9 --- /dev/null +++ b/src/EnergyPlus/PlantCentralHeatPumpSystem.hh @@ -0,0 +1,494 @@ +// EnergyPlus, Copyright (c) 1996-present, The Board of Trustees of the University of Illinois, +// The Regents of the University of California, through Lawrence Berkeley National Laboratory +// (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge +// National Laboratory, managed by UT-Battelle, Alliance for Energy Innovation, LLC, and other +// contributors. All rights reserved. +// +// NOTICE: This Software was developed under funding from the U.S. Department of Energy and the +// U.S. Government consequently retains certain rights. As such, the U.S. Government has been +// granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, +// worldwide license in the Software to reproduce, distribute copies to the public, prepare +// derivative works, and perform publicly and display publicly, and to permit others to do so. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// (1) Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// (2) Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory, +// the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific prior +// written permission. +// +// (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form +// without changes from the version obtained under this License, or (ii) Licensee makes a +// reference solely to the software portion of its product, Licensee must refer to the +// software as "EnergyPlus version X" software, where "X" is the version number Licensee +// obtained under this License and may not use a different name for the software. Except as +// specifically required in this Section (4), Licensee shall not use in a company name, a +// product name, in advertising, publicity, or other promotional activities any name, trade +// name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly +// similar designation, without the U.S. Department of Energy's prior written consent. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef PlantCentralHeatPumpSystem_hh_INCLUDED +#define PlantCentralHeatPumpSystem_hh_INCLUDED + +// C++ Headers +#include + +// EnergyPlus Headers +#include +#include +#include + +namespace EnergyPlus { + +// Forward declarations +struct EnergyPlusData; + +namespace PlantCentralHeatPumpSystem { + + enum class CurrentMode + { + Invalid = -1, + Off, + CoolingOnly, + HeatingOnly, + HeatRecovery, + CoolingDominant, + HeatingDominant, + Num + }; + + enum class CondenserTemperatureMode + { + Invalid = -1, + EnteringCondenser, + LeavingCondenser, + Num + }; + + enum class SolverConvergenceStatus + { + Invalid = -1, + NotRequired, + Converged, + Stagnated, + IterationLimit, + Num + }; + + struct SolverStatistics + { + SolverConvergenceStatus outerStatus = SolverConvergenceStatus::NotRequired; + SolverConvergenceStatus partLoadStatus = SolverConvergenceStatus::NotRequired; + int outerIterations = 0; + int partLoadIterations = 0; + int curveEvaluations = 0; + Real64 temperatureResidual = 0.0; + Real64 partLoadBracketWidth = 0.0; + Real64 loadResidual = 0.0; + }; + + struct SolverWarningData + { + int count = 0; + int recurringIndex = 0; + }; + + struct ModuleResult + { + CurrentMode currentMode = CurrentMode::Off; + bool isAvailable = false; + bool isRunning = false; + + Real64 requestedCoolingLoad = 0.0; // Cooling load presented to this module [W] + Real64 requestedHeatingLoad = 0.0; // Heating load presented to this module [W] + Real64 availableEvaporatorCapacity = 0.0; // Available evaporator capacity at the operating temperatures [W] + Real64 availableCondenserCapacity = 0.0; // Available condenser capacity at the operating temperatures [W] + Real64 qEvaporator = 0.0; // Refrigerant evaporator heat transfer [W] + Real64 qCondenser = 0.0; // Refrigerant condenser heat transfer [W] + + Real64 compressorPower = 0.0; // Compressor electric power counted once [W] + Real64 coolingPower = 0.0; // Compressor power assigned to cooling reporting [W] + Real64 heatingPower = 0.0; // Compressor power assigned to heating reporting [W] + Real64 motorHeatToRefrigerant = 0.0; // Compressor power delivered to the refrigerant [W] + Real64 motorHeatLoss = 0.0; // Compressor power rejected outside the refrigerant cycle [W] + Real64 falseLoadRate = 0.0; // False loading above useful water-side load [W] + + Real64 partLoadRatio = 0.0; + Real64 cyclingRatio = 0.0; + Real64 unloadingRatio = 0.0; + Real64 capacityTemperatureModifier = 0.0; + Real64 eirTemperatureModifier = 0.0; + Real64 eirPartLoadModifier = 0.0; + Real64 capacityCurveEvaporatorTemp = 0.0; // Evaporator temperature used by the capacity curve [C] + Real64 capacityCurveCondenserTemp = 0.0; // Condenser temperature used by the capacity curve [C] + Real64 eirCurveEvaporatorTemp = 0.0; // Evaporator temperature used by the EIR temperature curve [C] + Real64 eirCurveCondenserTemp = 0.0; // Condenser temperature used by the EIR temperature curve [C] + Real64 eirPartLoadCurvePLR = 0.0; // PLR used to evaluate the EIR part-load curve + Real64 eirPartLoadCurveCondenserTemp = 0.0; // Condenser temperature used by a bivariate EIR part-load curve [C] + Real64 actualCOP = 0.0; + + Real64 evaporatorInletTemp = 0.0; + Real64 evaporatorOutletTemp = 0.0; + Real64 evaporatorMassFlowRate = 0.0; + Real64 condenserInletTemp = 0.0; + Real64 condenserOutletTemp = 0.0; + Real64 condenserMassFlowRate = 0.0; + + Real64 coolingInletTemp = 0.0; + Real64 coolingOutletTemp = 0.0; + Real64 coolingMassFlowRate = 0.0; + Real64 heatingInletTemp = 0.0; + Real64 heatingOutletTemp = 0.0; + Real64 heatingMassFlowRate = 0.0; + Real64 sourceInletTemp = 0.0; + Real64 sourceOutletTemp = 0.0; + Real64 sourceMassFlowRate = 0.0; + + Real64 coolingDelivered = 0.0; // Useful chilled-water cooling [W] + Real64 heatingDelivered = 0.0; // Useful hot-water heating [W] + Real64 heatRecovered = 0.0; // Condenser heat routed to heat recovery [W] + Real64 sourceHeatTransfer = 0.0; // Positive rejects to source; negative extracts from source [W] + Real64 unmetCoolingLoad = 0.0; // Remaining system cooling load after this module [W] + Real64 unmetHeatingLoad = 0.0; // Remaining system heating load after this module [W] + + SolverStatistics solver; + + Real64 falseLoadEnergy = 0.0; + Real64 coolingEnergy = 0.0; + Real64 heatingEnergy = 0.0; + Real64 evaporatorEnergy = 0.0; + Real64 condenserEnergy = 0.0; + + void updateEnergies(Real64 secondsInTimeStep) + { + this->falseLoadEnergy = this->falseLoadRate * secondsInTimeStep; + this->coolingEnergy = this->coolingPower * secondsInTimeStep; + this->heatingEnergy = this->heatingPower * secondsInTimeStep; + this->evaporatorEnergy = this->qEvaporator * secondsInTimeStep; + this->condenserEnergy = this->qCondenser * secondsInTimeStep; + } + + void updatePowerAccounting(Real64 openMotorEfficiency) + { + this->compressorPower = this->coolingPower + this->heatingPower; + this->motorHeatToRefrigerant = this->compressorPower * openMotorEfficiency; + this->motorHeatLoss = this->compressorPower - this->motorHeatToRefrigerant; + this->isRunning = this->currentMode == CurrentMode::CoolingOnly || this->currentMode == CurrentMode::HeatingOnly || + this->currentMode == CurrentMode::HeatRecovery || this->currentMode == CurrentMode::CoolingDominant || + this->currentMode == CurrentMode::HeatingDominant; + } + + [[nodiscard]] Real64 moduleEnergyBalanceResidual() const + { + return this->qCondenser - this->qEvaporator - this->motorHeatToRefrigerant - this->falseLoadRate; + } + + [[nodiscard]] Real64 routingEnergyBalanceResidual() const + { + return this->heatingDelivered + this->sourceHeatTransfer - this->coolingDelivered - this->motorHeatToRefrigerant - this->falseLoadRate; + } + }; + + struct PerformanceData + { + std::string Name; + CondenserTemperatureMode coolingCondenserTemperatureMode = CondenserTemperatureMode::Invalid; + CondenserTemperatureMode heatingCondenserTemperatureMode = CondenserTemperatureMode::Invalid; + bool constantFlow = false; + bool variableFlow = false; + int coolingCapacityTemperatureCurveIndex = 0; + int coolingEIRTemperatureCurveIndex = 0; + int coolingEIRPartLoadCurveIndex = 0; + int heatingCapacityTemperatureCurveIndex = 0; + int heatingEIRTemperatureCurveIndex = 0; + int heatingEIRPartLoadCurveIndex = 0; + Real64 referenceCoolingCapacity = 0.0; + bool referenceCoolingCapacityWasAutoSized = false; + Real64 referenceCoolingCOP = 0.0; + Real64 coolingReferenceEvaporatorOutletTemp = 0.0; + Real64 coolingReferenceCondenserInletTemp = 0.0; + Real64 coolingReferenceCondenserOutletTemp = 0.0; + Real64 coolingMaximumPartLoadRatio = 0.0; + Real64 coolingOptimumPartLoadRatio = 0.0; + Real64 coolingMinimumPartLoadRatio = 0.0; + Real64 heatingToCoolingCapacityRatio = 0.0; + Real64 heatingToCoolingPowerRatio = 0.0; + Real64 referenceHeatingCapacity = 0.0; + Real64 referenceHeatingCOP = 0.0; + Real64 referenceHeatingPower = 0.0; + Real64 heatingReferenceEvaporatorOutletTemp = 0.0; + Real64 heatingReferenceCondenserInletTemp = 0.0; + Real64 heatingReferenceCondenserOutletTemp = 0.0; + Real64 minimumEvaporatorOutletTemp = 0.0; + Real64 heatingMaximumPartLoadRatio = 0.0; + Real64 heatingOptimumPartLoadRatio = 0.0; + Real64 heatingMinimumPartLoadRatio = 0.0; + Real64 designEvaporatorVolFlowRate = 0.0; + bool designEvaporatorVolFlowRateWasAutoSized = false; + Real64 designCondenserVolFlowRate = 0.0; + bool designCondenserVolFlowRateWasAutoSized = false; + Real64 designHeatingVolFlowRate = 0.0; + Real64 compressorMotorEfficiency = 0.0; + Real64 sizingFactor = 1.0; + }; + + struct ModuleSizingData + { + Real64 referenceCoolingCapacity = 0.0; + Real64 referenceHeatingCapacity = 0.0; + Real64 referenceHeatingCOP = 0.0; + Real64 referenceHeatingPower = 0.0; + Real64 designEvaporatorVolFlowRate = 0.0; + Real64 temporaryEvaporatorVolFlowRate = 0.0; + Real64 designCondenserVolFlowRate = 0.0; + Real64 temporaryCondenserVolFlowRate = 0.0; + Real64 maximumCondenserMassFlowRate = 0.0; + Real64 maximumEvaporatorMassFlowRate = 0.0; + Real64 maximumCoolingMassFlowRate = 0.0; + Real64 maximumHeatingMassFlowRate = 0.0; + Real64 maximumSourceEvaporatorMassFlowRate = 0.0; + Real64 maximumSourceCondenserMassFlowRate = 0.0; + }; + + struct ModePerformanceData + { + CondenserTemperatureMode condenserMode = CondenserTemperatureMode::Invalid; + int capacityTemperatureCurveIndex = 0; + int eirTemperatureCurveIndex = 0; + int eirPartLoadCurveIndex = 0; + Real64 referenceEvaporatorCapacity = 0.0; + Real64 referenceCOP = 0.0; + Real64 referenceEvaporatorLeavingTemp = 0.0; + Real64 referenceCondenserEnteringTemp = 0.0; + Real64 referenceCondenserLeavingTemp = 0.0; + Real64 minimumPartLoadRatio = 0.0; + Real64 maximumPartLoadRatio = 0.0; + Real64 optimumPartLoadRatio = 0.0; + }; + + struct Module + { + PerformanceData const *performance = nullptr; + Sched::Schedule *availabilitySchedule = nullptr; + bool variableFlow = false; + ModuleSizingData sizing; + Real64 minimumEvaporatorOutletTemp = 0.0; + int capacityCurveErrorCount = 0; + int capacityCurveErrorIndex = 0; + SolverWarningData coolingSolverWarning; + SolverWarningData heatingSolverWarning; + SolverWarningData heatingPartLoadSolverWarning; + SolverWarningData simultaneousSolverWarning; + SolverWarningData simultaneousPartLoadSolverWarning; + ModuleResult result; + + void initialize(PerformanceData const &performanceData, Sched::Schedule *schedule); + [[nodiscard]] PerformanceData const &performanceData() const; + [[nodiscard]] std::string const &name() const; + [[nodiscard]] bool isAvailable() const; + [[nodiscard]] ModePerformanceData coolingModePerformance() const; + [[nodiscard]] ModePerformanceData heatingModePerformance() const; + void mapResultToPlantConnections(); + void updateResultEnergies(Real64 secondsInTimeStep); + void resetResult(Real64 evaporatorInletTemp, Real64 condenserInletTemp); + }; + + struct SystemReportData + { + Real64 coolingElectricEnergy = 0.0; // System cooling electric consumption [J] + Real64 heatingElectricEnergy = 0.0; // System heating electric consumption [J] + Real64 coolingHeatTransferEnergy = 0.0; // Chilled water heat transfer energy [J] + Real64 heatingHeatTransferEnergy = 0.0; // Hot water heat transfer energy [J] + Real64 sourceHeatTransferEnergy = 0.0; // Source loop heat transfer energy [J] + Real64 coolingElectricPower = 0.0; // System cooling electric consumption rate [W] + Real64 heatingElectricPower = 0.0; // System heating electric consumption rate [W] + Real64 coolingHeatTransferRate = 0.0; // Chilled water heat transfer rate [W] + Real64 heatingHeatTransferRate = 0.0; // Hot water heat transfer rate [W] + Real64 sourceHeatTransferRate = 0.0; // Source loop heat transfer rate [W] + Real64 coolingInletTemp = 0.0; // Chilled water inlet temperature [C] + Real64 heatingInletTemp = 0.0; // Hot water inlet temperature [C] + Real64 sourceInletTemp = 0.0; // Source loop inlet temperature [C] + Real64 coolingOutletTemp = 0.0; // Chilled water Outlet temperature [C] + Real64 heatingOutletTemp = 0.0; // Hot water Outlet temperature [C] + Real64 sourceOutletTemp = 0.0; // Source loop Outlet temperature [C] + Real64 coolingMassFlowRate = 0.0; // Cooling loop mass flow rate [kg/s] + Real64 heatingMassFlowRate = 0.0; // Heating loop mass flow rate [kg/s] + Real64 sourceMassFlowRate = 0.0; // Source loop mass flow rate [kg/s] + }; + + struct CentralHeatPumpSystem : PlantComponent + { + std::string Name; // User identifier + bool allModulesVariableFlow = false; // True when every module uses variable-flow control + Sched::Schedule *ancillaryPowerSched = nullptr; // Schedule value for ancillary power control + int coolingInletNodeNum = 0; // Node number on the inlet side of the plant (Chilled Water side) + int coolingOutletNodeNum = 0; // Node number on the outlet side of the plant (Chilled Water side) + int heatingInletNodeNum = 0; // Node number on the inlet side of the plant (Hot water side) + int heatingOutletNodeNum = 0; // Node number on the outlet side of the plant (Hot water side) + int sourceInletNodeNum = 0; // Node number on the inlet side of the plant (source side) + int sourceOutletNodeNum = 0; // Node number on the outlet side of the plant (source side) + int coolingSetpointNodeNum = 0; // Node number of the cooling setpoint temperature node + int heatingSetpointNodeNum = 0; // Node number of the heating setpoint temperature node + bool checkMinimumEvaporatorOutletTemp = true; // True if minimum evaporator outlet temperature warning is enabled + Real64 coolingMassFlowRateMax = 0.0; // Maximum chilled water mass flow rate + Real64 heatingMassFlowRateMax = 0.0; // Maximum hot water mass flow rate + Real64 sourceMassFlowRateMax = 0.0; // Maximum Source loop mass flow rate + Real64 requestedCoolingLoad = 0.0; // Cooling demand for the central heat pump system + Real64 requestedHeatingLoad = 0.0; // Heating demand for the central heat pump system + Real64 ancillaryPower = 0.0; // System ancillary power + std::vector modules; // Expanded runtime modules + bool coolingSetpointErrorIssued = false; // true if setpoint warning issued + bool heatingSetpointErrorIssued = false; // true if setpoint warning issued + PlantLocation coolingPlantLoc = {}; // Chilled water plant loop component index + PlantLocation heatingPlantLoc = {}; // Hot water plant loop component index + PlantLocation sourcePlantLoc = {}; // Source plant loop component location + Real64 coolingVolFlowRate = 0.0; // Cooling loop volume flow rate [m3/s] + Real64 heatingVolFlowRate = 0.0; // Heating loop volume flow rate [m3/s] + Real64 sourceVolFlowRate = 0.0; // Source loop volume flow rate [m3/s] + bool plantScanPending = true; + bool environmentInitPending = true; + bool isCoolingDominant = false; + bool isHeatingDominant = false; + SystemReportData report; + bool setupOutputVarsFlag = true; + bool mySizesReported = false; + + static PlantComponent *factory(EnergyPlusData &state, std::string const &objectName); + + void getSizingFactor(Real64 &sizingFactor) override; + + void getDesignCapacities( + EnergyPlusData &state, const PlantLocation &calledFromLocation, Real64 &maxLoad, Real64 &minLoad, Real64 &optimalLoad) override; + + void setupOutputVars(EnergyPlusData &state); + + void initialize(EnergyPlusData &state, + Real64 load, // Demand Load + int loopNum, // Loop Number Index + bool runFlag = true); + + void initializeDesignFlowLimits(EnergyPlusData &state); + + void resolveFlowMode(EnergyPlusData &state); + + void resetOffState(EnergyPlusData &state, bool releasePlantFlows = true); + + void simulate([[maybe_unused]] EnergyPlusData &state, + const PlantLocation &calledFromLocation, + bool firstHVACIteration, + Real64 ¤tLoad, + bool runFlag) override; + + void size(EnergyPlusData &state); + + void calculate(EnergyPlusData &state, Real64 &load, int loopNum); + + void calculateCoolingOnly( + EnergyPlusData &state, Real64 coolingMassFlowRate, Real64 sourceMassFlowRate, Real64 coolingInletTemp, Real64 sourceInletTemp); + + void calculateHeatingOnly( + EnergyPlusData &state, Real64 heatingMassFlowRate, Real64 sourceMassFlowRate, Real64 heatingInletTemp, Real64 sourceInletTemp); + + ModuleResult solveCoolingOnly(EnergyPlusData &state, + std::size_t moduleIndex, + Real64 coolingLoad, + Real64 evaporatorMassFlowRateMax, + Real64 condenserMassFlowRate, + Real64 evaporatorInletTemp, + Real64 condenserInletTemp); + + ModuleResult solveHeatingOnly(EnergyPlusData &state, + std::size_t moduleIndex, + Real64 heatingLoad, + Real64 evaporatorMassFlowRate, + Real64 condenserMassFlowRateMax, + Real64 evaporatorInletTemp, + Real64 condenserInletTemp); + + ModuleResult solveSimultaneous(EnergyPlusData &state, + std::size_t moduleIndex, + Real64 coolingLoad, + Real64 heatingLoad, + Real64 maximumCoolingMassFlowRate, + Real64 maximumHeatingMassFlowRate, + Real64 maximumSourceMassFlowRate, + Real64 coolingInletTemp, + Real64 heatingInletTemp, + Real64 sourceInletTemp); + + void calculateSimultaneous(EnergyPlusData &state, + Real64 coolingMassFlowRate, + Real64 heatingMassFlowRate, + Real64 sourceMassFlowRate, + Real64 coolingInletTemp, + Real64 heatingInletTemp, + Real64 sourceInletTemp); + + void updateReportingAndNodes(EnergyPlusData const &state, + Real64 coolingMassFlowRate, + Real64 heatingMassFlowRate, + Real64 sourceMassFlowRate, + Real64 coolingInletTemp, + Real64 heatingInletTemp, + Real64 sourceInletTemp); + + static Real64 + selectCondenserCurveTemperature(ModePerformanceData const &modePerformance, Real64 condenserEnteringTemp, Real64 condenserLeavingTemp); + + static Real64 evaluateCapacityTemperatureModifier( + EnergyPlusData &state, Module &module, ModePerformanceData const &modePerformance, Real64 evaporatorOutletTemp, Real64 condenserTemp); + + void onInitLoopEquip([[maybe_unused]] EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation) override; + + void oneTimeInit(EnergyPlusData &state) override; + }; + + void getCentralHeatPumpSystemInput(EnergyPlusData &state); + + void getPerformanceInput(EnergyPlusData &state); + +} // namespace PlantCentralHeatPumpSystem + +struct PlantCentralHeatPumpSystemData : BaseGlobalStruct +{ + + bool getSystemInputFlag = true; // When TRUE, calls subroutine to read input file. + int numPerformanceReferences = 0; // Number of system performance references + std::vector systems; + std::vector performanceDefinitions; + + void init_constant_state([[maybe_unused]] EnergyPlusData &state) override + { + } + + void init_state([[maybe_unused]] EnergyPlusData &state) override + { + } + + void clear_state() override + { + new (this) PlantCentralHeatPumpSystemData(); + } +}; + +} // namespace EnergyPlus + +#endif diff --git a/tst/EnergyPlus/unit/CMakeLists.txt b/tst/EnergyPlus/unit/CMakeLists.txt index 9ec0fc51f5b..9f4510b4a6f 100644 --- a/tst/EnergyPlus/unit/CMakeLists.txt +++ b/tst/EnergyPlus/unit/CMakeLists.txt @@ -209,7 +209,7 @@ set(test_src Pipes.unit.cc Plant/Branch.unit.cc Plant/Subcomponent.unit.cc - PlantCentralGSHP.unit.cc + PlantCentralHeatPumpSystem.unit.cc PlantChillers.unit.cc PlantComponentTemperatureSources.unit.cc PlantCondLoopOperation.unit.cc diff --git a/tst/EnergyPlus/unit/PlantCentralGSHP.unit.cc b/tst/EnergyPlus/unit/PlantCentralGSHP.unit.cc deleted file mode 100644 index b5777c54282..00000000000 --- a/tst/EnergyPlus/unit/PlantCentralGSHP.unit.cc +++ /dev/null @@ -1,786 +0,0 @@ -// EnergyPlus, Copyright (c) 1996-present, The Board of Trustees of the University of Illinois, -// The Regents of the University of California, through Lawrence Berkeley National Laboratory -// (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge -// National Laboratory, managed by UT-Battelle, Alliance for Energy Innovation, LLC, and other -// contributors. All rights reserved. -// -// NOTICE: This Software was developed under funding from the U.S. Department of Energy and the -// U.S. Government consequently retains certain rights. As such, the U.S. Government has been -// granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, -// worldwide license in the Software to reproduce, distribute copies to the public, prepare -// derivative works, and perform publicly and display publicly, and to permit others to do so. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// (1) Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// (2) Redistributions in binary form must reproduce the above copyright notice, this list of -// conditions and the following disclaimer in the documentation and/or other materials -// provided with the distribution. -// -// (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory, -// the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific prior -// written permission. -// -// (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form -// without changes from the version obtained under this License, or (ii) Licensee makes a -// reference solely to the software portion of its product, Licensee must refer to the -// software as "EnergyPlus version X" software, where "X" is the version number Licensee -// obtained under this License and may not use a different name for the software. Except as -// specifically required in this Section (4), Licensee shall not use in a company name, a -// product name, in advertising, publicity, or other promotional activities any name, trade -// name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly -// similar designation, without the U.S. Department of Energy's prior written consent. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR -// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. - -// EnergyPlus::PlantCentralGSHP Unit Tests - -// Google Test Headers -#include - -// EnergyPlus Headers -#include "Fixtures/EnergyPlusFixture.hh" -#include -#include -#include -#include -#include -#include -#include - -using namespace EnergyPlus; - -TEST_F(EnergyPlusFixture, ChillerHeater_Autosize) -{ - state->init_state(*state); - // Allocate One Wrapper with One module (=distinct ChillerHeaterPerformance:Electric:EIR) - // but with a number of identical number module of 2 in CentralHeatPumpSystem - int NumWrappers = 1; - state->dataPlantCentralGSHP->numWrappers = NumWrappers; - state->dataPlantCentralGSHP->Wrapper.allocate(NumWrappers); - - int NumberOfComp = 1; - state->dataPlantCentralGSHP->Wrapper(1).NumOfComp = NumberOfComp; - state->dataPlantCentralGSHP->Wrapper(1).WrapperComp.allocate(NumberOfComp); - - state->dataPlantCentralGSHP->Wrapper(1).WrapperComp(1).WrapperPerformanceObjectType = "CHILLERHEATERPERFORMANCE:ELECTRIC:EIR"; - state->dataPlantCentralGSHP->Wrapper(1).WrapperComp(1).WrapperIdenticalObjectNum = 2; - state->dataPlantCentralGSHP->Wrapper(1).WrapperComp(1).chSched = Sched::GetScheduleAlwaysOn(*state); - state->dataPlantCentralGSHP->Wrapper(1).ChillerHeaterNums = 2; - state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater.allocate(2); - - int NumChillerHeaters = 1; - state->dataPlantCentralGSHP->numChillerHeaters = NumChillerHeaters; - state->dataPlantCentralGSHP->ChillerHeater.allocate(NumChillerHeaters); - state->dataPlantCentralGSHP->ChillerHeater(1).ConstantFlow = false; - state->dataPlantCentralGSHP->ChillerHeater(1).VariableFlow = true; - - state->dataPlantCentralGSHP->ChillerHeater(1).SizFac = 1.2; - - state->dataPlantCentralGSHP->ChillerHeater(1).RefCapCooling = DataSizing::AutoSize; - state->dataPlantCentralGSHP->ChillerHeater(1).RefCapCoolingWasAutoSized = true; - - state->dataPlantCentralGSHP->ChillerHeater(1).EvapVolFlowRate = DataSizing::AutoSize; - state->dataPlantCentralGSHP->ChillerHeater(1).EvapVolFlowRateWasAutoSized = true; - - state->dataPlantCentralGSHP->ChillerHeater(1).CondVolFlowRate = DataSizing::AutoSize; - state->dataPlantCentralGSHP->ChillerHeater(1).CondVolFlowRateWasAutoSized = true; - - // Needed for calcs - state->dataPlantCentralGSHP->ChillerHeater(1).RefCOPCooling = 1.5; - state->dataPlantCentralGSHP->ChillerHeater(1).OpenMotorEff = 0.98; - state->dataPlantCentralGSHP->ChillerHeater(1).TempRefCondInCooling = 29.4; - state->dataPlantCentralGSHP->ChillerHeater(1).ClgHtgToCoolingCapRatio = 0.74; - state->dataPlantCentralGSHP->ChillerHeater(1).ClgHtgtoCogPowerRatio = 1.38; - - // Add the References onto the wrapper - state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1) = state->dataPlantCentralGSHP->ChillerHeater(1); - state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(2) = state->dataPlantCentralGSHP->ChillerHeater(1); - - // De-allocate temporary arrays (happens in GetInput too...) - state->dataPlantCentralGSHP->ChillerHeater.deallocate(); - - state->dataPlnt->PlantLoop.allocate(2); - state->dataSize->PlantSizData.allocate(2); - - // Chilled Water Loop - int PltSizNum = 1; - state->dataPlnt->PlantLoop(PltSizNum).PlantSizNum = 1; - state->dataPlnt->PlantLoop(PltSizNum).FluidName = "WATER"; - state->dataPlnt->PlantLoop(PltSizNum).glycol = Fluid::GetWater(*state); - state->dataSize->PlantSizData(PltSizNum).DesVolFlowRate = 1.0; - state->dataSize->PlantSizData(PltSizNum).DeltaT = 10.0; - state->dataSize->PlantSizData(PltSizNum).LoopType = DataSizing::TypeOfPlantLoop::Cooling; - // Assign to the wrapper - state->dataPlantCentralGSHP->Wrapper(1).CWPlantLoc.loopNum = PltSizNum; - PlantUtilities::SetPlantLocationLinks(*state, state->dataPlantCentralGSHP->Wrapper(1).CWPlantLoc); - - // Condenser Loop - int PltSizCondNum = 2; - state->dataPlnt->PlantLoop(PltSizCondNum).PlantSizNum = PltSizCondNum; - state->dataPlnt->PlantLoop(PltSizCondNum).FluidName = "WATER"; - state->dataPlnt->PlantLoop(PltSizCondNum).glycol = Fluid::GetWater(*state); - state->dataSize->PlantSizData(PltSizCondNum).DeltaT = 5.6; - state->dataSize->PlantSizData(PltSizCondNum).LoopType = DataSizing::TypeOfPlantLoop::Condenser; - // Assign to the wrapper - state->dataPlantCentralGSHP->Wrapper(1).GLHEPlantLoc.loopNum = PltSizCondNum; - PlantUtilities::SetPlantLocationLinks(*state, state->dataPlantCentralGSHP->Wrapper(1).GLHEPlantLoc); - - // Calculate expected values - Real64 rho_evap = state->dataPlnt->PlantLoop(PltSizNum).glycol->getDensity(*state, Constant::CWInitConvTemp, "ChillerHeater_Autosize_TEST"); - - Real64 Cp_evap = state->dataPlnt->PlantLoop(PltSizNum).glycol->getSpecificHeat(*state, Constant::CWInitConvTemp, "ChillerHeater_Autosize_TEST"); - - Real64 rho_cond = state->dataPlnt->PlantLoop(PltSizCondNum).glycol->getDensity(*state, Constant::CWInitConvTemp, "ChillerHeater_Autosize_TEST"); - - Real64 Cp_cond = state->dataPlnt->PlantLoop(PltSizCondNum) - .glycol->getSpecificHeat( - *state, state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).TempRefCondInCooling, "ChillerHeater_Autosize_TEST"); - - // Note: Each individual chiller heater module is sized to be capable of supporting the total load on the wrapper - - // Flow is multiplied by the SizFac - Real64 EvapVolFlowRateExpected = - state->dataSize->PlantSizData(PltSizNum).DesVolFlowRate * state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).SizFac; - - Real64 RefCapCoolingExpected = rho_evap * Cp_evap * EvapVolFlowRateExpected * state->dataSize->PlantSizData(PltSizNum).DeltaT; - - Real64 CondVolFlowRateExpected = RefCapCoolingExpected * - (1.0 + (1.0 / state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).RefCOPCooling) * - state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).OpenMotorEff) / - (rho_cond * Cp_cond * state->dataSize->PlantSizData(PltSizCondNum).DeltaT); - - // now call sizing routine - state->dataPlnt->PlantFirstSizesOkayToFinalize = true; - state->dataPlantCentralGSHP->Wrapper(1).SizeWrapper(*state); - - // Careful of actually using PlantCentralGSHP::Wrapper(1).ChillerHeater(1) and not PlantCentralGSHP::ChillerHeater since this array isn't used - // anymore by the module - EXPECT_DOUBLE_EQ(EvapVolFlowRateExpected, state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).EvapVolFlowRate); - EXPECT_DOUBLE_EQ(RefCapCoolingExpected, state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).RefCapCooling); - - EXPECT_DOUBLE_EQ(CondVolFlowRateExpected, state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).CondVolFlowRate); - EXPECT_DOUBLE_EQ(CondVolFlowRateExpected, state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(2).CondVolFlowRate); - - // Ensure that stuff that other quantities that depends on RefCapCooling are also initialized properly - // Heating Cap - Real64 RefCapClgHtgExpected = RefCapCoolingExpected * state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).ClgHtgToCoolingCapRatio; - EXPECT_DOUBLE_EQ(RefCapClgHtgExpected, state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).RefCapClgHtg); - - // Heating Power: Calc cooling Power = Cap / COP, and multiply by ratio - Real64 RefPowerClgHtgExpected = (RefCapCoolingExpected / state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).RefCOPCooling) * - state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).ClgHtgtoCogPowerRatio; - EXPECT_DOUBLE_EQ(RefPowerClgHtgExpected, state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).RefPowerClgHtg); - - // Heating COP = Heating Cap / Heating Power - Real64 RefCOPClgHtgExpected = RefCapClgHtgExpected / RefPowerClgHtgExpected; - EXPECT_DOUBLE_EQ(RefCOPClgHtgExpected, state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater(1).RefCOPClgHtg); -} - -TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_Control_Schedule_fix) -{ - std::string const idf_objects = delimited_string({ - - "Schedule:Compact,", - "Always1, !-Name", - "On/Off, !-Schedule Type Limits Name", - "Through: 12/31, !-Field 1", - "For: AllDays, !-Field 2", - "Until: 24:00, 1; !-Field 3 ", - - "CentralHeatPumpSystem,", - "ChW_Loop HeatPump1, !-Name", - "ChW_Loop HeatPump1 ChW Inlet, !-Cooling Loop Inlet Node Name", - "ChW_Loop HeatPump1 ChW Outlet, !-Cooling Loop Outlet Node Name", - "ChW_Loop HeatPump1 Cnd Inlet, !-Source Loop Inlet Node Name", - "ChW_Loop HeatPump1 Cnd Outlet, !-Source Loop Outlet Node Name", - "ChW_Loop HeatPump1 HHW Inlet, !-Heating Loop Inlet Node Name", - "ChW_Loop HeatPump1 HHW Outlet, !-Heating Loop Outlet Node Name", - "460, !-Ancillary Power{W}", - ", !-Ancillary Operation Schedule Name", - "ChillerHeaterPerformance:Electric:EIR, !-Chiller Heater Modules Performance Component Object Type 1", - "ChW_Loop HeatPump1 Module, !-Chiller Heater Modules Performance Component Name 1", - "Always_1_typo, !-Chiller Heater Modules Control Schedule Name 1", - "2; !-Number of Chiller Heater Modules 1", - - "ChillerHeaterPerformance:Electric:EIR,", - " ChW_Loop HeatPump1 Module, !- Name", - " autosize, !- Reference Cooling Mode Evaporator Capacity {W}", - " 1.5, !- Reference Cooling Mode COP {W/W}", - " 6.67, !- Reference Cooling Mode Leaving Chilled Water Temperature {C}", - " 29.4, !- Reference Cooling Mode Entering Condenser Fluid Temperature {C}", - " 35.0, !- Reference Cooling Mode Leaving Condenser Water Temperature {C}", - " 0.74, !- Reference Heating Mode Cooling Capacity Ratio", - " 0.925, !- Reference Heating Mode Cooling Power Input Ratio", - " 6.67, !- Reference Heating Mode Leaving Chilled Water Temperature {C}", - " 60, !- Reference Heating Mode Leaving Condenser Water Temperature {C}", - " 29.4, !- Reference Heating Mode Entering Condenser Fluid Temperature {C}", - " 5, !- Heating Mode Entering Chilled Water Temperature Low Limit {C}", - " VariableFlow, !- Chilled Water Flow Mode Type", - " autosize, !- Design Chilled Water Flow Rate {m3/s}", - " autosize, !- Design Condenser Water Flow Rate {m3/s}", - " 0.01684, !- Design Hot Water Flow Rate {m3/s}", - " 1, !- Compressor Motor Efficiency", - " EnteringCondenser, !- Cooling Mode Temperature Curve Condenser Water Independent Variable", - " ChillerHeaterClgCapFT, !- Cooling Mode Cooling Capacity Function of Temperature Curve Name", - " ChillerHeaterClgEIRFT, !- Cooling Mode Electric Input to Cooling Output Ratio Function of Temperature Curve Name", - " ChillerHeaterClgEIRFPLR, !- Cooling Mode Electric Input to Cooling Output Ratio Function of Part Load Ratio Curve Name", - " 1, !- Cooling Mode Cooling Capacity Optimum Part Load Ratio", - " LeavingCondenser, !- Heating Mode Temperature Curve Condenser Water Independent Variable", - " ChillerHeaterHtgCapFT, !- Heating Mode Cooling Capacity Function of Temperature Curve Name", - " ChillerHeaterHtgEIRFT, !- Heating Mode Electric Input to Cooling Output Ratio Function of Temperature Curve Name", - " ChillerHeaterHtgEIRFPLR, !- Heating Mode Electric Input to Cooling Output Ratio Function of Part Load Ratio Curve Name", - " 1, !- Heating Mode Cooling Capacity Optimum Part Load Ratio", - " 1; !- Sizing Factor", - - "Curve:Biquadratic,", - " ChillerHeaterClgCapFT, !- Name", - " 0.950829, !- Coefficient1 Constant", - " 3.419327E-02, !- Coefficient2 x", - " 2.66642E-04, !- Coefficient3 x**2", - " -1.733397E-03, !- Coefficient4 y", - " -1.762417E-04, !- Coefficient5 y**2", - " -3.69198E-05, !- Coefficient6 x*y", - " 4.44, !- Minimum Value of x", - " 12.78, !- Maximum Value of x", - " 12.78, !- Minimum Value of y", - " 29.44, !- Maximum Value of y", - " , !- Minimum Curve Output", - " , !- Maximum Curve Output", - " Temperature, !- Input Unit Type for X", - " Temperature, !- Input Unit Type for Y", - " Dimensionless; !- Output Unit Type", - - "Curve:Biquadratic,", - " ChillerHeaterHtgCapFT, !- Name", - " 0.9415266, !- Coefficient1 Constant", - " 5.527431E-02, !- Coefficient2 x", - " 3.573558E-04, !- Coefficient3 x**2", - " 1.258391E-03, !- Coefficient4 y", - " -6.420546E-05, !- Coefficient5 y**2", - " -5.350989E-04, !- Coefficient6 x*y", - " 4.44, !- Minimum Value of x", - " 15.56, !- Maximum Value of x", - " 35, !- Minimum Value of y", - " 57.22, !- Maximum Value of y", - " , !- Minimum Curve Output", - " , !- Maximum Curve Output", - " Temperature, !- Input Unit Type for X", - " Temperature, !- Input Unit Type for Y", - " Dimensionless; !- Output Unit Type", - - "Curve:Biquadratic,", - " ChillerHeaterClgEIRFT, !- Name", - " 0.7362431, !- Coefficient1 Constant", - " 2.136491E-02, !- Coefficient2 x", - " 3.638909E-04, !- Coefficient3 x**2", - " -4.284947E-03, !- Coefficient4 y", - " 3.389817E-04, !- Coefficient5 y**2", - " -3.632396E-04, !- Coefficient6 x*y", - " 4.44, !- Minimum Value of x", - " 12.78, !- Maximum Value of x", - " 12.78, !- Minimum Value of y", - " 29.44, !- Maximum Value of y", - " , !- Minimum Curve Output", - " , !- Maximum Curve Output", - " Temperature, !- Input Unit Type for X", - " Temperature, !- Input Unit Type for Y", - " Dimensionless; !- Output Unit Type", - - "Curve:Biquadratic,", - " ChillerHeaterHtgEIRFT, !- Name", - " 0.2286246, !- Coefficient1 Constant", - " 2.498714E-02, !- Coefficient2 x", - " -1.267106E-05, !- Coefficient3 x**2", - " 9.327184E-03, !- Coefficient4 y", - " 5.892037E-05, !- Coefficient5 y**2", - " -3.268512E-04, !- Coefficient6 x*y", - " 4.44, !- Minimum Value of x", - " 15.56, !- Maximum Value of x", - " 35.0, !- Minimum Value of y", - " 57.22, !- Maximum Value of y", - " , !- Minimum Curve Output", - " , !- Maximum Curve Output", - " Temperature, !- Input Unit Type for X", - " Temperature, !- Input Unit Type for Y", - " Dimensionless; !- Output Unit Type", - - " Curve:Cubic,", - " ChillerHeaterClgEIRFPLR, !- Name", - " 0.0, !- Coefficient1 Constant", - " 1.22895, !- Coefficient2 x", - " -0.751383, !- Coefficient3 x**2", - " 0.517396, !- Coefficient4 x**3", - " 0.2, !- Minimum Value of x", - " 1; !- Maximum Value of x", - - "Curve:Cubic,", - " ChillerHeaterHtgEIRFPLR, !- Name", - " 0.0, !- Coefficient1 Constant", - " 1.12853, !- Coefficient2 x", - " -0.0264962, !- Coefficient3 x**2", - " -0.103811, !- Coefficient4 x**3", - " 0.3, !- Minimum Value of x", - " 1; !- Maximum Value of x" - - }); - - ASSERT_TRUE(process_idf(idf_objects)); - - state->init_state(*state); - - // May not need for direct wrapper input processing call (need when caling factory) - state->dataPlantCentralGSHP->getWrapperInputFlag = true; - - // call the central heat pump system input processing function - PlantCentralGSHP::GetWrapperInput(*state); - - // verify that under this scenario of not finding a schedule match, ScheduleAlwaysOn is the treated default - EXPECT_EQ(state->dataPlantCentralGSHP->Wrapper(1).WrapperComp(1).chSched, Sched::GetScheduleAlwaysOn(*state)); - - // verify that node names were processed correctly - EXPECT_EQ(state->dataBranchNodeConnections->NumOfNodeConnections, 6); - - EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(1).NodeName, "CHW_LOOP HEATPUMP1 CHW INLET"); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(1).ConnectionType, Node::ConnectionType::Inlet); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(1).FluidStream, Node::CompFluidStream::Primary); - - EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(2).NodeName, "CHW_LOOP HEATPUMP1 CHW OUTLET"); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(2).ConnectionType, Node::ConnectionType::Outlet); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(2).FluidStream, Node::CompFluidStream::Primary); - - EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(3).NodeName, "CHW_LOOP HEATPUMP1 CND INLET"); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(3).ConnectionType, Node::ConnectionType::Inlet); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(3).FluidStream, Node::CompFluidStream::Secondary); - - EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(4).NodeName, "CHW_LOOP HEATPUMP1 CND OUTLET"); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(4).ConnectionType, Node::ConnectionType::Outlet); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(4).FluidStream, Node::CompFluidStream::Secondary); - - EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(5).NodeName, "CHW_LOOP HEATPUMP1 HHW INLET"); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(5).ConnectionType, Node::ConnectionType::Inlet); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(5).FluidStream, Node::CompFluidStream::Tertiary); - - EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(6).NodeName, "CHW_LOOP HEATPUMP1 HHW OUTLET"); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(6).ConnectionType, Node::ConnectionType::Outlet); - EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(6).FluidStream, Node::CompFluidStream::Tertiary); -} - -TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_adjustChillerHeaterCondFlowTemp) -{ - state->dataFluid->init_state(*state); - state->dataPlantCentralGSHP->Wrapper.allocate(1); - state->dataPlantCentralGSHP->Wrapper(1).WrapperComp.allocate(1); - state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater.allocate(1); - auto &thisWrap = state->dataPlantCentralGSHP->Wrapper(1); - state->dataPlnt->PlantLoop.allocate(1); - state->dataPlnt->PlantLoop(1).FluidName = "WATER"; - state->dataPlnt->PlantLoop(1).glycol = Fluid::GetWater(*state); - thisWrap.HWPlantLoc.loopNum = 1; - PlantUtilities::SetPlantLocationLinks(*state, thisWrap.HWPlantLoc); - - Real64 qCondenser; - Real64 condMassFlowRate; - Real64 condOutletTemp; - Real64 condInletTemp; - Real64 condDeltaTemp; - Real64 expCondenser; - Real64 expMassFlowRate; - Real64 expOutletTemp; - Real64 constexpr allowedTolerance = 0.0001; - - // Test 1: Variable Flow--qCondenser is less than what the conditions say (mass flow reduced, nothing else changes) - qCondenser = 1000.0; - condMassFlowRate = 1.0; - condOutletTemp = 60.0; - condInletTemp = 59.0; - condDeltaTemp = 1.0; - thisWrap.VariableFlowCH = true; - expCondenser = 1000.0; - expMassFlowRate = 0.23897; - expOutletTemp = 60.0; - thisWrap.adjustChillerHeaterCondFlowTemp(*state, qCondenser, condMassFlowRate, condOutletTemp, condInletTemp, condDeltaTemp); - EXPECT_NEAR(qCondenser, expCondenser, allowedTolerance); - EXPECT_NEAR(condMassFlowRate, expMassFlowRate, allowedTolerance); - EXPECT_NEAR(condOutletTemp, expOutletTemp, allowedTolerance); - - // Test 2: Variable Flow--qCondenser is greater than what conditions say (load reduced, nothing else changes) - qCondenser = 5000.0; - condMassFlowRate = 1.0; - condOutletTemp = 60.0; - condInletTemp = 59.0; - condDeltaTemp = 1.0; - thisWrap.VariableFlowCH = true; - expCondenser = 4184.6; - expMassFlowRate = 1.0; - expOutletTemp = 60.0; - thisWrap.adjustChillerHeaterCondFlowTemp(*state, qCondenser, condMassFlowRate, condOutletTemp, condInletTemp, condDeltaTemp); - EXPECT_NEAR(qCondenser, expCondenser, allowedTolerance); - EXPECT_NEAR(condMassFlowRate, expMassFlowRate, allowedTolerance); - EXPECT_NEAR(condOutletTemp, expOutletTemp, allowedTolerance); - - // Test 3: Constant Flow--Outlet Temp greater than calculated outlet temp (outlet temp changes, nothing else changes) - qCondenser = 1000.0; - condMassFlowRate = 1.0; - condOutletTemp = 60.0; - condInletTemp = 59.0; - condDeltaTemp = 1.0; - thisWrap.VariableFlowCH = false; - expCondenser = 1000.0; - expMassFlowRate = 1.0; - expOutletTemp = 59.23897; - thisWrap.adjustChillerHeaterCondFlowTemp(*state, qCondenser, condMassFlowRate, condOutletTemp, condInletTemp, condDeltaTemp); - EXPECT_NEAR(qCondenser, expCondenser, allowedTolerance); - EXPECT_NEAR(condMassFlowRate, expMassFlowRate, allowedTolerance); - EXPECT_NEAR(condOutletTemp, expOutletTemp, allowedTolerance); - - // Test 4: Constant Flow--Outlet Temp less than calculated outlet temp (load changes, nothing else changes) - qCondenser = 8369.2; - condMassFlowRate = 1.0; - condOutletTemp = 60.0; - condInletTemp = 59.0; - condDeltaTemp = 1.0; - thisWrap.VariableFlowCH = false; - expCondenser = 4184.6; - expMassFlowRate = 1.0; - expOutletTemp = 60.0; - thisWrap.adjustChillerHeaterCondFlowTemp(*state, qCondenser, condMassFlowRate, condOutletTemp, condInletTemp, condDeltaTemp); - EXPECT_NEAR(qCondenser, expCondenser, allowedTolerance); - EXPECT_NEAR(condMassFlowRate, expMassFlowRate, allowedTolerance); - EXPECT_NEAR(condOutletTemp, expOutletTemp, allowedTolerance); -} - -TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_adjustChillerHeaterEvapFlowTemp) -{ - state->dataFluid->init_state(*state); - state->dataPlantCentralGSHP->Wrapper.allocate(1); - state->dataPlantCentralGSHP->Wrapper(1).WrapperComp.allocate(1); - state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater.allocate(1); - auto &thisWrap = state->dataPlantCentralGSHP->Wrapper(1); - state->dataPlnt->PlantLoop.allocate(1); - state->dataPlnt->PlantLoop(1).FluidName = "WATER"; - state->dataPlnt->PlantLoop(1).glycol = Fluid::GetWater(*state); - thisWrap.HWPlantLoc.loopNum = 1; - PlantUtilities::SetPlantLocationLinks(*state, thisWrap.HWPlantLoc); - - Real64 qEvaporator; - Real64 evapMassFlowRate; - Real64 evapOutletTemp; - Real64 evapInletTemp; - Real64 expMassFlowRate; - Real64 expOutletTemp; - Real64 constexpr allowedTolerance = 0.0001; - - // Test 1a: qEvaporator is too low, flow rate set to zero and outlet temp set to inlet temp - qEvaporator = 0.00001; - evapMassFlowRate = 1.0; - evapOutletTemp = 34.0; - evapInletTemp = 35.0; - thisWrap.VariableFlowCH = false; - expMassFlowRate = 0.0; - expOutletTemp = 35.0; - thisWrap.adjustChillerHeaterEvapFlowTemp(*state, qEvaporator, evapMassFlowRate, evapOutletTemp, evapInletTemp); - EXPECT_NEAR(evapMassFlowRate, expMassFlowRate, allowedTolerance); - EXPECT_NEAR(evapOutletTemp, expOutletTemp, allowedTolerance); - - // Test 1b: delta T is zero, load and flow rate set to zero and outlet temp set to inlet temp - qEvaporator = 1000.0; - evapMassFlowRate = 1.0; - evapOutletTemp = 35.0; - evapInletTemp = 35.0; - thisWrap.VariableFlowCH = false; - expMassFlowRate = 0.0; - expOutletTemp = 35.0; - thisWrap.adjustChillerHeaterEvapFlowTemp(*state, qEvaporator, evapMassFlowRate, evapOutletTemp, evapInletTemp); - EXPECT_NEAR(evapMassFlowRate, expMassFlowRate, allowedTolerance); - EXPECT_NEAR(evapOutletTemp, expOutletTemp, allowedTolerance); - - // Test 2a: Variable Flow, Load higher than max flow rate passed in, keep flow rate and adjust outlet temp - qEvaporator = 5000.0; - evapMassFlowRate = 1.0; - evapOutletTemp = 34.0; - evapInletTemp = 35.0; - thisWrap.VariableFlowCH = true; - expMassFlowRate = 1.0; - expOutletTemp = 33.80383; - thisWrap.adjustChillerHeaterEvapFlowTemp(*state, qEvaporator, evapMassFlowRate, evapOutletTemp, evapInletTemp); - EXPECT_NEAR(evapMassFlowRate, expMassFlowRate, allowedTolerance); - EXPECT_NEAR(evapOutletTemp, expOutletTemp, allowedTolerance); - - // Test 2b: Variable Flow, Load lower than max flow rate passed in, adjust flow rate and keep outlet temp - qEvaporator = 1045.0; - evapMassFlowRate = 1.0; - evapOutletTemp = 34.0; - evapInletTemp = 35.0; - thisWrap.VariableFlowCH = true; - expMassFlowRate = 0.25; - expOutletTemp = 34.0; - thisWrap.adjustChillerHeaterEvapFlowTemp(*state, qEvaporator, evapMassFlowRate, evapOutletTemp, evapInletTemp); - EXPECT_NEAR(evapMassFlowRate, expMassFlowRate, allowedTolerance); - EXPECT_NEAR(evapOutletTemp, expOutletTemp, allowedTolerance); - - // Test 3: Constant Flow--adjust outlet temperature - qEvaporator = 2090.0; - evapMassFlowRate = 1.0; - evapOutletTemp = 34.0; - evapInletTemp = 35.0; - thisWrap.VariableFlowCH = false; - expMassFlowRate = 1.0; - expOutletTemp = 34.5; - thisWrap.adjustChillerHeaterEvapFlowTemp(*state, qEvaporator, evapMassFlowRate, evapOutletTemp, evapInletTemp); - EXPECT_NEAR(evapMassFlowRate, expMassFlowRate, allowedTolerance); - EXPECT_NEAR(evapOutletTemp, expOutletTemp, allowedTolerance); -} - -TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_setChillerHeaterCondTemp) -{ - state->dataPlantCentralGSHP->Wrapper.allocate(1); - state->dataPlantCentralGSHP->Wrapper(1).WrapperComp.allocate(1); - state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater.allocate(1); - auto &thisWrap = state->dataPlantCentralGSHP->Wrapper(1); - auto &thisCH = thisWrap.ChillerHeater(1); - - Real64 functionAnswer; - Real64 expectedAnswer; - Real64 constexpr allowedTolerance = 0.001; - Real64 condEnterTemp; - Real64 condLeaveTemp; - int chillNum = 1; - - // Test 1: get the condenser entering temperature - functionAnswer = 0.0; - thisCH.CondMode = EnergyPlus::PlantCentralGSHP::CondenserModeTemperature::EnteringCondenser; - condEnterTemp = 55.5; - condLeaveTemp = 44.4; - expectedAnswer = 55.5; - functionAnswer = thisWrap.setChillerHeaterCondTemp(*state, chillNum, condEnterTemp, condLeaveTemp); - EXPECT_NEAR(functionAnswer, expectedAnswer, allowedTolerance); - - // Test 2: get the condenser leaving temperature - functionAnswer = 0.0; - thisCH.CondMode = EnergyPlus::PlantCentralGSHP::CondenserModeTemperature::LeavingCondenser; - condEnterTemp = 55.5; - condLeaveTemp = 44.4; - expectedAnswer = 44.4; - functionAnswer = thisWrap.setChillerHeaterCondTemp(*state, chillNum, condEnterTemp, condLeaveTemp); - EXPECT_NEAR(functionAnswer, expectedAnswer, allowedTolerance); -} - -TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_checkEvapOutletTemp) -{ - state->dataPlantCentralGSHP->Wrapper.allocate(1); - state->dataPlantCentralGSHP->Wrapper(1).WrapperComp.allocate(1); - state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater.allocate(1); - auto &thisWrap = state->dataPlantCentralGSHP->Wrapper(1); - auto &thisCH = thisWrap.ChillerHeater(1); - - int chNum = 1; - Real64 evapOutletTemp; - Real64 lowTempLimitEout; - Real64 evapInletTemp; - Real64 qEvaporator; - Real64 evapMassFlowRate; - Real64 Cp = 4000.0; - Real64 expQEvap; - Real64 expTout; - Real64 constexpr allowedTolerance = 0.0001; - - // Test 1a: Evaporator outlet temperature lower the evaporator outlet low temperature limit, adjust outlet and load - thisCH.EvapOutletNode.TempMin = 5.0; - evapInletTemp = 10.0; - evapOutletTemp = 8.0; - lowTempLimitEout = 9.0; - qEvaporator = 4000.0; - evapMassFlowRate = 0.5; - expQEvap = 2000.0; - expTout = 9.0; - PlantCentralGSHP::CurrentMode controlMode = PlantCentralGSHP::CurrentMode::CoolingDominant; - thisWrap.checkEvapOutletTemp(*state, chNum, evapOutletTemp, lowTempLimitEout, evapInletTemp, qEvaporator, evapMassFlowRate, Cp, controlMode); - EXPECT_NEAR(qEvaporator, expQEvap, allowedTolerance); - EXPECT_NEAR(evapOutletTemp, expTout, allowedTolerance); - - // Test 1b: Evaporator outlet temperature lower the evaporator outlet low temperature limit and inlet temp at or below lowTempLimitEout, - // zero flow and set outlet temperature to inlet temperature - thisCH.EvapOutletNode.TempMin = 5.0; - evapInletTemp = 8.0; - evapOutletTemp = 7.0; - lowTempLimitEout = 9.0; - qEvaporator = 2000.0; - evapMassFlowRate = 0.5; - expQEvap = 0.0; - expTout = 8.0; - thisWrap.checkEvapOutletTemp(*state, chNum, evapOutletTemp, lowTempLimitEout, evapInletTemp, qEvaporator, evapMassFlowRate, Cp, controlMode); - EXPECT_NEAR(qEvaporator, expQEvap, allowedTolerance); - EXPECT_NEAR(evapOutletTemp, expTout, allowedTolerance); - - // Test 2a: Evaporator outlet temperature lower the node minimum temperature limit, adjust outlet and load - thisCH.EvapOutletNode.TempMin = 9.0; - evapInletTemp = 10.0; - evapOutletTemp = 8.0; - lowTempLimitEout = 5.0; - qEvaporator = 4000.0; - evapMassFlowRate = 0.5; - expQEvap = 2000.0; - expTout = 9.0; - thisWrap.checkEvapOutletTemp(*state, chNum, evapOutletTemp, lowTempLimitEout, evapInletTemp, qEvaporator, evapMassFlowRate, Cp, controlMode); - EXPECT_NEAR(qEvaporator, expQEvap, allowedTolerance); - EXPECT_NEAR(evapOutletTemp, expTout, allowedTolerance); - - // Test 2b: Evaporator outlet temperature lower the node minimum temperature limit and inlet temp at or below node temperature limt, - // zero flow and set outlet temperature to inlet temperature - thisCH.EvapOutletNode.TempMin = 9.0; - evapInletTemp = 8.0; - evapOutletTemp = 7.0; - lowTempLimitEout = 5.0; - qEvaporator = 2000.0; - evapMassFlowRate = 0.5; - expQEvap = 0.0; - expTout = 8.0; - thisWrap.checkEvapOutletTemp(*state, chNum, evapOutletTemp, lowTempLimitEout, evapInletTemp, qEvaporator, evapMassFlowRate, Cp, controlMode); - EXPECT_NEAR(qEvaporator, expQEvap, allowedTolerance); - EXPECT_NEAR(evapOutletTemp, expTout, allowedTolerance); - - // Test 3: Everything is fine, no changes to anything - thisCH.EvapOutletNode.TempMin = 5.0; - evapInletTemp = 8.0; - evapOutletTemp = 6.0; - lowTempLimitEout = 5.0; - qEvaporator = 4000.0; - evapMassFlowRate = 0.5; - expQEvap = 4000.0; - expTout = 6.0; - thisWrap.checkEvapOutletTemp(*state, chNum, evapOutletTemp, lowTempLimitEout, evapInletTemp, qEvaporator, evapMassFlowRate, Cp, controlMode); - EXPECT_NEAR(qEvaporator, expQEvap, allowedTolerance); - EXPECT_NEAR(evapOutletTemp, expTout, allowedTolerance); -} - -TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_calcPLRAndCyclingRatio) -{ - state->dataPlantCentralGSHP->Wrapper.allocate(1); - state->dataPlantCentralGSHP->Wrapper(1).WrapperComp.allocate(1); - state->dataPlantCentralGSHP->Wrapper(1).ChillerHeater.allocate(1); - auto &thisWrap = state->dataPlantCentralGSHP->Wrapper(1); - - Real64 availChillerCap; - Real64 actualPartLoadRatio; - Real64 minPartLoadRatio; - Real64 maxPartLoadRatio; - Real64 qEvaporator; - Real64 frac; - Real64 expPLR; - Real64 expFrac; - Real64 expFalseLoad; - Real64 constexpr allowedTolerance = 0.0001; - - // Test 1: available chiller capacity less than zero (PLR should be zero, frac should be 1.0) - availChillerCap = -10000.0; - actualPartLoadRatio = -1.0; - minPartLoadRatio = 0.1; - maxPartLoadRatio = 1.0; - qEvaporator = 50000.0; - frac = -1.0; - expPLR = 0.0; - expFrac = 1.0; - expFalseLoad = 0.0; - state->dataPlantCentralGSHP->ChillerCyclingRatio = -1.0; - state->dataPlantCentralGSHP->ChillerPartLoadRatio = -1.0; - state->dataPlantCentralGSHP->ChillerFalseLoadRate = -1.0; - thisWrap.calcPLRAndCyclingRatio(*state, availChillerCap, actualPartLoadRatio, minPartLoadRatio, maxPartLoadRatio, qEvaporator, frac); - EXPECT_NEAR(frac, expFrac, allowedTolerance); - EXPECT_NEAR(actualPartLoadRatio, expPLR, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerCyclingRatio, expFrac, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerPartLoadRatio, expPLR, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerFalseLoadRate, expFalseLoad, allowedTolerance); - - // Test 2a: valid chiller capacity and evaporator load, negative minPLR - availChillerCap = 50000.0; - actualPartLoadRatio = -1.0; - minPartLoadRatio = -0.1; - maxPartLoadRatio = 1.0; - qEvaporator = 10000.0; - frac = -1.0; - expPLR = 0.2; - expFrac = 1.0; - expFalseLoad = 0.0; - state->dataPlantCentralGSHP->ChillerCyclingRatio = -1.0; - state->dataPlantCentralGSHP->ChillerPartLoadRatio = -1.0; - state->dataPlantCentralGSHP->ChillerFalseLoadRate = -1.0; - thisWrap.calcPLRAndCyclingRatio(*state, availChillerCap, actualPartLoadRatio, minPartLoadRatio, maxPartLoadRatio, qEvaporator, frac); - EXPECT_NEAR(frac, expFrac, allowedTolerance); - EXPECT_NEAR(actualPartLoadRatio, expPLR, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerCyclingRatio, expFrac, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerPartLoadRatio, expPLR, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerFalseLoadRate, expFalseLoad, allowedTolerance); - - // Test 2b: valid chiller capacity and evaporator load, actualPLR lower then minPLR - availChillerCap = 50000.0; - actualPartLoadRatio = -1.0; - minPartLoadRatio = 0.4; - maxPartLoadRatio = 1.0; - qEvaporator = 10000.0; - frac = -1.0; - expPLR = 0.4; - expFrac = 0.5; - expFalseLoad = 0.0; - state->dataPlantCentralGSHP->ChillerCyclingRatio = -1.0; - state->dataPlantCentralGSHP->ChillerPartLoadRatio = -1.0; - state->dataPlantCentralGSHP->ChillerFalseLoadRate = -1.0; - thisWrap.calcPLRAndCyclingRatio(*state, availChillerCap, actualPartLoadRatio, minPartLoadRatio, maxPartLoadRatio, qEvaporator, frac); - EXPECT_NEAR(frac, expFrac, allowedTolerance); - EXPECT_NEAR(actualPartLoadRatio, expPLR, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerCyclingRatio, expFrac, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerPartLoadRatio, expPLR, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerFalseLoadRate, expFalseLoad, allowedTolerance); - - // Test 2c: valid chiller capacity and evaporator load, actualPLR higher then minPLR - availChillerCap = 50000.0; - actualPartLoadRatio = -1.0; - minPartLoadRatio = 0.4; - maxPartLoadRatio = 1.0; - qEvaporator = 30000.0; - frac = -1.0; - expPLR = 0.6; - expFrac = 1.0; - expFalseLoad = 0.0; - state->dataPlantCentralGSHP->ChillerCyclingRatio = -1.0; - state->dataPlantCentralGSHP->ChillerPartLoadRatio = -1.0; - state->dataPlantCentralGSHP->ChillerFalseLoadRate = -1.0; - thisWrap.calcPLRAndCyclingRatio(*state, availChillerCap, actualPartLoadRatio, minPartLoadRatio, maxPartLoadRatio, qEvaporator, frac); - EXPECT_NEAR(frac, expFrac, allowedTolerance); - EXPECT_NEAR(actualPartLoadRatio, expPLR, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerCyclingRatio, expFrac, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerPartLoadRatio, expPLR, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerFalseLoadRate, expFalseLoad, allowedTolerance); - - // Test 2d: valid chiller capacity and evaporator load, actualPLR higher then maxPLR - availChillerCap = 50000.0; - actualPartLoadRatio = -1.0; - minPartLoadRatio = 0.4; - maxPartLoadRatio = 1.0; - qEvaporator = 60000.0; - frac = -1.0; - expPLR = 1.0; - expFrac = 1.0; - expFalseLoad = 0.0; - state->dataPlantCentralGSHP->ChillerCyclingRatio = -1.0; - state->dataPlantCentralGSHP->ChillerPartLoadRatio = -1.0; - state->dataPlantCentralGSHP->ChillerFalseLoadRate = -1.0; - thisWrap.calcPLRAndCyclingRatio(*state, availChillerCap, actualPartLoadRatio, minPartLoadRatio, maxPartLoadRatio, qEvaporator, frac); - EXPECT_NEAR(frac, expFrac, allowedTolerance); - EXPECT_NEAR(actualPartLoadRatio, expPLR, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerCyclingRatio, expFrac, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerPartLoadRatio, expPLR, allowedTolerance); - EXPECT_NEAR(state->dataPlantCentralGSHP->ChillerFalseLoadRate, expFalseLoad, allowedTolerance); -} diff --git a/tst/EnergyPlus/unit/PlantCentralHeatPumpSystem.unit.cc b/tst/EnergyPlus/unit/PlantCentralHeatPumpSystem.unit.cc new file mode 100644 index 00000000000..fdabdc6891c --- /dev/null +++ b/tst/EnergyPlus/unit/PlantCentralHeatPumpSystem.unit.cc @@ -0,0 +1,2522 @@ +// EnergyPlus, Copyright (c) 1996-present, The Board of Trustees of the University of Illinois, +// The Regents of the University of California, through Lawrence Berkeley National Laboratory +// (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge +// National Laboratory, managed by UT-Battelle, Alliance for Energy Innovation, LLC, and other +// contributors. All rights reserved. +// +// NOTICE: This Software was developed under funding from the U.S. Department of Energy and the +// U.S. Government consequently retains certain rights. As such, the U.S. Government has been +// granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, +// worldwide license in the Software to reproduce, distribute copies to the public, prepare +// derivative works, and perform publicly and display publicly, and to permit others to do so. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// (1) Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// (2) Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory, +// the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific prior +// written permission. +// +// (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form +// without changes from the version obtained under this License, or (ii) Licensee makes a +// reference solely to the software portion of its product, Licensee must refer to the +// software as "EnergyPlus version X" software, where "X" is the version number Licensee +// obtained under this License and may not use a different name for the software. Except as +// specifically required in this Section (4), Licensee shall not use in a company name, a +// product name, in advertising, publicity, or other promotional activities any name, trade +// name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly +// similar designation, without the U.S. Department of Energy's prior written consent. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +// EnergyPlus::PlantCentralHeatPumpSystem Unit Tests + +// Google Test Headers +#include + +// EnergyPlus Headers +#include "Fixtures/EnergyPlusFixture.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace EnergyPlus; + +namespace { + +using EnergyPlus::PlantCentralHeatPumpSystem::CurrentMode; + +struct LoopRouting +{ + Real64 cooling = 0.0; // Heat removed from the chilled-water loop [W] + Real64 heating = 0.0; // Heat added to the hot-water loop [W] + Real64 source = 0.0; // Positive rejects heat to source; negative extracts heat from source [W] +}; + +struct ModeEnergyContract +{ + CurrentMode mode = CurrentMode::Invalid; + Real64 qEvaporator = 0.0; + Real64 qCondenser = 0.0; + Real64 compressorPower = 0.0; + Real64 falseLoad = 0.0; + Real64 openMotorEfficiency = 1.0; + Real64 partLoadRatio = 0.0; + Real64 cyclingRatio = 0.0; + LoopRouting routing; +}; + +struct PartLoadContract +{ + Real64 availableCapacity = 0.0; + Real64 requestedLoad = 0.0; + Real64 deliveredLoad = 0.0; + Real64 minimumPartLoadRatio = 0.0; + Real64 maximumPartLoadRatio = 1.0; + Real64 reportedPartLoadRatio = 0.0; + Real64 cyclingRatio = 0.0; + Real64 curveEvaluationPartLoadRatio = 0.0; +}; + +struct FlowAllocationContract +{ + Real64 systemAvailableFlow = 0.0; + std::vector requestedFlows; + std::vector moduleMaximumFlows; + std::vector allocatedFlows; +}; + +constexpr Real64 contractTolerance = 1.0e-9; + +bool contractNear(Real64 const actual, Real64 const expected, Real64 const tolerance = contractTolerance) +{ + Real64 const scale = std::max({1.0, std::abs(actual), std::abs(expected)}); + return std::abs(actual - expected) <= tolerance * scale; +} + +std::string_view modeName(CurrentMode const mode) +{ + switch (mode) { + case CurrentMode::Off: + return "Off"; + case CurrentMode::CoolingOnly: + return "CoolingOnly"; + case CurrentMode::HeatingOnly: + return "HeatingOnly"; + case CurrentMode::HeatRecovery: + return "HeatRecovery"; + case CurrentMode::CoolingDominant: + return "CoolingDominant"; + case CurrentMode::HeatingDominant: + return "HeatingDominant"; + default: + return "Invalid"; + } +} + +::testing::AssertionResult checkModeEnergyContract(ModeEnergyContract const &point) +{ + auto fail = [&point](std::string_view const message) { return ::testing::AssertionFailure() << modeName(point.mode) << ": " << message; }; + + if (point.mode == CurrentMode::Invalid || point.mode == CurrentMode::Num) { + return fail("invalid operating mode"); + } + + std::array const values = {point.qEvaporator, + point.qCondenser, + point.compressorPower, + point.falseLoad, + point.openMotorEfficiency, + point.partLoadRatio, + point.cyclingRatio, + point.routing.cooling, + point.routing.heating, + point.routing.source}; + if (!std::all_of(values.begin(), values.end(), [](Real64 const value) { return std::isfinite(value); })) { + return fail("contract contains a non-finite value"); + } + if (point.qEvaporator < 0.0 || point.qCondenser < 0.0 || point.compressorPower < 0.0 || point.falseLoad < 0.0) { + return fail("heat-transfer rates, power, and false load must be nonnegative"); + } + if (point.openMotorEfficiency < 0.0 || point.openMotorEfficiency > 1.0) { + return fail("open motor efficiency is outside [0, 1]"); + } + + if (point.mode == CurrentMode::Off) { + Real64 const magnitude = point.qEvaporator + point.qCondenser + point.compressorPower + point.falseLoad + std::abs(point.routing.cooling) + + std::abs(point.routing.heating) + std::abs(point.routing.source) + std::abs(point.partLoadRatio) + + std::abs(point.cyclingRatio); + if (!contractNear(magnitude, 0.0)) { + return fail("off mode has nonzero heat transfer, power, routing, PLR, or cycling"); + } + return ::testing::AssertionSuccess(); + } + + if (point.compressorPower <= 0.0) { + return fail("an active Electric:EIR operating point must have positive compressor power"); + } + if (point.partLoadRatio <= 0.0 || point.partLoadRatio > 1.0) { + return fail("active-mode PLR is outside (0, 1]"); + } + if (point.cyclingRatio <= 0.0 || point.cyclingRatio > 1.0) { + return fail("active-mode cycling ratio is outside (0, 1]"); + } + + Real64 const refrigerantPower = point.compressorPower * point.openMotorEfficiency; + Real64 const moduleResidual = point.qCondenser - point.qEvaporator - refrigerantPower - point.falseLoad; + if (!contractNear(moduleResidual, 0.0)) { + return fail("module energy residual is " + std::to_string(moduleResidual) + " W"); + } + + if (point.routing.cooling < 0.0 || point.routing.heating < 0.0) { + return fail("useful cooling and heating routing must be nonnegative"); + } + + switch (point.mode) { + case CurrentMode::CoolingOnly: + if (!contractNear(point.routing.cooling, point.qEvaporator) || !contractNear(point.routing.heating, 0.0) || + !contractNear(point.routing.source, point.qCondenser)) { + return fail("cooling-only heat is not fully routed to chilled water and the Source loop"); + } + break; + case CurrentMode::HeatingOnly: + if (!contractNear(point.routing.cooling, 0.0) || !contractNear(point.routing.heating, point.qCondenser) || + !contractNear(point.routing.source, -point.qEvaporator)) { + return fail("heating-only heat is not fully routed from the Source loop to hot water"); + } + break; + case CurrentMode::HeatRecovery: + if (!contractNear(point.routing.cooling, point.qEvaporator) || !contractNear(point.routing.heating, point.qCondenser) || + !contractNear(point.routing.source, 0.0)) { + return fail("balanced heat recovery must route the complete evaporator and condenser loads with zero source transfer"); + } + break; + case CurrentMode::CoolingDominant: + if (point.routing.heating <= 0.0 || point.routing.heating >= point.qCondenser || !contractNear(point.routing.cooling, point.qEvaporator) || + !contractNear(point.routing.source, point.qCondenser - point.routing.heating)) { + return fail("cooling-dominant operation must recover part of condenser heat and reject the residual to source"); + } + break; + case CurrentMode::HeatingDominant: + if (point.routing.cooling <= 0.0 || point.routing.cooling >= point.qEvaporator || !contractNear(point.routing.heating, point.qCondenser) || + !contractNear(point.routing.source, -(point.qEvaporator - point.routing.cooling))) { + return fail("heating-dominant operation must preserve useful cooling and extract only the residual from source"); + } + break; + default: + return fail("unsupported operating mode"); + } + + Real64 const systemResidual = point.routing.heating + point.routing.source - point.routing.cooling - refrigerantPower - point.falseLoad; + if (!contractNear(systemResidual, 0.0)) { + return fail("three-loop routing energy residual is " + std::to_string(systemResidual) + " W"); + } + + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult checkPartLoadContract(PartLoadContract const &point) +{ + if (point.availableCapacity <= 0.0) { + return ::testing::AssertionFailure() << "available capacity must be positive"; + } + if (point.minimumPartLoadRatio <= 0.0 || point.maximumPartLoadRatio < point.minimumPartLoadRatio) { + return ::testing::AssertionFailure() << "invalid PLR bounds"; + } + + Real64 const requestedPLR = std::max(0.0, point.requestedLoad / point.availableCapacity); + Real64 const expectedPLR = std::clamp(requestedPLR, point.minimumPartLoadRatio, point.maximumPartLoadRatio); + Real64 const expectedCycling = requestedPLR < point.minimumPartLoadRatio ? requestedPLR / point.minimumPartLoadRatio : 1.0; + Real64 const expectedDeliveredLoad = std::min(point.requestedLoad, point.availableCapacity * point.maximumPartLoadRatio); + + if (!contractNear(point.reportedPartLoadRatio, expectedPLR)) { + return ::testing::AssertionFailure() << "reported PLR " << point.reportedPartLoadRatio << " does not equal final operating PLR " + << expectedPLR; + } + if (!contractNear(point.cyclingRatio, expectedCycling)) { + return ::testing::AssertionFailure() << "cycling ratio " << point.cyclingRatio << " does not equal " << expectedCycling; + } + if (!contractNear(point.deliveredLoad, expectedDeliveredLoad)) { + return ::testing::AssertionFailure() << "delivered load " << point.deliveredLoad << " does not equal " << expectedDeliveredLoad; + } + if (!contractNear(point.curveEvaluationPartLoadRatio, point.reportedPartLoadRatio)) { + return ::testing::AssertionFailure() << "EIRFPLR was evaluated at " << point.curveEvaluationPartLoadRatio << " instead of final reported PLR " + << point.reportedPartLoadRatio; + } + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult checkSequentialFlowAllocation(FlowAllocationContract const &flow) +{ + if (flow.requestedFlows.size() != flow.moduleMaximumFlows.size() || flow.requestedFlows.size() != flow.allocatedFlows.size()) { + return ::testing::AssertionFailure() << "flow vectors have different sizes"; + } + if (flow.systemAvailableFlow < 0.0) { + return ::testing::AssertionFailure() << "system available flow is negative"; + } + + Real64 remainingFlow = flow.systemAvailableFlow; + for (std::size_t module = 0; module < flow.allocatedFlows.size(); ++module) { + Real64 const expected = std::min({flow.requestedFlows[module], flow.moduleMaximumFlows[module], remainingFlow}); + if (!contractNear(flow.allocatedFlows[module], expected)) { + return ::testing::AssertionFailure() << "module " << module + 1 << " allocation " << flow.allocatedFlows[module] + << " does not equal sequentially available flow " << expected; + } + remainingFlow -= expected; + } + + Real64 const totalAllocated = std::accumulate(flow.allocatedFlows.begin(), flow.allocatedFlows.end(), 0.0); + if (totalAllocated > flow.systemAvailableFlow && !contractNear(totalAllocated, flow.systemAvailableFlow)) { + return ::testing::AssertionFailure() << "module flow sum " << totalAllocated << " exceeds system flow " << flow.systemAvailableFlow; + } + return ::testing::AssertionSuccess(); +} + +::testing::AssertionResult +checkLoopHeatTransfer(Real64 const reportedHeat, Real64 const massFlow, Real64 const specificHeat, Real64 const inletTemp, Real64 const outletTemp) +{ + if (reportedHeat < 0.0 || massFlow < 0.0 || specificHeat <= 0.0) { + return ::testing::AssertionFailure() << "invalid loop heat-transfer input"; + } + Real64 const nodeHeat = massFlow * specificHeat * std::abs(outletTemp - inletTemp); + if (!contractNear(reportedHeat, nodeHeat)) { + return ::testing::AssertionFailure() << "reported heat " << reportedHeat << " W does not match node heat " << nodeHeat << " W"; + } + return ::testing::AssertionSuccess(); +} + +std::string makeChillerHeaterValidationInput(Real64 const capacityRatio = 0.75, + Real64 const coolingOptimumPLR = 0.5, + Real64 const heatingOptimumPLR = 0.5, + Real64 const coolingMinimumPLR = 0.2) +{ + std::vector const lines{ + "ChillerHeaterPerformance:Electric:EIR,", + " Validation Module,", + " 10000,", + " 5.0,", + " 7.0,", + " 30.0,", + " 35.0,", + " " + std::to_string(capacityRatio) + ",", + " 1.0,", + " 7.0,", + " 50.0,", + " 30.0,", + " 3.0,", + " variableFlow,", + " 0.001,", + " 0.001,", + " 0.001,", + " 0.8,", + " LeavingCondenser,", + " Cooling Reference Curve,", + " Cooling Reference Curve,", + " Cooling PLR Curve,", + " " + std::to_string(coolingOptimumPLR) + ",", + " LeavingCondenser,", + " Heating Reference Curve,", + " Heating Reference Curve,", + " Heating PLR Curve,", + " " + std::to_string(heatingOptimumPLR) + ",", + " 1.0;", + + "Curve:Biquadratic,", + " Cooling Reference Curve,", + " 0.0, 0.0, 0.0, 0.0285714285714286, 0.0, 0.0,", + " -100.0, 100.0, -100.0, 100.0;", + + "Curve:Biquadratic,", + " Heating Reference Curve,", + " 0.0, 0.0, 0.0, 0.02, 0.0, 0.0,", + " -100.0, 100.0, -100.0, 100.0;", + + "Curve:Bicubic,", + " Cooling PLR Curve,", + " 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,", + " -100.0, 100.0,", + " " + std::to_string(coolingMinimumPLR) + ", 1.0;", + + "Curve:Quadratic,", + " Heating PLR Curve,", + " 1.0, 0.0, 0.0,", + " 0.3, 1.0;", + }; + return std::accumulate(lines.begin(), lines.end(), std::string(), [](std::string result, std::string const &line) { + result += line; + result += '\n'; + return result; + }); +} + +nlohmann::json makeChillerHeaterNativeJSON(bool const includeOptionalFields = true, bool const autosize = false) +{ + nlohmann::json performance = { + {"reference_cooling_mode_evaporator_capacity", autosize ? nlohmann::json("Autosize") : nlohmann::json(10000.0)}, + {"reference_cooling_mode_cop", 5.0}, + {"cooling_mode_cooling_capacity_function_of_temperature_curve_name", "Reference Temperature Curve"}, + {"cooling_mode_electric_input_to_cooling_output_ratio_function_of_temperature_curve_name", "Reference Temperature Curve"}, + {"cooling_mode_electric_input_to_cooling_output_ratio_function_of_part_load_ratio_curve_name", "Reference PLR Curve"}, + {"heating_mode_cooling_capacity_function_of_temperature_curve_name", "Reference Temperature Curve"}, + {"heating_mode_electric_input_to_cooling_output_ratio_function_of_temperature_curve_name", "Reference Temperature Curve"}, + {"heating_mode_electric_input_to_cooling_output_ratio_function_of_part_load_ratio_curve_name", "Reference PLR Curve"}, + }; + + if (includeOptionalFields) { + performance.update({ + {"reference_cooling_mode_leaving_chilled_water_temperature", 7.0}, + {"reference_cooling_mode_entering_condenser_fluid_temperature", 30.0}, + {"reference_cooling_mode_leaving_condenser_water_temperature", 35.0}, + {"reference_heating_mode_cooling_capacity_ratio", 0.75}, + {"reference_heating_mode_cooling_power_input_ratio", 1.0}, + {"reference_heating_mode_leaving_chilled_water_temperature", 7.0}, + {"reference_heating_mode_leaving_condenser_water_temperature", 50.0}, + {"reference_heating_mode_entering_condenser_fluid_temperature", 30.0}, + {"heating_mode_entering_chilled_water_temperature_low_limit", 3.0}, + {"chilled_water_flow_mode_type", "VariableFlow"}, + {"design_chilled_water_flow_rate", autosize ? nlohmann::json("Autosize") : nlohmann::json(0.001)}, + {"design_condenser_water_flow_rate", autosize ? nlohmann::json("Autosize") : nlohmann::json(0.001)}, + {"design_hot_water_flow_rate", 0.001}, + {"compressor_motor_efficiency", 0.8}, + {"cooling_mode_temperature_curve_condenser_water_independent_variable", "LeavingCondenser"}, + {"cooling_mode_cooling_capacity_optimum_part_load_ratio", 0.5}, + {"heating_mode_temperature_curve_condenser_water_independent_variable", "EnteringCondenser"}, + {"heating_mode_cooling_capacity_optimum_part_load_ratio", 0.5}, + {"sizing_factor", 1.2}, + }); + } + + nlohmann::json const temperatureCurve = { + {"coefficient1_constant", 1.0}, + {"coefficient2_x", 0.0}, + {"coefficient3_x_2", 0.0}, + {"coefficient4_y", 0.0}, + {"coefficient5_y_2", 0.0}, + {"coefficient6_x_y", 0.0}, + {"minimum_value_of_x", -100.0}, + {"maximum_value_of_x", 100.0}, + {"minimum_value_of_y", -100.0}, + {"maximum_value_of_y", 100.0}, + }; + nlohmann::json const partLoadCurve = { + {"coefficient1_constant", 1.0}, + {"coefficient2_x", 0.0}, + {"coefficient3_x_2", 0.0}, + {"minimum_value_of_x", 0.0}, + {"maximum_value_of_x", 1.0}, + }; + + return { + {"ChillerHeaterPerformance:Electric:EIR", {{"Native Mixed Case Module", performance}}}, + {"Curve:Biquadratic", {{"Reference Temperature Curve", temperatureCurve}}}, + {"Curve:Quadratic", {{"Reference PLR Curve", partLoadCurve}}}, + }; +} + +struct FixedModuleGroupFieldKeys +{ + std::string performanceObjectType; + std::string performanceName; + std::string controlScheduleName; + std::string numberOfModules; +}; + +FixedModuleGroupFieldKeys fixedModuleGroupFieldKeys(int const groupNumber) +{ + std::string const suffix = std::to_string(groupNumber); + FixedModuleGroupFieldKeys keys{ + "chiller_heater_modules_performance_component_object_type_" + suffix, + "chiller_heater_modules_performance_component_name_" + suffix, + "chiller_heater_modules_control_schedule_name_" + suffix, + "number_of_chiller_heater_modules_" + suffix, + }; + if (groupNumber == 3) { + keys.performanceObjectType = "chiller_heater_performance_component_object_type_3"; + keys.performanceName = "chiller_heater_performance_component_name_3"; + } else if (groupNumber == 5) { + keys.performanceName = "chiller_heater_models_performance_component_name_5"; + } else if (groupNumber == 11) { + keys.controlScheduleName = "chiller_heater_module_control_schedule_name_11"; + } else if (groupNumber == 18) { + keys.controlScheduleName = "chiller_heater_modules_control_control_schedule_name_18"; + } + return keys; +} + +nlohmann::json makeNativeSystemJSON(bool const useAllGroups) +{ + auto epJSON = makeChillerHeaterNativeJSON(); + epJSON["ScheduleTypeLimits"]["Fraction"] = { + {"lower_limit_value", 0.0}, + {"upper_limit_value", 1.0}, + {"numeric_type", "Continuous"}, + {"unit_type", "Dimensionless"}, + }; + epJSON["Schedule:Constant"] = { + {"Ancillary Schedule", {{"schedule_type_limits_name", "Fraction"}, {"hourly_value", 0.5}}}, + {"Module Schedule", {{"schedule_type_limits_name", "Fraction"}, {"hourly_value", 1.0}}}, + {"Module Schedule 1", {{"schedule_type_limits_name", "Fraction"}, {"hourly_value", 1.0}}}, + {"Module Schedule 3", {{"schedule_type_limits_name", "Fraction"}, {"hourly_value", 1.0}}}, + {"Module Schedule 4", {{"schedule_type_limits_name", "Fraction"}, {"hourly_value", 1.0}}}, + {"Module Schedule 6", {{"schedule_type_limits_name", "Fraction"}, {"hourly_value", 1.0}}}, + }; + + nlohmann::json system = { + {"cooling_loop_inlet_node_name", useAllGroups ? "Cooling Inlet" : "Sparse Cooling Inlet"}, + {"cooling_loop_outlet_node_name", useAllGroups ? "Cooling Outlet" : "Sparse Cooling Outlet"}, + {"source_loop_inlet_node_name", useAllGroups ? "Source Inlet" : "Sparse Source Inlet"}, + {"source_loop_outlet_node_name", useAllGroups ? "Source Outlet" : "Sparse Source Outlet"}, + {"heating_loop_inlet_node_name", useAllGroups ? "Heating Inlet" : "Sparse Heating Inlet"}, + {"heating_loop_outlet_node_name", useAllGroups ? "Heating Outlet" : "Sparse Heating Outlet"}, + {"ancillary_power", 25.0}, + {"ancillary_operation_schedule_name", "Ancillary Schedule"}, + }; + + auto addGroup = [&system](int const groupNumber, int const count, std::string_view const scheduleName) { + auto const keys = fixedModuleGroupFieldKeys(groupNumber); + system[keys.performanceObjectType] = "ChillerHeaterPerformance:Electric:EIR"; + system[keys.performanceName] = "Native Mixed Case Module"; + system[keys.numberOfModules] = count; + if (!scheduleName.empty()) { + system[keys.controlScheduleName] = scheduleName; + } + }; + + if (useAllGroups) { + for (int group = 1; group <= 20; ++group) { + addGroup(group, 1, "Module Schedule"); + } + epJSON["CentralHeatPumpSystem"]["Native Many Groups Wrapper"] = std::move(system); + } else { + addGroup(1, 1, "Module Schedule 1"); + addGroup(2, 2, ""); + addGroup(3, 1, "Module Schedule 3"); + addGroup(4, 1, "Module Schedule 4"); + addGroup(5, 1, "Missing Module Schedule"); + addGroup(6, 2, "Module Schedule 6"); + epJSON["CentralHeatPumpSystem"]["Native Sparse Wrapper"] = std::move(system); + } + return epJSON; +} + +std::string makeAllModuleGroupsIDF() +{ + std::string idf = R"IDF( +CentralHeatPumpSystem, + Native Many Groups Wrapper, + Cooling Inlet, + Cooling Outlet, + Source Inlet, + Source Outlet, + Heating Inlet, + Heating Outlet, + 25.0, + Ancillary Schedule, +)IDF"; + for (int group = 1; group <= 20; ++group) { + idf += " ChillerHeaterPerformance:Electric:EIR,\n"; + idf += " Native Mixed Case Module,\n"; + idf += " Module Schedule,\n"; + idf += group == 20 ? " 1;\n" : " 1,\n"; + } + idf += R"IDF( +ScheduleTypeLimits, + Fraction, + 0.0, + 1.0, + Continuous, + Dimensionless; +Schedule:Constant, + Ancillary Schedule, + Fraction, + 0.5; +Schedule:Constant, + Module Schedule, + Fraction, + 1.0; +ChillerHeaterPerformance:Electric:EIR, + Native Mixed Case Module, + 10000, + 5.0, + 7.0, + 30.0, + 35.0, + 0.75, + 1.0, + 7.0, + 50.0, + 30.0, + 3.0, + variableFlow, + 0.001, + 0.001, + 0.001, + 0.8, + LeavingCondenser, + Reference Temperature Curve, + Reference Temperature Curve, + Reference PLR Curve, + 0.5, + EnteringCondenser, + Reference Temperature Curve, + Reference Temperature Curve, + Reference PLR Curve, + 0.5, + 1.2; +Curve:Biquadratic, + Reference Temperature Curve, + 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, + -100.0, 100.0, -100.0, 100.0; +Curve:Quadratic, + Reference PLR Curve, + 1.0, 0.0, 0.0, + 0.0, 1.0; +)IDF"; + return idf; +} +} // namespace + +TEST_F(EnergyPlusFixture, ChillerHeater_Autosize) +{ + state->init_state(*state); + // Allocate one system with One module (=distinct ChillerHeaterPerformance:Electric:EIR) + // but with a number of identical number module of 2 in CentralHeatPumpSystem + state->dataPlantCentralHeatPumpSystem->systems.resize(1); + + state->dataPlantCentralHeatPumpSystem->systems[0].modules.resize(2); + + state->dataPlantCentralHeatPumpSystem->performanceDefinitions.resize(1); + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].constantFlow = false; + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].variableFlow = true; + + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].sizingFactor = 1.2; + + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].referenceCoolingCapacity = DataSizing::AutoSize; + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].referenceCoolingCapacityWasAutoSized = true; + + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].designEvaporatorVolFlowRate = DataSizing::AutoSize; + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].designEvaporatorVolFlowRateWasAutoSized = true; + + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].designCondenserVolFlowRate = DataSizing::AutoSize; + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].designCondenserVolFlowRateWasAutoSized = true; + + // Needed for calcs + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].referenceCoolingCOP = 1.5; + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].compressorMotorEfficiency = 0.98; + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].coolingReferenceCondenserInletTemp = 29.4; + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].heatingToCoolingCapacityRatio = 0.74; + state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0].heatingToCoolingPowerRatio = 1.38; + + // Both modules share one retained immutable performance definition. + auto const &performance = state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0]; + auto *availabilitySchedule = Sched::GetScheduleAlwaysOn(*state); + state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].initialize(performance, availabilitySchedule); + state->dataPlantCentralHeatPumpSystem->systems[0].modules[1].initialize(performance, availabilitySchedule); + auto &module1 = state->dataPlantCentralHeatPumpSystem->systems[0].modules[0]; + auto &module2 = state->dataPlantCentralHeatPumpSystem->systems[0].modules[1]; + EXPECT_EQ(&performance, module1.performance); + EXPECT_EQ(&performance, module2.performance); + EXPECT_EQ(availabilitySchedule, module1.availabilitySchedule); + EXPECT_EQ(availabilitySchedule, module2.availabilitySchedule); + module1.result.qEvaporator = 100.0; + EXPECT_DOUBLE_EQ(0.0, module2.result.qEvaporator); + + state->dataPlnt->PlantLoop.allocate(2); + state->dataSize->PlantSizData.allocate(2); + + // Chilled Water Loop + int PltSizNum = 1; + state->dataPlnt->PlantLoop(PltSizNum).PlantSizNum = 1; + state->dataPlnt->PlantLoop(PltSizNum).FluidName = "WATER"; + state->dataPlnt->PlantLoop(PltSizNum).glycol = Fluid::GetWater(*state); + state->dataSize->PlantSizData(PltSizNum).DesVolFlowRate = 1.0; + state->dataSize->PlantSizData(PltSizNum).DeltaT = 10.0; + state->dataSize->PlantSizData(PltSizNum).LoopType = DataSizing::TypeOfPlantLoop::Cooling; + // Assign to the system + state->dataPlantCentralHeatPumpSystem->systems[0].coolingPlantLoc.loopNum = PltSizNum; + PlantUtilities::SetPlantLocationLinks(*state, state->dataPlantCentralHeatPumpSystem->systems[0].coolingPlantLoc); + + // Condenser Loop + int PltSizCondNum = 2; + state->dataPlnt->PlantLoop(PltSizCondNum).PlantSizNum = PltSizCondNum; + state->dataPlnt->PlantLoop(PltSizCondNum).FluidName = "WATER"; + state->dataPlnt->PlantLoop(PltSizCondNum).glycol = Fluid::GetWater(*state); + state->dataSize->PlantSizData(PltSizCondNum).DeltaT = 5.6; + state->dataSize->PlantSizData(PltSizCondNum).DesVolFlowRate = 1.0; + state->dataSize->PlantSizData(PltSizCondNum).LoopType = DataSizing::TypeOfPlantLoop::Condenser; + // Assign to the system + state->dataPlantCentralHeatPumpSystem->systems[0].sourcePlantLoc.loopNum = PltSizCondNum; + PlantUtilities::SetPlantLocationLinks(*state, state->dataPlantCentralHeatPumpSystem->systems[0].sourcePlantLoc); + state->dataPlantCentralHeatPumpSystem->systems[0].coolingInletNodeNum = 1; + state->dataPlantCentralHeatPumpSystem->systems[0].heatingInletNodeNum = 2; + state->dataPlantCentralHeatPumpSystem->systems[0].sourceInletNodeNum = 3; + + // Calculate expected values + Real64 rho_evap = state->dataPlnt->PlantLoop(PltSizNum).glycol->getDensity(*state, Constant::CWInitConvTemp, "ChillerHeater_Autosize_TEST"); + + Real64 Cp_evap = state->dataPlnt->PlantLoop(PltSizNum).glycol->getSpecificHeat(*state, Constant::CWInitConvTemp, "ChillerHeater_Autosize_TEST"); + + Real64 rho_cond = + state->dataPlnt->PlantLoop(PltSizCondNum) + .glycol->getDensity(*state, + state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].performanceData().coolingReferenceCondenserInletTemp, + "ChillerHeater_Autosize_TEST"); + + Real64 Cp_cond = state->dataPlnt->PlantLoop(PltSizCondNum) + .glycol->getSpecificHeat( + *state, + state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].performanceData().coolingReferenceCondenserInletTemp, + "ChillerHeater_Autosize_TEST"); + + // Note: Each individual chiller heater module is sized to be capable of supporting the total load on the system + + // Flow is multiplied by the sizingFactor + Real64 expectedEvaporatorVolFlowRate = state->dataSize->PlantSizData(PltSizNum).DesVolFlowRate * + state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].performanceData().sizingFactor; + + Real64 expectedReferenceCoolingCapacity = rho_evap * Cp_evap * expectedEvaporatorVolFlowRate * state->dataSize->PlantSizData(PltSizNum).DeltaT; + + Real64 expectedCondenserVolFlowRate = + expectedReferenceCoolingCapacity * + (1.0 + (1.0 / state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].performanceData().referenceCoolingCOP) * + state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].performanceData().compressorMotorEfficiency) / + (rho_cond * Cp_cond * state->dataSize->PlantSizData(PltSizCondNum).DeltaT); + + // now call sizing routine + state->dataPlnt->PlantFirstSizesOkayToFinalize = true; + state->dataPlantCentralHeatPumpSystem->systems[0].size(*state); + + // Autosized values are system-specific module sizing state; the shared definition remains unchanged. + EXPECT_DOUBLE_EQ(expectedEvaporatorVolFlowRate, state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].sizing.designEvaporatorVolFlowRate); + EXPECT_DOUBLE_EQ(expectedReferenceCoolingCapacity, state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].sizing.referenceCoolingCapacity); + + EXPECT_DOUBLE_EQ(expectedCondenserVolFlowRate, state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].sizing.designCondenserVolFlowRate); + EXPECT_DOUBLE_EQ(expectedCondenserVolFlowRate, state->dataPlantCentralHeatPumpSystem->systems[0].modules[1].sizing.designCondenserVolFlowRate); + + // Ensure that stuff that other quantities that depends on referenceCoolingCapacity are also initialized properly + // Heating Cap + Real64 expectedReferenceHeatingCapacity = + expectedReferenceCoolingCapacity * + state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].performanceData().heatingToCoolingCapacityRatio; + EXPECT_DOUBLE_EQ(expectedReferenceHeatingCapacity, state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].sizing.referenceHeatingCapacity); + + // Heating Power: Calc cooling Power = Cap / COP, and multiply by ratio + Real64 expectedReferenceHeatingPower = + (expectedReferenceCoolingCapacity / state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].performanceData().referenceCoolingCOP) * + state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].performanceData().heatingToCoolingPowerRatio; + EXPECT_DOUBLE_EQ(expectedReferenceHeatingPower, state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].sizing.referenceHeatingPower); + + // Heating COP = Heating Cap / Heating Power + Real64 expectedReferenceHeatingCOP = expectedReferenceHeatingCapacity / expectedReferenceHeatingPower; + EXPECT_DOUBLE_EQ(expectedReferenceHeatingCOP, state->dataPlantCentralHeatPumpSystem->systems[0].modules[0].sizing.referenceHeatingCOP); + + ASSERT_EQ(3u, state->dataSize->CompDesWaterFlow.size()); + EXPECT_DOUBLE_EQ(2.0 * expectedEvaporatorVolFlowRate, state->dataSize->CompDesWaterFlow(1).DesVolFlowRate); + EXPECT_DOUBLE_EQ(0.0, state->dataSize->CompDesWaterFlow(2).DesVolFlowRate); + EXPECT_DOUBLE_EQ(2.0 * std::max(expectedEvaporatorVolFlowRate, expectedCondenserVolFlowRate), + state->dataSize->CompDesWaterFlow(3).DesVolFlowRate); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_DesignCapacityReportingUsesConnectionsAndStageability) +{ + state->init_state(*state); + + PlantCentralHeatPumpSystem::CentralHeatPumpSystem system; + system.coolingPlantLoc.loopNum = 1; + system.heatingPlantLoc.loopNum = 2; + system.sourcePlantLoc.loopNum = 3; + system.modules.resize(2); + + PlantCentralHeatPumpSystem::PerformanceData performance1; + PlantCentralHeatPumpSystem::PerformanceData performance2; + system.modules[0].initialize(performance1, nullptr); + system.modules[1].initialize(performance2, nullptr); + + auto &module1 = system.modules[0]; + module1.sizing.referenceCoolingCapacity = 10000.0; + performance1.referenceCoolingCOP = 5.0; + performance1.coolingMinimumPartLoadRatio = 0.20; + performance1.coolingMaximumPartLoadRatio = 1.00; + performance1.coolingOptimumPartLoadRatio = 0.80; + module1.sizing.referenceHeatingCapacity = 8000.0; + module1.sizing.referenceHeatingPower = 2000.0; + performance1.heatingMinimumPartLoadRatio = 0.25; + performance1.heatingMaximumPartLoadRatio = 1.10; + performance1.heatingOptimumPartLoadRatio = 0.75; + performance1.compressorMotorEfficiency = 0.80; + + auto &module2 = system.modules[1]; + module2.sizing.referenceCoolingCapacity = 6000.0; + performance2.referenceCoolingCOP = 3.0; + performance2.coolingMinimumPartLoadRatio = 0.10; + performance2.coolingMaximumPartLoadRatio = 1.20; + performance2.coolingOptimumPartLoadRatio = 0.70; + module2.sizing.referenceHeatingCapacity = 12000.0; + module2.sizing.referenceHeatingPower = 3000.0; + performance2.heatingMinimumPartLoadRatio = 0.30; + performance2.heatingMaximumPartLoadRatio = 0.90; + performance2.heatingOptimumPartLoadRatio = 0.60; + performance2.compressorMotorEfficiency = 0.50; + + Real64 maximumLoad = 0.0; + Real64 minimumLoad = 0.0; + Real64 optimumLoad = 0.0; + PlantLocation calledFromLocation; + + calledFromLocation.loopNum = system.coolingPlantLoc.loopNum; + system.getDesignCapacities(*state, calledFromLocation, maximumLoad, minimumLoad, optimumLoad); + EXPECT_DOUBLE_EQ(17200.0, maximumLoad); + EXPECT_DOUBLE_EQ(600.0, minimumLoad); + EXPECT_DOUBLE_EQ(12200.0, optimumLoad); + + calledFromLocation.loopNum = system.heatingPlantLoc.loopNum; + system.getDesignCapacities(*state, calledFromLocation, maximumLoad, minimumLoad, optimumLoad); + EXPECT_DOUBLE_EQ(22710.0, maximumLoad); + EXPECT_DOUBLE_EQ(2400.0, minimumLoad); + EXPECT_DOUBLE_EQ(15300.0, optimumLoad); + + calledFromLocation.loopNum = system.sourcePlantLoc.loopNum; + system.getDesignCapacities(*state, calledFromLocation, maximumLoad, minimumLoad, optimumLoad); + EXPECT_DOUBLE_EQ(20000.0, maximumLoad); + EXPECT_DOUBLE_EQ(700.0, minimumLoad); + EXPECT_DOUBLE_EQ(14180.0, optimumLoad); + + performance2.heatingMaximumPartLoadRatio = 1.20; + performance2.heatingOptimumPartLoadRatio = 0.80; + system.getDesignCapacities(*state, calledFromLocation, maximumLoad, minimumLoad, optimumLoad); + EXPECT_DOUBLE_EQ(23200.0, maximumLoad); + EXPECT_DOUBLE_EQ(700.0, minimumLoad); + EXPECT_DOUBLE_EQ(15600.0, optimumLoad); + + calledFromLocation.loopNum = 4; + system.getDesignCapacities(*state, calledFromLocation, maximumLoad, minimumLoad, optimumLoad); + EXPECT_DOUBLE_EQ(0.0, maximumLoad); + EXPECT_DOUBLE_EQ(0.0, minimumLoad); + EXPECT_DOUBLE_EQ(0.0, optimumLoad); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_SourceOnlySizingUsesSourcePlantSizingData) +{ + state->init_state(*state); + + state->dataPlnt->PlantLoop.allocate(3); + state->dataSize->PlantSizData.allocate(1); + auto *water = Fluid::GetWater(*state); + ASSERT_NE(nullptr, water); + + state->dataPlnt->PlantLoop(1).PlantSizNum = 0; + state->dataPlnt->PlantLoop(1).glycol = water; + state->dataPlnt->PlantLoop(2).PlantSizNum = 1; + state->dataPlnt->PlantLoop(2).glycol = water; + state->dataPlnt->PlantLoop(3).PlantSizNum = 0; + state->dataPlnt->PlantLoop(3).glycol = water; + state->dataSize->PlantSizData(1).LoopType = DataSizing::TypeOfPlantLoop::Condenser; + state->dataSize->PlantSizData(1).DesVolFlowRate = 0.002; + state->dataSize->PlantSizData(1).DeltaT = 5.0; + + PlantCentralHeatPumpSystem::CentralHeatPumpSystem system; + system.coolingPlantLoc.loopNum = 1; + system.sourcePlantLoc.loopNum = 2; + system.heatingPlantLoc.loopNum = 3; + PlantUtilities::SetPlantLocationLinks(*state, system.coolingPlantLoc); + PlantUtilities::SetPlantLocationLinks(*state, system.sourcePlantLoc); + PlantUtilities::SetPlantLocationLinks(*state, system.heatingPlantLoc); + system.coolingInletNodeNum = 11; + system.heatingInletNodeNum = 12; + system.sourceInletNodeNum = 13; + system.modules.resize(1); + + PlantCentralHeatPumpSystem::PerformanceData performance; + system.modules[0].initialize(performance, nullptr); + auto &module = system.modules[0]; + performance.Name = "SOURCE SIZING ONLY"; + performance.sizingFactor = 1.0; + module.sizing.designEvaporatorVolFlowRate = 0.001; + module.sizing.referenceCoolingCapacity = 10000.0; + performance.referenceCoolingCOP = 4.0; + performance.compressorMotorEfficiency = 0.80; + performance.coolingReferenceCondenserInletTemp = 30.0; + module.sizing.designCondenserVolFlowRate = DataSizing::AutoSize; + performance.designCondenserVolFlowRateWasAutoSized = true; + performance.designHeatingVolFlowRate = 0.0007; + + Real64 const sourceDensity = + water->getDensity(*state, performance.coolingReferenceCondenserInletTemp, "PlantCentralHeatPumpSystem source sizing test"); + Real64 const sourceSpecificHeat = + water->getSpecificHeat(*state, performance.coolingReferenceCondenserInletTemp, "PlantCentralHeatPumpSystem source sizing test"); + Real64 const expectedSourceCondenserFlow = module.sizing.referenceCoolingCapacity * + (1.0 + performance.compressorMotorEfficiency / performance.referenceCoolingCOP) / + (state->dataSize->PlantSizData(1).DeltaT * sourceSpecificHeat * sourceDensity); + + state->dataPlnt->PlantFirstSizesOkayToFinalize = true; + system.size(*state); + + EXPECT_NEAR(expectedSourceCondenserFlow, module.sizing.designCondenserVolFlowRate, 1.0e-12); + EXPECT_DOUBLE_EQ(module.sizing.designEvaporatorVolFlowRate, module.sizing.temporaryEvaporatorVolFlowRate); + EXPECT_NEAR(expectedSourceCondenserFlow, module.sizing.temporaryCondenserVolFlowRate, 1.0e-12); + ASSERT_EQ(3u, state->dataSize->CompDesWaterFlow.size()); + EXPECT_DOUBLE_EQ(module.sizing.designEvaporatorVolFlowRate, state->dataSize->CompDesWaterFlow(1).DesVolFlowRate); + EXPECT_DOUBLE_EQ(performance.designHeatingVolFlowRate, state->dataSize->CompDesWaterFlow(2).DesVolFlowRate); + EXPECT_NEAR(std::max(module.sizing.designEvaporatorVolFlowRate, expectedSourceCondenserFlow), + state->dataSize->CompDesWaterFlow(3).DesVolFlowRate, + 1.0e-12); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_HardSizedWarningsRetainCalculatedDesignValues) +{ + state->init_state(*state); + OutputReportPredefined::SetPredefinedTables(*state); + + state->dataPlnt->PlantLoop.allocate(3); + state->dataSize->PlantSizData.allocate(2); + auto *water = Fluid::GetWater(*state); + ASSERT_NE(nullptr, water); + for (int loopNum = 1; loopNum <= 3; ++loopNum) { + state->dataPlnt->PlantLoop(loopNum).glycol = water; + } + state->dataPlnt->PlantLoop(1).PlantSizNum = 1; + state->dataPlnt->PlantLoop(2).PlantSizNum = 2; + state->dataSize->PlantSizData(1).LoopType = DataSizing::TypeOfPlantLoop::Cooling; + state->dataSize->PlantSizData(1).DesVolFlowRate = 0.010; + state->dataSize->PlantSizData(1).DeltaT = 6.0; + state->dataSize->PlantSizData(2).LoopType = DataSizing::TypeOfPlantLoop::Condenser; + state->dataSize->PlantSizData(2).DesVolFlowRate = 0.020; + state->dataSize->PlantSizData(2).DeltaT = 5.0; + + PlantCentralHeatPumpSystem::CentralHeatPumpSystem system; + system.coolingPlantLoc.loopNum = 1; + system.sourcePlantLoc.loopNum = 2; + system.heatingPlantLoc.loopNum = 3; + PlantUtilities::SetPlantLocationLinks(*state, system.coolingPlantLoc); + PlantUtilities::SetPlantLocationLinks(*state, system.sourcePlantLoc); + PlantUtilities::SetPlantLocationLinks(*state, system.heatingPlantLoc); + system.coolingInletNodeNum = 21; + system.heatingInletNodeNum = 22; + system.sourceInletNodeNum = 23; + system.modules.resize(1); + + PlantCentralHeatPumpSystem::PerformanceData performance; + system.modules[0].initialize(performance, nullptr); + auto &module = system.modules[0]; + performance.Name = "HARD SIZED MODULE"; + performance.sizingFactor = 1.0; + module.sizing.designEvaporatorVolFlowRate = 0.001; + module.sizing.referenceCoolingCapacity = 1000.0; + performance.referenceCoolingCOP = 5.0; + performance.compressorMotorEfficiency = 1.0; + performance.coolingReferenceCondenserInletTemp = 30.0; + module.sizing.designCondenserVolFlowRate = 0.001; + performance.designHeatingVolFlowRate = 0.002; + + state->dataGlobal->DisplayExtraWarnings = true; + state->dataSize->AutoVsHardSizingThreshold = 0.01; + state->dataPlnt->PlantFirstSizesOkayToFinalize = true; + state->dataPlnt->PlantFinalSizesOkayToReport = true; + system.size(*state); + + EXPECT_DOUBLE_EQ(0.001, module.sizing.designEvaporatorVolFlowRate); + EXPECT_DOUBLE_EQ(1000.0, module.sizing.referenceCoolingCapacity); + EXPECT_DOUBLE_EQ(0.001, module.sizing.designCondenserVolFlowRate); + EXPECT_DOUBLE_EQ(0.001, module.sizing.temporaryEvaporatorVolFlowRate); + EXPECT_DOUBLE_EQ(0.001, module.sizing.temporaryCondenserVolFlowRate); + EXPECT_TRUE(compare_err_stream_substring("User-Specified Reference Chilled Water Flow Rate", false)); + EXPECT_TRUE(compare_err_stream_substring("User-Specified Reference Capacity", false)); + EXPECT_TRUE(compare_err_stream_substring("User-Specified Reference Condenser Water Flow Rate", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_Control_Schedule_fix) +{ + std::string const idf_objects = delimited_string({ + + "Schedule:Compact,", + "Always1, !-Name", + "On/Off, !-Schedule Type Limits Name", + "Through: 12/31, !-Field 1", + "For: AllDays, !-Field 2", + "Until: 24:00, 1; !-Field 3 ", + + "CentralHeatPumpSystem,", + "ChW_Loop HeatPump1, !-Name", + "ChW_Loop HeatPump1 ChW Inlet, !-Cooling Loop Inlet Node Name", + "ChW_Loop HeatPump1 ChW Outlet, !-Cooling Loop Outlet Node Name", + "ChW_Loop HeatPump1 Cnd Inlet, !-Source Loop Inlet Node Name", + "ChW_Loop HeatPump1 Cnd Outlet, !-Source Loop Outlet Node Name", + "ChW_Loop HeatPump1 HHW Inlet, !-Heating Loop Inlet Node Name", + "ChW_Loop HeatPump1 HHW Outlet, !-Heating Loop Outlet Node Name", + "460, !-Ancillary Power{W}", + ", !-Ancillary Operation Schedule Name", + "ChillerHeaterPerformance:Electric:EIR, !-Chiller Heater Modules Performance Component Object Type 1", + "ChW_Loop HeatPump1 Module, !-Chiller Heater Modules Performance Component Name 1", + "Always_1_typo, !-Chiller Heater Modules Control Schedule Name 1", + "2; !-Number of Chiller Heater Modules 1", + + "ChillerHeaterPerformance:Electric:EIR,", + " ChW_Loop HeatPump1 Module, !- Name", + " autosize, !- Reference Cooling Mode Evaporator Capacity {W}", + " 1.5, !- Reference Cooling Mode COP {W/W}", + " 6.67, !- Reference Cooling Mode Leaving Chilled Water Temperature {C}", + " 29.4, !- Reference Cooling Mode Entering Condenser Fluid Temperature {C}", + " 35.0, !- Reference Cooling Mode Leaving Condenser Water Temperature {C}", + " 0.74, !- Reference Heating Mode Cooling Capacity Ratio", + " 0.925, !- Reference Heating Mode Cooling Power Input Ratio", + " 6.67, !- Reference Heating Mode Leaving Chilled Water Temperature {C}", + " 60, !- Reference Heating Mode Leaving Condenser Water Temperature {C}", + " 29.4, !- Reference Heating Mode Entering Condenser Fluid Temperature {C}", + " 5, !- Heating Mode Entering Chilled Water Temperature Low Limit {C}", + " variableFlow, !- Chilled Water Flow Mode Type", + " autosize, !- Design Chilled Water Flow Rate {m3/s}", + " autosize, !- Design Condenser Water Flow Rate {m3/s}", + " 0.01684, !- Design Hot water Flow Rate {m3/s}", + " 1, !- Compressor Motor Efficiency", + " EnteringCondenser, !- Cooling Mode Temperature Curve Condenser Water Independent Variable", + " ChillerHeaterClgCapFT, !- Cooling Mode Cooling Capacity Function of Temperature Curve Name", + " ChillerHeaterClgEIRFT, !- Cooling Mode Electric Input to Cooling Output Ratio Function of Temperature Curve Name", + " ChillerHeaterClgEIRFPLR, !- Cooling Mode Electric Input to Cooling Output Ratio Function of Part Load Ratio Curve Name", + " 1, !- Cooling Mode Cooling Capacity Optimum Part Load Ratio", + " LeavingCondenser, !- Heating Mode Temperature Curve Condenser Water Independent Variable", + " ChillerHeaterHtgCapFT, !- Heating Mode Cooling Capacity Function of Temperature Curve Name", + " ChillerHeaterHtgEIRFT, !- Heating Mode Electric Input to Cooling Output Ratio Function of Temperature Curve Name", + " ChillerHeaterHtgEIRFPLR, !- Heating Mode Electric Input to Cooling Output Ratio Function of Part Load Ratio Curve Name", + " 1, !- Heating Mode Cooling Capacity Optimum Part Load Ratio", + " 1; !- Sizing Factor", + + "Curve:Biquadratic,", + " ChillerHeaterClgCapFT, !- Name", + " 0.950829, !- Coefficient1 Constant", + " 3.419327E-02, !- Coefficient2 x", + " 2.66642E-04, !- Coefficient3 x**2", + " -1.733397E-03, !- Coefficient4 y", + " -1.762417E-04, !- Coefficient5 y**2", + " -3.69198E-05, !- Coefficient6 x*y", + " 4.44, !- Minimum Value of x", + " 12.78, !- Maximum Value of x", + " 12.78, !- Minimum Value of y", + " 29.44, !- Maximum Value of y", + " , !- Minimum Curve Output", + " , !- Maximum Curve Output", + " Temperature, !- Input Unit Type for X", + " Temperature, !- Input Unit Type for Y", + " Dimensionless; !- Output Unit Type", + + "Curve:Biquadratic,", + " ChillerHeaterHtgCapFT, !- Name", + " 0.9415266, !- Coefficient1 Constant", + " 5.527431E-02, !- Coefficient2 x", + " 3.573558E-04, !- Coefficient3 x**2", + " 1.258391E-03, !- Coefficient4 y", + " -6.420546E-05, !- Coefficient5 y**2", + " -5.350989E-04, !- Coefficient6 x*y", + " 4.44, !- Minimum Value of x", + " 15.56, !- Maximum Value of x", + " 35, !- Minimum Value of y", + " 57.22, !- Maximum Value of y", + " , !- Minimum Curve Output", + " , !- Maximum Curve Output", + " Temperature, !- Input Unit Type for X", + " Temperature, !- Input Unit Type for Y", + " Dimensionless; !- Output Unit Type", + + "Curve:Biquadratic,", + " ChillerHeaterClgEIRFT, !- Name", + " 0.7362431, !- Coefficient1 Constant", + " 2.136491E-02, !- Coefficient2 x", + " 3.638909E-04, !- Coefficient3 x**2", + " -4.284947E-03, !- Coefficient4 y", + " 3.389817E-04, !- Coefficient5 y**2", + " -3.632396E-04, !- Coefficient6 x*y", + " 4.44, !- Minimum Value of x", + " 12.78, !- Maximum Value of x", + " 12.78, !- Minimum Value of y", + " 29.44, !- Maximum Value of y", + " , !- Minimum Curve Output", + " , !- Maximum Curve Output", + " Temperature, !- Input Unit Type for X", + " Temperature, !- Input Unit Type for Y", + " Dimensionless; !- Output Unit Type", + + "Curve:Biquadratic,", + " ChillerHeaterHtgEIRFT, !- Name", + " 0.2286246, !- Coefficient1 Constant", + " 2.498714E-02, !- Coefficient2 x", + " -1.267106E-05, !- Coefficient3 x**2", + " 9.327184E-03, !- Coefficient4 y", + " 5.892037E-05, !- Coefficient5 y**2", + " -3.268512E-04, !- Coefficient6 x*y", + " 4.44, !- Minimum Value of x", + " 15.56, !- Maximum Value of x", + " 35.0, !- Minimum Value of y", + " 57.22, !- Maximum Value of y", + " , !- Minimum Curve Output", + " , !- Maximum Curve Output", + " Temperature, !- Input Unit Type for X", + " Temperature, !- Input Unit Type for Y", + " Dimensionless; !- Output Unit Type", + + " Curve:Cubic,", + " ChillerHeaterClgEIRFPLR, !- Name", + " 0.0, !- Coefficient1 Constant", + " 1.22895, !- Coefficient2 x", + " -0.751383, !- Coefficient3 x**2", + " 0.517396, !- Coefficient4 x**3", + " 0.2, !- Minimum Value of x", + " 1; !- Maximum Value of x", + + "Curve:Cubic,", + " ChillerHeaterHtgEIRFPLR, !- Name", + " 0.0, !- Coefficient1 Constant", + " 1.12853, !- Coefficient2 x", + " -0.0264962, !- Coefficient3 x**2", + " -0.103811, !- Coefficient4 x**3", + " 0.3, !- Minimum Value of x", + " 1; !- Maximum Value of x" + + }); + + ASSERT_TRUE(process_idf(idf_objects)); + + state->init_state(*state); + + // May not need for direct system input processing call (need when caling factory) + state->dataPlantCentralHeatPumpSystem->getSystemInputFlag = true; + + // call the central heat pump system input processing function + PlantCentralHeatPumpSystem::getCentralHeatPumpSystemInput(*state); + + // verify that under this scenario of not finding a schedule match, ScheduleAlwaysOn is the treated default + EXPECT_EQ(state->dataPlantCentralHeatPumpSystem->systems[0].ancillaryPowerSched, Sched::GetScheduleAlwaysOn(*state)); + EXPECT_TRUE(state->dataPlantCentralHeatPumpSystem->systems[0].allModulesVariableFlow); + auto const &module = state->dataPlantCentralHeatPumpSystem->systems[0].modules[0]; + EXPECT_TRUE(module.variableFlow); + EXPECT_EQ(module.availabilitySchedule, Sched::GetScheduleAlwaysOn(*state)); + EXPECT_FALSE(module.performanceData().constantFlow); + ASSERT_FALSE(state->dataPlantCentralHeatPumpSystem->performanceDefinitions.empty()); + EXPECT_EQ(1u, state->dataPlantCentralHeatPumpSystem->performanceDefinitions.size()); + EXPECT_EQ(1, state->dataPlantCentralHeatPumpSystem->numPerformanceReferences); + EXPECT_EQ(&state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0], module.performance); + + // verify that node names were processed correctly + EXPECT_EQ(state->dataBranchNodeConnections->NumOfNodeConnections, 6); + + EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(1).NodeName, "CHW_LOOP HEATPUMP1 CHW INLET"); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(1).ConnectionType, Node::ConnectionType::Inlet); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(1).FluidStream, Node::CompFluidStream::Primary); + + EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(2).NodeName, "CHW_LOOP HEATPUMP1 CHW OUTLET"); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(2).ConnectionType, Node::ConnectionType::Outlet); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(2).FluidStream, Node::CompFluidStream::Primary); + + EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(3).NodeName, "CHW_LOOP HEATPUMP1 CND INLET"); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(3).ConnectionType, Node::ConnectionType::Inlet); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(3).FluidStream, Node::CompFluidStream::Secondary); + + EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(4).NodeName, "CHW_LOOP HEATPUMP1 CND OUTLET"); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(4).ConnectionType, Node::ConnectionType::Outlet); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(4).FluidStream, Node::CompFluidStream::Secondary); + + EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(5).NodeName, "CHW_LOOP HEATPUMP1 HHW INLET"); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(5).ConnectionType, Node::ConnectionType::Inlet); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(5).FluidStream, Node::CompFluidStream::Tertiary); + + EXPECT_EQ(state->dataBranchNodeConnections->NodeConnections(6).NodeName, "CHW_LOOP HEATPUMP1 HHW OUTLET"); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(6).ConnectionType, Node::ConnectionType::Outlet); + EXPECT_ENUM_EQ(state->dataBranchNodeConnections->NodeConnections(6).FluidStream, Node::CompFluidStream::Tertiary); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_FlowModeResolutionIsSystemScopedAndMutuallyExclusive) +{ + state->init_state(*state); + + PlantCentralHeatPumpSystem::CentralHeatPumpSystem mixedSystem; + mixedSystem.Name = "MIXED WRAPPER"; + mixedSystem.modules.resize(2); + PlantCentralHeatPumpSystem::PerformanceData constantPerformance; + PlantCentralHeatPumpSystem::PerformanceData variablePerformance; + constantPerformance.constantFlow = true; + variablePerformance.variableFlow = true; + mixedSystem.modules[0].initialize(constantPerformance, nullptr); + mixedSystem.modules[1].initialize(variablePerformance, nullptr); + + mixedSystem.resolveFlowMode(*state); + + EXPECT_FALSE(mixedSystem.allModulesVariableFlow); + for (auto const &module : mixedSystem.modules) { + EXPECT_FALSE(module.variableFlow); + } + EXPECT_TRUE(constantPerformance.constantFlow); + EXPECT_TRUE(variablePerformance.variableFlow); + EXPECT_TRUE(compare_err_stream_substring("MIXED WRAPPER contains both constant-flow and variable-flow", true)); + + PlantCentralHeatPumpSystem::CentralHeatPumpSystem variableSystem; + variableSystem.Name = "VARIABLE WRAPPER"; + variableSystem.modules.resize(2); + PlantCentralHeatPumpSystem::PerformanceData variablePerformance1; + PlantCentralHeatPumpSystem::PerformanceData variablePerformance2; + variablePerformance1.variableFlow = true; + variablePerformance2.variableFlow = true; + variableSystem.modules[0].initialize(variablePerformance1, nullptr); + variableSystem.modules[1].initialize(variablePerformance2, nullptr); + + variableSystem.resolveFlowMode(*state); + + EXPECT_TRUE(variableSystem.allModulesVariableFlow); + for (auto const &module : variableSystem.modules) { + EXPECT_TRUE(module.variableFlow); + } + EXPECT_FALSE(has_err_output()); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_StandardContainersResetToEmpty) +{ + auto &data = *state->dataPlantCentralHeatPumpSystem; + + EXPECT_TRUE(data.systems.empty()); + EXPECT_TRUE(data.performanceDefinitions.empty()); + + data.systems.resize(1); + data.systems.front().modules.resize(2); + data.performanceDefinitions.resize(2); + data.numPerformanceReferences = 2; + + data.clear_state(); + + EXPECT_TRUE(data.systems.empty()); + EXPECT_TRUE(data.performanceDefinitions.empty()); + EXPECT_EQ(0, data.numPerformanceReferences); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_NativePerformanceInputReadsCompleteObjectAndMarksItUsed) +{ + ASSERT_TRUE(process_json(makeChillerHeaterNativeJSON(true, true))); + state->init_state(*state); + + EXPECT_NO_THROW(PlantCentralHeatPumpSystem::getPerformanceInput(*state)); + EXPECT_FALSE(has_err_output()); + + ASSERT_FALSE(state->dataPlantCentralHeatPumpSystem->performanceDefinitions.empty()); + auto const &performance = state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0]; + EXPECT_EQ("NATIVE MIXED CASE MODULE", performance.Name); + EXPECT_EQ(PlantCentralHeatPumpSystem::CondenserTemperatureMode::LeavingCondenser, performance.coolingCondenserTemperatureMode); + EXPECT_EQ(PlantCentralHeatPumpSystem::CondenserTemperatureMode::EnteringCondenser, performance.heatingCondenserTemperatureMode); + EXPECT_FALSE(performance.constantFlow); + EXPECT_TRUE(performance.variableFlow); + EXPECT_EQ(DataSizing::AutoSize, performance.referenceCoolingCapacity); + EXPECT_TRUE(performance.referenceCoolingCapacityWasAutoSized); + EXPECT_EQ(DataSizing::AutoSize, performance.designEvaporatorVolFlowRate); + EXPECT_TRUE(performance.designEvaporatorVolFlowRateWasAutoSized); + EXPECT_EQ(DataSizing::AutoSize, performance.designCondenserVolFlowRate); + EXPECT_TRUE(performance.designCondenserVolFlowRateWasAutoSized); + EXPECT_DOUBLE_EQ(0.001, performance.designHeatingVolFlowRate); + EXPECT_DOUBLE_EQ(0.8, performance.compressorMotorEfficiency); + EXPECT_DOUBLE_EQ(0.5, performance.coolingOptimumPartLoadRatio); + EXPECT_DOUBLE_EQ(0.5, performance.heatingOptimumPartLoadRatio); + EXPECT_DOUBLE_EQ(1.2, performance.sizingFactor); + EXPECT_GT(performance.coolingCapacityTemperatureCurveIndex, 0); + EXPECT_GT(performance.heatingEIRPartLoadCurveIndex, 0); + + state->dataGlobal->DisplayUnusedObjects = true; + state->dataGlobal->DisplayAllWarnings = true; + state->dataInputProcessing->inputProcessor->reportOrphanRecordObjects(*state); + EXPECT_FALSE(compare_err_stream_substring("Native Mixed Case Module", true, false)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_NativePerformanceInputAppliesSchemaDefaults) +{ + ASSERT_TRUE(process_json(makeChillerHeaterNativeJSON(false))); + state->init_state(*state); + + EXPECT_NO_THROW(PlantCentralHeatPumpSystem::getPerformanceInput(*state)); + EXPECT_FALSE(has_err_output()); + + auto const &performance = state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0]; + EXPECT_TRUE(performance.constantFlow); + EXPECT_FALSE(performance.variableFlow); + EXPECT_EQ(PlantCentralHeatPumpSystem::CondenserTemperatureMode::EnteringCondenser, performance.coolingCondenserTemperatureMode); + EXPECT_EQ(PlantCentralHeatPumpSystem::CondenserTemperatureMode::LeavingCondenser, performance.heatingCondenserTemperatureMode); + EXPECT_DOUBLE_EQ(6.67, performance.coolingReferenceEvaporatorOutletTemp); + EXPECT_DOUBLE_EQ(29.44, performance.coolingReferenceCondenserInletTemp); + EXPECT_DOUBLE_EQ(35.0, performance.coolingReferenceCondenserOutletTemp); + EXPECT_DOUBLE_EQ(0.75, performance.heatingToCoolingCapacityRatio); + EXPECT_DOUBLE_EQ(1.38, performance.heatingToCoolingPowerRatio); + EXPECT_DOUBLE_EQ(6.67, performance.heatingReferenceEvaporatorOutletTemp); + EXPECT_DOUBLE_EQ(49.0, performance.heatingReferenceCondenserOutletTemp); + EXPECT_DOUBLE_EQ(29.44, performance.heatingReferenceCondenserInletTemp); + EXPECT_DOUBLE_EQ(12.22, performance.minimumEvaporatorOutletTemp); + EXPECT_DOUBLE_EQ(0.0, performance.designEvaporatorVolFlowRate); + EXPECT_DOUBLE_EQ(0.0, performance.designCondenserVolFlowRate); + EXPECT_DOUBLE_EQ(0.0, performance.designHeatingVolFlowRate); + EXPECT_DOUBLE_EQ(1.0, performance.compressorMotorEfficiency); + EXPECT_DOUBLE_EQ(1.0, performance.coolingOptimumPartLoadRatio); + EXPECT_DOUBLE_EQ(1.0, performance.heatingOptimumPartLoadRatio); + EXPECT_DOUBLE_EQ(1.0, performance.sizingFactor); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_NativePerformanceInputReportsReferencedField) +{ + auto epJSON = makeChillerHeaterNativeJSON(); + epJSON["ChillerHeaterPerformance:Electric:EIR"]["Native Mixed Case Module"]["cooling_mode_cooling_capacity_function_of_temperature_curve_name"] = + "Missing Temperature Curve"; + ASSERT_TRUE(process_json(epJSON)); + state->init_state(*state); + + EXPECT_THROW(PlantCentralHeatPumpSystem::getPerformanceInput(*state), std::runtime_error); + EXPECT_TRUE(compare_err_stream_substring("Cooling Mode Cooling Capacity Function of Temperature Curve Name=MISSING TEMPERATURE CURVE", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_NativePerformanceInputRejectsCaseInsensitiveDuplicateNames) +{ + auto epJSON = makeChillerHeaterNativeJSON(); + auto &objects = epJSON["ChillerHeaterPerformance:Electric:EIR"]; + objects["native mixed case module"] = objects["Native Mixed Case Module"]; + ASSERT_TRUE(process_json(epJSON)); + state->init_state(*state); + + EXPECT_THROW(PlantCentralHeatPumpSystem::getPerformanceInput(*state), std::runtime_error); + EXPECT_TRUE(compare_err_stream_substring("duplicate name.", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_IDFAndNativePerformanceInputsProduceEquivalentState) +{ + std::string const idf = delimited_string({ + "ChillerHeaterPerformance:Electric:EIR,", + " Native Mixed Case Module,", + " 10000,", + " 5.0,", + " 7.0,", + " 30.0,", + " 35.0,", + " 0.75,", + " 1.0,", + " 7.0,", + " 50.0,", + " 30.0,", + " 3.0,", + " variableFlow,", + " 0.001,", + " 0.001,", + " 0.001,", + " 0.8,", + " LeavingCondenser,", + " Reference Temperature Curve,", + " Reference Temperature Curve,", + " Reference PLR Curve,", + " 0.5,", + " EnteringCondenser,", + " Reference Temperature Curve,", + " Reference Temperature Curve,", + " Reference PLR Curve,", + " 0.5,", + " 1.2;", + + "Curve:Biquadratic,", + " Reference Temperature Curve,", + " 1.0, 0.0, 0.0, 0.0, 0.0, 0.0,", + " -100.0, 100.0, -100.0, 100.0;", + + "Curve:Quadratic,", + " Reference PLR Curve,", + " 1.0, 0.0, 0.0,", + " 0.0, 1.0;", + }); + + ASSERT_TRUE(process_idf(idf)); + state->init_state(*state); + EXPECT_NO_THROW(PlantCentralHeatPumpSystem::getPerformanceInput(*state)); + EXPECT_FALSE(has_err_output()); + PlantCentralHeatPumpSystem::PerformanceData const idfPerformance = state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0]; + + state->dataPlantCentralHeatPumpSystem->clear_state(); + state->dataCurveManager->clear_state(); + state->dataInputProcessing->clear_state(); + state->dataIPShortCut->clear_state(); + EXPECT_TRUE(compare_err_stream("", true)); + + ASSERT_TRUE(process_json(makeChillerHeaterNativeJSON())); + Curve::GetCurveInput(*state); + EXPECT_NO_THROW(PlantCentralHeatPumpSystem::getPerformanceInput(*state)); + EXPECT_TRUE(compare_err_stream("", true)); + auto const &nativePerformance = state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0]; + + EXPECT_EQ(idfPerformance.Name, nativePerformance.Name); + EXPECT_EQ(idfPerformance.coolingCondenserTemperatureMode, nativePerformance.coolingCondenserTemperatureMode); + EXPECT_EQ(idfPerformance.heatingCondenserTemperatureMode, nativePerformance.heatingCondenserTemperatureMode); + EXPECT_EQ(idfPerformance.constantFlow, nativePerformance.constantFlow); + EXPECT_EQ(idfPerformance.variableFlow, nativePerformance.variableFlow); + EXPECT_EQ(idfPerformance.referenceCoolingCapacityWasAutoSized, nativePerformance.referenceCoolingCapacityWasAutoSized); + EXPECT_EQ(idfPerformance.designEvaporatorVolFlowRateWasAutoSized, nativePerformance.designEvaporatorVolFlowRateWasAutoSized); + EXPECT_EQ(idfPerformance.designCondenserVolFlowRateWasAutoSized, nativePerformance.designCondenserVolFlowRateWasAutoSized); + EXPECT_DOUBLE_EQ(idfPerformance.referenceCoolingCapacity, nativePerformance.referenceCoolingCapacity); + EXPECT_DOUBLE_EQ(idfPerformance.referenceCoolingCOP, nativePerformance.referenceCoolingCOP); + EXPECT_DOUBLE_EQ(idfPerformance.coolingReferenceEvaporatorOutletTemp, nativePerformance.coolingReferenceEvaporatorOutletTemp); + EXPECT_DOUBLE_EQ(idfPerformance.coolingReferenceCondenserInletTemp, nativePerformance.coolingReferenceCondenserInletTemp); + EXPECT_DOUBLE_EQ(idfPerformance.coolingReferenceCondenserOutletTemp, nativePerformance.coolingReferenceCondenserOutletTemp); + EXPECT_DOUBLE_EQ(idfPerformance.heatingToCoolingCapacityRatio, nativePerformance.heatingToCoolingCapacityRatio); + EXPECT_DOUBLE_EQ(idfPerformance.heatingToCoolingPowerRatio, nativePerformance.heatingToCoolingPowerRatio); + EXPECT_DOUBLE_EQ(idfPerformance.heatingReferenceEvaporatorOutletTemp, nativePerformance.heatingReferenceEvaporatorOutletTemp); + EXPECT_DOUBLE_EQ(idfPerformance.heatingReferenceCondenserOutletTemp, nativePerformance.heatingReferenceCondenserOutletTemp); + EXPECT_DOUBLE_EQ(idfPerformance.heatingReferenceCondenserInletTemp, nativePerformance.heatingReferenceCondenserInletTemp); + EXPECT_DOUBLE_EQ(idfPerformance.minimumEvaporatorOutletTemp, nativePerformance.minimumEvaporatorOutletTemp); + EXPECT_DOUBLE_EQ(idfPerformance.designEvaporatorVolFlowRate, nativePerformance.designEvaporatorVolFlowRate); + EXPECT_DOUBLE_EQ(idfPerformance.designCondenserVolFlowRate, nativePerformance.designCondenserVolFlowRate); + EXPECT_DOUBLE_EQ(idfPerformance.designHeatingVolFlowRate, nativePerformance.designHeatingVolFlowRate); + EXPECT_DOUBLE_EQ(idfPerformance.compressorMotorEfficiency, nativePerformance.compressorMotorEfficiency); + EXPECT_DOUBLE_EQ(idfPerformance.coolingOptimumPartLoadRatio, nativePerformance.coolingOptimumPartLoadRatio); + EXPECT_DOUBLE_EQ(idfPerformance.heatingOptimumPartLoadRatio, nativePerformance.heatingOptimumPartLoadRatio); + EXPECT_DOUBLE_EQ(idfPerformance.sizingFactor, nativePerformance.sizingFactor); + EXPECT_DOUBLE_EQ(idfPerformance.coolingMinimumPartLoadRatio, nativePerformance.coolingMinimumPartLoadRatio); + EXPECT_DOUBLE_EQ(idfPerformance.coolingMaximumPartLoadRatio, nativePerformance.coolingMaximumPartLoadRatio); + EXPECT_DOUBLE_EQ(idfPerformance.heatingMinimumPartLoadRatio, nativePerformance.heatingMinimumPartLoadRatio); + EXPECT_DOUBLE_EQ(idfPerformance.heatingMaximumPartLoadRatio, nativePerformance.heatingMaximumPartLoadRatio); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_IDFAndNativeSystemInputsProduceEquivalentStateForAllFixedGroups) +{ + ASSERT_TRUE(process_idf(makeAllModuleGroupsIDF())); + state->init_state(*state); + EXPECT_NO_THROW(PlantCentralHeatPumpSystem::getCentralHeatPumpSystemInput(*state)); + EXPECT_TRUE(compare_err_stream("", true)); + + ASSERT_EQ(1u, state->dataPlantCentralHeatPumpSystem->systems.size()); + ASSERT_EQ(1u, state->dataPlantCentralHeatPumpSystem->performanceDefinitions.size()); + EXPECT_EQ(20, state->dataPlantCentralHeatPumpSystem->numPerformanceReferences); + auto const &idfSystem = state->dataPlantCentralHeatPumpSystem->systems[0]; + ASSERT_EQ(20u, idfSystem.modules.size()); + std::array const idfNodeNames = { + state->dataLoopNodes->NodeID(idfSystem.coolingInletNodeNum), + state->dataLoopNodes->NodeID(idfSystem.coolingOutletNodeNum), + state->dataLoopNodes->NodeID(idfSystem.sourceInletNodeNum), + state->dataLoopNodes->NodeID(idfSystem.sourceOutletNodeNum), + state->dataLoopNodes->NodeID(idfSystem.heatingInletNodeNum), + state->dataLoopNodes->NodeID(idfSystem.heatingOutletNodeNum), + }; + std::vector idfPerformanceNames; + std::vector idfScheduleNames; + for (auto const &module : idfSystem.modules) { + idfPerformanceNames.push_back(module.name()); + ASSERT_NE(nullptr, module.availabilitySchedule); + idfScheduleNames.push_back(module.availabilitySchedule->Name); + } + std::string const idfName = idfSystem.Name; + std::string const idfAncillaryScheduleName = idfSystem.ancillaryPowerSched->Name; + Real64 const idfancillaryPower = idfSystem.ancillaryPower; + bool const idfVariableFlow = idfSystem.allModulesVariableFlow; + + state->dataPlantCentralHeatPumpSystem->clear_state(); + state->dataCurveManager->clear_state(); + state->dataInputProcessing->clear_state(); + state->dataIPShortCut->clear_state(); + EXPECT_TRUE(compare_err_stream("", true)); + + ASSERT_TRUE(process_json(makeNativeSystemJSON(true))); + Curve::GetCurveInput(*state); + EXPECT_NO_THROW(PlantCentralHeatPumpSystem::getCentralHeatPumpSystemInput(*state)); + EXPECT_TRUE(compare_err_stream("", true)); + + ASSERT_EQ(1u, state->dataPlantCentralHeatPumpSystem->systems.size()); + ASSERT_EQ(1u, state->dataPlantCentralHeatPumpSystem->performanceDefinitions.size()); + EXPECT_EQ(20, state->dataPlantCentralHeatPumpSystem->numPerformanceReferences); + auto const &nativeSystem = state->dataPlantCentralHeatPumpSystem->systems[0]; + ASSERT_EQ(20u, nativeSystem.modules.size()); + std::array const nativeNodeNames = { + state->dataLoopNodes->NodeID(nativeSystem.coolingInletNodeNum), + state->dataLoopNodes->NodeID(nativeSystem.coolingOutletNodeNum), + state->dataLoopNodes->NodeID(nativeSystem.sourceInletNodeNum), + state->dataLoopNodes->NodeID(nativeSystem.sourceOutletNodeNum), + state->dataLoopNodes->NodeID(nativeSystem.heatingInletNodeNum), + state->dataLoopNodes->NodeID(nativeSystem.heatingOutletNodeNum), + }; + std::vector nativePerformanceNames; + std::vector nativeScheduleNames; + for (auto const &module : nativeSystem.modules) { + EXPECT_EQ(&state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0], module.performance); + nativePerformanceNames.push_back(module.name()); + ASSERT_NE(nullptr, module.availabilitySchedule); + nativeScheduleNames.push_back(module.availabilitySchedule->Name); + } + + EXPECT_EQ(idfName, nativeSystem.Name); + EXPECT_EQ(idfNodeNames, nativeNodeNames); + EXPECT_EQ(idfPerformanceNames, nativePerformanceNames); + EXPECT_EQ(idfScheduleNames, nativeScheduleNames); + EXPECT_EQ(idfAncillaryScheduleName, nativeSystem.ancillaryPowerSched->Name); + EXPECT_DOUBLE_EQ(idfancillaryPower, nativeSystem.ancillaryPower); + EXPECT_EQ(idfVariableFlow, nativeSystem.allModulesVariableFlow); + EXPECT_EQ(6, state->dataBranchNodeConnections->NumOfNodeConnections); + + state->dataGlobal->DisplayUnusedObjects = true; + state->dataGlobal->DisplayAllWarnings = true; + state->dataInputProcessing->inputProcessor->reportOrphanRecordObjects(*state); + EXPECT_FALSE(compare_err_stream_substring("Object=CentralHeatPumpSystem=Native Many Groups Wrapper", true, false)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_NativeSystemInputReadsFixedGroupsAndSchedules) +{ + ASSERT_TRUE(process_json(makeNativeSystemJSON(false))); + state->init_state(*state); + EXPECT_NO_THROW(PlantCentralHeatPumpSystem::getCentralHeatPumpSystemInput(*state)); + + ASSERT_EQ(1u, state->dataPlantCentralHeatPumpSystem->systems.size()); + ASSERT_EQ(1u, state->dataPlantCentralHeatPumpSystem->performanceDefinitions.size()); + EXPECT_EQ(6, state->dataPlantCentralHeatPumpSystem->numPerformanceReferences); + auto const &system = state->dataPlantCentralHeatPumpSystem->systems[0]; + EXPECT_EQ("NATIVE SPARSE WRAPPER", system.Name); + EXPECT_DOUBLE_EQ(25.0, system.ancillaryPower); + ASSERT_NE(nullptr, system.ancillaryPowerSched); + EXPECT_EQ("ANCILLARY SCHEDULE", system.ancillaryPowerSched->Name); + ASSERT_EQ(8u, system.modules.size()); + + auto *alwaysOn = Sched::GetScheduleAlwaysOn(*state); + std::array const expectedScheduleNames = { + "MODULE SCHEDULE 1", + alwaysOn->Name, + alwaysOn->Name, + "MODULE SCHEDULE 3", + "MODULE SCHEDULE 4", + alwaysOn->Name, + "MODULE SCHEDULE 6", + "MODULE SCHEDULE 6", + }; + for (std::size_t moduleIndex = 0; moduleIndex < system.modules.size(); ++moduleIndex) { + auto const &module = system.modules[moduleIndex]; + EXPECT_EQ(&state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0], module.performance); + ASSERT_NE(nullptr, module.availabilitySchedule); + EXPECT_EQ(expectedScheduleNames[moduleIndex], module.availabilitySchedule->Name); + } + EXPECT_EQ(6, state->dataBranchNodeConnections->NumOfNodeConnections); + EXPECT_TRUE(compare_err_stream_substring("MISSING MODULE SCHEDULE", false)); + EXPECT_TRUE(compare_err_stream_substring("the AlwaysOn schedule will be used", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_PerformanceReferencesRemainStableAcrossVectorBinding) +{ + auto epJSON = makeNativeSystemJSON(false); + auto &performanceObjects = epJSON["ChillerHeaterPerformance:Electric:EIR"]; + performanceObjects["Second Native Module"] = performanceObjects.at("Native Mixed Case Module"); + auto &systemObject = epJSON["CentralHeatPumpSystem"]["Native Sparse Wrapper"]; + systemObject[fixedModuleGroupFieldKeys(6).performanceName] = "Second Native Module"; + + ASSERT_TRUE(process_json(epJSON)); + state->init_state(*state); + EXPECT_NO_THROW(PlantCentralHeatPumpSystem::getCentralHeatPumpSystemInput(*state)); + + auto const &data = *state->dataPlantCentralHeatPumpSystem; + ASSERT_EQ(2u, data.performanceDefinitions.size()); + auto const firstPerformance = std::find_if(data.performanceDefinitions.begin(), data.performanceDefinitions.end(), [](auto const &performance) { + return performance.Name == "NATIVE MIXED CASE MODULE"; + }); + auto const secondPerformance = std::find_if(data.performanceDefinitions.begin(), data.performanceDefinitions.end(), [](auto const &performance) { + return performance.Name == "SECOND NATIVE MODULE"; + }); + ASSERT_NE(data.performanceDefinitions.end(), firstPerformance); + ASSERT_NE(data.performanceDefinitions.end(), secondPerformance); + ASSERT_NE(&*firstPerformance, &*secondPerformance); + + ASSERT_EQ(1u, data.systems.size()); + auto const &modules = data.systems.front().modules; + ASSERT_EQ(8u, modules.size()); + EXPECT_EQ(&*firstPerformance, modules.front().performance); + EXPECT_EQ(&*firstPerformance, modules[5].performance); + EXPECT_EQ(&*secondPerformance, modules[6].performance); + EXPECT_EQ(&*secondPerformance, modules.back().performance); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_NativeSystemSchemaRequiresFirstModuleGroup) +{ + auto epJSON = makeNativeSystemJSON(true); + epJSON["CentralHeatPumpSystem"]["Native Many Groups Wrapper"].erase(fixedModuleGroupFieldKeys(1).performanceObjectType); + + EXPECT_FALSE(process_json(epJSON, false)); + EXPECT_TRUE(compare_err_stream_substring("Missing required property", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_NativeSystemInputRejectsInvalidPerformanceReference) +{ + auto epJSON = makeNativeSystemJSON(false); + auto const group2Keys = fixedModuleGroupFieldKeys(2); + epJSON["CentralHeatPumpSystem"]["Native Sparse Wrapper"][group2Keys.performanceName] = "Missing Performance"; + ASSERT_TRUE(process_json(epJSON)); + state->init_state(*state); + + EXPECT_THROW(PlantCentralHeatPumpSystem::getCentralHeatPumpSystemInput(*state), std::runtime_error); + EXPECT_TRUE(compare_err_stream_substring(group2Keys.performanceName + " = MISSING PERFORMANCE, item not found.", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_NativeSystemInputRejectsCaseInsensitiveDuplicateNames) +{ + auto epJSON = makeNativeSystemJSON(true); + auto &systems = epJSON["CentralHeatPumpSystem"]; + systems["native many groups wrapper"] = systems["Native Many Groups Wrapper"]; + ASSERT_TRUE(process_json(epJSON)); + state->init_state(*state); + + EXPECT_THROW(PlantCentralHeatPumpSystem::getCentralHeatPumpSystemInput(*state), std::runtime_error); + EXPECT_TRUE(compare_err_stream_substring("duplicate name.", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_InputValidationUsesConfiguredReferenceTemperaturesAndBicubicPLRDomain) +{ + ASSERT_TRUE(process_idf(makeChillerHeaterValidationInput())); + state->init_state(*state); + + EXPECT_NO_THROW(PlantCentralHeatPumpSystem::getPerformanceInput(*state)); + EXPECT_FALSE(has_err_output()); + + ASSERT_FALSE(state->dataPlantCentralHeatPumpSystem->performanceDefinitions.empty()); + auto const &chillerHeater = state->dataPlantCentralHeatPumpSystem->performanceDefinitions[0]; + EXPECT_DOUBLE_EQ(0.2, chillerHeater.coolingMinimumPartLoadRatio); + EXPECT_DOUBLE_EQ(1.0, chillerHeater.coolingMaximumPartLoadRatio); + EXPECT_DOUBLE_EQ(0.3, chillerHeater.heatingMinimumPartLoadRatio); + EXPECT_DOUBLE_EQ(1.0, chillerHeater.heatingMaximumPartLoadRatio); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_InputValidationRejectsOptimumPLROutsideCurveDomain) +{ + ASSERT_TRUE(process_idf(makeChillerHeaterValidationInput(0.75, 0.1))); + state->init_state(*state); + + EXPECT_THROW(PlantCentralHeatPumpSystem::getPerformanceInput(*state), std::runtime_error); + EXPECT_TRUE(compare_err_stream_substring("Cooling Mode Cooling Capacity Optimum Part Load Ratio must be within", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_InputValidationRejectsInvalidPartLoadCurveDomain) +{ + ASSERT_TRUE(process_idf(makeChillerHeaterValidationInput(0.75, 0.5, 0.5, -0.1))); + state->init_state(*state); + + EXPECT_THROW(PlantCentralHeatPumpSystem::getPerformanceInput(*state), std::runtime_error); + EXPECT_TRUE(compare_err_stream_substring("Part-load ratio limits [-0.100, 1.000] must include 1.0", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_RuntimeRejectsNonpositiveHeatingCapacityRatio) +{ + ASSERT_TRUE(process_idf(makeChillerHeaterValidationInput(-0.75))); + state->init_state(*state); + + EXPECT_THROW(PlantCentralHeatPumpSystem::getPerformanceInput(*state), std::runtime_error); + EXPECT_TRUE(compare_err_stream_substring("Reference Heating Mode Cooling Capacity Ratio=-0.75", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_RuntimeValidationRejectsInvalidStaticPerformanceInputs) +{ + ASSERT_TRUE(process_idf(makeChillerHeaterValidationInput())); + auto &performanceObject = state->dataInputProcessing->inputProcessor->epJSON["ChillerHeaterPerformance:Electric:EIR"]["Validation Module"]; + performanceObject["reference_cooling_mode_evaporator_capacity"] = -10000.0; + performanceObject["reference_cooling_mode_cop"] = -5.0; + performanceObject["reference_heating_mode_cooling_capacity_ratio"] = -0.75; + performanceObject["reference_heating_mode_cooling_power_input_ratio"] = -1.0; + performanceObject["compressor_motor_efficiency"] = 1.1; + state->init_state(*state); + + EXPECT_THROW(PlantCentralHeatPumpSystem::getPerformanceInput(*state), std::runtime_error); + EXPECT_TRUE(compare_err_stream_substring("Reference Cooling Mode Evaporator Capacity=-10000.00", false)); + EXPECT_TRUE(compare_err_stream_substring("Reference Cooling Mode COP=-5.00", false)); + EXPECT_TRUE(compare_err_stream_substring("Reference Heating Mode Cooling Capacity Ratio=-0.75", false)); + EXPECT_TRUE(compare_err_stream_substring("Reference Heating Mode Cooling Power Input Ratio=-1.00", false)); + EXPECT_TRUE(compare_err_stream_substring("Compressor Motor Efficiency = 1.100", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_AncillaryScheduleDefaultsAndScalesPowerAndEnergy) +{ + state->init_state(*state); + state->dataLoopNodes->Node.allocate(6); + state->dataHVACGlobal->TimeStepSysSec = 600.0; + + PlantCentralHeatPumpSystem::CentralHeatPumpSystem system; + system.coolingInletNodeNum = 1; + system.coolingOutletNodeNum = 2; + system.heatingInletNodeNum = 3; + system.heatingOutletNodeNum = 4; + system.sourceInletNodeNum = 5; + system.sourceOutletNodeNum = 6; + system.ancillaryPower = 100.0; + system.modules.resize(1); + + auto setCoolingResult = [&]() { + auto &result = system.modules[0].result; + result = PlantCentralHeatPumpSystem::ModuleResult(); + result.currentMode = CurrentMode::CoolingOnly; + result.coolingPower = 50.0; + result.compressorPower = 50.0; + result.coolingDelivered = 1000.0; + result.sourceHeatTransfer = 1050.0; + result.coolingMassFlowRate = 1.0; + result.coolingOutletTemp = 7.0; + result.sourceMassFlowRate = 1.0; + result.sourceOutletTemp = 30.25; + }; + + setCoolingResult(); + system.ancillaryPowerSched = nullptr; + system.updateReportingAndNodes(*state, 1.0, 0.0, 1.0, 12.0, 40.0, 30.0); + EXPECT_DOUBLE_EQ(150.0, system.report.coolingElectricPower); + EXPECT_DOUBLE_EQ(90000.0, system.report.coolingElectricEnergy); + + setCoolingResult(); + system.ancillaryPowerSched = Sched::GetScheduleAlwaysOn(*state); + system.updateReportingAndNodes(*state, 1.0, 0.0, 1.0, 12.0, 40.0, 30.0); + EXPECT_DOUBLE_EQ(150.0, system.report.coolingElectricPower); + EXPECT_DOUBLE_EQ(90000.0, system.report.coolingElectricEnergy); + + Sched::ScheduleConstant fractionalSchedule; + fractionalSchedule.currentVal = 0.25; + setCoolingResult(); + system.ancillaryPowerSched = &fractionalSchedule; + system.updateReportingAndNodes(*state, 1.0, 0.0, 1.0, 12.0, 40.0, 30.0); + EXPECT_DOUBLE_EQ(75.0, system.report.coolingElectricPower); + EXPECT_DOUBLE_EQ(45000.0, system.report.coolingElectricEnergy); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_OffStateClearsAuthoritativeState) +{ + state->init_state(*state); + state->dataLoopNodes->Node.allocate(6); + + PlantCentralHeatPumpSystem::CentralHeatPumpSystem system; + system.coolingInletNodeNum = 1; + system.coolingOutletNodeNum = 2; + system.heatingInletNodeNum = 3; + system.heatingOutletNodeNum = 4; + system.sourceInletNodeNum = 5; + system.sourceOutletNodeNum = 6; + state->dataLoopNodes->Node(1).Temp = 12.0; + state->dataLoopNodes->Node(3).Temp = 40.0; + state->dataLoopNodes->Node(5).Temp = 15.0; + state->dataLoopNodes->Node(1).MassFlowRateRequest = 1.0; + state->dataLoopNodes->Node(3).MassFlowRateRequest = 2.0; + state->dataLoopNodes->Node(5).MassFlowRateRequest = 3.0; + + system.requestedCoolingLoad = 1000.0; + system.requestedHeatingLoad = 1200.0; + system.isCoolingDominant = true; + system.report.coolingHeatTransferRate = 1000.0; + system.report.heatingHeatTransferRate = 1200.0; + system.report.sourceHeatTransferRate = 200.0; + system.modules.resize(1); + PlantCentralHeatPumpSystem::PerformanceData performance; + system.modules[0].initialize(performance, nullptr); + auto &chillerHeater = system.modules[0]; + chillerHeater.result.currentMode = CurrentMode::CoolingDominant; + chillerHeater.result.coolingPower = 500.0; + chillerHeater.result.coolingDelivered = 1000.0; + + system.resetOffState(*state, false); + + EXPECT_DOUBLE_EQ(0.0, system.requestedCoolingLoad); + EXPECT_DOUBLE_EQ(0.0, system.requestedHeatingLoad); + EXPECT_FALSE(system.isCoolingDominant); + EXPECT_FALSE(system.isHeatingDominant); + EXPECT_EQ(CurrentMode::Off, chillerHeater.result.currentMode); + EXPECT_DOUBLE_EQ(0.0, system.report.coolingHeatTransferRate); + EXPECT_DOUBLE_EQ(0.0, system.report.heatingHeatTransferRate); + EXPECT_DOUBLE_EQ(0.0, system.report.sourceHeatTransferRate); + EXPECT_DOUBLE_EQ(12.0, state->dataLoopNodes->Node(2).Temp); + EXPECT_DOUBLE_EQ(40.0, state->dataLoopNodes->Node(4).Temp); + EXPECT_DOUBLE_EQ(15.0, state->dataLoopNodes->Node(6).Temp); + EXPECT_DOUBLE_EQ(0.0, state->dataLoopNodes->Node(1).MassFlowRateRequest); + EXPECT_DOUBLE_EQ(0.0, state->dataLoopNodes->Node(3).MassFlowRateRequest); + EXPECT_DOUBLE_EQ(0.0, state->dataLoopNodes->Node(5).MassFlowRateRequest); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_InactiveConnectionPreservesActiveLoad) +{ + state->init_state(*state); + state->dataLoopNodes->Node.allocate(6); + state->dataPlnt->PlantLoop.allocate(3); + + auto *water = Fluid::GetWater(*state); + ASSERT_NE(nullptr, water); + for (int loopNum = 1; loopNum <= 3; ++loopNum) { + auto &plantLoop = state->dataPlnt->PlantLoop(loopNum); + plantLoop.glycol = water; + auto &loopSide = plantLoop.LoopSide(DataPlant::LoopSideLocation::Supply); + loopSide.Branch.allocate(1); + loopSide.Branch(1).Comp.allocate(1); + } + + PlantCentralHeatPumpSystem::CentralHeatPumpSystem system; + system.coolingPlantLoc = PlantLocation(1, DataPlant::LoopSideLocation::Supply, 1, 1); + system.heatingPlantLoc = PlantLocation(2, DataPlant::LoopSideLocation::Supply, 1, 1); + system.sourcePlantLoc = PlantLocation(3, DataPlant::LoopSideLocation::Supply, 1, 1); + PlantUtilities::SetPlantLocationLinks(*state, system.coolingPlantLoc); + PlantUtilities::SetPlantLocationLinks(*state, system.heatingPlantLoc); + PlantUtilities::SetPlantLocationLinks(*state, system.sourcePlantLoc); + + system.coolingInletNodeNum = 1; + system.coolingOutletNodeNum = 2; + system.heatingInletNodeNum = 3; + system.heatingOutletNodeNum = 4; + system.sourceInletNodeNum = 5; + system.sourceOutletNodeNum = 6; + state->dataLoopNodes->Node(1).Temp = 12.0; + state->dataLoopNodes->Node(3).Temp = 40.0; + state->dataLoopNodes->Node(5).Temp = 15.0; + state->dataLoopNodes->Node(5).MassFlowRate = 1.0; + state->dataLoopNodes->Node(1).MassFlowRateRequest = 1.0; + state->dataLoopNodes->Node(5).MassFlowRateRequest = 2.0; + + system.setupOutputVarsFlag = false; + system.plantScanPending = false; + system.environmentInitPending = false; + system.requestedCoolingLoad = 1000.0; + system.report.coolingHeatTransferRate = 1000.0; + system.report.sourceHeatTransferRate = 1200.0; + system.report.sourceInletTemp = 15.0; + system.report.sourceOutletTemp = 15.3; + system.report.sourceMassFlowRate = 1.0; + system.modules.resize(1); + PlantCentralHeatPumpSystem::PerformanceData performance; + performance.compressorMotorEfficiency = 0.80; + system.modules[0].initialize(performance, nullptr); + system.modules[0].result.currentMode = CurrentMode::CoolingOnly; + system.modules[0].result.coolingDelivered = 1000.0; + + Real64 sourceLoad = 0.0; + system.simulate(*state, system.sourcePlantLoc, false, sourceLoad, false); + EXPECT_DOUBLE_EQ(1000.0, system.requestedCoolingLoad); + EXPECT_DOUBLE_EQ(1000.0, system.report.coolingHeatTransferRate); + EXPECT_EQ(CurrentMode::CoolingOnly, system.modules[0].result.currentMode); + + Real64 heatingLoad = 0.0; + system.simulate(*state, system.heatingPlantLoc, false, heatingLoad, false); + EXPECT_DOUBLE_EQ(0.0, heatingLoad); + EXPECT_DOUBLE_EQ(1000.0, system.requestedCoolingLoad); + EXPECT_DOUBLE_EQ(0.0, system.requestedHeatingLoad); + EXPECT_DOUBLE_EQ(1000.0, system.report.coolingHeatTransferRate); + EXPECT_EQ(CurrentMode::CoolingOnly, system.modules[0].result.currentMode); + EXPECT_DOUBLE_EQ(1.0, state->dataLoopNodes->Node(1).MassFlowRateRequest); + EXPECT_DOUBLE_EQ(2.0, state->dataLoopNodes->Node(5).MassFlowRateRequest); + + Real64 coolingLoad = 0.0; + system.simulate(*state, system.coolingPlantLoc, false, coolingLoad, false); + EXPECT_DOUBLE_EQ(0.0, coolingLoad); + EXPECT_DOUBLE_EQ(0.0, system.requestedCoolingLoad); + EXPECT_DOUBLE_EQ(0.0, system.report.coolingHeatTransferRate); + EXPECT_EQ(CurrentMode::Off, system.modules[0].result.currentMode); + EXPECT_DOUBLE_EQ(0.0, state->dataLoopNodes->Node(1).MassFlowRateRequest); + EXPECT_DOUBLE_EQ(0.0, state->dataLoopNodes->Node(5).MassFlowRateRequest); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_FailedPlantScanTerminatesInitialization) +{ + state->init_state(*state); + state->dataLoopNodes->Node.allocate(6); + + PlantCentralHeatPumpSystem::CentralHeatPumpSystem system; + system.Name = "UNCONNECTED WRAPPER"; + system.setupOutputVarsFlag = false; + system.environmentInitPending = false; + system.coolingInletNodeNum = 1; + system.coolingOutletNodeNum = 2; + system.heatingInletNodeNum = 3; + system.heatingOutletNodeNum = 4; + system.sourceInletNodeNum = 5; + system.sourceOutletNodeNum = 6; + + EXPECT_THROW(system.initialize(*state, 0.0, 1, false), std::runtime_error); + EXPECT_TRUE(compare_err_stream_substring("could not be located on all three connected plant loops", true)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_selectCondenserCurveTemperature) +{ + PlantCentralHeatPumpSystem::ModePerformanceData modePerformance; + Real64 constexpr allowedTolerance = 0.001; + Real64 constexpr condEnterTemp = 55.5; + Real64 constexpr condLeaveTemp = 44.4; + + modePerformance.condenserMode = PlantCentralHeatPumpSystem::CondenserTemperatureMode::EnteringCondenser; + EXPECT_NEAR(55.5, + PlantCentralHeatPumpSystem::CentralHeatPumpSystem::selectCondenserCurveTemperature(modePerformance, condEnterTemp, condLeaveTemp), + allowedTolerance); + + modePerformance.condenserMode = PlantCentralHeatPumpSystem::CondenserTemperatureMode::LeavingCondenser; + EXPECT_NEAR(44.4, + PlantCentralHeatPumpSystem::CentralHeatPumpSystem::selectCondenserCurveTemperature(modePerformance, condEnterTemp, condLeaveTemp), + allowedTolerance); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_ModeEnergyAndRoutingContracts) +{ + std::array const operatingPoints = { + ModeEnergyContract{CurrentMode::Off, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, {0.0, 0.0, 0.0}}, + ModeEnergyContract{CurrentMode::CoolingOnly, 9000.0, 10900.0, 2000.0, 0.0, 0.95, 0.75, 1.0, {9000.0, 0.0, 10900.0}}, + ModeEnergyContract{CurrentMode::HeatingOnly, 7500.0, 9750.0, 2500.0, 0.0, 0.90, 0.60, 1.0, {0.0, 9750.0, -7500.0}}, + ModeEnergyContract{CurrentMode::HeatRecovery, 8000.0, 10000.0, 2000.0, 0.0, 1.0, 0.80, 1.0, {8000.0, 10000.0, 0.0}}, + ModeEnergyContract{CurrentMode::CoolingDominant, 8000.0, 10000.0, 2000.0, 0.0, 1.0, 0.80, 1.0, {8000.0, 3500.0, 6500.0}}, + ModeEnergyContract{CurrentMode::HeatingDominant, 8000.0, 10000.0, 2000.0, 0.0, 1.0, 0.80, 1.0, {3000.0, 10000.0, -5000.0}}, + }; + + for (auto const &point : operatingPoints) { + SCOPED_TRACE(modeName(point.mode)); + EXPECT_TRUE(checkModeEnergyContract(point)); + } +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_ModeContractsRejectKnownDefectSignatures) +{ + // Issue #7838 / current mode-3 example signature: most condenser energy disappears and there is no source transfer. + ModeEnergyContract const issue7838Point{CurrentMode::HeatRecovery, 10790.844, 48.8987, 9602.5995, 0.0, 1.0, 0.20, 1.0, {10790.844, 48.8987, 0.0}}; + EXPECT_FALSE(checkModeEnergyContract(issue7838Point)); + + // Issue #10065 signature: an active Electric:EIR module meets simultaneous loads with zero compressor power. + ModeEnergyContract const issue10065Point{CurrentMode::HeatRecovery, 8000.0, 8000.0, 0.0, 0.0, 1.0, 0.80, 1.0, {8000.0, 8000.0, 0.0}}; + EXPECT_FALSE(checkModeEnergyContract(issue10065Point)); + + // Heating-dominant useful cooling cannot be silently reclassified as source extraction. + ModeEnergyContract const lostCoolingRoute{CurrentMode::HeatingDominant, 8000.0, 10000.0, 2000.0, 0.0, 1.0, 0.80, 1.0, {0.0, 10000.0, -8000.0}}; + EXPECT_FALSE(checkModeEnergyContract(lostCoolingRoute)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_FinalPartLoadContracts) +{ + std::array const points = { + PartLoadContract{10000.0, 1000.0, 1000.0, 0.30, 1.0, 0.30, 1.0 / 3.0, 0.30}, + PartLoadContract{10000.0, 6000.0, 6000.0, 0.30, 1.0, 0.60, 1.0, 0.60}, + PartLoadContract{10000.0, 12000.0, 10000.0, 0.30, 1.0, 1.00, 1.0, 1.00}, + }; + + for (auto const &point : points) { + EXPECT_TRUE(checkPartLoadContract(point)); + } + + // Issue #8191 / current mode-2 signature: post-scaled PLR, full cycling, and a full-load EIRFPLR evaluation. + PartLoadContract const issue8191Point{10000.0, 1000.0, 1000.0, 0.30, 1.0, 0.10, 1.0, 1.0}; + EXPECT_FALSE(checkPartLoadContract(issue8191Point)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_SingleModeSolversUseFinalStateAndCloseEnergyBalances) +{ + std::string const idf_objects = delimited_string({ + "FluidProperties:GlycolConcentration,", + " Source Fluid,", + " PropyleneGlycol,", + " ,", + " 0.30;", + + "Curve:Biquadratic,", + " Constant Temperature Modifier,", + " 1.0,", + " 0.0,", + " 0.0,", + " 0.0,", + " 0.0,", + " 0.0,", + " -100.0,", + " 100.0,", + " -100.0,", + " 100.0;", + + "Curve:Quadratic,", + " Linear Part Load EIR,", + " 0.0,", + " 1.0,", + " 0.0,", + " 0.3,", + " 1.0;", + + "Curve:Bicubic,", + " Bivariate Part Load EIR,", + " 0.0,", + " 0.0,", + " 0.0,", + " 1.0,", + " 0.0,", + " 0.0,", + " 0.0,", + " 0.0,", + " 0.0,", + " 0.0,", + " -100.0,", + " 100.0,", + " 0.3,", + " 1.0;", + }); + + ASSERT_TRUE(process_idf(idf_objects)); + EXPECT_FALSE(has_err_output()); + state->init_state(*state); + + state->dataPlnt->PlantLoop.allocate(3); + auto *water = Fluid::GetWater(*state); + auto *sourceGlycol = Fluid::GetGlycol(*state, "SOURCE FLUID"); + ASSERT_NE(nullptr, water); + ASSERT_NE(nullptr, sourceGlycol); + state->dataPlnt->PlantLoop(1).glycol = water; + state->dataPlnt->PlantLoop(2).glycol = sourceGlycol; + state->dataPlnt->PlantLoop(3).glycol = water; + + PlantCentralHeatPumpSystem::CentralHeatPumpSystem system; + system.coolingPlantLoc.loopNum = 1; + system.sourcePlantLoc.loopNum = 2; + system.heatingPlantLoc.loopNum = 3; + PlantUtilities::SetPlantLocationLinks(*state, system.coolingPlantLoc); + PlantUtilities::SetPlantLocationLinks(*state, system.sourcePlantLoc); + PlantUtilities::SetPlantLocationLinks(*state, system.heatingPlantLoc); + + state->dataLoopNodes->Node.allocate(8); + system.coolingSetpointNodeNum = 1; + system.heatingSetpointNodeNum = 2; + system.coolingInletNodeNum = 3; + system.coolingOutletNodeNum = 4; + system.heatingInletNodeNum = 5; + system.heatingOutletNodeNum = 6; + system.sourceInletNodeNum = 7; + system.sourceOutletNodeNum = 8; + state->dataLoopNodes->Node(system.coolingInletNodeNum).Temp = 12.0; + state->dataLoopNodes->Node(system.heatingInletNodeNum).Temp = 40.0; + state->dataLoopNodes->Node(system.sourceInletNodeNum).Temp = 15.0; + state->dataLoopNodes->Node(system.coolingSetpointNodeNum).TempSetPoint = 7.0; + state->dataLoopNodes->Node(system.heatingSetpointNodeNum).TempSetPoint = 45.0; + + system.modules.resize(2); + PlantCentralHeatPumpSystem::PerformanceData performance1; + performance1.compressorMotorEfficiency = 0.80; + performance1.minimumEvaporatorOutletTemp = 5.0; + performance1.referenceCoolingCOP = 5.0; + performance1.coolingCondenserTemperatureMode = PlantCentralHeatPumpSystem::CondenserTemperatureMode::EnteringCondenser; + performance1.coolingCapacityTemperatureCurveIndex = Curve::GetCurveIndex(*state, "CONSTANT TEMPERATURE MODIFIER"); + performance1.coolingEIRTemperatureCurveIndex = performance1.coolingCapacityTemperatureCurveIndex; + performance1.coolingEIRPartLoadCurveIndex = Curve::GetCurveIndex(*state, "LINEAR PART LOAD EIR"); + performance1.coolingMinimumPartLoadRatio = 0.3; + performance1.coolingMaximumPartLoadRatio = 1.0; + performance1.heatingCondenserTemperatureMode = PlantCentralHeatPumpSystem::CondenserTemperatureMode::EnteringCondenser; + performance1.heatingCapacityTemperatureCurveIndex = performance1.coolingCapacityTemperatureCurveIndex; + performance1.heatingEIRTemperatureCurveIndex = performance1.coolingEIRTemperatureCurveIndex; + performance1.heatingEIRPartLoadCurveIndex = Curve::GetCurveIndex(*state, "BIVARIATE PART LOAD EIR"); + performance1.heatingMinimumPartLoadRatio = 0.3; + performance1.heatingMaximumPartLoadRatio = 1.0; + + ASSERT_GT(performance1.coolingCapacityTemperatureCurveIndex, 0); + ASSERT_GT(performance1.coolingEIRPartLoadCurveIndex, 0); + ASSERT_GT(performance1.heatingEIRPartLoadCurveIndex, 0); + + system.modules[0].initialize(performance1, nullptr); + auto &chillerHeater = system.modules[0]; + chillerHeater.sizing.referenceCoolingCapacity = 10000.0; + chillerHeater.sizing.referenceHeatingCapacity = 10000.0; + chillerHeater.sizing.referenceHeatingCOP = 4.0; + chillerHeater.minimumEvaporatorOutletTemp = 5.0; + + auto coolingResult = system.solveCoolingOnly(*state, 0, 1000.0, 1.0, 1.0, 12.0, 30.0); + + EXPECT_EQ(CurrentMode::CoolingOnly, coolingResult.currentMode); + EXPECT_NEAR(1000.0, coolingResult.qEvaporator, 1.0e-6); + EXPECT_NEAR(200.0, coolingResult.coolingPower, 1.0e-6); + EXPECT_NEAR(1160.0, coolingResult.qCondenser, 1.0e-6); + EXPECT_NEAR(0.30, coolingResult.partLoadRatio, 1.0e-9); + EXPECT_NEAR(1.0 / 3.0, coolingResult.cyclingRatio, 1.0e-9); + EXPECT_NEAR(coolingResult.partLoadRatio, coolingResult.eirPartLoadCurvePLR, 1.0e-12); + EXPECT_NEAR(coolingResult.evaporatorOutletTemp, coolingResult.capacityCurveEvaporatorTemp, 1.0e-12); + EXPECT_NEAR(30.0, coolingResult.capacityCurveCondenserTemp, 1.0e-12); + EXPECT_NE(7.0, coolingResult.evaporatorOutletTemp); + EXPECT_NEAR(0.0, coolingResult.moduleEnergyBalanceResidual(), 1.0e-9); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::Converged, coolingResult.solver.outerStatus); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::NotRequired, coolingResult.solver.partLoadStatus); + EXPECT_GT(coolingResult.solver.outerIterations, 0); + EXPECT_EQ(0, coolingResult.solver.partLoadIterations); + EXPECT_EQ(3 * coolingResult.solver.outerIterations + 4, coolingResult.solver.curveEvaluations); + chillerHeater.result = coolingResult; + chillerHeater.mapResultToPlantConnections(); + EXPECT_NEAR(0.0, chillerHeater.result.routingEnergyBalanceResidual(), 1.0e-9); + + system.allModulesVariableFlow = true; + performance1.coolingCondenserTemperatureMode = PlantCentralHeatPumpSystem::CondenserTemperatureMode::LeavingCondenser; + coolingResult = system.solveCoolingOnly(*state, 0, 1000.0, 1.0, 1.0, 12.0, 30.0); + Real64 const coolingCp = water->getSpecificHeat(*state, 12.0, "PlantCentralHeatPumpSystem solver test"); + EXPECT_NEAR(7.0, coolingResult.evaporatorOutletTemp, 1.0e-9); + EXPECT_NEAR(1000.0 / (coolingCp * 5.0), coolingResult.evaporatorMassFlowRate, 1.0e-9); + EXPECT_NEAR(coolingResult.condenserOutletTemp, coolingResult.capacityCurveCondenserTemp, 1.0e-12); + EXPECT_NEAR(0.0, coolingResult.moduleEnergyBalanceResidual(), 1.0e-9); + + auto const tinyHeatingResult = system.solveHeatingOnly(*state, 0, 0.5 * HVAC::SmallLoad, 1.0, 1.0, 15.0, 40.0); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::NotRequired, tinyHeatingResult.solver.outerStatus); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::NotRequired, tinyHeatingResult.solver.partLoadStatus); + EXPECT_EQ(0, tinyHeatingResult.solver.outerIterations); + EXPECT_EQ(0, tinyHeatingResult.solver.partLoadIterations); + EXPECT_EQ(0, tinyHeatingResult.solver.curveEvaluations); + + auto heatingResult = system.solveHeatingOnly(*state, 0, 1200.0, 1.0, 1.0, 15.0, 40.0); + EXPECT_EQ(CurrentMode::HeatingOnly, heatingResult.currentMode); + EXPECT_NEAR(1000.0, heatingResult.qEvaporator, 1.0e-6); + EXPECT_NEAR(250.0, heatingResult.heatingPower, 1.0e-6); + EXPECT_NEAR(1200.0, heatingResult.qCondenser, 1.0e-6); + EXPECT_NEAR(0.30, heatingResult.partLoadRatio, 1.0e-9); + EXPECT_NEAR(1.0 / 3.0, heatingResult.cyclingRatio, 1.0e-9); + EXPECT_NEAR(heatingResult.partLoadRatio, heatingResult.eirPartLoadCurvePLR, 1.0e-12); + EXPECT_NEAR(40.0, heatingResult.eirPartLoadCurveCondenserTemp, 1.0e-12); + EXPECT_NEAR(heatingResult.evaporatorOutletTemp, heatingResult.capacityCurveEvaporatorTemp, 1.0e-12); + EXPECT_NEAR(40.0, heatingResult.capacityCurveCondenserTemp, 1.0e-12); + EXPECT_NEAR(45.0, heatingResult.condenserOutletTemp, 1.0e-9); + EXPECT_NEAR(0.0, heatingResult.moduleEnergyBalanceResidual(), 1.0e-9); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::NotRequired, heatingResult.solver.partLoadStatus); + chillerHeater.result = heatingResult; + chillerHeater.mapResultToPlantConnections(); + EXPECT_NEAR(0.0, chillerHeater.result.routingEnergyBalanceResidual(), 1.0e-9); + + auto simultaneousResult = system.solveSimultaneous(*state, 0, 1000.0, 1200.0, 1.0, 1.0, 1.0, 12.0, 40.0, 15.0); + EXPECT_EQ(CurrentMode::HeatRecovery, simultaneousResult.currentMode); + EXPECT_NEAR(1000.0, simultaneousResult.qEvaporator, 1.0e-6); + EXPECT_NEAR(250.0, simultaneousResult.compressorPower, 1.0e-6); + EXPECT_NEAR(1200.0, simultaneousResult.qCondenser, 1.0e-6); + EXPECT_NEAR(1000.0, simultaneousResult.coolingDelivered, 1.0e-6); + EXPECT_NEAR(1200.0, simultaneousResult.heatingDelivered, 1.0e-6); + EXPECT_NEAR(0.0, simultaneousResult.sourceHeatTransfer, 1.0e-6); + EXPECT_NEAR(0.0, simultaneousResult.moduleEnergyBalanceResidual(), 1.0e-9); + EXPECT_NEAR(0.0, simultaneousResult.routingEnergyBalanceResidual(), 1.0e-9); + + simultaneousResult = system.solveSimultaneous(*state, 0, 1000.0, 600.0, 1.0, 1.0, 1.0, 12.0, 40.0, 15.0); + EXPECT_EQ(CurrentMode::CoolingDominant, simultaneousResult.currentMode); + EXPECT_NEAR(1000.0, simultaneousResult.qEvaporator, 1.0e-6); + EXPECT_NEAR(1200.0, simultaneousResult.qCondenser, 1.0e-6); + EXPECT_NEAR(1000.0, simultaneousResult.coolingDelivered, 1.0e-6); + EXPECT_NEAR(600.0, simultaneousResult.heatingDelivered, 1.0e-6); + EXPECT_NEAR(600.0, simultaneousResult.sourceHeatTransfer, 1.0e-6); + EXPECT_GT(simultaneousResult.capacityCurveCondenserTemp, 15.0); + EXPECT_LT(simultaneousResult.capacityCurveCondenserTemp, 40.0); + EXPECT_NEAR(0.0, simultaneousResult.moduleEnergyBalanceResidual(), 1.0e-9); + EXPECT_NEAR(0.0, simultaneousResult.routingEnergyBalanceResidual(), 1.0e-9); + + simultaneousResult = system.solveSimultaneous(*state, 0, 1000.0, 600.0, 1.0, 1.0, 0.0, 12.0, 40.0, 15.0); + EXPECT_EQ(CurrentMode::HeatRecovery, simultaneousResult.currentMode); + EXPECT_NEAR(500.0, simultaneousResult.qEvaporator, 1.0e-6); + EXPECT_NEAR(600.0, simultaneousResult.qCondenser, 1.0e-6); + EXPECT_NEAR(500.0, simultaneousResult.coolingDelivered, 1.0e-6); + EXPECT_NEAR(600.0, simultaneousResult.heatingDelivered, 1.0e-6); + EXPECT_NEAR(500.0, simultaneousResult.unmetCoolingLoad, 1.0e-6); + EXPECT_NEAR(0.0, simultaneousResult.sourceHeatTransfer, 1.0e-6); + EXPECT_NEAR(0.0, simultaneousResult.moduleEnergyBalanceResidual(), 1.0e-9); + EXPECT_NEAR(0.0, simultaneousResult.routingEnergyBalanceResidual(), 1.0e-9); + + simultaneousResult = system.solveSimultaneous(*state, 0, 500.0, 1200.0, 1.0, 1.0, 1.0, 12.0, 40.0, 15.0); + EXPECT_EQ(CurrentMode::HeatingDominant, simultaneousResult.currentMode); + EXPECT_NEAR(1000.0, simultaneousResult.qEvaporator, 1.0e-6); + EXPECT_NEAR(1200.0, simultaneousResult.qCondenser, 1.0e-6); + EXPECT_NEAR(500.0, simultaneousResult.coolingDelivered, 1.0e-6); + EXPECT_NEAR(1200.0, simultaneousResult.heatingDelivered, 1.0e-6); + EXPECT_NEAR(-500.0, simultaneousResult.sourceHeatTransfer, 1.0e-6); + EXPECT_GT(simultaneousResult.capacityCurveEvaporatorTemp, 7.0); + EXPECT_LT(simultaneousResult.capacityCurveEvaporatorTemp, 15.0); + EXPECT_NEAR(0.0, simultaneousResult.moduleEnergyBalanceResidual(), 1.0e-9); + EXPECT_NEAR(0.0, simultaneousResult.routingEnergyBalanceResidual(), 1.0e-9); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::Converged, simultaneousResult.solver.outerStatus); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::Converged, simultaneousResult.solver.partLoadStatus); + EXPECT_GT(simultaneousResult.solver.partLoadIterations, 0); + EXPECT_LE(simultaneousResult.solver.partLoadIterations, 50 * simultaneousResult.solver.outerIterations); + EXPECT_LT(simultaneousResult.solver.partLoadIterations, 80 * simultaneousResult.solver.outerIterations); + EXPECT_LE(simultaneousResult.solver.partLoadBracketWidth, 1.0e-12); + EXPECT_LE(simultaneousResult.solver.loadResidual, 1.0e-7); + EXPECT_GT(simultaneousResult.solver.curveEvaluations, 0); + + system.allModulesVariableFlow = false; + performance1.heatingCondenserTemperatureMode = PlantCentralHeatPumpSystem::CondenserTemperatureMode::LeavingCondenser; + performance1.heatingReferenceCondenserOutletTemp = 40.5; + heatingResult = system.solveHeatingOnly(*state, 0, 5000.0, 1.0, 1.0, 15.0, 40.0); + EXPECT_GT(heatingResult.condenserOutletTemp, performance1.heatingReferenceCondenserOutletTemp); + EXPECT_NEAR(heatingResult.condenserOutletTemp, heatingResult.capacityCurveCondenserTemp, 1.0e-12); + EXPECT_NEAR(heatingResult.condenserOutletTemp, heatingResult.eirPartLoadCurveCondenserTemp, 1.0e-12); + EXPECT_NEAR(0.0, heatingResult.unmetHeatingLoad, 1.0e-6); + EXPECT_NEAR(0.0, heatingResult.moduleEnergyBalanceResidual(), 1.0e-9); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::Converged, heatingResult.solver.outerStatus); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::Converged, heatingResult.solver.partLoadStatus); + EXPECT_GT(heatingResult.solver.partLoadIterations, 0); + EXPECT_LE(heatingResult.solver.partLoadIterations, 50 * heatingResult.solver.outerIterations); + EXPECT_LT(heatingResult.solver.partLoadIterations, 80 * heatingResult.solver.outerIterations); + EXPECT_LE(heatingResult.solver.partLoadBracketWidth, 1.0e-12); + EXPECT_LE(heatingResult.solver.loadResidual, 1.0e-7); + EXPECT_GT(heatingResult.solver.curveEvaluations, 0); + + performance1.coolingCondenserTemperatureMode = PlantCentralHeatPumpSystem::CondenserTemperatureMode::EnteringCondenser; + performance1.heatingCondenserTemperatureMode = PlantCentralHeatPumpSystem::CondenserTemperatureMode::EnteringCondenser; + PlantCentralHeatPumpSystem::PerformanceData performance2 = performance1; + system.modules[1].initialize(performance2, nullptr); + chillerHeater.sizing.maximumEvaporatorMassFlowRate = 1.0; + chillerHeater.sizing.maximumCondenserMassFlowRate = 1.0; + system.modules[1].sizing.referenceCoolingCapacity = 5000.0; + system.modules[1].sizing.referenceHeatingCapacity = 5000.0; + system.modules[1].sizing.referenceHeatingCOP = 4.0; + system.modules[1].sizing.maximumEvaporatorMassFlowRate = 1.0; + system.modules[1].sizing.maximumCondenserMassFlowRate = 1.0; + system.requestedCoolingLoad = 18000.0; + system.requestedHeatingLoad = 21600.0; + + system.calculateSimultaneous(*state, 2.0, 2.0, 2.0, 12.0, 40.0, 15.0); + EXPECT_FALSE(system.isCoolingDominant); + EXPECT_FALSE(system.isHeatingDominant); + EXPECT_EQ(CurrentMode::HeatRecovery, system.modules[0].result.currentMode); + EXPECT_EQ(CurrentMode::HeatRecovery, system.modules[1].result.currentMode); + EXPECT_NEAR(10000.0, system.modules[0].result.coolingDelivered, 1.0e-6); + EXPECT_NEAR(12000.0, system.modules[0].result.heatingDelivered, 1.0e-6); + EXPECT_NEAR(8000.0, system.modules[1].result.requestedCoolingLoad, 1.0e-6); + EXPECT_NEAR(9600.0, system.modules[1].result.requestedHeatingLoad, 1.0e-6); + EXPECT_NEAR(5000.0, system.modules[1].result.coolingDelivered, 1.0e-6); + EXPECT_NEAR(6000.0, system.modules[1].result.heatingDelivered, 1.0e-6); + EXPECT_NEAR(3000.0, system.modules[1].result.unmetCoolingLoad, 1.0e-6); + EXPECT_NEAR(3600.0, system.modules[1].result.unmetHeatingLoad, 1.0e-6); + EXPECT_NEAR(15000.0, system.report.coolingHeatTransferRate, 1.0e-6); + EXPECT_NEAR(18000.0, system.report.heatingHeatTransferRate, 1.0e-6); + EXPECT_NEAR(0.0, system.report.sourceHeatTransferRate, 1.0e-6); + EXPECT_NEAR(3750.0, system.report.coolingElectricPower + system.report.heatingElectricPower, 1.0e-6); + + auto const firstDispatchResult = system.modules[1].result; + system.calculateSimultaneous(*state, 2.0, 2.0, 2.0, 12.0, 40.0, 15.0); + EXPECT_EQ(firstDispatchResult.currentMode, system.modules[1].result.currentMode); + EXPECT_NEAR(firstDispatchResult.qEvaporator, system.modules[1].result.qEvaporator, 1.0e-9); + EXPECT_NEAR(firstDispatchResult.qCondenser, system.modules[1].result.qCondenser, 1.0e-9); + EXPECT_NEAR(firstDispatchResult.unmetCoolingLoad, system.modules[1].result.unmetCoolingLoad, 1.0e-9); + EXPECT_NEAR(firstDispatchResult.unmetHeatingLoad, system.modules[1].result.unmetHeatingLoad, 1.0e-9); + + auto configureConnectionPerformance = [&system](std::size_t const moduleIndex, Real64 const referenceCapacity) { + auto &module = system.modules[moduleIndex]; + module.sizing.referenceCoolingCapacity = referenceCapacity; + module.sizing.referenceHeatingCapacity = referenceCapacity; + module.sizing.referenceHeatingCOP = 4.0; + module.sizing.maximumCoolingMassFlowRate = 0.20; + module.sizing.maximumHeatingMassFlowRate = 0.10; + module.sizing.maximumSourceEvaporatorMassFlowRate = 0.12; + module.sizing.maximumSourceCondenserMassFlowRate = 0.15; + }; + configureConnectionPerformance(0, 10000.0); + configureConnectionPerformance(1, 5000.0); + + system.requestedCoolingLoad = 20000.0; + system.requestedHeatingLoad = 0.0; + system.calculateCoolingOnly(*state, 0.30, 0.25, 12.0, 15.0); + auto const &coolingModule1 = system.modules[0].result; + auto const &coolingModule2 = system.modules[1].result; + EXPECT_EQ(CurrentMode::CoolingOnly, coolingModule1.currentMode); + EXPECT_EQ(CurrentMode::CoolingOnly, coolingModule2.currentMode); + EXPECT_NEAR(0.20, coolingModule1.coolingMassFlowRate, 1.0e-12); + EXPECT_NEAR(0.10, coolingModule2.coolingMassFlowRate, 1.0e-12); + EXPECT_NEAR(0.15, coolingModule1.sourceMassFlowRate, 1.0e-12); + EXPECT_NEAR(0.10, coolingModule2.sourceMassFlowRate, 1.0e-12); + EXPECT_LE(coolingModule1.coolingMassFlowRate + coolingModule2.coolingMassFlowRate, 0.30); + EXPECT_LE(coolingModule1.sourceMassFlowRate + coolingModule2.sourceMassFlowRate, 0.25); + EXPECT_NEAR(7.0, system.report.coolingOutletTemp, 1.0e-9); + Real64 const sourceCp = sourceGlycol->getSpecificHeat(*state, 15.0, "PlantCentralHeatPumpSystem connection test"); + EXPECT_NEAR(system.report.sourceHeatTransferRate, 0.25 * sourceCp * (system.report.sourceOutletTemp - system.report.sourceInletTemp), 1.0e-6); + EXPECT_NEAR(0.0, coolingModule1.routingEnergyBalanceResidual(), 1.0e-9); + EXPECT_NEAR(0.0, coolingModule2.routingEnergyBalanceResidual(), 1.0e-9); + + system.allModulesVariableFlow = true; + system.requestedCoolingLoad = 1000.0; + system.calculateCoolingOnly(*state, 0.30, 0.25, 12.0, 15.0); + Real64 const expectedVariableCoolingFlow = 1000.0 / (coolingCp * 5.0); + EXPECT_NEAR(expectedVariableCoolingFlow, system.modules[0].result.coolingMassFlowRate, 1.0e-9); + EXPECT_NEAR(0.0, system.modules[1].result.coolingMassFlowRate, 1.0e-12); + EXPECT_NEAR(12.0 - 1000.0 / (0.30 * coolingCp), system.report.coolingOutletTemp, 1.0e-9); + EXPECT_NEAR(system.report.sourceHeatTransferRate, 0.25 * sourceCp * (system.report.sourceOutletTemp - system.report.sourceInletTemp), 1.0e-6); + + system.allModulesVariableFlow = false; + system.requestedCoolingLoad = 0.0; + system.requestedHeatingLoad = 20000.0; + system.calculateHeatingOnly(*state, 0.15, 0.18, 40.0, 15.0); + auto const &heatingModule1 = system.modules[0].result; + auto const &heatingModule2 = system.modules[1].result; + EXPECT_EQ(CurrentMode::HeatingOnly, heatingModule1.currentMode); + EXPECT_EQ(CurrentMode::HeatingOnly, heatingModule2.currentMode); + EXPECT_NEAR(0.10, heatingModule1.heatingMassFlowRate, 1.0e-12); + EXPECT_NEAR(0.05, heatingModule2.heatingMassFlowRate, 1.0e-12); + EXPECT_NEAR(0.12, heatingModule1.sourceMassFlowRate, 1.0e-12); + EXPECT_NEAR(0.06, heatingModule2.sourceMassFlowRate, 1.0e-12); + EXPECT_LE(heatingModule1.heatingMassFlowRate + heatingModule2.heatingMassFlowRate, 0.15); + EXPECT_LE(heatingModule1.sourceMassFlowRate + heatingModule2.sourceMassFlowRate, 0.18); + EXPECT_NEAR(45.0, system.report.heatingOutletTemp, 1.0e-9); + EXPECT_NEAR(-system.report.sourceHeatTransferRate, 0.18 * sourceCp * (system.report.sourceInletTemp - system.report.sourceOutletTemp), 1.0e-6); + EXPECT_NEAR(0.0, heatingModule1.routingEnergyBalanceResidual(), 1.0e-9); + EXPECT_NEAR(0.0, heatingModule2.routingEnergyBalanceResidual(), 1.0e-9); + + system.requestedCoolingLoad = 1000.0; + system.requestedHeatingLoad = 500.0; + system.calculateSimultaneous(*state, 0.20, 0.10, 0.30, 12.0, 40.0, 15.0); + EXPECT_EQ(CurrentMode::CoolingDominant, system.modules[0].result.currentMode); + EXPECT_NEAR(0.15, system.modules[0].result.sourceMassFlowRate, 1.0e-12); + + system.requestedCoolingLoad = 500.0; + system.requestedHeatingLoad = 1200.0; + system.calculateSimultaneous(*state, 0.20, 0.10, 0.30, 12.0, 40.0, 15.0); + EXPECT_EQ(CurrentMode::HeatingDominant, system.modules[0].result.currentMode); + EXPECT_NEAR(0.12, system.modules[0].result.sourceMassFlowRate, 1.0e-12); + + auto &overAllocatedResult1 = system.modules[0].result; + auto &overAllocatedResult2 = system.modules[1].result; + overAllocatedResult1 = PlantCentralHeatPumpSystem::ModuleResult(); + overAllocatedResult2 = PlantCentralHeatPumpSystem::ModuleResult(); + overAllocatedResult1.heatingMassFlowRate = 0.20; + overAllocatedResult1.heatingOutletTemp = 46.0; + overAllocatedResult2.heatingMassFlowRate = 0.10; + overAllocatedResult2.heatingOutletTemp = 44.0; + system.updateReportingAndNodes(*state, 0.0, 0.15, 0.0, 12.0, 40.0, 15.0); + EXPECT_NEAR((0.20 * 46.0 + 0.10 * 44.0) / 0.30, system.report.heatingOutletTemp, 1.0e-12); + EXPECT_NE(40.0, system.report.heatingOutletTemp); + + system.modules[0].sizing.designEvaporatorVolFlowRate = 0.0010; + system.modules[0].sizing.designCondenserVolFlowRate = 0.0005; + performance1.designHeatingVolFlowRate = 0.0003; + system.modules[1].sizing.designEvaporatorVolFlowRate = 0.0020; + system.modules[1].sizing.designCondenserVolFlowRate = 0.0025; + performance2.designHeatingVolFlowRate = 0.0004; + system.initializeDesignFlowLimits(*state); + + Real64 const coolingDensity = water->getDensity(*state, Constant::CWInitConvTemp, "PlantCentralHeatPumpSystem design flow test"); + Real64 const heatingDensity = water->getDensity(*state, Constant::HWInitConvTemp, "PlantCentralHeatPumpSystem design flow test"); + Real64 const sourceDensity = sourceGlycol->getDensity(*state, Constant::CWInitConvTemp, "PlantCentralHeatPumpSystem design flow test"); + EXPECT_NEAR(0.0030, system.coolingVolFlowRate, 1.0e-12); + EXPECT_NEAR(0.0007, system.heatingVolFlowRate, 1.0e-12); + EXPECT_NEAR(0.0035, system.sourceVolFlowRate, 1.0e-12); + EXPECT_NEAR(0.0030 * coolingDensity, system.coolingMassFlowRateMax, 1.0e-9); + EXPECT_NEAR(0.0007 * heatingDensity, system.heatingMassFlowRateMax, 1.0e-9); + EXPECT_NEAR(0.0035 * sourceDensity, system.sourceMassFlowRateMax, 1.0e-9); + EXPECT_NEAR(0.0010 * coolingDensity, system.modules[0].sizing.maximumCoolingMassFlowRate, 1.0e-9); + EXPECT_NEAR(0.0003 * heatingDensity, system.modules[0].sizing.maximumHeatingMassFlowRate, 1.0e-9); + EXPECT_NEAR(0.0010 * sourceDensity, system.modules[0].sizing.maximumSourceEvaporatorMassFlowRate, 1.0e-9); + EXPECT_NEAR(0.0005 * sourceDensity, system.modules[0].sizing.maximumSourceCondenserMassFlowRate, 1.0e-9); + EXPECT_NEAR(system.coolingMassFlowRateMax, state->dataLoopNodes->Node(system.coolingInletNodeNum).MassFlowRateMax, 1.0e-9); + EXPECT_NEAR(system.heatingMassFlowRateMax, state->dataLoopNodes->Node(system.heatingInletNodeNum).MassFlowRateMax, 1.0e-9); + EXPECT_NEAR(system.sourceMassFlowRateMax, state->dataLoopNodes->Node(system.sourceInletNodeNum).MassFlowRateMax, 1.0e-9); + + auto *adversarialPartLoadCurve = state->dataCurveManager->curves(performance1.heatingEIRPartLoadCurveIndex); + adversarialPartLoadCurve->coeff.fill(0.0); + constexpr Real64 steepPartLoadSlope = 1.0e10; + adversarialPartLoadCurve->coeff[0] = -performance1.heatingMinimumPartLoadRatio * steepPartLoadSlope; + adversarialPartLoadCurve->coeff[3] = steepPartLoadSlope; + auto const exhaustedResult = system.solveHeatingOnly(*state, 0, 5000.0, 1.0, 1.0, 15.0, 40.0); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::IterationLimit, exhaustedResult.solver.partLoadStatus); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::Converged, exhaustedResult.solver.outerStatus); + EXPECT_EQ(50 * exhaustedResult.solver.outerIterations, exhaustedResult.solver.partLoadIterations); + EXPECT_GT(exhaustedResult.solver.loadResidual, 1.0e-7); + auto const repeatedExhaustedResult = system.solveHeatingOnly(*state, 0, 5000.0, 1.0, 1.0, 15.0, 40.0); + EXPECT_EQ(PlantCentralHeatPumpSystem::SolverConvergenceStatus::IterationLimit, repeatedExhaustedResult.solver.partLoadStatus); + EXPECT_EQ(2, system.modules[0].heatingPartLoadSolverWarning.count); + EXPECT_GT(system.modules[0].heatingPartLoadSolverWarning.recurringIndex, 0); + EXPECT_EQ(0, system.modules[0].heatingSolverWarning.count); + EXPECT_NEAR(0.0, exhaustedResult.moduleEnergyBalanceResidual(), 1.0e-9); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_SequentialFlowAllocationContracts) +{ + FlowAllocationContract const variableFlow{1.20, {0.40, 0.80}, {0.75, 1.00}, {0.40, 0.80}}; + EXPECT_TRUE(checkSequentialFlowAllocation(variableFlow)); + + FlowAllocationContract const heterogeneousConstantFlow{1.50, {0.80, 0.80, 0.80}, {1.00, 0.50, 1.00}, {0.80, 0.50, 0.20}}; + EXPECT_TRUE(checkSequentialFlowAllocation(heterogeneousConstantFlow)); + + FlowAllocationContract const overAllocated{1.50, {0.80, 0.80, 0.80}, {1.00, 0.50, 1.00}, {0.80, 0.50, 0.80}}; + EXPECT_FALSE(checkSequentialFlowAllocation(overAllocated)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_WaterAndGlycolNodeHeatTransferContracts) +{ + std::string const idf_objects = delimited_string({"FluidProperties:GlycolConcentration,", + " GLHXFluid, !- Name", + " PropyleneGlycol, !- Glycol Type", + " , !- User Defined Glycol Name", + " 0.3; !- Glycol Concentration"}); + + ASSERT_TRUE(process_idf(idf_objects)); + EXPECT_FALSE(has_err_output()); + state->init_state(*state); + + auto *water = Fluid::GetWater(*state); + auto *sourceGlycol = Fluid::GetGlycol(*state, "GLHXFLUID"); + ASSERT_NE(nullptr, water); + ASSERT_NE(nullptr, sourceGlycol); + + Real64 constexpr coolingInletTemp = 12.0; + Real64 constexpr coolingOutletTemp = 7.0; + Real64 constexpr coolingMassFlow = 1.0; + Real64 const coolingCp = water->getSpecificHeat(*state, coolingInletTemp, "PlantCentralHeatPumpSystem contract test"); + Real64 const cooling = coolingMassFlow * coolingCp * (coolingInletTemp - coolingOutletTemp); + EXPECT_TRUE(checkLoopHeatTransfer(cooling, coolingMassFlow, coolingCp, coolingInletTemp, coolingOutletTemp)); + + Real64 constexpr heatingInletTemp = 40.0; + Real64 constexpr heatingOutletTemp = 45.0; + Real64 constexpr heatingMassFlow = 0.8; + Real64 const heatingCp = water->getSpecificHeat(*state, heatingInletTemp, "PlantCentralHeatPumpSystem contract test"); + Real64 const heating = heatingMassFlow * heatingCp * (heatingOutletTemp - heatingInletTemp); + EXPECT_TRUE(checkLoopHeatTransfer(heating, heatingMassFlow, heatingCp, heatingInletTemp, heatingOutletTemp)); + + Real64 constexpr sourceInletTemp = 15.0; + Real64 constexpr sourceOutletTemp = 17.0; + Real64 constexpr sourceMassFlow = 1.2; + Real64 const sourceCp = sourceGlycol->getSpecificHeat(*state, sourceInletTemp, "PlantCentralHeatPumpSystem contract test"); + Real64 const sourceHeat = sourceMassFlow * sourceCp * (sourceOutletTemp - sourceInletTemp); + EXPECT_TRUE(checkLoopHeatTransfer(sourceHeat, sourceMassFlow, sourceCp, sourceInletTemp, sourceOutletTemp)); + + EXPECT_FALSE(contractNear(sourceCp, coolingCp)); + EXPECT_FALSE(checkLoopHeatTransfer(sourceHeat, sourceMassFlow, coolingCp, sourceInletTemp, sourceOutletTemp)); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_AuthoritativeResultMapsPlantConnections) +{ + PlantCentralHeatPumpSystem::PerformanceData performance; + performance.compressorMotorEfficiency = 0.80; + PlantCentralHeatPumpSystem::Module chillerHeater; + chillerHeater.initialize(performance, nullptr); + + auto configureResult = [&chillerHeater](CurrentMode const mode) { + auto &result = chillerHeater.result; + result = PlantCentralHeatPumpSystem::ModuleResult(); + result.currentMode = mode; + result.isAvailable = true; + result.qEvaporator = 8000.0; + result.qCondenser = 9600.0; + result.coolingPower = 2000.0; + result.evaporatorInletTemp = 12.0; + result.evaporatorOutletTemp = 7.0; + result.evaporatorMassFlowRate = 0.40; + result.condenserInletTemp = 30.0; + result.condenserOutletTemp = 35.0; + result.condenserMassFlowRate = 0.50; + chillerHeater.mapResultToPlantConnections(); + }; + + std::array const coolingModes = {CurrentMode::CoolingOnly, CurrentMode::CoolingDominant}; + for (CurrentMode const mode : coolingModes) { + SCOPED_TRACE(modeName(mode)); + configureResult(mode); + auto const &result = chillerHeater.result; + EXPECT_TRUE(result.isAvailable); + EXPECT_TRUE(result.isRunning); + EXPECT_DOUBLE_EQ(8000.0, result.coolingDelivered); + EXPECT_DOUBLE_EQ(9600.0, result.sourceHeatTransfer); + EXPECT_DOUBLE_EQ(0.40, result.coolingMassFlowRate); + EXPECT_DOUBLE_EQ(0.50, result.sourceMassFlowRate); + EXPECT_DOUBLE_EQ(0.0, result.heatingMassFlowRate); + EXPECT_NEAR(0.0, result.moduleEnergyBalanceResidual(), contractTolerance); + EXPECT_NEAR(0.0, result.routingEnergyBalanceResidual(), contractTolerance); + } + + std::array const heatingModes = {CurrentMode::HeatingOnly, CurrentMode::HeatingDominant}; + for (CurrentMode const mode : heatingModes) { + SCOPED_TRACE(modeName(mode)); + configureResult(mode); + auto const &result = chillerHeater.result; + EXPECT_DOUBLE_EQ(9600.0, result.heatingDelivered); + EXPECT_DOUBLE_EQ(-8000.0, result.sourceHeatTransfer); + EXPECT_DOUBLE_EQ(0.50, result.heatingMassFlowRate); + EXPECT_DOUBLE_EQ(0.40, result.sourceMassFlowRate); + EXPECT_DOUBLE_EQ(0.0, result.coolingMassFlowRate); + EXPECT_NEAR(0.0, result.routingEnergyBalanceResidual(), contractTolerance); + } + + configureResult(CurrentMode::HeatRecovery); + auto const &result = chillerHeater.result; + EXPECT_DOUBLE_EQ(8000.0, result.coolingDelivered); + EXPECT_DOUBLE_EQ(9600.0, result.heatingDelivered); + EXPECT_DOUBLE_EQ(9600.0, result.heatRecovered); + EXPECT_DOUBLE_EQ(0.0, result.sourceHeatTransfer); + EXPECT_DOUBLE_EQ(0.40, result.coolingMassFlowRate); + EXPECT_DOUBLE_EQ(0.50, result.heatingMassFlowRate); + EXPECT_DOUBLE_EQ(0.0, result.sourceMassFlowRate); + EXPECT_DOUBLE_EQ(2000.0, result.compressorPower); + EXPECT_DOUBLE_EQ(1600.0, result.motorHeatToRefrigerant); + EXPECT_DOUBLE_EQ(400.0, result.motorHeatLoss); + EXPECT_NEAR(0.0, result.moduleEnergyBalanceResidual(), contractTolerance); + EXPECT_NEAR(0.0, result.routingEnergyBalanceResidual(), contractTolerance); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_AuthoritativeResultDrivesEnergyIntegration) +{ + PlantCentralHeatPumpSystem::PerformanceData performance; + performance.compressorMotorEfficiency = 0.80; + PlantCentralHeatPumpSystem::Module chillerHeater; + chillerHeater.initialize(performance, nullptr); + auto &result = chillerHeater.result; + result.currentMode = CurrentMode::HeatRecovery; + result.isAvailable = true; + result.qEvaporator = 7000.0; + result.qCondenser = 7800.0; + result.coolingPower = 1000.0; + result.partLoadRatio = 0.75; + result.cyclingRatio = 0.50; + result.capacityTemperatureModifier = 0.95; + result.eirTemperatureModifier = 1.05; + result.eirPartLoadModifier = 0.90; + result.actualCOP = 4.0; + result.evaporatorInletTemp = 12.0; + result.evaporatorOutletTemp = 8.0; + result.evaporatorMassFlowRate = 0.40; + result.condenserInletTemp = 30.0; + result.condenserOutletTemp = 35.0; + result.condenserMassFlowRate = 0.50; + chillerHeater.mapResultToPlantConnections(); + chillerHeater.updateResultEnergies(60.0); + + EXPECT_DOUBLE_EQ(7000.0, result.qEvaporator); + EXPECT_DOUBLE_EQ(7800.0, result.qCondenser); + EXPECT_DOUBLE_EQ(1000.0, result.coolingPower); + EXPECT_DOUBLE_EQ(60000.0, result.coolingEnergy); + EXPECT_DOUBLE_EQ(420000.0, result.evaporatorEnergy); + EXPECT_DOUBLE_EQ(468000.0, result.condenserEnergy); + EXPECT_DOUBLE_EQ(8.0, result.coolingOutletTemp); + EXPECT_DOUBLE_EQ(35.0, result.heatingOutletTemp); + EXPECT_DOUBLE_EQ(7000.0, result.coolingDelivered); + EXPECT_DOUBLE_EQ(7800.0, result.heatingDelivered); + EXPECT_DOUBLE_EQ(1000.0, result.compressorPower); + EXPECT_DOUBLE_EQ(800.0, result.motorHeatToRefrigerant); + EXPECT_DOUBLE_EQ(200.0, result.motorHeatLoss); +} + +TEST_F(EnergyPlusFixture, Test_CentralHeatPumpSystem_AuthoritativeResultResetIsComplete) +{ + PlantCentralHeatPumpSystem::PerformanceData performance; + performance.compressorMotorEfficiency = 0.80; + PlantCentralHeatPumpSystem::Module chillerHeater; + chillerHeater.initialize(performance, nullptr); + chillerHeater.result.currentMode = CurrentMode::CoolingOnly; + chillerHeater.result.isAvailable = true; + chillerHeater.result.qEvaporator = 8000.0; + chillerHeater.result.qCondenser = 9600.0; + chillerHeater.result.coolingPower = 2000.0; + chillerHeater.result.evaporatorMassFlowRate = 0.40; + chillerHeater.result.condenserMassFlowRate = 0.50; + chillerHeater.mapResultToPlantConnections(); + + chillerHeater.resetResult(12.0, 30.0); + + EXPECT_EQ(CurrentMode::Off, chillerHeater.result.currentMode); + EXPECT_FALSE(chillerHeater.result.isRunning); + EXPECT_FALSE(chillerHeater.result.isAvailable); + EXPECT_DOUBLE_EQ(0.0, chillerHeater.result.compressorPower); + EXPECT_DOUBLE_EQ(0.0, chillerHeater.result.qEvaporator); + EXPECT_DOUBLE_EQ(0.0, chillerHeater.result.qCondenser); + EXPECT_DOUBLE_EQ(0.0, chillerHeater.result.evaporatorMassFlowRate); + EXPECT_DOUBLE_EQ(0.0, chillerHeater.result.condenserMassFlowRate); + EXPECT_DOUBLE_EQ(12.0, chillerHeater.result.evaporatorInletTemp); + EXPECT_DOUBLE_EQ(12.0, chillerHeater.result.evaporatorOutletTemp); + EXPECT_DOUBLE_EQ(30.0, chillerHeater.result.condenserInletTemp); + EXPECT_DOUBLE_EQ(30.0, chillerHeater.result.condenserOutletTemp); + EXPECT_DOUBLE_EQ(0.0, chillerHeater.result.coolingDelivered); + EXPECT_DOUBLE_EQ(0.0, chillerHeater.result.heatingDelivered); + EXPECT_DOUBLE_EQ(0.0, chillerHeater.result.sourceHeatTransfer); +}