diff --git a/doc/engineering-reference/src/surface-heat-balance-manager-processes/surface-heat-balance-with-moveable-insulation.tex b/doc/engineering-reference/src/surface-heat-balance-manager-processes/surface-heat-balance-with-moveable-insulation.tex index 3d317fd8baa..45865a02d13 100644 --- a/doc/engineering-reference/src/surface-heat-balance-manager-processes/surface-heat-balance-with-moveable-insulation.tex +++ b/doc/engineering-reference/src/surface-heat-balance-manager-processes/surface-heat-balance-with-moveable-insulation.tex @@ -1,14 +1,91 @@ -\section{Surface Heat Balance With Moveable Insulation}\label{surface-heat-balance-with-moveable-insulation} +\section{Surface Heat Balances With Moveable Insulation}\label{surface-heat-balances-with-moveable-insulation} -\subsection{Basic Heat Balance Cases}\label{basic-heat-balance-cases} +In EnergyPlus, moveable insulation can be present either on the interior or exterior side of a particular construction. Different heat balances are impacted depending on the location of the moveable insulation. Having moveable insulation on the interior side results in a modified form of the inside surface heat balance equation. Having moveable insulation on the exterior side results in different cases for the outside surface heat balance. Information on the modeling equations for each of these types of moveable insulation are shown in the next several sections. -A heat balance must exist at the outside surface-air interface. The incoming conductive, convective, and radiative fluxes must sum up to zero: +\subsection{Inside Heat Balance with Interior Moveable Insulation}\label{inside-heat-balance-with-interior-moveable-insulation} + +There are two different heat balances which must be maintained to model interior moveable insulation. At both the interface between the zone air and the moveable insulation and the interface between the moveable insulation and the surface (wall, roof, etc.), the following general heat balance must be maintained: \begin{equation} Conductive + Convective + Radiative = 0 \label{eq:BasicSteadyStateHeatBalanceEquation} \end{equation} +One significant complication of the inside heat balance is the fact that surfaces within the same zone can interact with each other radiatively. This means that a solution for all surface temperatures must be done at the same time to maintain a radiation heat balance among the surfaces. This requires some iteration of the inside surface heat balance as will be seen in the equations that are shown later in this subsection. + +Another complication of the inside heat balance relates to the presense of moveable insulation. When moveable insulation is present, a second heat balance equation is required to determine the temperature at both the air-moveable insulation interface as well as the moveable insulation-surface interface. This sets up a system of two equations with two unknowns: the temperatures at the air-moveable insulation interface and the moveable insulation-surface interface. + +Applying the basic steady state heat balance equation at each of these interfaces results in the following two equations: + +\begin{equation} +H_c \cdot \left( T_a - T_{mi} \right) + Q_{lw} + H_{mi} \cdot \left( T_i - T_{mi} \right) + I_t \cdot \left( T_{old} - T_{mi} \right) = 0 +\label{eq:InsideHBAirMovInsInterface} +\end{equation} + +\begin{equation} +H_{mi} \cdot \left( T_{mi} - T_i \right) + Q_{sw} + Q_{cond} = 0 +\label{eq:InsideHBMovInsSurfInterface} +\end{equation} + +where: + +\(H_c\) is the convective heat transfer coefficient between the moveable insulation and the air + +\(T_a\) is the zone air temperature + +\(T_{mi}\) is the temperature at the interface between the air and the moveable insulation + +\(Q_{lw}\) is the long wavelength radiation incident on the interface between the air and the moveable insulation + +\(H_{mi}\) is the U-value of the moveable insulation material + +\(T_i\) is the temperature at the interface between the moveable insulation and the surface + +\(I_t\) is a damping constant for iterating to achieve a stable radiant exchange between zone surfaces + +\(T_{old}\) is the previous surface temperature at the last solution iteration + +\(Q_{sw}\) is the short wavelength radiation incident on the interface between the moveable insulation and the surface + +\(Q_{cond}\) is the conduction heat transfer through the surface. + +Equation~\ref{eq:InsideHBAirMovInsInterface} is the heat balance at the air-moveable insulation interface and can be rearranged to solve for the temperature at this interface, T\(_{mi}\). Equation~\ref{eq:InsideHBMovInsSurfInterface} is the heat balance at the moveable insulation-surface interface and provides a second equation for T\(_{mi}\). Both equations leave T\(_{mi}\) as a function of T\(_{i}\) and other known quantities as shown below. + +It should be noted that there are some assumptions built into these equations. First, all long wavelength radiation whether from other surfaces or other elements (such as heat sources which add radiation to the zone) is all assumed to be incident at and influence the air-moveable insulation interface. This also means that no long wavelength radiation is transmitted through the moveable insulation to the moveable insulation-surface interface. Second, all short wavelength radiation is assumed to be transmitted through the moveable insulation to the moveable-insulation interface. This is a simplification that is consistent with transparent insulation and assumes that the moveable insulation material itself does not absorb any short wavelength as it passes through it. Third, the term Q\(_{cond}\) includes several terms that all relate to the method for calculating heat conduction through the actual surface to which the moveable insulation is attached. Finally, the moveable insulation material is sufficiently lightweight thermally that it has no thermal mass and it can be treated as an equivalent resistance. + +Equation~\ref{eq:InsideHBAirMovInsInterface} can be rearranged to obtain: + +\begin{equation} +\left( H_c + H_{mi} + I_t \right) \cdot T_m = H_c \cdot T_a + H_m \cdot T_i + Q_{lw} + I_t \cdot T_{old} +\label{eq:RearrangedHBAirMovIns} +\end{equation} + +When Equation~\ref{eq:InsideHBMovInsSurfInterface} is rearranged to solve for \(T_m\) and this is substituted back into Equation~\ref{eq:RearrangedHBAirMovIns}, one obtains a single equation that allows for the solution of \(T_i\). This can then be used to solve for \(T_{mi}\). + +The C++ code that is used to solve for \(T_i\) and \(T_{mi}\) for cases where moveable insulation is present on the interior side is shown below. + +\begin{lstlisting} +F1 = HMovInsul / (HMovInsul + HConvIn_surf + IterDampConst); + +TempSurfIn(SurfNum) = (CTFConstInPart(SurfNum) + QRadSWInAbs(SurfNum) + construct.CTFCross(0) * TH11 + + F1 * (QRadThermInAbs(SurfNum) + HConvIn_surf * RefAirTemp(SurfNum) + + NetLWRadToSurf(SurfNum) + QHTRadSysSurf(SurfNum) + + QCoolingPanelSurf(SurfNum) + QHWBaseboardSurf(SurfNum) + + QSteamBaseboardSurf(SurfNum) + QElecBaseboardSurf(SurfNum) + + QAdditionalHeatSourceInside(SurfNum) + + IterDampConst * TempInsOld(SurfNum))) + / (construct.CTFInside(0) + HMovInsul - F1 * HMovInsul); + +TempSurfInTmp(SurfNum) = (construct.CTFInside(0) * TempSurfIn(SurfNum) + + HMovInsul * TempSurfIn(SurfNum) - + QRadSWInAbs(SurfNum) - CTFConstInPart(SurfNum) - + construct.CTFCross(0) * TH11) / (HMovInsul); +\end{lstlisting} + +\subsection{Outside Heat Balance Cases for Exterior Moveable Insulation}\label{outside-heat-balance-cases-for-exterior-moveable-insulation} + +Just like at the inside surface-air interface, a heat balance must exist at the outside surface-air interface. The incoming conductive, convective, and radiative fluxes must sum up to zero as shown in Equation~\ref{eq:BasicSteadyStateHeatBalanceEquation}. + In contrast to the internal surface heat balance that treats all surfaces simultaneously, the external thermal balance for each surface is performed independent of all other surfaces. This implies that there is no direct interaction between the individual surfaces. TARP includes four possible representations for the basic outside surface heat balance. The first two depend on which of the optimal surface conductance algorithms the user selects. The simple outside surface conductance that includes both the convective and thermal interchange between the surface and the environment in a single coefficient, is represented by the thermal network in Figure~\ref{fig:thermal-network-for-simple-outside-surface}. Equation~\ref{eq:BasicSteadyStateHeatBalanceEquation} can also be expressed as: diff --git a/doc/input-output-reference/src/overview/group-thermal-zone-description-geometry.tex b/doc/input-output-reference/src/overview/group-thermal-zone-description-geometry.tex index b07340fbaad..eea114825d2 100644 --- a/doc/input-output-reference/src/overview/group-thermal-zone-description-geometry.tex +++ b/doc/input-output-reference/src/overview/group-thermal-zone-description-geometry.tex @@ -2403,6 +2403,7 @@ \subsection{Surface Output Variables/Reports}\label{surface-output-variablesrepo \begin{lstlisting} Zone,Sum,Surface Inside Face Heat Balance Calculation Iteration Count [] Zone,Average,Surface Inside Face Temperature [C] +Zone,Average,Surface Inside Face Interior Movable Insulation Temperature [C] Zone,Average,Surface Outside Face Temperature [C] Zone,Average,Surface Inside Face Adjacent Air Temperature [C] Zone,Average,Surface Inside Face Convection Heat Transfer Coefficient [W/m2-K] @@ -2655,6 +2656,16 @@ \subsubsection{Surface Inside Face Temperature {[}C{]}}\label{surface-inside-fac This is the temperature of the surface's inside face, in degrees Celsius. Former Name: Prior to version 7.1 this output was called Surface Inside Temperature. +\subsubsection{Surface Inside Face Interior Movable Insulation Temperature {[}C{]}}\label{surface-inside-face-interior-movable-insulation-temperature-c} + +This is the temperature of movable insulation installed on the inside of the construction at the movable insulation's inside face (the one facing the zone), in degrees Celsius. This variable is only valid when the surface has movable insulation and the movable insulation is actually scheduled to be present. For surfaces that have no movable insulation or the movable insulation is not scheduled to be present, the value of this variable is set to the Surface Inside Face Temperature. It should be noted that users can limit how often this output variable is generating by using a schedule to control when this output is produced. For example, this user could add the following syntax to an input file that includes movable insulation: + +\begin{lstlisting} + Output:Variable,Zone1:WestWall,Surface Inside Face Interior Movable Insulation Temperature,timestep,MovableInsulationSchedule; +\end{lstlisting} + +In this example, interior movable insulation is used on the west wall of zone 1 and is only present when the schedule (MovableInsulationSchedule) is greater than zero. Adding this output variable syntax to the input file reports the temperature at the inside face of the interior movable insulation only when the movable insulation is present. The use of the movable insulation schedule in the output variable designation limits when this value shows up in the EnergyPlus output file to when it is actually scheduled to be present. At other times, no value will be reported. The limiting of when the output is generated allows the user to generate useful statistics instead of having those statistics influenced by values for when the movable insulation is not present. If the user does not use the output variable schedule feature, the output for this variable will equal the surface inside face temperature when the movable insulation is not present. + \subsubsection{Surface Outside Face Temperature {[}C{]}}\label{surface-outside-face-temperature-c} This is the temperature of the surface's outside face, in degrees Celsius. Former Name: Prior to version 7.1, this output was called Surface Outside Temperature. diff --git a/src/EnergyPlus/ChillerElectricEIR.cc b/src/EnergyPlus/ChillerElectricEIR.cc index a606f6b3a56..3ab3fbfddf2 100644 --- a/src/EnergyPlus/ChillerElectricEIR.cc +++ b/src/EnergyPlus/ChillerElectricEIR.cc @@ -1839,6 +1839,7 @@ namespace ChillerElectricEIR { if (PlantFinalSizesOkayToReport) { if (ChillerIPLVFlagArr(EIRChillNum)) { + Real64 IPLV; CalcChillerIPLV(ElectricEIRChiller(EIRChillNum).Name, TypeOf_Chiller_ElectricEIR, ElectricEIRChiller(EIRChillNum).RefCap, @@ -1847,7 +1848,8 @@ namespace ChillerElectricEIR { ElectricEIRChiller(EIRChillNum).ChillerCapFT, ElectricEIRChiller(EIRChillNum).ChillerEIRFT, ElectricEIRChiller(EIRChillNum).ChillerEIRFPLR, - ElectricEIRChiller(EIRChillNum).MinUnloadRat); + ElectricEIRChiller(EIRChillNum).MinUnloadRat, + IPLV); ChillerIPLVFlagArr(EIRChillNum) = false; } // create predefined report diff --git a/src/EnergyPlus/ChillerReformulatedEIR.cc b/src/EnergyPlus/ChillerReformulatedEIR.cc index e19688f345f..4f268a3860c 100644 --- a/src/EnergyPlus/ChillerReformulatedEIR.cc +++ b/src/EnergyPlus/ChillerReformulatedEIR.cc @@ -1567,6 +1567,7 @@ namespace ChillerReformulatedEIR { if (PlantFinalSizesOkayToReport) { if (MyFlag(EIRChillNum)) { + Real64 IPLV; CalcChillerIPLV(ElecReformEIRChiller(EIRChillNum).Name, TypeOf_Chiller_ElectricReformEIR, ElecReformEIRChiller(EIRChillNum).RefCap, @@ -1576,6 +1577,7 @@ namespace ChillerReformulatedEIR { ElecReformEIRChiller(EIRChillNum).ChillerEIRFT, ElecReformEIRChiller(EIRChillNum).ChillerEIRFPLR, ElecReformEIRChiller(EIRChillNum).MinUnloadRat, + IPLV, ElecReformEIRChiller(EIRChillNum).EvapVolFlowRate, ElecReformEIRChiller(EIRChillNum).CDLoopNum, ElecReformEIRChiller(EIRChillNum).CompPowerToCondenserFrac); diff --git a/src/EnergyPlus/DataHeatBalSurface.cc b/src/EnergyPlus/DataHeatBalSurface.cc index a7b2589b000..fb0b2f069c0 100644 --- a/src/EnergyPlus/DataHeatBalSurface.cc +++ b/src/EnergyPlus/DataHeatBalSurface.cc @@ -102,6 +102,7 @@ namespace DataHeatBalSurface { Array1D TempSource; // Temperature at the source location for each heat transfer surface Array1D TempUserLoc; // Temperature at the user specified location for each heat transfer surface Array1D TempSurfInRep; // Temperature of the Inside Surface for each heat transfer surface + Array1D TempSurfInMovInsRep; // Temperature of interior movable insulation on the side facing the zone // (report) Array1D QConvInReport; // Surface convection heat gain at inside face [J] Array1D QdotConvInRep; // Surface convection heat transfer rate at inside face surface [W] @@ -272,6 +273,7 @@ namespace DataHeatBalSurface { TempSource.deallocate(); TempUserLoc.deallocate(); TempSurfInRep.deallocate(); + TempSurfInMovInsRep.deallocate(); QConvInReport.deallocate(); QdotConvInRep.deallocate(); QdotConvInRepPerArea.deallocate(); diff --git a/src/EnergyPlus/DataHeatBalSurface.hh b/src/EnergyPlus/DataHeatBalSurface.hh index ac3d1361e16..4bf3647fe20 100644 --- a/src/EnergyPlus/DataHeatBalSurface.hh +++ b/src/EnergyPlus/DataHeatBalSurface.hh @@ -89,6 +89,7 @@ namespace DataHeatBalSurface { extern Array1D TempSource; // Temperature at the source location for each heat transfer surface extern Array1D TempUserLoc; // Temperature at the user specified location for each heat transfer surface extern Array1D TempSurfInRep; // Temperature of the Inside Surface for each heat transfer surface + extern Array1D TempSurfInMovInsRep; // Temperature of interior movable insulation on the side facing the zone // (report) extern Array1D QConvInReport; // Surface convection heat gain at inside face [J] extern Array1D QdotConvInRep; // Surface convection heat transfer rate at inside face surface [W] diff --git a/src/EnergyPlus/DataPhotovoltaics.cc b/src/EnergyPlus/DataPhotovoltaics.cc index 3f50646ecbf..608aec2b9a0 100644 --- a/src/EnergyPlus/DataPhotovoltaics.cc +++ b/src/EnergyPlus/DataPhotovoltaics.cc @@ -121,6 +121,18 @@ namespace DataPhotovoltaics { // Object Data Array1D PVarray; + + void clear_state() + { + NumPVs = 0; + Num1DiodePVModuleTypes = 0; + NumSimplePVModuleTypes = 0; + NumSNLPVModuleTypes = 0; + + ShuntResistance = 0; + + PVarray.deallocate(); + } // ___________________________________________________________________________ // EnergyPlus V1.2 and beyond include models for photovoltaic calculations called diff --git a/src/EnergyPlus/DataPhotovoltaics.hh b/src/EnergyPlus/DataPhotovoltaics.hh index 2784286d786..66e4dc76394 100644 --- a/src/EnergyPlus/DataPhotovoltaics.hh +++ b/src/EnergyPlus/DataPhotovoltaics.hh @@ -344,6 +344,8 @@ namespace DataPhotovoltaics { // Object Data extern Array1D PVarray; + void clear_state(); + } // namespace DataPhotovoltaics } // namespace EnergyPlus diff --git a/src/EnergyPlus/ElectricPowerServiceManager.cc b/src/EnergyPlus/ElectricPowerServiceManager.cc index c9be8a79e81..1be14b28116 100644 --- a/src/EnergyPlus/ElectricPowerServiceManager.cc +++ b/src/EnergyPlus/ElectricPowerServiceManager.cc @@ -2038,6 +2038,32 @@ GeneratorController::GeneratorController(std::string const &objectName, ShowContinueError("Invalid availability schedule = " + availSchedName); ShowContinueError("Schedule was not found "); errorsFound = true; + } else { + if (generatorType == GeneratorType::pvWatts) { + ShowWarningError(routineName + DataIPShortCuts::cCurrentModuleObject + ", Availability Schedule for Generator:PVWatts '" + objectName + "' will be be ignored (runs all the time)."); + } else if (generatorType == GeneratorType::pV) { + // It should only warn if Performance type is SimplePV (DataPhotovoltaics::iSimplePVModel). + // Except you need GetPVInput to have run already etc + // Note: you can't use DataIPShortCuts::cAlphaArgs etc or it'll override what will still need to be processed in + // ElectPowerLoadCenter::ElectPowerLoadCenter after this function is called + int PVNum = inputProcessor->getObjectItemNum(objectType, UtilityRoutines::MakeUPPERCase(objectName)); + int NumAlphas; // Number of PV Array parameter alpha names being passed + int NumNums; // Number of PV Array numeric parameters are being passed + int IOStat; + Array1D_string Alphas(5); // Alpha items for object + Array1D Numbers(2); // Numeric items for object + inputProcessor->getObjectItem(objectType, + PVNum, + Alphas, + NumAlphas, + Numbers, + NumNums, + IOStat); + if (UtilityRoutines::SameString(Alphas(3), "PhotovoltaicPerformance:Simple")) { + ShowWarningError(routineName + DataIPShortCuts::cCurrentModuleObject + ", Availability Schedule for Generator:Photovoltaics '" + objectName + "' of Type PhotovoltaicPerformance:Simple will be be ignored (runs all the time)."); + ShowContinueError("To limit this Generator:Photovoltaic's output, please use the Inverter's availability schedule instead."); + } + } } } diff --git a/src/EnergyPlus/HeatBalanceSurfaceManager.cc b/src/EnergyPlus/HeatBalanceSurfaceManager.cc index 59980669aa5..2baee57dfa6 100644 --- a/src/EnergyPlus/HeatBalanceSurfaceManager.cc +++ b/src/EnergyPlus/HeatBalanceSurfaceManager.cc @@ -1379,6 +1379,7 @@ namespace HeatBalanceSurfaceManager { TH.dimension(2, MaxCTFTerms, TotSurfaces, 0.0); TempSurfOut.dimension(TotSurfaces, 0.0); TempSurfInRep.dimension(TotSurfaces, 0.0); + TempSurfInMovInsRep.dimension(TotSurfaces, 0.0); QConvInReport.dimension(TotSurfaces, 0.0); QdotConvInRepPerArea.dimension(TotSurfaces, 0.0); QdotConvInRep.dimension(TotSurfaces, 0.0); @@ -1501,6 +1502,8 @@ namespace HeatBalanceSurfaceManager { if (!Surface(loop).HeatTransSurf) continue; SetupOutputVariable( "Surface Inside Face Temperature", OutputProcessor::Unit::C, TempSurfInRep(loop), "Zone", "State", Surface(loop).Name); + SetupOutputVariable( + "Surface Inside Face Interior Movable Insulation Temperature", OutputProcessor::Unit::C, TempSurfInMovInsRep(loop), "Zone", "State", Surface(loop).Name); if (Surface(loop).ExtBoundCond != KivaFoundation) { SetupOutputVariable( @@ -2066,6 +2069,7 @@ namespace HeatBalanceSurfaceManager { HGrdExtSurf = 0.0; TempSurfOut = 0.0; TempSurfInRep = 0.0; + TempSurfInMovInsRep = 0.0; QConvInReport = 0.0; QdotConvInRep = 0.0; QdotConvInRepPerArea = 0.0; @@ -5028,6 +5032,18 @@ namespace HeatBalanceSurfaceManager { } // loop over zones } + void ReportIntMovInsInsideSurfTemp() + { + int SurfNum; + TempSurfInMovInsRep = TempSurfIn; + for (SurfNum = 1; SurfNum <= TotSurfaces; ++SurfNum) { + if (Surface(SurfNum).MaterialMovInsulInt > 0) { + if (GetCurrentScheduleValue(Surface(SurfNum).SchedMovInsulInt) > 0.0) { + TempSurfInMovInsRep(SurfNum) = TempSurfInTmp(SurfNum); + } + } + } + } // End of Reporting subroutines for the HB Module // ***************************************************************************** @@ -6649,6 +6665,8 @@ namespace HeatBalanceSurfaceManager { } } + ReportIntMovInsInsideSurfTemp(); + CalculateZoneMRT(ZoneToResimulate); // Update here so that the proper value of MRT is available to radiant systems } diff --git a/src/EnergyPlus/HeatBalanceSurfaceManager.hh b/src/EnergyPlus/HeatBalanceSurfaceManager.hh index 327103ab472..9c26fd8cbba 100644 --- a/src/EnergyPlus/HeatBalanceSurfaceManager.hh +++ b/src/EnergyPlus/HeatBalanceSurfaceManager.hh @@ -140,6 +140,8 @@ namespace HeatBalanceSurfaceManager { // ***************************************************************************** void ReportSurfaceHeatBalance(); + + void ReportIntMovInsInsideSurfTemp(); // End of Reporting subroutines for the HB Module // ***************************************************************************** diff --git a/src/EnergyPlus/InputProcessing/InputProcessor.cc b/src/EnergyPlus/InputProcessing/InputProcessor.cc index f8d07b52d52..154ca72649a 100644 --- a/src/EnergyPlus/InputProcessing/InputProcessor.cc +++ b/src/EnergyPlus/InputProcessing/InputProcessor.cc @@ -159,10 +159,10 @@ json const &InputProcessor::getFields(std::string const &objectType) return it2.value(); } -json const & InputProcessor::getPatternProperties(json const &schema_obj) +json const &InputProcessor::getPatternProperties(json const &schema_obj) { std::string pattern_property; - auto const & pattern_properties = schema_obj["patternProperties"]; + auto const &pattern_properties = schema_obj["patternProperties"]; int dot_star_present = pattern_properties.count(".*"); int no_whitespace_present = pattern_properties.count(R"(^.*\S.*$)"); if (dot_star_present) { @@ -238,6 +238,18 @@ void InputProcessor::markObjectAsUsed(const std::string &objectType, const std:: } } +void cleanEPJSON(json &epjson) +{ + if (epjson.type() == json::value_t::object) { + epjson.erase("idf_order"); + epjson.erase("idf_max_fields"); + epjson.erase("idf_max_extensible_fields"); + for (auto it = epjson.begin(); it != epjson.end(); ++it) { + cleanEPJSON(epjson[it.key()]); + } + } +} + void InputProcessor::processInput() { std::ifstream input_stream(DataStringGlobals::inputFileName, std::ifstream::in); @@ -280,12 +292,17 @@ void InputProcessor::processInput() if (!DataGlobals::isEpJSON) { bool success = true; epJSON = idf_parser->decode(input_file, schema, success); + // bool hasErrors = processErrors(); // if ( !success || hasErrors ) { // ShowFatalError( "Errors occurred on processing input file. Preceding condition(s) cause termination." ); // } + if (DataGlobals::outputEpJSONConversion) { - input_file = epJSON.dump(4, ' ', false, json::error_handler_t::replace); + json epJSONClean = epJSON; + cleanEPJSON(epJSONClean); + input_file = epJSONClean.dump(4, ' ', false, json::error_handler_t::replace); + //input_file = epJSON.dump(4, ' ', false, json::error_handler_t::replace); std::string convertedIDF(DataStringGlobals::outputDirPathName + DataStringGlobals::inputFileNameOnly + ".epJSON"); FileSystem::makeNativePath(convertedIDF); std::ofstream convertedFS(convertedIDF, std::ofstream::out); diff --git a/src/EnergyPlus/InputProcessing/InputProcessor.hh b/src/EnergyPlus/InputProcessing/InputProcessor.hh index 6b796febaff..ce75a986cbc 100644 --- a/src/EnergyPlus/InputProcessing/InputProcessor.hh +++ b/src/EnergyPlus/InputProcessing/InputProcessor.hh @@ -72,6 +72,8 @@ class Validation; namespace EnergyPlus { +void cleanEPJSON(nlohmann::json &epjson); + class InputProcessor { public: diff --git a/src/EnergyPlus/MixedAir.cc b/src/EnergyPlus/MixedAir.cc index 3c2cd5cd925..1c872977041 100644 --- a/src/EnergyPlus/MixedAir.cc +++ b/src/EnergyPlus/MixedAir.cc @@ -3330,6 +3330,13 @@ namespace MixedAir { thisOAController.MixMassFlow = AirLoopFlow(AirLoopNum).ReqSupplyFrac * AirLoopFlow(AirLoopNum).DesSupply; } else { thisOAController.MixMassFlow = Node(thisOAController.RetNode).MassFlowRate + thisOAController.ExhMassFlow; + + // The following was commented out after discussion on PR 7382, it can be reopened for discussion anytime + // found this equation results in flow that exceeds the design flow rate when there is exhaust flow rate is greater than + // the design supply air flow rate. Capped the mixed air flow rate at design supply air flow rate, issue #77379 + // thisOAController.MixMassFlow = Node(thisOAController.RetNode).MassFlowRate + thisOAController.ExhMassFlow; + // thisOAController.MixMassFlow = + // min(Node(thisOAController.RetNode).MassFlowRate + thisOAController.ExhMassFlow, AirLoopFlow(AirLoopNum).DesSupply); } } else { thisOAController.ExhMassFlow = 0.0; @@ -3807,6 +3814,8 @@ namespace MixedAir { } else { curAirLoopControlInfo.ResimAirLoopFlag = false; } + } else if (curAirLoopControlInfo.HeatingActiveFlag) { + this->HRHeatingCoilActive = 1; } else { this->HRHeatingCoilActive = 0; } @@ -4735,7 +4744,8 @@ namespace MixedAir { } else if (this->HeatRecoveryBypassControlType == BypassWhenOAFlowGreaterThanMinimum) { Real64 OAMassFlowMin = OutAirMinFrac * AirLoopFlow(AirLoopNum).DesSupply; Real64 OAMassFlowActual = OASignal * this->MixMassFlow; - if (OAMassFlowActual > OAMassFlowMin) { + Real64 reasonablySmallMassFlow = 1e-6; + if (OAMassFlowActual > (OAMassFlowMin + reasonablySmallMassFlow)) { AirLoopControlInfo(AirLoopNum).HeatRecoveryBypass = true; this->HeatRecoveryBypassStatus = 1; } diff --git a/src/EnergyPlus/ScheduleManager.cc b/src/EnergyPlus/ScheduleManager.cc index 1be98532876..70d3e507b80 100644 --- a/src/EnergyPlus/ScheduleManager.cc +++ b/src/EnergyPlus/ScheduleManager.cc @@ -2833,24 +2833,35 @@ namespace ScheduleManager { for (ScheduleIndex = 1; ScheduleIndex <= NumSchedules; ++ScheduleIndex) { - // Determine which Week Schedule is used - // Cant use stored day of year because of leap year inconsistency - WeekSchedulePointer = Schedule(ScheduleIndex).WeekSchedulePointer(DayOfYear_Schedule); - - // Now, which day? - if (DayOfWeek <= 7 && HolidayIndex > 0) { - DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(7 + HolidayIndex); - } else { - DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(DayOfWeek); - } - - // Hourly Value - if (WhichHour <= 24) { - Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(TimeStep, WhichHour); - } else if (TimeStep <= NumOfTimeStepInHour) { - Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(TimeStep, WhichHour - 24); + if (Schedule(ScheduleIndex).EMSActuatedOn) { + Schedule(ScheduleIndex).CurrentValue = Schedule(ScheduleIndex).EMSValue; } else { - Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(NumOfTimeStepInHour, WhichHour - 24); + // Hourly Value + if (WhichHour <= 24) { + WeekSchedulePointer = Schedule(ScheduleIndex).WeekSchedulePointer(DayOfYear_Schedule); + if (DayOfWeek <= 7 && HolidayIndex > 0) { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(7 + HolidayIndex); + } else { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(DayOfWeek); + } + Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(TimeStep, WhichHour); + } else if (TimeStep <= NumOfTimeStepInHour) { + WeekSchedulePointer = Schedule(ScheduleIndex).WeekSchedulePointer(DayOfYear_Schedule + 1); + if (DayOfWeek <= 7 && HolidayIndex > 0) { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(7 + HolidayIndex); + } else { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(DayOfWeek); + } + Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(TimeStep, WhichHour - 24); + } else { + WeekSchedulePointer = Schedule(ScheduleIndex).WeekSchedulePointer(DayOfYear_Schedule + 1); + if (DayOfWeek <= 7 && HolidayIndex > 0) { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(7 + HolidayIndex); + } else { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(DayOfWeek); + } + Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(NumOfTimeStepInHour, WhichHour - 24); + } } } } @@ -4970,28 +4981,36 @@ namespace ScheduleManager { WhichHour = HourOfDay + DSTIndicator; for (ScheduleIndex = 1; ScheduleIndex <= NumSchedules; ++ScheduleIndex) { - // Determine which Week Schedule is used - // Cant use stored day of year because of leap year inconsistency - WeekSchedulePointer = Schedule(ScheduleIndex).WeekSchedulePointer(DayOfYear_Schedule); - - // Now, which day? - if (DayOfWeek <= 7 && HolidayIndex > 0) { - DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(7 + HolidayIndex); - } else { - DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(DayOfWeek); - } - - // Hourly Value - if (WhichHour <= 24) { - Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(TimeStep, WhichHour); - } else if (TimeStep <= NumOfTimeStepInHour) { - Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(TimeStep, WhichHour - 24); - } else { - Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(NumOfTimeStepInHour, WhichHour - 24); - } if (Schedule(ScheduleIndex).EMSActuatedOn) { Schedule(ScheduleIndex).CurrentValue = Schedule(ScheduleIndex).EMSValue; + } else { + // Hourly Value + if (WhichHour <= 24) { + WeekSchedulePointer = Schedule(ScheduleIndex).WeekSchedulePointer(DayOfYear_Schedule); + if (DayOfWeek <= 7 && HolidayIndex > 0) { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(7 + HolidayIndex); + } else { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(DayOfWeek); + } + Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(TimeStep, WhichHour); + } else if (TimeStep <= NumOfTimeStepInHour) { + WeekSchedulePointer = Schedule(ScheduleIndex).WeekSchedulePointer(DayOfYear_Schedule + 1); + if (DayOfWeek <= 7 && HolidayIndex > 0) { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(7 + HolidayIndex); + } else { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(DayOfWeek); + } + Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(TimeStep, WhichHour - 24); + } else { + WeekSchedulePointer = Schedule(ScheduleIndex).WeekSchedulePointer(DayOfYear_Schedule + 1); + if (DayOfWeek <= 7 && HolidayIndex > 0) { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(7 + HolidayIndex); + } else { + DaySchedulePointer = WeekSchedule(WeekSchedulePointer).DaySchedulePointer(DayOfWeek); + } + Schedule(ScheduleIndex).CurrentValue = DaySchedule(DaySchedulePointer).TSValue(NumOfTimeStepInHour, WhichHour - 24); + } } } } diff --git a/src/EnergyPlus/StandardRatings.cc b/src/EnergyPlus/StandardRatings.cc index 933a59f1e56..7791d6cdd9e 100644 --- a/src/EnergyPlus/StandardRatings.cc +++ b/src/EnergyPlus/StandardRatings.cc @@ -226,6 +226,7 @@ namespace StandardRatings { int const EIRFTempCurveIndex, // Index for the energy input ratio modifier curve int const EIRFPLRCurveIndex, // Index for the EIR vs part-load ratio curve Real64 const MinUnloadRat, // Minimum unloading ratio + Real64 &IPLV, Optional EvapVolFlowRate, // Reference water volumetric flow rate through the evaporator [m3/s] Optional_int_const CondLoopNum, // condenser water plant loop index number Optional OpenMotorEff // Open chiller motor efficiency [fraction, 0 to 1] @@ -308,7 +309,6 @@ namespace StandardRatings { // to EnteringWaterTempReduced above [C] static Real64 Cp(0.0); // Water specific heat [J/(kg*C)] static Real64 Rho(0.0); // Water density [kg/m3] - static Real64 IPLV(0.0); // Integerated Part Load Value in SI [W/W] static Real64 EIR(0.0); // Inverse of COP at reduced capacity test conditions (100%, 75%, 50%, and 25%) static Real64 Power(0.0); // Power at reduced capacity test conditions (100%, 75%, 50%, and 25%) static Real64 COPReduced(0.0); // COP at reduced capacity test conditions (100%, 75%, 50%, and 25%) @@ -380,9 +380,10 @@ namespace StandardRatings { ChillerEIRFT = CurveValue(EIRFTempCurveIndex, EvapOutletTemp, CondenserInletTemp); - if (ReducedPLR(RedCapNum) >= MinUnloadRat) { - ChillerEIRFPLR = CurveValue(EIRFPLRCurveIndex, ReducedPLR(RedCapNum)); - PartLoadRatio = ReducedPLR(RedCapNum); + PartLoadRatio = ReducedPLR(RedCapNum) / ChillerCapFT; + + if (PartLoadRatio >= MinUnloadRat) { + ChillerEIRFPLR = CurveValue(EIRFPLRCurveIndex, PartLoadRatio); } else { ChillerEIRFPLR = CurveValue(EIRFPLRCurveIndex, MinUnloadRat); PartLoadRatio = MinUnloadRat; @@ -428,9 +429,10 @@ namespace StandardRatings { ChillerEIRFT = CurveValue(EIRFTempCurveIndex, EvapOutletTemp, CondenserOutletTemp); - if (ReducedPLR(RedCapNum) >= MinUnloadRat) { - ChillerEIRFPLR = CurveValue(EIRFPLRCurveIndex, CondenserOutletTemp, ReducedPLR(RedCapNum)); - PartLoadRatio = ReducedPLR(RedCapNum); + PartLoadRatio = ReducedPLR(RedCapNum) / ChillerCapFT; + + if (PartLoadRatio >= MinUnloadRat) { + ChillerEIRFPLR = CurveValue(EIRFPLRCurveIndex, CondenserOutletTemp, PartLoadRatio); } else { ChillerEIRFPLR = CurveValue(EIRFPLRCurveIndex, CondenserOutletTemp, MinUnloadRat); PartLoadRatio = MinUnloadRat; diff --git a/src/EnergyPlus/StandardRatings.hh b/src/EnergyPlus/StandardRatings.hh index aea784f070f..f63de5a2ff7 100644 --- a/src/EnergyPlus/StandardRatings.hh +++ b/src/EnergyPlus/StandardRatings.hh @@ -149,6 +149,7 @@ namespace StandardRatings { int const EIRFTempCurveIndex, // Index for the energy input ratio modifier curve int const EIRFPLRCurveIndex, // Index for the EIR vs part-load ratio curve Real64 const MinUnloadRat, // Minimum unloading ratio + Real64 &IPLV, Optional EvapVolFlowRate = _, // Reference water volumetric flow rate through the evaporator [m3/s] Optional_int_const CondLoopNum = _, // condenser water plant loop index number Optional OpenMotorEff = _ // Open chiller motor efficiency [fraction, 0 to 1] diff --git a/src/EnergyPlus/WaterThermalTanks.cc b/src/EnergyPlus/WaterThermalTanks.cc index f6d2024660c..853c9ec8a4e 100644 --- a/src/EnergyPlus/WaterThermalTanks.cc +++ b/src/EnergyPlus/WaterThermalTanks.cc @@ -7547,6 +7547,25 @@ namespace WaterThermalTanks { Tank.TankTemp = TankTemp; // Final tank temperature for carry-over to next timestep Tank.TankTempAvg = TankTempAvg; // Average tank temperature over the timestep for reporting + + if (!WarmupFlag) { + // Warn for potential freezing when avg of final temp over all nodes is below 2°C (nearing 0°C) + if (Tank.TankTemp < 2) { + if (Tank.FreezingErrorIndex == 0) { + ShowWarningError(RoutineName + ": " + Tank.Type +" = '" + + Tank.Name + "': Temperature of tank < 2C indicates of possibility of freeze. Tank Temperature = " + + General::RoundSigDigits(Tank.TankTemp, 2) + " C."); + ShowContinueErrorTimeStamp(""); + } + ShowRecurringWarningErrorAtEnd(Tank.Type +" = '" + Tank.Name + "': Temperature of tank < 2C indicates of possibility of freeze", + Tank.FreezingErrorIndex, + Tank.TankTemp, // Report Max + Tank.TankTemp, // Report Min + _, // Don't report Sum + "{C}", // Max Unit + "{C}"); // Min Unit + } + } Tank.UseOutletTemp = TankTempAvg; // Because entire tank is at same temperature Tank.SourceOutletTemp = TankTempAvg; // Because entire tank is at same temperature if (Tank.HeatPumpNum > 0) { @@ -7895,6 +7914,7 @@ namespace WaterThermalTanks { using DataGlobals::HourOfDay; using DataGlobals::TimeStep; using DataGlobals::TimeStepZone; + using DataGlobals::WarmupFlag; using DataHVACGlobals::SysTimeElapsed; using DataHVACGlobals::TimeStepSys; using FluidProperties::GetDensityGlycol; @@ -8419,6 +8439,25 @@ namespace WaterThermalTanks { Tank.TankTemp = sum(Tank.Node, &StratifiedNodeData::Temp) / Tank.Nodes; Tank.TankTempAvg = sum(Tank.Node, &StratifiedNodeData::TempAvg) / Tank.Nodes; + if (!WarmupFlag) { + // Warn for potential freezing when avg of final temp over all nodes is below 2°C (nearing 0°C) + if (Tank.TankTemp < 2) { + if (Tank.FreezingErrorIndex == 0) { + ShowWarningError(RoutineName + ": " + Tank.Type +" = '" + + Tank.Name + "': Temperature of tank < 2C indicates of possibility of freeze. Tank Temperature = " + + General::RoundSigDigits(Tank.TankTemp, 2) + " C."); + ShowContinueErrorTimeStamp(""); + } + ShowRecurringWarningErrorAtEnd(Tank.Type +" = '" + Tank.Name + "': Temperature of tank < 2C indicates of possibility of freeze", + Tank.FreezingErrorIndex, + Tank.TankTemp, // Report Max + Tank.TankTemp, // Report Min + _, // Don't report Sum + "{C}", // Max Unit + "{C}"); // Min Unit + } + } + if (Tank.UseOutletStratNode > 0) { Tank.UseOutletTemp = Tank.Node(Tank.UseOutletStratNode).TempAvg; // Revised use outlet temperature to ensure energy balance. Assumes a constant CP. CR8341/CR8570 @@ -9180,7 +9219,7 @@ namespace WaterThermalTanks { } else if (WaterHeaterDesuperheater(DesuperheaterNum).ReclaimHeatingSource == COIL_DX_VARIABLE_COOLING) { DataHeatBalance::HeatReclaimVS_DXCoil(SourceID).AvailCapacity -= WaterHeaterDesuperheater(DesuperheaterNum).HeaterRate; } else if (WaterHeaterDesuperheater(DesuperheaterNum).ReclaimHeatingSource == COIL_AIR_WATER_HEATPUMP_EQ) { - DataHeatBalance::HeatReclaimSimple_WAHPCoil(SourceID).AvailCapacity -= WaterHeaterDesuperheater(DesuperheaterNum).HeaterRate; + DataHeatBalance::HeatReclaimSimple_WAHPCoil(SourceID).AvailCapacity -= WaterHeaterDesuperheater(DesuperheaterNum).HeaterRate; DataHeatBalance::HeatReclaimSimple_WAHPCoil(SourceID).WaterHeatingDesuperheaterReclaimedHeat(DesuperheaterNum) = WaterHeaterDesuperheater(DesuperheaterNum).HeaterRate; } } diff --git a/src/EnergyPlus/WaterThermalTanks.hh b/src/EnergyPlus/WaterThermalTanks.hh index e21ab638f4b..afc2e4bd39b 100644 --- a/src/EnergyPlus/WaterThermalTanks.hh +++ b/src/EnergyPlus/WaterThermalTanks.hh @@ -426,6 +426,7 @@ namespace WaterThermalTanks { int DesuperheaterNum; // Index to desuperheating coil bool ShowSetPointWarning; // Warn when set point is greater than max tank temp limit int MaxCycleErrorIndex; // recurring error index + int FreezingErrorIndex; // recurring error index for freeze conditions WaterHeaterSizingData Sizing; // ancillary data for autosizing // Default Constructor @@ -460,7 +461,7 @@ namespace WaterThermalTanks { HeaterEnergy(0.0), HeaterEnergy1(0.0), HeaterEnergy2(0.0), FuelEnergy(0.0), FuelEnergy1(0.0), FuelEnergy2(0.0), VentEnergy(0.0), OffCycParaFuelEnergy(0.0), OffCycParaEnergyToTank(0.0), OnCycParaFuelEnergy(0.0), OnCycParaEnergyToTank(0.0), NetHeatTransferEnergy(0.0), FirstRecoveryDone(false), FirstRecoveryFuel(0.0), HeatPumpNum(0), DesuperheaterNum(0), - ShowSetPointWarning(true), MaxCycleErrorIndex(0) + ShowSetPointWarning(true), MaxCycleErrorIndex(0), FreezingErrorIndex(0) { } diff --git a/testfiles/Generator_PVWatts.idf b/testfiles/Generator_PVWatts.idf index 9ee3db82cd9..2631cb7cbe1 100644 --- a/testfiles/Generator_PVWatts.idf +++ b/testfiles/Generator_PVWatts.idf @@ -161,8 +161,6 @@ 1, !- Upper Limit Value Discrete; !- Numeric Type - Schedule:Constant,AlwaysOn,OnOff,1; - BuildingSurface:Detailed, Zn001:Wall001, !- Name Wall, !- Surface Type @@ -279,17 +277,17 @@ PVWatts1, !- Generator 1 Name Generator:PVWatts, !- Generator 1 Object Type 4000, !- Generator 1 Rated Electric Power Output {W} - AlwaysOn, !- Generator 1 Availability Schedule Name + , !- Generator 1 Availability Schedule Name , !- Generator 1 Rated Thermal to Electrical Power Ratio PVWatts2, !- Generator 2 Name Generator:PVWatts, !- Generator 2 Object Type 3000, !- Generator 2 Rated Electric Power Output {W} - AlwaysOn, !- Generator 2 Availability Schedule Name + , !- Generator 2 Availability Schedule Name , !- Generator 2 Rated Thermal to Electrical Power Ratio PVWatts3, !- Generator 3 Name Generator:PVWatts, !- Generator 3 Object Type 3000, !- Generator 3 Rated Electric Power Output {W} - AlwaysOn, !- Generator 3 Availability Schedule Name + , !- Generator 3 Availability Schedule Name ; !- Generator 3 Rated Thermal to Electrical Power Ratio Generator:PVWatts, diff --git a/testfiles/GeneratorswithPV.idf b/testfiles/GeneratorswithPV.idf index 7a179765765..4b9e2958742 100644 --- a/testfiles/GeneratorswithPV.idf +++ b/testfiles/GeneratorswithPV.idf @@ -710,27 +710,27 @@ Simple PV South Vertical,!- Generator 1 Name Generator:Photovoltaic, !- Generator 1 Object Type 20000, !- Generator 1 Rated Electric Power Output {W} - always on, !- Generator 1 Availability Schedule Name + , !- Generator 1 Availability Schedule Name , !- Generator 1 Rated Thermal to Electrical Power Ratio Simple PV Lat Tilt, !- Generator 2 Name Generator:Photovoltaic, !- Generator 2 Object Type 20000, !- Generator 2 Rated Electric Power Output {W} - always on, !- Generator 2 Availability Schedule Name + , !- Generator 2 Availability Schedule Name , !- Generator 2 Rated Thermal to Electrical Power Ratio Simple PV Flat, !- Generator 3 Name Generator:Photovoltaic, !- Generator 3 Object Type 20000, !- Generator 3 Rated Electric Power Output {W} - always on, !- Generator 3 Availability Schedule Name + , !- Generator 3 Availability Schedule Name , !- Generator 3 Rated Thermal to Electrical Power Ratio Simple PV Flat:Shaded to East, !- Generator 4 Name Generator:Photovoltaic, !- Generator 4 Object Type 20000, !- Generator 4 Rated Electric Power Output {W} - always on, !- Generator 4 Availability Schedule Name + , !- Generator 4 Availability Schedule Name , !- Generator 4 Rated Thermal to Electrical Power Ratio Simple PV Flat:Shaded to West, !- Generator 5 Name Generator:Photovoltaic, !- Generator 5 Object Type 20000, !- Generator 5 Rated Electric Power Output {W} - always on, !- Generator 5 Availability Schedule Name + , !- Generator 5 Availability Schedule Name , !- Generator 5 Rated Thermal to Electrical Power Ratio TRNSYSPV South Vertical, !- Generator 6 Name Generator:Photovoltaic, !- Generator 6 Object Type @@ -790,7 +790,7 @@ SIMPLE INTEGRATED PV, !- Generator 17 Name Generator:Photovoltaic, !- Generator 17 Object Type 20000, !- Generator 17 Rated Electric Power Output {W} - always on, !- Generator 17 Availability Schedule Name + , !- Generator 17 Availability Schedule Name , !- Generator 17 Rated Thermal to Electrical Power Ratio TRNSYSPV INTEGRATED PV, !- Generator 18 Name Generator:Photovoltaic, !- Generator 18 Object Type @@ -805,12 +805,13 @@ SIMPLE Roof Paver PV, !- Generator 20 Name Generator:Photovoltaic, !- Generator 20 Object Type 20000, !- Generator 20 Rated Electric Power Output {W} - always on, !- Generator 20 Availability Schedule Name + , !- Generator 20 Availability Schedule Name , !- Generator 20 Rated Thermal to Electrical Power Ratio TRNSYSPV Roof Paver PV, !- Generator 21 Name Generator:Photovoltaic, !- Generator 21 Object Type 20000, !- Generator 21 Rated Electric Power Output {W} - always on; !- Generator 21 Availability Schedule Name + , !- Generator 21 Availability Schedule Name + ; !- Generator 21 Rated Thermal to Electrical Power Ratio Generator:Photovoltaic, SIMPLE INTEGRATED PV, !- Name diff --git a/testfiles/MovableIntInsulationSimple.idf b/testfiles/MovableIntInsulationSimple.idf index b38a44b0e1b..64dc32d633a 100644 --- a/testfiles/MovableIntInsulationSimple.idf +++ b/testfiles/MovableIntInsulationSimple.idf @@ -497,6 +497,8 @@ Output:Variable,*,Surface Inside Face Temperature,timestep; + Output:Variable,Zn001:Wall001,Surface Inside Face Interior Movable Insulation Temperature,timestep; + Output:Variable,*,Surface Outside Face Temperature,timestep; Output:VariableDictionary,Regular; diff --git a/testfiles/ShopWithPVandBattery.idf b/testfiles/ShopWithPVandBattery.idf index 5f23e4e84aa..84518c8f5a6 100644 --- a/testfiles/ShopWithPVandBattery.idf +++ b/testfiles/ShopWithPVandBattery.idf @@ -4987,27 +4987,27 @@ PV:ZN_1_FLR_1_SEC_1_Ceiling, !- Generator 1 Name Generator:Photovoltaic, !- Generator 1 Object Type 9000.0, !- Generator 1 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 1 Availability Schedule Name + , !- Generator 1 Availability Schedule Name , !- Generator 1 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_2_Ceiling, !- Generator 2 Name Generator:Photovoltaic, !- Generator 2 Object Type 6000.0, !- Generator 2 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 2 Availability Schedule Name + , !- Generator 2 Availability Schedule Name , !- Generator 2 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_3_Ceiling, !- Generator 3 Name Generator:Photovoltaic, !- Generator 3 Object Type 9000.0, !- Generator 3 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 3 Availability Schedule Name + , !- Generator 3 Availability Schedule Name , !- Generator 3 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_4_Ceiling, !- Generator 4 Name Generator:Photovoltaic, !- Generator 4 Object Type 6000.0, !- Generator 4 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 4 Availability Schedule Name + , !- Generator 4 Availability Schedule Name , !- Generator 4 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_5_Ceiling, !- Generator 5 Name Generator:Photovoltaic, !- Generator 5 Object Type 9000.0, !- Generator 5 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 5 Availability Schedule Name + , !- Generator 5 Availability Schedule Name ; !- Generator 5 Rated Thermal to Electrical Power Ratio Generator:Photovoltaic, diff --git a/testfiles/ShopWithPVandStorage.idf b/testfiles/ShopWithPVandStorage.idf index adb7a21d520..a61e3f04cb0 100644 --- a/testfiles/ShopWithPVandStorage.idf +++ b/testfiles/ShopWithPVandStorage.idf @@ -4931,27 +4931,27 @@ PV:ZN_1_FLR_1_SEC_1_Ceiling, !- Generator 1 Name Generator:Photovoltaic, !- Generator 1 Object Type 9000.0, !- Generator 1 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 1 Availability Schedule Name + , !- Generator 1 Availability Schedule Name , !- Generator 1 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_2_Ceiling, !- Generator 2 Name Generator:Photovoltaic, !- Generator 2 Object Type 6000.0, !- Generator 2 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 2 Availability Schedule Name + , !- Generator 2 Availability Schedule Name , !- Generator 2 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_3_Ceiling, !- Generator 3 Name Generator:Photovoltaic, !- Generator 3 Object Type 9000.0, !- Generator 3 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 3 Availability Schedule Name + , !- Generator 3 Availability Schedule Name , !- Generator 3 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_4_Ceiling, !- Generator 4 Name Generator:Photovoltaic, !- Generator 4 Object Type 6000.0, !- Generator 4 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 4 Availability Schedule Name + , !- Generator 4 Availability Schedule Name , !- Generator 4 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_5_Ceiling, !- Generator 5 Name Generator:Photovoltaic, !- Generator 5 Object Type 9000.0, !- Generator 5 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 5 Availability Schedule Name + , !- Generator 5 Availability Schedule Name ; !- Generator 5 Rated Thermal to Electrical Power Ratio Generator:Photovoltaic, diff --git a/testfiles/ShopWithSimplePVT.idf b/testfiles/ShopWithSimplePVT.idf index d8f4238acaa..942eee6ad9b 100644 --- a/testfiles/ShopWithSimplePVT.idf +++ b/testfiles/ShopWithSimplePVT.idf @@ -4608,52 +4608,52 @@ PV:ZN_1_FLR_1_SEC_1_Ceiling, !- Generator 1 Name Generator:Photovoltaic, !- Generator 1 Object Type 9000.0, !- Generator 1 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 1 Availability Schedule Name + , !- Generator 1 Availability Schedule Name , !- Generator 1 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_2_Ceiling, !- Generator 2 Name Generator:Photovoltaic, !- Generator 2 Object Type 6000.0, !- Generator 2 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 2 Availability Schedule Name + , !- Generator 2 Availability Schedule Name , !- Generator 2 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_3_Ceiling, !- Generator 3 Name Generator:Photovoltaic, !- Generator 3 Object Type 9000.0, !- Generator 3 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 3 Availability Schedule Name + , !- Generator 3 Availability Schedule Name , !- Generator 3 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_4_Ceiling, !- Generator 4 Name Generator:Photovoltaic, !- Generator 4 Object Type 6000.0, !- Generator 4 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 4 Availability Schedule Name + , !- Generator 4 Availability Schedule Name , !- Generator 4 Rated Thermal to Electrical Power Ratio PV:ZN_1_FLR_1_SEC_5_Ceiling, !- Generator 5 Name Generator:Photovoltaic, !- Generator 5 Object Type 9000.0, !- Generator 5 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 5 Availability Schedule Name + , !- Generator 5 Availability Schedule Name , !- Generator 5 Rated Thermal to Electrical Power Ratio Collector 1 PV, !- Generator 6 Name Generator:Photovoltaic, !- Generator 6 Object Type 800.0, !- Generator 6 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 6 Availability Schedule Name + , !- Generator 6 Availability Schedule Name 1.5, !- Generator 6 Rated Thermal to Electrical Power Ratio Collector 2 PV, !- Generator 7 Name Generator:Photovoltaic, !- Generator 7 Object Type 800.0, !- Generator 7 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 7 Availability Schedule Name + , !- Generator 7 Availability Schedule Name 1.5, !- Generator 7 Rated Thermal to Electrical Power Ratio Collector 3 PV, !- Generator 8 Name Generator:Photovoltaic, !- Generator 8 Object Type 800.0, !- Generator 8 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 8 Availability Schedule Name + , !- Generator 8 Availability Schedule Name 1.5, !- Generator 8 Rated Thermal to Electrical Power Ratio Collector 4 PV, !- Generator 9 Name Generator:Photovoltaic, !- Generator 9 Object Type 800.0, !- Generator 9 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 9 Availability Schedule Name + , !- Generator 9 Availability Schedule Name 1.5, !- Generator 9 Rated Thermal to Electrical Power Ratio Collector 5 PV, !- Generator 10 Name Generator:Photovoltaic, !- Generator 10 Object Type 800.0, !- Generator 10 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 10 Availability Schedule Name + , !- Generator 10 Availability Schedule Name 1.5; !- Generator 10 Rated Thermal to Electrical Power Ratio PhotovoltaicPerformance:Simple, diff --git a/testfiles/TranspiredCollectors.idf b/testfiles/TranspiredCollectors.idf index 545410ae8b8..527c476ae7f 100644 --- a/testfiles/TranspiredCollectors.idf +++ b/testfiles/TranspiredCollectors.idf @@ -5463,12 +5463,13 @@ PV:ZN15:S_Wall, !- Generator 1 Name Generator:Photovoltaic, !- Generator 1 Object Type 100000.000, !- Generator 1 Rated Electric Power Output {W} - ALWAYS_ON, !- Generator 1 Availability Schedule Name + , !- Generator 1 Availability Schedule Name , !- Generator 1 Rated Thermal to Electrical Power Ratio PV_UTSC:ZN14:S_Wall, !- Generator 2 Name Generator:Photovoltaic, !- Generator 2 Object Type 100000.000, !- Generator 2 Rated Electric Power Output {W} - ALWAYS_ON; !- Generator 2 Availability Schedule Name + , !- Generator 2 Availability Schedule Name + ; !- Generator 2 Rated Thermal to Electrical Power Ratio Generator:Photovoltaic, PV:ZN15:S_Wall, !- Name diff --git a/tst/EnergyPlus/unit/ElectricPowerServiceManager.unit.cc b/tst/EnergyPlus/unit/ElectricPowerServiceManager.unit.cc index 6f69ff7281a..e323c5e73c5 100644 --- a/tst/EnergyPlus/unit/ElectricPowerServiceManager.unit.cc +++ b/tst/EnergyPlus/unit/ElectricPowerServiceManager.unit.cc @@ -800,3 +800,229 @@ TEST_F(EnergyPlusFixture, ManageElectricPowerTest_TransformerLossTest) // check the transformer loss rate for load and no load condition EXPECT_EQ(expectedtransformerObjLossRate, 0.0); } + + +// #7151: If an ElectricLoadCenter:Generators lists a Generator:Phototoltaic of type Simple PV with an availability schedule, warn that it will be unused. +// Any other performance type shouldn't warn +TEST_F(EnergyPlusFixture, ElectricLoadCenter_WarnAvailabilitySchedule_Photovoltaic_Simple) +{ + + std::string const idf_objects = delimited_string({ + "ElectricLoadCenter:Distribution,", + " PV Electric Load Center, !- Name", + " PV Generator List, !- Generator List Name", + " Baseload, !- Generator Operation Scheme Type", + " 0, !- Generator Demand Limit Scheme Purchased Electric Demand Limit {W}", + " , !- Generator Track Schedule Name Scheme Schedule Name", + " , !- Generator Track Meter Scheme Meter Name", + " DirectCurrentWithInverter, !- Electrical Buss Type", + " Simple Ideal Inverter; !- Inverter Name", + + "ElectricLoadCenter:Inverter:Simple,", + " Simple Ideal Inverter, !- Name", + " PV_ON, !- Availability Schedule Name", + " , !- Zone Name", + " 0.0, !- Radiative Fraction", + " 1.0; !- Inverter Efficiency", + + "ScheduleTypeLimits,", + " OnOff, !- Name", + " 0, !- Lower Limit Value", + " 1, !- Upper Limit Value", + " Discrete; !- Numeric Type", + + "Schedule:Compact,", + " PV_ON, !- Name", + " OnOff, !- Schedule Type Limits Name", + " Through: 12/31, !- Field 1", + " For: AllDays, !- Field 2", + " Until: 11:00, !- Field 3", + " 0.0, !- Field 4", + " Until: 15:00, !- Field 5", + " 1.0, !- Field 6", + " Until: 24:00, !- Field 7", + " 0.0; !- Field 8", + + "ElectricLoadCenter:Generators,", + " PV Generator List, !- Name", + " SimplePV, !- Generator 1 Name", + " Generator:Photovoltaic, !- Generator 1 Object Type", + " 20000, !- Generator 1 Rated Electric Power Output {W}", + " PV_ON, !- Generator 1 Availability Schedule Name", + " , !- Generator 1 Rated Thermal to Electrical Power Ratio", + " SimplePV2, !- Generator 2 Name", + " Generator:Photovoltaic, !- Generator 2 Object Type", + " 20000, !- Generator 2 Rated Electric Power Output {W}", + " , !- Generator 2 Availability Schedule Name", + " , !- Generator 2 Rated Thermal to Electrical Power Ratio", + " TRNSYSPV INTEGRATED PV, !- Generator 3 Name", + " Generator:Photovoltaic, !- Generator 3 Object Type", + " 20000, !- Generator 3 Rated Electric Power Output {W}", + " , !- Generator 3 Availability Schedule Name", + " ; !- Generator 3 Rated Thermal to Electrical Power Ratio", + + "Shading:Site:Detailed,", + " FlatSurface, !- Name", + " , !- Transmittance Schedule Name", + " 4, !- Number of Vertices", + " 40.0,2.0,0.0, !- X,Y,Z ==> Vertex 1 {m}", + " 40.0,0.00,0.0, !- X,Y,Z ==> Vertex 2 {m}", + " 45.0,0.00,0.0, !- X,Y,Z ==> Vertex 3 {m}", + " 45.0,2.0,0.0; !- X,Y,Z ==> Vertex 4 {m}", + + "PhotovoltaicPerformance:Simple,", + " 12percentEffPVFullArea, !- Name", + " 1.0, !- Fraction of Surface Area with Active Solar Cells {dimensionless}", + " Fixed, !- Conversion Efficiency Input Mode", + " 0.12; !- Value for Cell Efficiency if Fixed", + + "Generator:Photovoltaic,", + " SimplePV, !- Name", + " FlatSurface, !- Surface Name", + " PhotovoltaicPerformance:Simple, !- Photovoltaic Performance Object Type", + " 12percentEffPVFullArea, !- Module Performance Name", + " Decoupled, !- Heat Transfer Integration Mode", + " 1.0, !- Number of Series Strings in Parallel {dimensionless}", + " 1.0; !- Number of Modules in Series {dimensionless}", + + "Generator:Photovoltaic,", + " SimplePV2, !- Name", + " FlatSurface, !- Surface Name", + " PhotovoltaicPerformance:Simple, !- Photovoltaic Performance Object Type", + " 12percentEffPVFullArea, !- Module Performance Name", + " Decoupled, !- Heat Transfer Integration Mode", + " 1.0, !- Number of Series Strings in Parallel {dimensionless}", + " 1.0; !- Number of Modules in Series {dimensionless}", + + "Generator:Photovoltaic,", + " TRNSYSPV INTEGRATED PV, !- Name", + " FlatSurface, !- Surface Name", + " PhotovoltaicPerformance:EquivalentOne-Diode, !- Photovoltaic Performance Object Type", + " Example PV Model Inputs, !- Module Performance Name", + " IntegratedSurfaceOutsideFace, !- Heat Transfer Integration Mode", + " 3.0, !- Number of Series Strings in Parallel {dimensionless}", + " 6.0; !- Number of Modules in Series {dimensionless}", + + "PhotovoltaicPerformance:EquivalentOne-Diode,", + " Example PV Model Inputs, !- Name", + " CrystallineSilicon, !- Cell type", + " 36, !- Number of Cells in Series {dimensionless}", + " 0.63, !- Active Area {m2}", + " 0.9, !- Transmittance Absorptance Product {dimensionless}", + " 1.12, !- Semiconductor Bandgap {eV}", + " 1000000, !- Shunt Resistance {ohms}", + " 4.75, !- Short Circuit Current {A}", + " 21.4, !- Open Circuit Voltage {V}", + " 25.0, !- Reference Temperature {C}", + " 1000.0, !- Reference Insolation {W/m2}", + " 4.45, !- Module Current at Maximum Power {A}", + " 17, !- Module Voltage at Maximum Power {V}", + " 0.00065, !- Temperature Coefficient of Short Circuit Current {A/K}", + " -0.08, !- Temperature Coefficient of Open Circuit Voltage {V/K}", + " 20, !- Nominal Operating Cell Temperature Test Ambient Temperature {C}", + " 47, !- Nominal Operating Cell Temperature Test Cell Temperature {C}", + " 800.0, !- Nominal Operating Cell Temperature Test Insolation {W/m2}", + " 30.0, !- Module Heat Loss Coefficient {W/m2-K}", + " 50000; !- Total Heat Capacity {J/m2-K}", + + }); + + ASSERT_TRUE(process_idf(idf_objects)); + + createFacilityElectricPowerServiceObject(); + facilityElectricServiceObj->elecLoadCenterObjs.emplace_back(new ElectPowerLoadCenter(1)); + + // Should warn only for SimplePV because SimplePV2 doesn't have a schedule, and the other one is a Perf One-Diode and not "Simple" + std::string const error_string = delimited_string({ + " ** Warning ** GeneratorController constructor ElectricLoadCenter:Generators, Availability Schedule for Generator:Photovoltaics 'SIMPLEPV' of Type PhotovoltaicPerformance:Simple will be be ignored (runs all the time).", + " ** ~~~ ** To limit this Generator:Photovoltaic's output, please use the Inverter's availability schedule instead.", + }); + EXPECT_TRUE(compare_err_stream(error_string, true)); + +} + +// #7151: If an ElectricLoadCenter:Generators lists a Generator:PVWatts with an availability schedule, warn that it will be unused +TEST_F(EnergyPlusFixture, ElectricLoadCenter_WarnAvailabilitySchedule_PVWatts) +{ + + std::string const idf_objects = delimited_string({ + "ElectricLoadCenter:Distribution,", + " PVWatts Electric Load Center, !- Name", + " PVWatts Generator List, !- Generator List Name", + " Baseload, !- Generator Operation Scheme Type", + " 0, !- Generator Demand Limit Scheme Purchased Electric Demand Limit {W}", + " , !- Generator Track Schedule Name Scheme Schedule Name", + " , !- Generator Track Meter Scheme Meter Name", + " DirectCurrentWithInverter, !- Electrical Buss Type", + " PVWatts Inverter; !- Inverter Name", + + "ElectricLoadCenter:Inverter:PVWatts,", + " PVWatts Inverter, !- Name", + " 1.10, !- DC to AC Size Ratio", + " 0.96; !- Inverter Efficiency", + + "ScheduleTypeLimits,", + " OnOff, !- Name", + " 0, !- Lower Limit Value", + " 1, !- Upper Limit Value", + " Discrete; !- Numeric Type", + + "Schedule:Compact,", + " PV_ON, !- Name", + " OnOff, !- Schedule Type Limits Name", + " Through: 12/31, !- Field 1", + " For: AllDays, !- Field 2", + " Until: 11:00, !- Field 3", + " 0.0, !- Field 4", + " Until: 15:00, !- Field 5", + " 1.0, !- Field 6", + " Until: 24:00, !- Field 7", + " 0.0; !- Field 8", + + "ElectricLoadCenter:Generators,", + " PVWatts Generator List, !- Name", + " PVWatts1, !- Generator 1 Name", + " Generator:PVWatts, !- Generator 1 Object Type", + " 4000, !- Generator 1 Rated Electric Power Output {W}", + " PV_ON, !- Generator 1 Availability Schedule Name", + " , !- Generator 1 Rated Thermal to Electrical Power Ratio", + " PVWatts2, !- Generator 2 Name", + " Generator:PVWatts, !- Generator 2 Object Type", + " 3000, !- Generator 2 Rated Electric Power Output {W}", + " , !- Generator 2 Availability Schedule Name", + " ; !- Generator 2 Rated Thermal to Electrical Power Ratio", + + "Generator:PVWatts,", + " PVWatts1, !- Name", + " 5, !- PVWatts Version", + " 4000, !- DC System Capacity {W}", + " Standard, !- Module Type", + " FixedOpenRack, !- Array Type", + " 0.14, !- System Losses", + " TiltAzimuth, !- Array Geometry Type", + " 20, !- Tilt Angle {deg}", + " 180; !- Azimuth Angle {deg}", + + "Generator:PVWatts,", + " PVWatts2, !- Name", + " 5, !- PVWatts Version", + " 4000, !- DC System Capacity {W}", + " Standard, !- Module Type", + " FixedOpenRack, !- Array Type", + " 0.14, !- System Losses", + " TiltAzimuth, !- Array Geometry Type", + " 20, !- Tilt Angle {deg}", + " 180; !- Azimuth Angle {deg}", }); + + ASSERT_TRUE(process_idf(idf_objects)); + + createFacilityElectricPowerServiceObject(); + facilityElectricServiceObj->elecLoadCenterObjs.emplace_back(new ElectPowerLoadCenter(1)); + + // Should warn only for PVWatts1 because PVWatts2 doesn't have a schedule + std::string const error_string = delimited_string({ + " ** Warning ** GeneratorController constructor ElectricLoadCenter:Generators, Availability Schedule for Generator:PVWatts 'PVWATTS1' will be be ignored (runs all the time).", + }); + EXPECT_TRUE(compare_err_stream(error_string, true)); + +} diff --git a/tst/EnergyPlus/unit/Fixtures/EnergyPlusFixture.cc b/tst/EnergyPlus/unit/Fixtures/EnergyPlusFixture.cc index 90dd509e950..175a07b583d 100644 --- a/tst/EnergyPlus/unit/Fixtures/EnergyPlusFixture.cc +++ b/tst/EnergyPlus/unit/Fixtures/EnergyPlusFixture.cc @@ -93,6 +93,7 @@ #include #include #include +#include #include #include #include @@ -339,6 +340,7 @@ void EnergyPlusFixture::clear_all_states() DataMoistureBalance::clear_state(); DataMoistureBalanceEMPD::clear_state(); DataOutputs::clear_state(); + DataPhotovoltaics::clear_state(); DataPlant::clear_state(); DataRoomAirModel::clear_state(); DataRuntimeLanguage::clear_state(); diff --git a/tst/EnergyPlus/unit/HeatBalanceSurfaceManager.unit.cc b/tst/EnergyPlus/unit/HeatBalanceSurfaceManager.unit.cc index cf5b7484471..eb517e74df7 100644 --- a/tst/EnergyPlus/unit/HeatBalanceSurfaceManager.unit.cc +++ b/tst/EnergyPlus/unit/HeatBalanceSurfaceManager.unit.cc @@ -2502,4 +2502,52 @@ TEST_F(EnergyPlusFixture, HeatBalanceSurfaceManager_TestSurfTempCalcHeatBalanceA DataHeatBalance::ZoneWinHeatGainRepEnergy.deallocate(); } +TEST_F(EnergyPlusFixture, HeatBalanceSurfaceManager_TestReportIntMovInsInsideSurfTemp) +{ + + Real64 ExpectedResult1; + Real64 ExpectedResult2; + Real64 ExpectedResult3; + + DataSurfaces::clear_state(); + DataHeatBalSurface::clear_state(); + + DataSurfaces::TotSurfaces = 3; + DataSurfaces::Surface.allocate(DataSurfaces::TotSurfaces); + DataHeatBalSurface::TempSurfIn.allocate(DataSurfaces::TotSurfaces); + DataHeatBalSurface::TempSurfInTmp.allocate(DataSurfaces::TotSurfaces); + DataHeatBalSurface::TempSurfInMovInsRep.allocate(DataSurfaces::TotSurfaces); + + // Test 1 Data: Surface does NOT have movable insulation + DataSurfaces::Surface(1).MaterialMovInsulInt = 0; // No material means no movable insulation + DataSurfaces::Surface(1).SchedMovInsulInt = 0; // Schedule index of zero returns zero value (not scheduled) + DataHeatBalSurface::TempSurfIn(1) = 23.0; + DataHeatBalSurface::TempSurfInTmp(1) = 12.3; + DataHeatBalSurface::TempSurfInMovInsRep(1) = 1.23; + ExpectedResult1 = 23.0; // TempSurfInMovInsRep should be set to TempSurfIn + + // Test 2 Data: Surface does have movable insulation but it is scheduled OFF + DataSurfaces::Surface(2).MaterialMovInsulInt = 1; // Material index present means there is movable insulation + DataSurfaces::Surface(2).SchedMovInsulInt = 0; // Schedule index of zero returns zero value (not scheduled) + DataHeatBalSurface::TempSurfIn(2) = 123.0; + DataHeatBalSurface::TempSurfInTmp(2) = 12.3; + DataHeatBalSurface::TempSurfInMovInsRep(2) = 1.23; + ExpectedResult2 = 123.0; // TempSurfInMovInsRep should be set to TempSurfIn + + // Test 3 Data: Surface does have movable insulation and it is scheduled ON + DataSurfaces::Surface(3).MaterialMovInsulInt = 1; // Material index present means there is movable insulation + DataSurfaces::Surface(3).SchedMovInsulInt = -1; // Schedule index of -1 returns 1.0 value + DataHeatBalSurface::TempSurfIn(3) = 12.3; + DataHeatBalSurface::TempSurfInTmp(3) = 1.23; + DataHeatBalSurface::TempSurfInMovInsRep(3) = -9999.9; + ExpectedResult3 = 1.23; // TempSurfInMovInsRep should be set to TempSurfInTmp + + // Now call the subroutine which will run all of the test cases at once and then make the comparisons + HeatBalanceSurfaceManager::ReportIntMovInsInsideSurfTemp(); + EXPECT_NEAR(DataHeatBalSurface::TempSurfInMovInsRep(1),ExpectedResult1,0.00001); + EXPECT_NEAR(DataHeatBalSurface::TempSurfInMovInsRep(2),ExpectedResult2,0.00001); + EXPECT_NEAR(DataHeatBalSurface::TempSurfInMovInsRep(3),ExpectedResult3,0.00001); + +} + } // namespace EnergyPlus diff --git a/tst/EnergyPlus/unit/InputProcessor.unit.cc b/tst/EnergyPlus/unit/InputProcessor.unit.cc index e81b7aaef1c..6af0fbf5d54 100644 --- a/tst/EnergyPlus/unit/InputProcessor.unit.cc +++ b/tst/EnergyPlus/unit/InputProcessor.unit.cc @@ -3867,6 +3867,48 @@ TEST_F(InputProcessorFixture, FalseDuplicates_LowestLevel_AlphaNum) { } +TEST_F(InputProcessorFixture, clean_epjson) +{ + std::string const input("{\"Building\":{" + "\"Zone1\":{" + "\"idf_max_extensible_fields\":0," + "\"idf_max_fields\":8," + "\"idf_order\":1" + "}" + "}," + "\"GlobalGeometryRules\":{" + "\"\":{" + "\"coordinate_system\":\"Relative\"," + "\"daylighting_reference_point_coordinate_system\":\"Relative\"," + "\"idf_order\":0," + "\"rectangular_surface_coordinate_system\":\"Relative\"," + "\"starting_vertex_position\":\"UpperLeftCorner\"," + "\"vertex_entry_direction\":\"Counterclockwise\"" + "}" + "}}"); + + std::string const expected("{\"Building\":{" + "\"Zone1\":{" + "}" + "}," + "\"GlobalGeometryRules\":{" + "\"\":{" + "\"coordinate_system\":\"Relative\"," + "\"daylighting_reference_point_coordinate_system\":\"Relative\"," + "\"rectangular_surface_coordinate_system\":\"Relative\"," + "\"starting_vertex_position\":\"UpperLeftCorner\"," + "\"vertex_entry_direction\":\"Counterclockwise\"" + "}" + "}}"); + + json cleanInput = json::parse(input); + + cleanEPJSON(cleanInput); + std::string cleanstring = cleanInput.dump(); + + EXPECT_EQ(expected, cleanstring); +} + /* TEST_F( InputProcessorFixture, processIDF_json ) { diff --git a/tst/EnergyPlus/unit/MixedAir.unit.cc b/tst/EnergyPlus/unit/MixedAir.unit.cc index 0d9be869f09..39708a84c6d 100644 --- a/tst/EnergyPlus/unit/MixedAir.unit.cc +++ b/tst/EnergyPlus/unit/MixedAir.unit.cc @@ -6111,4 +6111,500 @@ TEST_F(EnergyPlusFixture, OAController_FixedMinimum_MinimumLimitTypeTest) EXPECT_TRUE(AirLoopControlInfo(AirLoopNum).HeatRecoveryBypass); } +TEST_F(EnergyPlusFixture, OAController_HighExhaustMassFlowTest) +{ + std::string const idf_objects = delimited_string({ + + " OutdoorAir:Node,", + " Outside Air Inlet Node; !- Name", + + " Controller:OutdoorAir,", + " OA Controller, !- Name", + " Relief Air Outlet Node, !- Relief Air Outlet Node Name", + " VAV Sys Inlet Node, !- Return Air Node Name", + " Mixed Air Node, !- Mixed Air Node Name", + " Outside Air Inlet Node, !- Actuator Node Name", + " 0.2, !- Minimum Outdoor Air Flow Rate {m3/s}", + " 1.0, !- Maximum Outdoor Air Flow Rate {m3/s}", + " DifferentialDryBulb, !- Economizer Control Type", + " ModulateFlow, !- Economizer Control Action Type", + " , !- Economizer Maximum Limit Dry-Bulb Temperature {C}", + " , !- Economizer Maximum Limit Enthalpy {J/kg}", + " , !- Economizer Maximum Limit Dewpoint Temperature {C}", + " , !- Electronic Enthalpy Limit Curve Name", + " , !- Economizer Minimum Limit Dry-Bulb Temperature {C}", + " NoLockout, !- Lockout Type", + " FixedMinimum, !- Minimum Limit Type", + " , !- Minimum Outdoor Air Schedule Name", + " , !- Minimum Fraction of Outdoor Air Schedule Name", + " , !- Maximum Fraction of Outdoor Air Schedule Name", + " , !- Mechanical Ventilation Controller Name", + " , !- Time of Day Economizer Control Schedule Name", + " No, !- High Humidity Control", + " , !- Humidistat Control Zone Name", + " , !- High Humidity Outdoor Air Flow Ratio", + " Yes, !- Control High Indoor Humidity Based on Outdoor Humidity Ratio", + " BypassWhenOAFlowGreaterThanMinimum; !- Heat Recovery Bypass Control Type", + + " HeatExchanger:AirToAir:SensibleAndLatent,", + " OA Heat Recovery, !- Name", + " , !- Availability Schedule Name", + " AUTOSIZE, !- Nominal Supply Air Flow Rate {m3/s}", + " 0.70, !- Sensible Effectiveness at 100% Heating Air Flow {dimensionless}", + " 0.60, !- Latent Effectiveness at 100% Heating Air Flow {dimensionless}", + " 0.70, !- Sensible Effectiveness at 75% Heating Air Flow {dimensionless}", + " 0.60, !- Latent Effectiveness at 75% Heating Air Flow {dimensionless}", + " 0.75, !- Sensible Effectiveness at 100% Cooling Air Flow {dimensionless}", + " 0.60, !- Latent Effectiveness at 100% Cooling Air Flow {dimensionless}", + " 0.75, !- Sensible Effectiveness at 75% Cooling Air Flow {dimensionless}", + " 0.60, !- Latent Effectiveness at 75% Cooling Air Flow {dimensionless}", + " Outside Air Inlet Node, !- Supply Air Inlet Node Name", + " OA HR Outlet Node, !- Supply Air Outlet Node Name", + " Relief Air Outlet Node, !- Exhaust Air Inlet Node Name", + " HR Exhaust Air Outlet Node, !- Exhaust Air Outlet Node Name", + " 1500.0, !- Nominal Electric Power {W}", + " Yes, !- Supply Air Outlet Temperature Control", + " Rotary, !- Heat Exchanger Type", + " ExhaustOnly, !- Frost Control Type", + " -23.3, !- Threshold Temperature {C}", + " 0.167, !- Initial Defrost Time Fraction {dimensionless}", + " 1.44; !- Rate of Defrost Time Fraction Increase {1/K}", + + " OutdoorAir:Mixer,", + " OA Mixer, !- Name", + " Mixed Air Node, !- Mixed Air Node Name", + " OA HR Outlet Node, !- Outdoor Air Stream Node Name", + " Relief Air Outlet Node, !- Relief Air Stream Node Name", + " VAV Sys Inlet Node; !- Return Air Stream Node Name", + + " AirLoopHVAC:ControllerList,", + " OA Sys Controller, !- Name", + " Controller:OutdoorAir, !- Controller 1 Object Type", + " OA Controller; !- Controller 1 Name", + + " AirLoopHVAC:OutdoorAirSystem:EquipmentList,", + " OA Sys Equipment list, !- Name", + " HeatExchanger:AirToAir:SensibleAndLatent, !- Component 1 Object Type", + " OA Heat Recovery, !- Component 1 Name", + " Coil:Heating:Electric, !- Component 2 Object Type", + " OA Sys Heating Coil, !- Component 2 Name", + " OutdoorAir:Mixer, !- Component 3 Object Type", + " OA Mixer; !- Component 3 Name", + + " AirLoopHVAC:OutdoorAirSystem,", + " OA Sys, !- Name", + " OA Sys controller, !- Controller List Name", + " OA Sys Equipment list; !- Outdoor Air Equipment List Name", + + " Coil:Heating:Electric,", + " OA Sys Heating Coil, !- Name", + " , !- Availability Schedule Name", + " 1, !- Efficiency", + " 2500, !- Nominal Capacity{ W }", + " Outside Air Inlet Node, !- Air Inlet Node Name", + " OA Sys HC Outlet Node, !- Air Outlet Node Name", + " OA Sys HC Outlet Node; !- Temperature Setpoint Node Name" + + }); + + ASSERT_TRUE(process_idf(idf_objects)); + GetOutsideAirSysInputs(); + EXPECT_EQ(1, NumOASystems); + EXPECT_EQ("OA SYS", OutsideAirSys(1).Name); + + EXPECT_EQ(3, OutsideAirSys(1).NumComponents); + EXPECT_EQ("OA HEAT RECOVERY", OutsideAirSys(1).ComponentName(1)); + EXPECT_EQ("OA SYS HEATING COIL", OutsideAirSys(1).ComponentName(2)); + EXPECT_EQ("OA MIXER", OutsideAirSys(1).ComponentName(3)); + + GetOAControllerInputs(); + EXPECT_EQ(5, OAController(1).OANode); + EXPECT_TRUE(OutAirNodeManager::CheckOutAirNodeNumber(OAController(1).OANode)); + + int OAControllerNum(1); + int AirLoopNum(1); + + DataHVACGlobals::NumPrimaryAirSys = 1; + DataEnvironment::StdBaroPress = DataEnvironment::StdPressureSeaLevel; + // assume dry air (zero humidity ratio) + DataEnvironment::StdRhoAir = Psychrometrics::PsyRhoAirFnPbTdbW(DataEnvironment::StdBaroPress, 20.0, 0.0); + + AirLoopFlow.allocate(1); + PrimaryAirSystem.allocate(1); + AirLoopControlInfo.allocate(1); + + auto &curAirLoopFlow(AirLoopFlow(AirLoopNum)); + auto &curOACntrl(OAController(OAControllerNum)); + auto &AirLoopCntrlInfo(AirLoopControlInfo(AirLoopNum)); + auto &PrimaryAirSys(PrimaryAirSystem(AirLoopNum)); + + PrimaryAirSys.NumBranches = 1; + PrimaryAirSys.Branch.allocate(1); + PrimaryAirSys.Branch(1).TotalComponents = 1; + PrimaryAirSys.Branch(1).Comp.allocate(1); + PrimaryAirSys.Branch(1).Comp(1).Name = "OA Sys"; + PrimaryAirSys.Branch(1).Comp(1).TypeOf = "AirLoopHVAC:OutdoorAirSystem"; + + Real64 DesignSupplyAirMassFlow = 1.0 * DataEnvironment::StdRhoAir; + Real64 MixedAirMassFlow = DesignSupplyAirMassFlow; + + curAirLoopFlow.DesSupply = DesignSupplyAirMassFlow; + + // Initialize common AirLoop data + AirLoopCntrlInfo.OASysNum = AirLoopNum; + AirLoopCntrlInfo.EconoLockout = false; + AirLoopCntrlInfo.NightVent = false; + AirLoopCntrlInfo.FanOpMode = DataHVACGlobals::ContFanCycCoil; + AirLoopCntrlInfo.LoopFlowRateSet = false; + AirLoopCntrlInfo.CheckHeatRecoveryBypassStatus = true; + AirLoopCntrlInfo.OASysComponentsSimulated = true; + AirLoopCntrlInfo.EconomizerFlowLocked = false; + AirLoopCntrlInfo.HeatRecoveryBypass = false; + AirLoopCntrlInfo.HeatRecoveryResimFlag = false; + AirLoopCntrlInfo.HeatingActiveFlag = false; + + // Initialize OA controller and node data + curOACntrl.MinOAMassFlowRate = curOACntrl.MinOA * DataEnvironment::StdRhoAir; + curOACntrl.MaxOAMassFlowRate = curOACntrl.MaxOA * DataEnvironment::StdRhoAir; + curOACntrl.InletNode = curOACntrl.OANode; + curOACntrl.RetTemp = 20.0; + curOACntrl.OATemp = -10.0; + curOACntrl.InletTemp = curOACntrl.OATemp; + curOACntrl.MixSetTemp = 5.0; + // exhaust mass flow rate is set to design supply flow rate + curOACntrl.ExhMassFlow = DesignSupplyAirMassFlow; + curOACntrl.MixMassFlow = MixedAirMassFlow; + + // Initialize air node data + Node(curOACntrl.MixNode).MassFlowRate = curOACntrl.MixMassFlow; + Node(curOACntrl.MixNode).MassFlowRateMaxAvail = curOACntrl.MixMassFlow; + Node(curOACntrl.RetNode).Temp = curOACntrl.RetTemp; + Node(curOACntrl.RetNode).Enthalpy = Psychrometrics::PsyHFnTdbW(curOACntrl.RetTemp, 0.0); + Node(curOACntrl.MixNode).TempSetPoint = curOACntrl.MixSetTemp; + Node(curOACntrl.OANode).Temp = curOACntrl.OATemp; + Node(curOACntrl.OANode).Enthalpy = Psychrometrics::PsyHFnTdbW(curOACntrl.InletTemp, 0.0); + + Real64 OAMassFlowActual(0.0); + Real64 OAMassFlowAMin(0.0); + Real64 OutAirMassFlowFracMin(0.0); + Real64 OutAirMassFlowFracActual(0.0); + + // check OA controller inputs + EXPECT_EQ(curOACntrl.MinOA, 0.2); // user specified minimum OA vol flow rate + EXPECT_TRUE(curOACntrl.FixedMin); // Economizer Minimum Limit Type = FixedMinimum + EXPECT_EQ(curOACntrl.Lockout, NoLockoutPossible); // NoLockout (ecoomizer always active) + EXPECT_EQ(curOACntrl.HeatRecoveryBypassControlType, DataHVACGlobals::BypassWhenOAFlowGreaterThanMinimum); + + // calc minimum OA mass flow for FixedMinimum + OAMassFlowAMin = curOACntrl.MinOA * DataEnvironment::StdRhoAir; + // calc minimum OA mass flow fraction + OutAirMassFlowFracMin = OAMassFlowAMin / DesignSupplyAirMassFlow; + + // calc actual OA mass flow fraction + OutAirMassFlowFracActual = (curOACntrl.MixSetTemp - curOACntrl.RetTemp) / (curOACntrl.InletTemp - curOACntrl.RetTemp); + EXPECT_EQ(0.5, OutAirMassFlowFracActual); + // calc actual OA mass flow + OAMassFlowActual = OutAirMassFlowFracActual * MixedAirMassFlow; + + // run OA controller and OA economizer + curOACntrl.CalcOAController(AirLoopNum, true); + // actual OA mass flow is not allowed to fall below exhaust mass flow + // actual OA mass flow is dectated by the amount of exhaust mass flow + OAMassFlowActual = max(curOACntrl.ExhMassFlow, curOACntrl.OAMassFlow); + OutAirMassFlowFracActual = OAMassFlowActual / curOACntrl.MixMassFlow; + // check min OA flow and fraction + EXPECT_EQ(OAMassFlowAMin, curAirLoopFlow.MinOutAir); + EXPECT_EQ(0.2, curAirLoopFlow.OAMinFrac); + EXPECT_EQ(OutAirMassFlowFracMin, curAirLoopFlow.OAMinFrac); + // check actual OA flow and fraction + EXPECT_EQ(OAMassFlowActual, curOACntrl.OAMassFlow); + EXPECT_EQ(OAMassFlowActual, curAirLoopFlow.OAFlow); + EXPECT_EQ(1.0, curAirLoopFlow.OAFrac); + EXPECT_EQ(OutAirMassFlowFracActual, curAirLoopFlow.OAFrac); + // check HX bypass status + EXPECT_GT(OAMassFlowActual, OAMassFlowAMin); + EXPECT_EQ(OAMassFlowActual, curOACntrl.MixMassFlow); + EXPECT_EQ(1, curOACntrl.HeatRecoveryBypassStatus); + EXPECT_TRUE(AirLoopCntrlInfo.HeatRecoveryBypass); + EXPECT_FALSE(AirLoopCntrlInfo.HeatingActiveFlag); + EXPECT_EQ(0, curOACntrl.HRHeatingCoilActive); + + // reduced mixed air temperature setpoint such that it increases + // the actual OA mass flow and trigger the heat recovery by pass + curOACntrl.MixSetTemp = 2.0; + AirLoopCntrlInfo.HeatingActiveFlag = true; // active heating coil + // calc actual OA mass flow fraction + OutAirMassFlowFracActual = (curOACntrl.MixSetTemp - curOACntrl.RetTemp) / (curOACntrl.InletTemp - curOACntrl.RetTemp); + EXPECT_EQ(0.6, OutAirMassFlowFracActual); + // calc actual OA mass flow + OAMassFlowActual = OutAirMassFlowFracActual * MixedAirMassFlow; + // run OA controller and OA economizer + curOACntrl.CalcOAController(AirLoopNum, true); + // actual OA mass flow is not allowed to fall below exhaust mass flow + // actual OA mass flow is dectated by the amount of exhaust mass flow + OAMassFlowActual = max(curOACntrl.ExhMassFlow, curOACntrl.OAMassFlow); + OutAirMassFlowFracActual = OAMassFlowActual / curOACntrl.MixMassFlow; + // check min OA flow and fraction + EXPECT_EQ(OAMassFlowAMin, curAirLoopFlow.MinOutAir); + EXPECT_EQ(0.2, curAirLoopFlow.OAMinFrac); + EXPECT_EQ(OutAirMassFlowFracMin, curAirLoopFlow.OAMinFrac); + // check actual OA flow and fraction + EXPECT_EQ(OAMassFlowActual, curOACntrl.OAMassFlow); + EXPECT_EQ(OAMassFlowActual, curAirLoopFlow.OAFlow); + EXPECT_EQ(1.0, curAirLoopFlow.OAFrac); + EXPECT_EQ(OutAirMassFlowFracActual, curAirLoopFlow.OAFrac); + // check HX bypass status + EXPECT_GT(OAMassFlowActual, OAMassFlowAMin); + EXPECT_EQ(OAMassFlowActual, curOACntrl.MixMassFlow); + EXPECT_EQ(1, curOACntrl.HeatRecoveryBypassStatus); + EXPECT_TRUE(AirLoopCntrlInfo.HeatRecoveryBypass); + EXPECT_TRUE(AirLoopCntrlInfo.HeatingActiveFlag); + EXPECT_EQ(1, curOACntrl.HRHeatingCoilActive); +} + +TEST_F(EnergyPlusFixture, OAController_LowExhaustMassFlowTest) +{ + std::string const idf_objects = delimited_string({ + + " OutdoorAir:Node,", + " Outside Air Inlet Node; !- Name", + + " Controller:OutdoorAir,", + " OA Controller, !- Name", + " Relief Air Outlet Node, !- Relief Air Outlet Node Name", + " VAV Sys Inlet Node, !- Return Air Node Name", + " Mixed Air Node, !- Mixed Air Node Name", + " Outside Air Inlet Node, !- Actuator Node Name", + " 0.5, !- Minimum Outdoor Air Flow Rate {m3/s}", + " 1.0, !- Maximum Outdoor Air Flow Rate {m3/s}", + " DifferentialDryBulb, !- Economizer Control Type", + " ModulateFlow, !- Economizer Control Action Type", + " , !- Economizer Maximum Limit Dry-Bulb Temperature {C}", + " , !- Economizer Maximum Limit Enthalpy {J/kg}", + " , !- Economizer Maximum Limit Dewpoint Temperature {C}", + " , !- Electronic Enthalpy Limit Curve Name", + " , !- Economizer Minimum Limit Dry-Bulb Temperature {C}", + " NoLockout, !- Lockout Type", + " FixedMinimum, !- Minimum Limit Type", + " , !- Minimum Outdoor Air Schedule Name", + " , !- Minimum Fraction of Outdoor Air Schedule Name", + " , !- Maximum Fraction of Outdoor Air Schedule Name", + " , !- Mechanical Ventilation Controller Name", + " , !- Time of Day Economizer Control Schedule Name", + " No, !- High Humidity Control", + " , !- Humidistat Control Zone Name", + " , !- High Humidity Outdoor Air Flow Ratio", + " Yes, !- Control High Indoor Humidity Based on Outdoor Humidity Ratio", + " BypassWhenOAFlowGreaterThanMinimum; !- Heat Recovery Bypass Control Type", + + " HeatExchanger:AirToAir:SensibleAndLatent,", + " OA Heat Recovery, !- Name", + " , !- Availability Schedule Name", + " AUTOSIZE, !- Nominal Supply Air Flow Rate {m3/s}", + " 0.70, !- Sensible Effectiveness at 100% Heating Air Flow {dimensionless}", + " 0.60, !- Latent Effectiveness at 100% Heating Air Flow {dimensionless}", + " 0.70, !- Sensible Effectiveness at 75% Heating Air Flow {dimensionless}", + " 0.60, !- Latent Effectiveness at 75% Heating Air Flow {dimensionless}", + " 0.75, !- Sensible Effectiveness at 100% Cooling Air Flow {dimensionless}", + " 0.60, !- Latent Effectiveness at 100% Cooling Air Flow {dimensionless}", + " 0.75, !- Sensible Effectiveness at 75% Cooling Air Flow {dimensionless}", + " 0.60, !- Latent Effectiveness at 75% Cooling Air Flow {dimensionless}", + " OA Sys HC Outlet Node, !- Supply Air Inlet Node Name", + " OA HR Outlet Node, !- Supply Air Outlet Node Name", + " Relief Air Outlet Node, !- Exhaust Air Inlet Node Name", + " HR Exhaust Air Outlet Node, !- Exhaust Air Outlet Node Name", + " 1500.0, !- Nominal Electric Power {W}", + " Yes, !- Supply Air Outlet Temperature Control", + " Rotary, !- Heat Exchanger Type", + " ExhaustOnly, !- Frost Control Type", + " -23.3, !- Threshold Temperature {C}", + " 0.167, !- Initial Defrost Time Fraction {dimensionless}", + " 1.44; !- Rate of Defrost Time Fraction Increase {1/K}", + + " OutdoorAir:Mixer,", + " OA Mixer, !- Name", + " Mixed Air Node, !- Mixed Air Node Name", + " OA HR Outlet Node, !- Outdoor Air Stream Node Name", + " Relief Air Outlet Node, !- Relief Air Stream Node Name", + " VAV Sys Inlet Node; !- Return Air Stream Node Name", + + " AirLoopHVAC:ControllerList,", + " OA Sys Controller, !- Name", + " Controller:OutdoorAir, !- Controller 1 Object Type", + " OA Controller; !- Controller 1 Name", + + " AirLoopHVAC:OutdoorAirSystem:EquipmentList,", + " OA Sys Equipment list, !- Name", + " HeatExchanger:AirToAir:SensibleAndLatent, !- Component 1 Object Type", + " OA Heat Recovery, !- Component 1 Name", + " Coil:Heating:Electric, !- Component 2 Object Type", + " OA Sys Heating Coil, !- Component 2 Name", + " OutdoorAir:Mixer, !- Component 3 Object Type", + " OA Mixer; !- Component 3 Name", + + " AirLoopHVAC:OutdoorAirSystem,", + " OA Sys, !- Name", + " OA Sys controller, !- Controller List Name", + " OA Sys Equipment list; !- Outdoor Air Equipment List Name", + + " Coil:Heating:Electric,", + " OA Sys Heating Coil, !- Name", + " , !- Availability Schedule Name", + " 1, !- Efficiency", + " 2500, !- Nominal Capacity{ W }", + " Outside Air Inlet Node, !- Air Inlet Node Name", + " OA Sys HC Outlet Node, !- Air Outlet Node Name", + " OA Sys HC Outlet Node; !- Temperature Setpoint Node Name" + + }); + + ASSERT_TRUE(process_idf(idf_objects)); + GetOutsideAirSysInputs(); + EXPECT_EQ(1, NumOASystems); + EXPECT_EQ("OA SYS", OutsideAirSys(1).Name); + + EXPECT_EQ(3, OutsideAirSys(1).NumComponents); + EXPECT_EQ("OA HEAT RECOVERY", OutsideAirSys(1).ComponentName(1)); + EXPECT_EQ("OA SYS HEATING COIL", OutsideAirSys(1).ComponentName(2)); + EXPECT_EQ("OA MIXER", OutsideAirSys(1).ComponentName(3)); + + GetOAControllerInputs(); + EXPECT_EQ(5, OAController(1).OANode); + EXPECT_TRUE(OutAirNodeManager::CheckOutAirNodeNumber(OAController(1).OANode)); + + int OAControllerNum(1); + int AirLoopNum(1); + + DataHVACGlobals::NumPrimaryAirSys = 1; + DataEnvironment::StdBaroPress = DataEnvironment::StdPressureSeaLevel; + // assume dry air (zero humidity ratio) + DataEnvironment::StdRhoAir = Psychrometrics::PsyRhoAirFnPbTdbW(DataEnvironment::StdBaroPress, 20.0, 0.0); + + AirLoopFlow.allocate(1); + PrimaryAirSystem.allocate(1); + AirLoopControlInfo.allocate(1); + + auto &curAirLoopFlow(AirLoopFlow(AirLoopNum)); + auto &curOACntrl(OAController(OAControllerNum)); + auto &AirLoopCntrlInfo(AirLoopControlInfo(AirLoopNum)); + auto &PrimaryAirSys(PrimaryAirSystem(AirLoopNum)); + + PrimaryAirSys.NumBranches = 1; + PrimaryAirSys.Branch.allocate(1); + PrimaryAirSys.Branch(1).TotalComponents = 1; + PrimaryAirSys.Branch(1).Comp.allocate(1); + PrimaryAirSys.Branch(1).Comp(1).Name = "OA Sys"; + PrimaryAirSys.Branch(1).Comp(1).TypeOf = "AirLoopHVAC:OutdoorAirSystem"; + + Real64 DesignSupplyAirMassFlow = 1.0 * DataEnvironment::StdRhoAir; + Real64 MixedAirMassFlow = DesignSupplyAirMassFlow; + + curAirLoopFlow.DesSupply = DesignSupplyAirMassFlow; + + // Initialize common AirLoop data + AirLoopCntrlInfo.OASysNum = AirLoopNum; + AirLoopCntrlInfo.EconoLockout = false; + AirLoopCntrlInfo.NightVent = false; + AirLoopCntrlInfo.FanOpMode = DataHVACGlobals::ContFanCycCoil; + AirLoopCntrlInfo.LoopFlowRateSet = false; + AirLoopCntrlInfo.CheckHeatRecoveryBypassStatus = true; + AirLoopCntrlInfo.OASysComponentsSimulated = true; + AirLoopCntrlInfo.EconomizerFlowLocked = false; + AirLoopCntrlInfo.HeatRecoveryBypass = false; + AirLoopCntrlInfo.HeatRecoveryResimFlag = false; + AirLoopCntrlInfo.HeatingActiveFlag = false; + + // Initialize OA controller and node data + curOACntrl.MinOAMassFlowRate = curOACntrl.MinOA * DataEnvironment::StdRhoAir; + curOACntrl.MaxOAMassFlowRate = curOACntrl.MaxOA * DataEnvironment::StdRhoAir; + curOACntrl.InletNode = curOACntrl.OANode; + curOACntrl.RetTemp = 20.0; + curOACntrl.OATemp = -10.0; + curOACntrl.InletTemp = curOACntrl.OATemp; + curOACntrl.MixSetTemp = 5.0; + // exhaust mass flow rate is set to a third of design supply flow rate + curOACntrl.ExhMassFlow = 0.30 * DesignSupplyAirMassFlow; + curOACntrl.MixMassFlow = MixedAirMassFlow; + + // Initialize air node data + Node(curOACntrl.MixNode).MassFlowRate = curOACntrl.MixMassFlow; + Node(curOACntrl.MixNode).MassFlowRateMaxAvail = curOACntrl.MixMassFlow; + Node(curOACntrl.RetNode).Temp = curOACntrl.RetTemp; + Node(curOACntrl.RetNode).Enthalpy = Psychrometrics::PsyHFnTdbW(curOACntrl.RetTemp, 0.0); + Node(curOACntrl.MixNode).TempSetPoint = curOACntrl.MixSetTemp; + Node(curOACntrl.OANode).Temp = curOACntrl.OATemp; + Node(curOACntrl.OANode).Enthalpy = Psychrometrics::PsyHFnTdbW(curOACntrl.InletTemp, 0.0); + + Real64 OAMassFlowActual(0.0); + Real64 OAMassFlowAMin(0.0); + Real64 OutAirMassFlowFracMin(0.0); + Real64 OutAirMassFlowFracActual(0.0); + + // check OA controller inputs + EXPECT_EQ(curOACntrl.MinOA, 0.5); // user specified minimum OA vol flow rate + EXPECT_TRUE(curOACntrl.FixedMin); // Economizer Minimum Limit Type = FixedMinimum + EXPECT_EQ(curOACntrl.Lockout, NoLockoutPossible); // NoLockout (ecoomizer always active) + EXPECT_EQ(curOACntrl.HeatRecoveryBypassControlType, DataHVACGlobals::BypassWhenOAFlowGreaterThanMinimum); + + // calc minimum OA mass flow for FixedMinimum + OAMassFlowAMin = curOACntrl.MinOA * DataEnvironment::StdRhoAir; + // calc minimum OA mass flow fraction + OutAirMassFlowFracMin = OAMassFlowAMin / DesignSupplyAirMassFlow; + + // calc actual OA mass flow fraction + OutAirMassFlowFracActual = (curOACntrl.MixSetTemp - curOACntrl.RetTemp) / (curOACntrl.InletTemp - curOACntrl.RetTemp); + EXPECT_EQ(0.5, OutAirMassFlowFracActual); + // calc actual OA mass flow + OAMassFlowActual = OutAirMassFlowFracActual * MixedAirMassFlow; + // run OA controller and OA economizer + curOACntrl.CalcOAController(AirLoopNum, true); + OAMassFlowActual = max(curOACntrl.ExhMassFlow, curOACntrl.OAMassFlow); + OutAirMassFlowFracActual = OAMassFlowActual / curOACntrl.MixMassFlow; + // check min OA flow and fraction + EXPECT_EQ(OAMassFlowAMin, curAirLoopFlow.MinOutAir); + EXPECT_EQ(0.5, curAirLoopFlow.OAMinFrac); + EXPECT_EQ(OutAirMassFlowFracMin, curAirLoopFlow.OAMinFrac); + // check actual OA flow and fraction + EXPECT_EQ(OAMassFlowActual, curOACntrl.OAMassFlow); + EXPECT_EQ(OAMassFlowActual, curAirLoopFlow.OAFlow); + EXPECT_EQ(0.5, curAirLoopFlow.OAFrac); + EXPECT_EQ(OutAirMassFlowFracActual, curAirLoopFlow.OAFrac); + // check HX bypass status + EXPECT_EQ(OAMassFlowActual, OAMassFlowAMin); + EXPECT_LT(OAMassFlowActual, curOACntrl.MixMassFlow); + EXPECT_EQ(0, curOACntrl.HeatRecoveryBypassStatus); + EXPECT_FALSE(AirLoopCntrlInfo.HeatRecoveryBypass); + EXPECT_FALSE(AirLoopCntrlInfo.HeatingActiveFlag); + EXPECT_EQ(0, curOACntrl.HRHeatingCoilActive); + + // reduced mixed air temperature setpoint such that it increases + // the actual OA mass flow and trigger the heat recovery by pass + curOACntrl.MixSetTemp = 2.0; + AirLoopCntrlInfo.HeatingActiveFlag = true; // active heating coil + // calc actual OA mass flow fraction + OutAirMassFlowFracActual = (curOACntrl.MixSetTemp - curOACntrl.RetTemp) / (curOACntrl.InletTemp - curOACntrl.RetTemp); + EXPECT_EQ(0.6, OutAirMassFlowFracActual); + // calc actual OA mass flow + OAMassFlowActual = OutAirMassFlowFracActual * MixedAirMassFlow; + // actual OA mass flow is not allowed to fall below exhaust mass flow + curOACntrl.CalcOAController(AirLoopNum, true); + OAMassFlowActual = max(curOACntrl.ExhMassFlow, curOACntrl.OAMassFlow); + OutAirMassFlowFracActual = OAMassFlowActual / curOACntrl.MixMassFlow; + // check min OA flow and fraction + EXPECT_EQ(OAMassFlowAMin, curAirLoopFlow.MinOutAir); + EXPECT_EQ(0.5, curAirLoopFlow.OAMinFrac); + EXPECT_EQ(OutAirMassFlowFracMin, curAirLoopFlow.OAMinFrac); + // check actual OA flow and fraction + EXPECT_EQ(OAMassFlowActual, curOACntrl.OAMassFlow); + EXPECT_EQ(OAMassFlowActual, curAirLoopFlow.OAFlow); + EXPECT_EQ(0.6, curAirLoopFlow.OAFrac); + EXPECT_EQ(OutAirMassFlowFracActual, curAirLoopFlow.OAFrac); + // check HX bypass status + EXPECT_GT(OAMassFlowActual, OAMassFlowAMin); + EXPECT_LT(OAMassFlowActual, curOACntrl.MixMassFlow); + EXPECT_EQ(1, curOACntrl.HeatRecoveryBypassStatus); + EXPECT_TRUE(AirLoopCntrlInfo.HeatRecoveryBypass); + EXPECT_TRUE(AirLoopCntrlInfo.HeatingActiveFlag); + EXPECT_EQ(1, curOACntrl.HRHeatingCoilActive); +} } // namespace EnergyPlus diff --git a/tst/EnergyPlus/unit/ScheduleManager.unit.cc b/tst/EnergyPlus/unit/ScheduleManager.unit.cc index c7f75dfdfd3..c860dd169c8 100644 --- a/tst/EnergyPlus/unit/ScheduleManager.unit.cc +++ b/tst/EnergyPlus/unit/ScheduleManager.unit.cc @@ -83,6 +83,97 @@ TEST_F(EnergyPlusFixture, ScheduleManager_isMinuteMultipleOfTimestep) EXPECT_FALSE(isMinuteMultipleOfTimestep(53, 12)); } +TEST_F(EnergyPlusFixture, ScheduleManager_UpdateScheduleValues) +{ + + ScheduleInputProcessed = true; + DataEnvironment::DSTIndicator = 0; + ScheduleManager::NumSchedules = 1; + ScheduleManager::Schedule.allocate(1); + Schedule(1).WeekSchedulePointer.allocate(367); + WeekSchedule.allocate(3); + WeekSchedule(1).DaySchedulePointer.allocate(12); + WeekSchedule(2).DaySchedulePointer.allocate(12); + WeekSchedule(3).DaySchedulePointer.allocate(12); + DataGlobals::NumOfTimeStepInHour = 1; + DaySchedule.allocate(3); + DaySchedule(1).TSValue.allocate(1, 24); + DaySchedule(2).TSValue.allocate(1, 24); + DaySchedule(3).TSValue.allocate(1, 24); + + for (int ScheduleIndex = 1; ScheduleIndex <= ScheduleManager::NumSchedules; ScheduleIndex++) { + for (int i = 1; i <= 366; i++) { + int x = 1; + if (i > 250) { + x = 3; + } else if (i > 249) { + x = 2; + } + Schedule(ScheduleIndex).WeekSchedulePointer(i) = x; + } + } + for (int WeekSchedulePointer = 1; WeekSchedulePointer <= 3; WeekSchedulePointer++) { + for (int dayOfWeek = 1; dayOfWeek <= 12; dayOfWeek++) { + int y = 1; + if (WeekSchedulePointer == 2) y = 2; + if (WeekSchedulePointer == 3) y = 3; + WeekSchedule(WeekSchedulePointer).DaySchedulePointer(dayOfWeek) = y; + } + } + for (int daySchedulePointer = 1; daySchedulePointer <= 3; daySchedulePointer++) { + for (int whichHour = 1; whichHour <= 24; whichHour++) { + Real64 schVal = 1.0; + if (daySchedulePointer == 2) schVal = 2.0; + if (daySchedulePointer == 3) schVal = 3.0; + DaySchedule(daySchedulePointer).TSValue(1, whichHour) = schVal; + } + } + + DataEnvironment::HolidayIndex = 0; + DataEnvironment::DayOfWeek = 1; + DataGlobals::TimeStep = 1; + DataGlobals::HourOfDay = 1; + + // check day schedules + EXPECT_EQ(DaySchedule(1).TSValue(1, 1), 1.0); // day < 250 points to this schedule + EXPECT_EQ(DaySchedule(1).TSValue(1, 24), 1.0); + + EXPECT_EQ(DaySchedule(2).TSValue(1, 1), 2.0); // day = 250 points to this schedule + EXPECT_EQ(DaySchedule(2).TSValue(1, 24), 2.0); + + EXPECT_EQ(DaySchedule(3).TSValue(1, 1), 3.0); // day > 250 points to this schedule + EXPECT_EQ(DaySchedule(3).TSValue(1, 24), 3.0); + + // schedule values are 1 through day 249, 2 for day 250, and 3 for remainder of year + DataEnvironment::DayOfYear_Schedule = 1; + UpdateScheduleValues(); + // expect 1.0 on day 1 + EXPECT_EQ(Schedule(1).CurrentValue, 1.0); + + DataEnvironment::DayOfYear_Schedule = 250; + UpdateScheduleValues(); + // expect 2.0 on day 250 + EXPECT_EQ(Schedule(1).CurrentValue, 2.0); + + // test end of day 250 with daylight savings time active + DataGlobals::HourOfDay = 24; + DataEnvironment::DSTIndicator = 1; + UpdateScheduleValues(); + // expect a 3 on day 251, which on day 250 at midnight with DST of hour 1 of day 251 + EXPECT_EQ(Schedule(1).CurrentValue, 3.0); + + DataGlobals::HourOfDay = 2; + DataEnvironment::DSTIndicator = 0; + DataEnvironment::DayOfYear_Schedule = 251; + UpdateScheduleValues(); + // expect 3.0 for remainder of year regardless of DST + EXPECT_EQ(Schedule(1).CurrentValue, 3.0); + DataGlobals::HourOfDay = 24; + DataEnvironment::DSTIndicator = 1; + UpdateScheduleValues(); + EXPECT_EQ(Schedule(1).CurrentValue, 3.0); +} + TEST_F(EnergyPlusFixture, ScheduleAnnualFullLoadHours_test) { // J.Glazer - August 2017 diff --git a/tst/EnergyPlus/unit/StandardRatings.unit.cc b/tst/EnergyPlus/unit/StandardRatings.unit.cc index 8bc94796926..8dacd2427c4 100644 --- a/tst/EnergyPlus/unit/StandardRatings.unit.cc +++ b/tst/EnergyPlus/unit/StandardRatings.unit.cc @@ -57,12 +57,15 @@ #include #include #include +#include +#include using namespace EnergyPlus; using namespace EnergyPlus::StandardRatings; using namespace EnergyPlus::CurveManager; using namespace EnergyPlus::DataHVACGlobals; using namespace EnergyPlus::DXCoils; +using namespace EnergyPlus::ChillerElectricEIR; namespace EnergyPlus { @@ -236,4 +239,94 @@ TEST_F(EnergyPlusFixture, SingleSpeedHeatingCoilCurveTest) // if one of the CAP or EIR curves value is less than zero, then HSPF is set to zero EXPECT_DOUBLE_EQ(HSPF, 0.0); } + +TEST_F(EnergyPlusFixture, ChillerIPLVTest) +{ + + using CurveManager::Cubic; + using CurveManager::BiQuadratic; + using CurveManager::NumCurves; + using StandardRatings::CalcChillerIPLV; + using DataPlant::TypeOf_Chiller_ElectricEIR; + + // Setup an air-cooled Chiller:Electric:EIR chiller + ChillerElectricEIR::ElectricEIRChiller.allocate(1); + ChillerElectricEIR::ElectricEIRChiller(1).Name = "Air Cooled Chiller"; + ChillerElectricEIR::ElectricEIRChiller(1).RefCap = 216000; // W + ChillerElectricEIR::ElectricEIRChiller(1).RefCOP = 2.81673861898309; // W/W + ChillerElectricEIR::ElectricEIRChiller(1).CondenserType = ChillerElectricEIR::AirCooled; + ChillerElectricEIR::ElectricEIRChiller(1).MinUnloadRat = 0.15; + + int CurveNum; + NumCurves = 3; + PerfCurve.allocate(NumCurves); + + // Cap=f(T) + CurveNum = 1; + PerfCurve(CurveNum).CurveType = BiQuadratic; + PerfCurve(CurveNum).NumDims = 2; + PerfCurve(CurveNum).ObjectType = "Curve:BiQuadratic"; + PerfCurve(CurveNum).InterpolationType = EvaluateCurveToLimits; + PerfCurve(CurveNum).Name = "AirCooledChillerScrewCmpCapfT"; + PerfCurve(CurveNum).Coeff1 = 0.98898813; + PerfCurve(CurveNum).Coeff2 = 0.036832851; + PerfCurve(CurveNum).Coeff3 = 0.000174006; + PerfCurve(CurveNum).Coeff4 = -0.000275634; + PerfCurve(CurveNum).Coeff5 = -0.000143667; + PerfCurve(CurveNum).Coeff6 = -0.000246286; + PerfCurve(CurveNum).Var1Min = 4.44; + PerfCurve(CurveNum).Var1Max = 10; + PerfCurve(CurveNum).Var2Min = 23.89; + PerfCurve(CurveNum).Var2Max = 46.11; + ChillerElectricEIR::ElectricEIRChiller(1).ChillerCapFT = 1; + + // EIR=f(T) + CurveNum = 2; + PerfCurve(CurveNum).CurveType = BiQuadratic; + PerfCurve(CurveNum).NumDims = 2; + PerfCurve(CurveNum).ObjectType = "Curve:BiQuadratic"; + PerfCurve(CurveNum).InterpolationType = EvaluateCurveToLimits; + PerfCurve(CurveNum).Name = "AirCooledChillerScrewCmpEIRfT"; + PerfCurve(CurveNum).Coeff1 = 0.814058418; + PerfCurve(CurveNum).Coeff2 = 0.002335553; + PerfCurve(CurveNum).Coeff3 = 0.000817786; + PerfCurve(CurveNum).Coeff4 = -0.017129784; + PerfCurve(CurveNum).Coeff5 = 0.000773288; + PerfCurve(CurveNum).Coeff6 = -0.000922024; + PerfCurve(CurveNum).Var1Min = 4.44; + PerfCurve(CurveNum).Var1Max = 10; + PerfCurve(CurveNum).Var2Min = 10; + PerfCurve(CurveNum).Var2Max = 46.11; + ChillerElectricEIR::ElectricEIRChiller(1).ChillerEIRFT = 2; + + // EIR=f(PLR) + CurveNum = 3; + PerfCurve(CurveNum).CurveType = Cubic; + PerfCurve(CurveNum).NumDims = 1; + PerfCurve(CurveNum).ObjectType = "Curve:Cubic"; + PerfCurve(CurveNum).InterpolationType = EvaluateCurveToLimits; + PerfCurve(CurveNum).Name = "AirCooledChillerScrewCmpEIRfPLR"; + PerfCurve(CurveNum).Coeff1 = -0.08117804; + PerfCurve(CurveNum).Coeff2 = 1.433532026; + PerfCurve(CurveNum).Coeff3 = -0.762289434; + PerfCurve(CurveNum).Coeff4 = 0.412199944; + PerfCurve(CurveNum).Var1Min = 0; + PerfCurve(CurveNum).Var1Max = 1; + ChillerElectricEIR::ElectricEIRChiller(1).ChillerEIRFPLR = 3; + + Real64 IPLV; + CalcChillerIPLV(ChillerElectricEIR::ElectricEIRChiller(1).Name, + TypeOf_Chiller_ElectricEIR, + ChillerElectricEIR::ElectricEIRChiller(1).RefCap, + ChillerElectricEIR::ElectricEIRChiller(1).RefCOP, + ChillerElectricEIR::ElectricEIRChiller(1).CondenserType, + ChillerElectricEIR::ElectricEIRChiller(1).ChillerCapFT, + ChillerElectricEIR::ElectricEIRChiller(1).ChillerEIRFT, + ChillerElectricEIR::ElectricEIRChiller(1).ChillerEIRFPLR, + ChillerElectricEIR::ElectricEIRChiller(1).MinUnloadRat, + IPLV); + + EXPECT_DOUBLE_EQ(round(IPLV * 100) / 100, 3.87); // 13.20 IPLV + +} } // namespace EnergyPlus diff --git a/tst/EnergyPlus/unit/WaterThermalTanks.unit.cc b/tst/EnergyPlus/unit/WaterThermalTanks.unit.cc index f2f56df1550..e8c0bb7eff4 100644 --- a/tst/EnergyPlus/unit/WaterThermalTanks.unit.cc +++ b/tst/EnergyPlus/unit/WaterThermalTanks.unit.cc @@ -1992,7 +1992,7 @@ TEST_F(EnergyPlusFixture, StratifiedTankCalc) for (int i = 0; i < Tank.Nodes; ++i) { NodeTemps[i] = Tank.Node[i].Temp; } - + // Verify there are no temperature inversions. for (int i = 0; i < Tank.Nodes - 1; ++i) { EXPECT_GE(NodeTemps[i], NodeTemps[i+1]); @@ -2205,7 +2205,7 @@ TEST_F(EnergyPlusFixture, DesuperheaterTimeAdvanceCheck){ "Schedule:Constant, Ambient Temp Schedule, , 20.0;", "Schedule:Constant, Inlet Water Temperature, , 10.0;", "Schedule:Constant, Desuperheater-Schedule, , 55.0;", - "Schedule:Constant, WH Setpoint Temp, , 50.0;", + "Schedule:Constant, WH Setpoint Temp, , 50.0;", " Zone,", " Zone_TES, !- Name", @@ -2426,7 +2426,7 @@ TEST_F(EnergyPlusFixture, DesuperheaterTimeAdvanceCheck){ Tank.SavedTankTemp = 52; // previous time step temperature Tank.AmbientTemp = 50.0; // Assume no loss Tank.UseInletTemp = 10; - Tank.UseMassFlowRate = 0.0; + Tank.UseMassFlowRate = 0.0; Tank.SourceOutletTemp = 50; Tank.SavedSourceOutletTemp = 52; Tank.TimeElapsed = 0.0; @@ -2494,7 +2494,7 @@ TEST_F(EnergyPlusFixture, StratifiedTank_GSHP_DesuperheaterSourceHeat) "Schedule:Constant, Ambient Temp Schedule, , 20.0;", "Schedule:Constant, Inlet Water Temperature, , 10.0;", "Schedule:Constant, Desuperheater-Schedule, , 60.0;", - "Schedule:Constant, WH Setpoint Temp, , 45.0;", + "Schedule:Constant, WH Setpoint Temp, , 45.0;", " Zone,", " Zone_TES, !- Name", @@ -2590,7 +2590,7 @@ TEST_F(EnergyPlusFixture, StratifiedTank_GSHP_DesuperheaterSourceHeat) " , !- Water Pump Power {W}", " 0.2; !- Fraction of Pump Heat to Water", - + "Coil:Cooling:WaterToAirHeatPump:EquationFit,", " GSHP_COIL1, !- Name", " Node 42, !- Water Inlet Node Name", @@ -2709,7 +2709,7 @@ TEST_F(EnergyPlusFixture, StratifiedTank_GSHP_DesuperheaterSourceHeat) Desuperheater.SetPointTemp = 60.0; Desuperheater.Mode = 1; Node(Desuperheater.WaterInletNode).Temp = Tank.SourceOutletTemp; - + HourOfDay = 0; TimeStep = 1; TimeStepZone = 1. / 60.; @@ -2721,7 +2721,7 @@ TEST_F(EnergyPlusFixture, StratifiedTank_GSHP_DesuperheaterSourceHeat) Tank.SetPointTemp = 45; Tank.SetPointTemp2 = 45; WaterThermalTanks::CalcDesuperheaterWaterHeater(TankNum, true); - // If there's no demand in water thermal tank, no heat is reclaimed + // If there's no demand in water thermal tank, no heat is reclaimed EXPECT_EQ(Desuperheater.HeaterRate, 0); for (int i = 1; i <= Tank.Nodes; ++i) { @@ -2765,7 +2765,7 @@ TEST_F(EnergyPlusFixture, Desuperheater_Multispeed_Coil_Test) "Schedule:Constant, Ambient Temp Schedule, , 20.0;", "Schedule:Constant, Inlet Water Temperature, , 10.0;", "Schedule:Constant, Desuperheater-Schedule, , 60.0;", - "Schedule:Constant, WH Setpoint Temp, , 45.0;", + "Schedule:Constant, WH Setpoint Temp, , 45.0;", " Zone,", " Zone_TES, !- Name", @@ -3125,3 +3125,214 @@ TEST_F(EnergyPlusFixture, Desuperheater_Multispeed_Coil_Test) EXPECT_EQ(Desuperheater.HeaterRate, 0.0); EXPECT_EQ(Tank.SourceRate, 0.0); } + +TEST_F(EnergyPlusFixture, MixedTank_WarnPotentialFreeze) +{ + std::string const idf_objects = delimited_string({ + " Schedule:Constant, Water Heater Setpoint Temperature, ,12;", + " Schedule:Constant, Tank Ambient Temperature, , -40;", // That's cold! + + " WaterHeater:Mixed,", + " ChilledWaterTank, !- Name", + " 0.07, !- Tank Volume {m3}", + " Water Heater Setpoint Temperature, !- Setpoint Temperature Schedule Name", + " 2, !- Deadband Temperature Difference {deltaC}", + " 30, !- Maximum Temperature Limit {C}", + " Cycle, !- Heater Control Type", + " 0, !- Heater Maximum Capacity {W}", + " , !- Heater Minimum Capacity {W}", + " 0, !- Heater Ignition Minimum Flow Rate {m3/s}", + " 0, !- Heater Ignition Delay {s}", + " Electricity, !- Heater Fuel Type", + " 0.8, !- Heater Thermal Efficiency", + " , !- Part Load Factor Curve Name", + " 0, !- Off Cycle Parasitic Fuel Consumption Rate {W}", + " Electricity, !- Off Cycle Parasitic Fuel Type", + " 0.8, !- Off Cycle Parasitic Heat Fraction to Tank", + " 0, !- On Cycle Parasitic Fuel Consumption Rate {W}", + " Electricity, !- On Cycle Parasitic Fuel Type", + " 0, !- On Cycle Parasitic Heat Fraction to Tank", + " Schedule, !- Ambient Temperature Indicator", + " Tank Ambient Temperature,!- Ambient Temperature Schedule Name", + " , !- Ambient Temperature Zone Name", + " , !- Ambient Temperature Outdoor Air Node Name", + " 6, !- Off Cycle Loss Coefficient to Ambient Temperature {W/K}", + " 1, !- Off Cycle Loss Fraction to Zone", + " 6, !- On Cycle Loss Coefficient to Ambient Temperature {W/K}", + " 1, !- On Cycle Loss Fraction to Zone", + " , !- Peak Use Flow Rate {m3/s}", + " , !- Use Flow Rate Fraction Schedule Name", + " , !- Cold Water Supply Temperature Schedule Name", + " , !- Use Side Inlet Node Name", + " , !- Use Side Outlet Node Name", + " 1, !- Use Side Effectiveness", + " , !- Source Side Inlet Node Name", + " , !- Source Side Outlet Node Name", + " 1, !- Source Side Effectiveness", + " Autosize, !- Use Side Design Flow Rate {m3/s}", + " Autosize, !- Source Side Design Flow Rate {m3/s}", + " 1.5; !- Indirect Water Heating Recovery Time {hr}", + }); + + ASSERT_TRUE(process_idf(idf_objects)); + + bool ErrorsFound = false; + //HeatBalanceManager::GetZoneData(ErrorsFound); // read zone data + //EXPECT_FALSE(ErrorsFound); + + //InternalHeatGains::GetInternalHeatGainsInput(); + //ErrorsFound = false; + EXPECT_FALSE(WaterThermalTanks::GetWaterThermalTankInputData(ErrorsFound)); + + int TankNum(1); + WaterThermalTanks::WaterThermalTankData &Tank = WaterThermalTanks::WaterThermalTank(TankNum); + + DataGlobals::HourOfDay = 0; + DataGlobals::TimeStep = 1; + DataGlobals::TimeStepZone = 1.0 / 60.0; // one-minute system time step + DataHVACGlobals::TimeStepSys = DataGlobals::TimeStepZone; + + Tank.TankTemp = 2.0; + Tank.AmbientTemp = -40; + Tank.UseInletTemp = 3.0; + Tank.SetPointTemp = 3.0; + Tank.SetPointTemp2 = Tank.SetPointTemp; + Tank.TimeElapsed = 0.0; + + // very low use mass flow rate + Tank.UseMassFlowRate = 0.00005; + // zero source mass flow rate + Tank.SourceMassFlowRate = 0.0; + + // Calls CalcWaterThermalTankMixed + WaterThermalTanks::CalcWaterThermalTank(TankNum); + + // expected tank avg temp less than starting value of 2 C + EXPECT_LT(Tank.TankTempAvg, 2.0); + // And the final tank temp too, which is the one triggering the warning + EXPECT_LT(Tank.TankTemp, 2.0); + + std::string const error_string = delimited_string({ + " ** Warning ** CalcWaterThermalTankMixed: WaterHeater:Mixed = 'CHILLEDWATERTANK': Temperature of tank < 2C indicates of possibility of freeze. Tank Temperature = 1.95 C.", + " ** ~~~ ** Environment=, at Simulation time= 00:-1 - 00:00" + }); + EXPECT_TRUE(compare_err_stream(error_string, true)); + +} + +TEST_F(EnergyPlusFixture, StratifiedTank_WarnPotentialFreeze) +{ + std::string const idf_objects = delimited_string({ + " Schedule:Constant, Water Heater Setpoint Temperature, ,12;", + " Schedule:Constant, Tank Ambient Temperature, , -40;", // That's cold! + + "WaterHeater:Stratified,", + " Stratified ChilledWaterTank, !- Name", + " , !- End-Use Subcategory", + " 0.17, !- Tank Volume {m3}", + " 1.4, !- Tank Height {m}", + " VerticalCylinder, !- Tank Shape", + " , !- Tank Perimeter {m}", + " 82.2222, !- Maximum Temperature Limit {C}", + " MasterSlave, !- Heater Priority Control", + " Water Heater Setpoint Temperature, !- Heater 1 Setpoint Temperature Schedule Name", + " 2.0, !- Heater 1 Deadband Temperature Difference {deltaC}", + " 0, !- Heater 1 Capacity {W}", + " 1.0, !- Heater 1 Height {m}", + " Water Heater Setpoint Temperature, !- Heater 2 Setpoint Temperature Schedule Name", + " 5.0, !- Heater 2 Deadband Temperature Difference {deltaC}", + " 0, !- Heater 2 Capacity {W}", + " 0.0, !- Heater 2 Height {m}", + " ELECTRICITY, !- Heater Fuel Type", + " 1, !- Heater Thermal Efficiency", + " , !- Off Cycle Parasitic Fuel Consumption Rate {W}", + " ELECTRICITY, !- Off Cycle Parasitic Fuel Type", + " , !- Off Cycle Parasitic Heat Fraction to Tank", + " , !- Off Cycle Parasitic Height {m}", + " , !- On Cycle Parasitic Fuel Consumption Rate {W}", + " ELECTRICITY, !- On Cycle Parasitic Fuel Type", + " , !- On Cycle Parasitic Heat Fraction to Tank", + " , !- On Cycle Parasitic Height {m}", + " SCHEDULE, !- Ambient Temperature Indicator", + " Tank Ambient Temperature,!- Ambient Temperature Schedule Name", + " , !- Ambient Temperature Zone Name", + " , !- Ambient Temperature Outdoor Air Node Name", + " 6, !- Uniform Skin Loss Coefficient per Unit Area to Ambient Temperature {W/m2-K}", + " 1, !- Skin Loss Fraction to Zone", + " 6, !- Off Cycle Flue Loss Coefficient to Ambient Temperature {W/K}", + " 1, !- Off Cycle Flue Loss Fraction to Zone", + " , !- Peak Use Flow Rate {m3/s}", + " , !- Use Flow Rate Fraction Schedule Name", + " , !- Cold Water Supply Temperature Schedule Name", + " , !- Use Side Inlet Node Name", + " , !- Use Side Outlet Node Name", + " , !- Use Side Effectiveness", + " 1.0, !- Use Side Inlet Height {m}", + " 0.5, !- Use Side Outlet Height {m}", + " , !- Source Side Inlet Node Name", + " , !- Source Side Outlet Node Name", + " , !- Source Side Effectiveness", + " , !- Source Side Inlet Height {m}", + " , !- Source Side Outlet Height {m}", + " FIXED, !- Inlet Mode", + " , !- Use Side Design Flow Rate {m3/s}", + " , !- Source Side Design Flow Rate {m3/s}", + " , !- Indirect Water Heating Recovery Time {hr}", + " 10, !- Number of Nodes", + " 0.1; !- Additional Destratification Conductivity {W/m-K}", + }); + + ASSERT_TRUE(process_idf(idf_objects)); + + bool ErrorsFound = false; + //HeatBalanceManager::GetZoneData(ErrorsFound); // read zone data + //EXPECT_FALSE(ErrorsFound); + + //InternalHeatGains::GetInternalHeatGainsInput(); + //ErrorsFound = false; + EXPECT_FALSE(WaterThermalTanks::GetWaterThermalTankInputData(ErrorsFound)); + + int TankNum(1); + WaterThermalTanks::WaterThermalTankData &Tank = WaterThermalTanks::WaterThermalTank(TankNum); + + DataGlobals::HourOfDay = 0; + DataGlobals::TimeStep = 1; + DataGlobals::TimeStepZone = 1.0 / 60.0; // one-minute system time step + DataHVACGlobals::TimeStepSys = DataGlobals::TimeStepZone; + + Tank.TankTemp = 2.0; + for (auto &node : Tank.Node) { + node.Temp = 2.0; + node.SavedTemp = 2.0; + } + + Tank.AmbientTemp = -40; + Tank.UseInletTemp = 3.0; + Tank.SetPointTemp = 3.0; + Tank.SetPointTemp2 = Tank.SetPointTemp; + Tank.TimeElapsed = 0.0; + + // very low use mass flow rate + Tank.UseMassFlowRate = 0.00005; + // zero source mass flow rate + Tank.SourceMassFlowRate = 0.0; + + // Calls CalcWaterThermalTankStratified + WaterThermalTanks::CalcWaterThermalTank(TankNum); + + // expected tank avg temp less than starting value of 2 C + EXPECT_LT(Tank.TankTempAvg, 2.0); + // And the final tank temp too, which is the one triggering the warning + EXPECT_LT(Tank.TankTemp, 2.0); + // Might as well check the node temps too + for (int i = 0; i < Tank.Nodes; ++i) { + EXPECT_LT(Tank.Node[i].Temp, 2.0) << "Node i=" << i; + } + + std::string const error_string = delimited_string({ + " ** Warning ** CalcWaterThermalTankStratified: WaterHeater:Stratified = 'STRATIFIED CHILLEDWATERTANK': Temperature of tank < 2C indicates of possibility of freeze. Tank Temperature = 1.75 C.", + " ** ~~~ ** Environment=, at Simulation time= 00:-1 - 00:00" + }); + EXPECT_TRUE(compare_err_stream(error_string, true)); + +}