diff --git a/doc/input-output-reference/src/overview/group-electric-load-center-generator.tex b/doc/input-output-reference/src/overview/group-electric-load-center-generator.tex index d0662505d35..39c2ee2f446 100644 --- a/doc/input-output-reference/src/overview/group-electric-load-center-generator.tex +++ b/doc/input-output-reference/src/overview/group-electric-load-center-generator.tex @@ -4473,7 +4473,7 @@ \subsubsection{Inputs}\label{inputs-20-000} \subsubsection{Outputs}\label{outputs-11-001} -Using the Generator:PV:Simple object makes a number of output variables available.\textbackslash{} +Using the Generator:Photovoltaic object makes a number of output variables available: \begin{itemize} \item @@ -4492,7 +4492,7 @@ \subsubsection{Outputs}\label{outputs-11-001} \paragraph{Generator Produced DC Electricity Rate {[}W{]}}\label{generator-produced-dc-electric-power-w-2} -This output variable is the power of DC electricity produced by the PV array in Watts.. +This output variable is the power of DC electricity produced by the PV array in Watts. \paragraph{Generator Produced DC Electricity Energy {[}J{]}}\label{generator-produced-dc-electric-energy-j} @@ -4500,11 +4500,11 @@ \subsubsection{Outputs}\label{outputs-11-001} \paragraph{Generator PV Array Efficiency {[]}}\label{generator-pv-array-efficiency} -This output variable is the resulting efficiency of the PV array . +This output variable is the resulting efficiency of the PV array. -The two more detailed PV models also have the following output variables. +The two more detailed PV models (PhotovoltaicPerformance:EquivalentOne-Diode and PhotovoltaicPerformance:Sandia) also have the following output variables: -\paragraph{Generator PV Cell Temperature {[} \SI{}{\celsius}{]}}\label{generator-pv-cell-temperature-c} +\paragraph{Generator PV Cell Temperature {[}\SI{}{\celsius}{]}}\label{generator-pv-cell-temperature-c} This output variable represents the temperature of the solar cell used in the calculation of cell performance. diff --git a/src/EnergyPlus/DataHVACGlobals.hh b/src/EnergyPlus/DataHVACGlobals.hh index a865a878083..48e1dd76fba 100644 --- a/src/EnergyPlus/DataHVACGlobals.hh +++ b/src/EnergyPlus/DataHVACGlobals.hh @@ -457,8 +457,9 @@ struct HVACGlobalsData : BaseGlobalStruct Real64 deviationFromSetPtThresholdHtg = -0.2; // heating threshold for reporting setpoint deviation Real64 deviationFromSetPtThresholdClg = 0.2; // cooling threshold for reporting setpoint deviation - bool SimAirLoopsFlag = false; // True when the air loops need to be (re)simulated - bool SimElecCircuitsFlag = false; // True when electic circuits need to be (re)simulated + bool SimAirLoopsFlag = false; // True when the air loops need to be (re)simulated + bool SimElecCircuitsFlag = false; // True when electic circuits need to be (re)simulated + bool PVSurfaceHeatBalanceResimFlag = false; bool SimPlantLoopsFlag = false; // True when the main plant loops need to be (re)simulated bool SimZoneEquipmentFlag = false; // True when zone equipment components need to be (re)simulated bool SimNonZoneEquipmentFlag = false; // True when non-zone equipment components need to be (re)simulated diff --git a/src/EnergyPlus/DataPhotovoltaics.hh b/src/EnergyPlus/DataPhotovoltaics.hh index 02e43d5c871..82418482101 100644 --- a/src/EnergyPlus/DataPhotovoltaics.hh +++ b/src/EnergyPlus/DataPhotovoltaics.hh @@ -316,7 +316,10 @@ namespace DataPhotovoltaics { int ExtVentCavPtr; // pointer to Exterior Vented Cavity EXTERIOR VENTED CAVITY int PVTPtr; // pointer to PVT model Real64 SurfaceSink; // PV power "sink" for integration - PVReportVariables Report; // report variables + bool SurfaceCouplingRunFlag = true; // run flag used before the surface heat balance + Real64 SurfaceCouplingSource = 0.0; // last integrated PV sink applied to its coupled model + bool SurfaceCouplingNeedsResim = false; + PVReportVariables Report; // report variables // nested structs for user input parameters SimplePVParamsStruct SimplePVModule; // simple model input params TRNSYSPVModuleParamsStruct TRNSYSPVModule; // equivalent one-diode input params diff --git a/src/EnergyPlus/HVACManager.cc b/src/EnergyPlus/HVACManager.cc index 63c7b2d494a..709bdbdd91b 100644 --- a/src/EnergyPlus/HVACManager.cc +++ b/src/EnergyPlus/HVACManager.cc @@ -80,6 +80,7 @@ #include #include #include +#include #include #include #include @@ -881,7 +882,8 @@ void SimHVAC(EnergyPlusData &state) // Main iteration loop for HVAC. If any of the simulation flags are // true, then specific components must be resimulated. while ((state.dataHVACGlobal->SimAirLoopsFlag || state.dataHVACGlobal->SimZoneEquipmentFlag || state.dataHVACGlobal->SimNonZoneEquipmentFlag || - state.dataHVACGlobal->SimPlantLoopsFlag || state.dataHVACGlobal->SimElecCircuitsFlag) && + state.dataHVACGlobal->SimPlantLoopsFlag || state.dataHVACGlobal->SimElecCircuitsFlag || + state.dataHVACGlobal->PVSurfaceHeatBalanceResimFlag) && (state.dataHVACMgr->HVACManageIteration <= state.dataConvergeParams->MaxIter)) { if (state.dataGlobal->stopSimulation) { @@ -1805,6 +1807,15 @@ void SimSelectedEquipment(EnergyPlusData &state, } PlantUtilities::ResetAllPlantInterConnectFlags(state); + if (state.dataHVACGlobal->PVSurfaceHeatBalanceResimFlag) { + HeatBalanceSurfaceManager::ResimulateSurfaceHeatBalanceForPV(state); + ZoneTempPredictorCorrector::PredictSystemLoads( + state, state.dataHVACGlobal->ShortenTimeStepSys, state.dataHVACGlobal->UseZoneTimeStepHistory, state.dataGlobal->TimeStepZone); + // The updated demand must be consumed during this HVAC iteration, even when the PV surface request + // was the only flag keeping the iteration loop active. + SimZoneEquipment = true; + } + if (state.dataGlobal->BeginEnvrnFlag && state.dataHVACMgr->MyEnvrnFlag2) { // Following comment is incorrect! (LKL) Even the first time through this does more than read in data. // Zone equipment data needs to be read in before air loop data to allow the diff --git a/src/EnergyPlus/HeatBalanceSurfaceManager.cc b/src/EnergyPlus/HeatBalanceSurfaceManager.cc index 651aed22b30..1bb84fa58c4 100644 --- a/src/EnergyPlus/HeatBalanceSurfaceManager.cc +++ b/src/EnergyPlus/HeatBalanceSurfaceManager.cc @@ -81,6 +81,7 @@ #include #include #include +#include #include #include #include @@ -111,6 +112,7 @@ #include #include #include +#include #include #include #include @@ -162,6 +164,17 @@ void ManageSurfaceHeatBalance(EnergyPlusData &state) } InitSurfaceHeatBalance(state); // Initialize all heat balance related parameters + // Surface-coupled PV must be initialized before its first temperature-dependent calculation. + if (state.dataPhotovoltaicState->GetInputFlag && + state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "Generator:Photovoltaic") > 0) { + Photovoltaics::GetPVInput(state); + state.dataPhotovoltaicState->GetInputFlag = false; + } + + for (int PVnum = 1; PVnum <= state.dataPhotovoltaic->NumPVs; ++PVnum) { + Photovoltaics::SimSurfaceCoupledPV(state, PVnum); + } + // Solve the zone heat balance 'Detailed' solution // Call the outside and inside surface heat balances if (state.dataHeatBalSurfMgr->ManageSurfaceHeatBalancefirstTime) { @@ -231,6 +244,26 @@ void ManageSurfaceHeatBalance(EnergyPlusData &state) state.dataHeatBalSurfMgr->ManageSurfaceHeatBalancefirstTime = false; } +void ResimulateSurfaceHeatBalanceForPV(EnergyPlusData &state) +{ + // Repeat the coupled surface and PV calculations after electric simulation changes the PV heat sink. + if (!state.dataHVACGlobal->PVSurfaceHeatBalanceResimFlag) { + return; + } + + for (int pass = 1; pass <= 2; ++pass) { + state.dataHVACGlobal->PVSurfaceHeatBalanceResimFlag = false; + CalcHeatBalanceOutsideSurf(state); + CalcHeatBalanceInsideSurf(state); + for (int PVnum = 1; PVnum <= state.dataPhotovoltaic->NumPVs; ++PVnum) { + Photovoltaics::SimSurfaceCoupledPV(state, PVnum); + } + if (!state.dataHVACGlobal->PVSurfaceHeatBalanceResimFlag) { + break; + } + } +} + // Beginning Initialization Section of the Module //****************************************************************************** @@ -5433,6 +5466,10 @@ void UpdateFinalSurfaceHeatBalance(EnergyPlusData &state) // Call the outside and inside surface heat balances CalcHeatBalanceOutsideSurf(state); CalcHeatBalanceInsideSurf(state); + + for (int PVnum = 1; PVnum <= state.dataPhotovoltaic->NumPVs; ++PVnum) { + Photovoltaics::SimSurfaceCoupledPV(state, PVnum); + } } } @@ -7238,7 +7275,6 @@ void CalcHeatBalanceOutsideSurf(EnergyPlusData &state, // // Locals // // SUBROUTINE ARGUMENT DEFINITIONS: // - //>>>>>>> origin/develop // SUBROUTINE PARAMETER DEFINITIONS: constexpr std::string_view RoutineNameGroundTemp("CalcHeatBalanceOutsideSurf:GroundTemp"); constexpr std::string_view RoutineNameGroundTempFC("CalcHeatBalanceOutsideSurf:GroundTempFC"); diff --git a/src/EnergyPlus/HeatBalanceSurfaceManager.hh b/src/EnergyPlus/HeatBalanceSurfaceManager.hh index e737258dac9..949cdfd1c7f 100644 --- a/src/EnergyPlus/HeatBalanceSurfaceManager.hh +++ b/src/EnergyPlus/HeatBalanceSurfaceManager.hh @@ -89,6 +89,8 @@ namespace HeatBalanceSurfaceManager { void ManageSurfaceHeatBalance(EnergyPlusData &state); + void ResimulateSurfaceHeatBalanceForPV(EnergyPlusData &state); + // Beginning Initialization Section of the Module //****************************************************************************** diff --git a/src/EnergyPlus/Photovoltaics.cc b/src/EnergyPlus/Photovoltaics.cc index 751526d4595..815d85eddc2 100644 --- a/src/EnergyPlus/Photovoltaics.cc +++ b/src/EnergyPlus/Photovoltaics.cc @@ -215,6 +215,13 @@ namespace Photovoltaics { } break; } + auto const cellIntegrationMode = state.dataPhotovoltaic->PVarray(PVnum).CellIntegrationMode; + if (cellIntegrationMode == CellIntegration::SurfaceOutsideFace || cellIntegrationMode == CellIntegration::TranspiredCollector || + cellIntegrationMode == CellIntegration::ExteriorVentedCavity || cellIntegrationMode == CellIntegration::PVTSolarCollector) { + // Surface coupling runs before the electric generator pass, so retain the current schedule state for the next coupling pass. + state.dataPhotovoltaic->PVarray(PVnum).SurfaceCouplingRunFlag = RunFlag; + } + ReportPV(state, PVnum); } @@ -841,7 +848,6 @@ namespace Photovoltaics { // Using/Aliasing Real64 TimeStepSysSec = state.dataHVACGlobal->TimeStepSysSec; - using TranspiredCollector::SetUTSCQdotSource; // SUBROUTINE LOCAL VARIABLE DECLARATIONS: int thisZone; // working index for zones @@ -861,24 +867,85 @@ namespace Photovoltaics { switch (state.dataPhotovoltaic->PVarray(PVnum).CellIntegrationMode) { // SurfaceSink is not multiplied... - case CellIntegration::SurfaceOutsideFace: { - state.dataHeatBalFanSys->QPVSysSource(state.dataPhotovoltaic->PVarray(PVnum).SurfacePtr) = - -1.0 * state.dataPhotovoltaic->PVarray(PVnum).SurfaceSink; - } break; + case CellIntegration::SurfaceOutsideFace: + case CellIntegration::TranspiredCollector: + case CellIntegration::ExteriorVentedCavity: + case CellIntegration::PVTSolarCollector: + UpdatePVIntegrationSource(state, PVnum); + break; + default: + break; + } + } + + void SimSurfaceCoupledPV(EnergyPlusData &state, int const PVnum) + { + // Recalculate PV that depends on a surface or collector temperature before the heat balance uses its current sink. + auto &pv = state.dataPhotovoltaic->PVarray(PVnum); + switch (pv.CellIntegrationMode) { + case CellIntegration::SurfaceOutsideFace: + case CellIntegration::TranspiredCollector: + case CellIntegration::ExteriorVentedCavity: + case CellIntegration::PVTSolarCollector: + break; + default: + return; + } + + switch (pv.PVModelType) { + case PVModel::Simple: + CalcSimplePV(state, PVnum); + break; + case PVModel::TRNSYS: + InitTRNSYSPV(state, PVnum); + CalcTRNSYSPV(state, PVnum, pv.SurfaceCouplingRunFlag); + break; + case PVModel::Sandia: + CalcSandiaPV(state, PVnum, pv.SurfaceCouplingRunFlag); + break; + default: + break; + } + + UpdatePVIntegrationSource(state, PVnum); + } + + void UpdatePVIntegrationSource(EnergyPlusData &state, int const PVnum) + { + // Publish the PV sink to its coupled thermal model and request another pass when the sink changes materially. + auto &pv = state.dataPhotovoltaic->PVarray(PVnum); + Real64 const previousSource = pv.SurfaceCouplingSource; + + switch (pv.CellIntegrationMode) { + case CellIntegration::SurfaceOutsideFace: + state.dataHeatBalFanSys->QPVSysSource(pv.SurfacePtr) = -pv.SurfaceSink; + break; case CellIntegration::TranspiredCollector: { - SetUTSCQdotSource(state, state.dataPhotovoltaic->PVarray(PVnum).UTSCPtr, -1.0 * state.dataPhotovoltaic->PVarray(PVnum).SurfaceSink); + TranspiredCollector::SetUTSCQdotSource(state, pv.UTSCPtr, -1.0 * pv.SurfaceSink); } break; case CellIntegration::ExteriorVentedCavity: { - SetVentedModuleQdotSource( - state, state.dataPhotovoltaic->PVarray(PVnum).ExtVentCavPtr, -1.0 * state.dataPhotovoltaic->PVarray(PVnum).SurfaceSink); + SetVentedModuleQdotSource(state, pv.ExtVentCavPtr, -1.0 * pv.SurfaceSink); } break; case CellIntegration::PVTSolarCollector: { - PhotovoltaicThermalCollectors::SetPVTQdotSource( - state, state.dataPhotovoltaic->PVarray(PVnum).PVTPtr, -1.0 * state.dataPhotovoltaic->PVarray(PVnum).SurfaceSink); + PhotovoltaicThermalCollectors::SetPVTQdotSource(state, pv.PVTPtr, -1.0 * pv.SurfaceSink); } break; default: break; } + + pv.SurfaceCouplingNeedsResim = std::abs(pv.SurfaceSink - previousSource) > 0.1; + pv.SurfaceCouplingSource = pv.SurfaceSink; + if (pv.SurfaceCouplingNeedsResim) { + state.dataHVACGlobal->SimElecCircuitsFlag = true; + if (pv.CellIntegrationMode == CellIntegration::SurfaceOutsideFace || pv.CellIntegrationMode == CellIntegration::ExteriorVentedCavity || + pv.CellIntegrationMode == CellIntegration::TranspiredCollector) { + state.dataHVACGlobal->PVSurfaceHeatBalanceResimFlag = true; + } + if (pv.CellIntegrationMode == CellIntegration::TranspiredCollector || pv.CellIntegrationMode == CellIntegration::PVTSolarCollector) { + state.dataHVACGlobal->SimAirLoopsFlag = true; + state.dataHVACGlobal->SimPlantLoopsFlag = true; + } + } } // ************* diff --git a/src/EnergyPlus/Photovoltaics.hh b/src/EnergyPlus/Photovoltaics.hh index 71dfda1d5cc..760e12fcb93 100644 --- a/src/EnergyPlus/Photovoltaics.hh +++ b/src/EnergyPlus/Photovoltaics.hh @@ -99,6 +99,10 @@ namespace Photovoltaics { void CalcSimplePV(EnergyPlusData &state, int const thisPV); + void SimSurfaceCoupledPV(EnergyPlusData &state, int const PVnum); + + void UpdatePVIntegrationSource(EnergyPlusData &state, int const PVnum); + void ReportPV(EnergyPlusData &state, int const PVnum); // ************* diff --git a/tst/EnergyPlus/unit/HeatBalanceSurfaceManager.unit.cc b/tst/EnergyPlus/unit/HeatBalanceSurfaceManager.unit.cc index 8ea9e01c6bf..3197719691e 100644 --- a/tst/EnergyPlus/unit/HeatBalanceSurfaceManager.unit.cc +++ b/tst/EnergyPlus/unit/HeatBalanceSurfaceManager.unit.cc @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,7 @@ #include #include #include +#include #include #include #include @@ -2805,6 +2807,15 @@ TEST_F(EnergyPlusFixture, HeatBalanceSurfaceManager_TestSurfTempCalcHeatBalanceA " A1 - 1 IN STUCCO, !- Outside Layer", " GP01; !- Layer 3", + " ConstructionProperty:InternalHeatSource,", + " PV Source, !- Name", + " EXTWALL:LIVING, !- Construction Name", + " 1, !- Thermal Source Present After Layer Number", + " 1, !- Temperature Calculation Requested After Layer Number", + " 1, !- Dimensions for the CTF Calculation", + " 0.1524, !- Tube Spacing {m}", + " 0.0; !- Two-Dimensional Temperature Calculation Position", + " Construction,", " FLOOR:LIVING, !- Name", " CC03, !- Outside Layer", @@ -2976,6 +2987,9 @@ TEST_F(EnergyPlusFixture, HeatBalanceSurfaceManager_TestSurfTempCalcHeatBalanceA SurfaceGeometry::SetupZoneGeometry(*state, ErrorsFound); EXPECT_FALSE(ErrorsFound); + // IsUsedCTF is only set once surfaces are set up, so CTFs can only be calculated after SetupZoneGeometry. + HeatBalanceManager::InitConductionTransferFunctions(*state); + // Clear schedule type warnings EXPECT_TRUE(has_err_output(true)); @@ -3076,6 +3090,44 @@ TEST_F(EnergyPlusFixture, HeatBalanceSurfaceManager_TestSurfTempCalcHeatBalanceA EXPECT_EQ(-0.1, state->dataHeatBalSurf->SurfQAdditionalHeatSourceOutside(1)); CalcHeatBalanceInsideSurf(*state); EXPECT_EQ(0.1, state->dataHeatBalSurf->SurfQAdditionalHeatSourceInside(6)); + + // Apply a PV sink after the initial surface pass, as happens when PV is recalculated during HVAC simulation. + auto const surfaceTemperatureBeforePV = state->dataHeatBalSurf->SurfTempOut(1); + state->dataHeatBal->AnyInternalHeatSourceInInput = true; + // SurfQsrcHist is only allocated when AnyInternalHeatSourceInInput is set, so allocate it before reading it below. + state->dataHeatBalSurf->SurfQsrcHist.dimension(state->dataSurface->TotSurfaces, Construction::MaxCTFTerms, 0.0); + auto const sourceHistoryBeforePV = state->dataHeatBalSurf->SurfQsrcHist(1, 1); + state->dataHeatBalFanSys->QPVSysSource.dimension(state->dataSurface->TotSurfaces, 0.0); + state->dataHeatBalFanSys->QPVSysSource(1) = -100.0; + state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag = true; + + // The resimulation must consume the request and incorporate the PV sink into the surface balance. + ResimulateSurfaceHeatBalanceForPV(*state); + + EXPECT_FALSE(state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag); + EXPECT_NE(sourceHistoryBeforePV, state->dataHeatBalSurf->SurfQsrcHist(1, 1)); + EXPECT_NE(surfaceTemperatureBeforePV, state->dataHeatBalSurf->SurfTempOut(1)); + + // A source change on the first coupled pass followed by a stable second pass is converged and + // must not leave a stale request for another outer HVAC iteration. + state->dataPhotovoltaic->PVarray.allocate(1); + state->dataPhotovoltaic->NumPVs = 1; + auto &pv = state->dataPhotovoltaic->PVarray(1); + pv.PVModelType = DataPhotovoltaics::PVModel::Simple; + pv.CellIntegrationMode = DataPhotovoltaics::CellIntegration::SurfaceOutsideFace; + pv.SurfacePtr = 1; + pv.SimplePVModule.EfficencyInputMode = DataPhotovoltaics::Efficiency::Fixed; + pv.SimplePVModule.AreaCol = 1.0; + pv.SimplePVModule.PVEfficiency = 0.1; + pv.SurfaceCouplingSource = 0.0; + state->dataHeatBal->SurfQRadSWOutIncident(1) = 1000.0; + state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag = true; + + ResimulateSurfaceHeatBalanceForPV(*state); + + EXPECT_DOUBLE_EQ(100.0, pv.SurfaceCouplingSource); + EXPECT_FALSE(pv.SurfaceCouplingNeedsResim); + EXPECT_FALSE(state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag); } TEST_F(EnergyPlusFixture, HeatBalanceSurfaceManager_TestReportIntMovInsInsideSurfTemp) diff --git a/tst/EnergyPlus/unit/Photovoltaics.unit.cc b/tst/EnergyPlus/unit/Photovoltaics.unit.cc index 2c06799d116..7fba857da76 100644 --- a/tst/EnergyPlus/unit/Photovoltaics.unit.cc +++ b/tst/EnergyPlus/unit/Photovoltaics.unit.cc @@ -54,11 +54,14 @@ #include "Fixtures/EnergyPlusFixture.hh" #include #include +#include #include #include #include #include +#include #include +#include using namespace EnergyPlus; @@ -165,3 +168,87 @@ TEST_F(EnergyPlusFixture, PV_TRNSYSDynamicTimeStepIndependentOfPVOrder) EXPECT_LT(dynamicPV.Report.CellTemp, 40.0); EXPECT_GT(dynamicPV.Report.CellTemp, 20.0); } + +// Demonstrates that a material change in an integrated PV heat sink is published +// to the coupled thermal model and requests the appropriate resimulation passes. +TEST_F(EnergyPlusFixture, PV_IntegrationSourceRequestsResimulation) +{ + // Start with a PV array coupled directly to a surface outside face. The PV + // sink is represented as a negative source in the surface heat balance. + state->dataPhotovoltaic->PVarray.allocate(1); + state->dataPhotovoltaic->NumPVs = 1; + state->dataPhotovoltaic->PVarray(1).CellIntegrationMode = DataPhotovoltaics::CellIntegration::SurfaceOutsideFace; + state->dataPhotovoltaic->PVarray(1).SurfacePtr = 1; + state->dataPhotovoltaic->PVarray(1).SurfaceSink = 100.0; + state->dataHeatBalFanSys->QPVSysSource.allocate(1); + + Photovoltaics::UpdatePVIntegrationSource(*state, 1); + + // A changed surface sink must request both a surface heat-balance pass and + // another electric-circuit simulation. + EXPECT_DOUBLE_EQ(state->dataHeatBalFanSys->QPVSysSource(1), -100.0); + EXPECT_TRUE(state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag); + EXPECT_TRUE(state->dataHVACGlobal->SimElecCircuitsFlag); + EXPECT_TRUE(state->dataPhotovoltaic->PVarray(1).SurfaceCouplingNeedsResim); + + // Re-publishing the same sink is below the change threshold and should not + // schedule another round of coupled calculations. + state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag = false; + state->dataHVACGlobal->SimElecCircuitsFlag = false; + state->dataPhotovoltaic->PVarray(1).SurfaceSink = 100.0; + Photovoltaics::UpdatePVIntegrationSource(*state, 1); + + EXPECT_FALSE(state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag); + EXPECT_FALSE(state->dataHVACGlobal->SimElecCircuitsFlag); + EXPECT_FALSE(state->dataPhotovoltaic->PVarray(1).SurfaceCouplingNeedsResim); + + // Exterior vented-cavity integration also requests a surface pass, but it + // does not require the air or plant loops to be repeated. + state->dataHeatBal->ExtVentedCavity.allocate(1); + state->dataHeatBal->ExtVentedCavity(1).ProjArea = 1.0; + state->dataPhotovoltaic->PVarray(1).CellIntegrationMode = DataPhotovoltaics::CellIntegration::ExteriorVentedCavity; + state->dataPhotovoltaic->PVarray(1).ExtVentCavPtr = 1; + state->dataPhotovoltaic->PVarray(1).SurfaceSink = 50.0; + Photovoltaics::UpdatePVIntegrationSource(*state, 1); + + EXPECT_TRUE(state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag); + EXPECT_TRUE(state->dataHVACGlobal->SimElecCircuitsFlag); + EXPECT_FALSE(state->dataHVACGlobal->SimAirLoopsFlag); + EXPECT_FALSE(state->dataHVACGlobal->SimPlantLoopsFlag); + + // A transpired collector couples PV output to air and plant-side results, + // so all three relevant solver flags must be raised for a changed sink. + state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag = false; + state->dataHVACGlobal->SimElecCircuitsFlag = false; + state->dataHeatBal->ExtVentedCavity(1).QdotSource = 0.0; + state->dataPhotovoltaic->PVarray(1).CellIntegrationMode = DataPhotovoltaics::CellIntegration::TranspiredCollector; + state->dataPhotovoltaic->PVarray(1).UTSCPtr = 1; + state->dataPhotovoltaic->PVarray(1).SurfaceSink = 25.0; + state->dataTranspiredCollector->UTSC.allocate(1); + state->dataTranspiredCollector->UTSC(1).ProjArea = 1.0; + Photovoltaics::UpdatePVIntegrationSource(*state, 1); + + EXPECT_TRUE(state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag); + EXPECT_TRUE(state->dataHVACGlobal->SimElecCircuitsFlag); + EXPECT_TRUE(state->dataHVACGlobal->SimAirLoopsFlag); + EXPECT_TRUE(state->dataHVACGlobal->SimPlantLoopsFlag); + + // PVT collectors receive the sink through the collector source term. They + // need air, plant, and electric resimulation, but no separate surface pass. + state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag = false; + state->dataHVACGlobal->SimElecCircuitsFlag = false; + state->dataHVACGlobal->SimAirLoopsFlag = false; + state->dataHVACGlobal->SimPlantLoopsFlag = false; + state->dataPhotovoltaicThermalCollector->PVT.allocate(1); + state->dataPhotovoltaicThermalCollector->PVT(1).AreaCol = 2.0; + state->dataPhotovoltaic->PVarray(1).CellIntegrationMode = DataPhotovoltaics::CellIntegration::PVTSolarCollector; + state->dataPhotovoltaic->PVarray(1).PVTPtr = 1; + state->dataPhotovoltaic->PVarray(1).SurfaceSink = 40.0; + Photovoltaics::UpdatePVIntegrationSource(*state, 1); + + EXPECT_DOUBLE_EQ(state->dataPhotovoltaicThermalCollector->PVT(1).QdotSource, -20.0); + EXPECT_FALSE(state->dataHVACGlobal->PVSurfaceHeatBalanceResimFlag); + EXPECT_TRUE(state->dataHVACGlobal->SimElecCircuitsFlag); + EXPECT_TRUE(state->dataHVACGlobal->SimAirLoopsFlag); + EXPECT_TRUE(state->dataHVACGlobal->SimPlantLoopsFlag); +}