Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 : #include <cmath>
50 : #include <iostream>
51 :
52 : // ObjexxFCL Headers
53 : #include <ObjexxFCL/Array.functions.hh>
54 : #include <ObjexxFCL/Fmath.hh>
55 :
56 : // EnergyPlus Headers
57 : #include <EnergyPlus/Autosizing/Base.hh>
58 : #include <EnergyPlus/BranchNodeConnections.hh>
59 : #include <EnergyPlus/Construction.hh>
60 : #include <EnergyPlus/ConvectionCoefficients.hh>
61 : #include <EnergyPlus/Data/EnergyPlusData.hh>
62 : #include <EnergyPlus/DataEnvironment.hh>
63 : #include <EnergyPlus/DataHVACGlobals.hh>
64 : #include <EnergyPlus/DataHeatBalSurface.hh>
65 : #include <EnergyPlus/DataHeatBalance.hh>
66 : #include <EnergyPlus/DataIPShortCuts.hh>
67 : #include <EnergyPlus/DataLoopNode.hh>
68 : #include <EnergyPlus/DataPhotovoltaics.hh>
69 : #include <EnergyPlus/DataSizing.hh>
70 : #include <EnergyPlus/DataSurfaces.hh>
71 : #include <EnergyPlus/EMSManager.hh>
72 : #include <EnergyPlus/FluidProperties.hh>
73 : #include <EnergyPlus/General.hh>
74 : #include <EnergyPlus/GeneralRoutines.hh>
75 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
76 : #include <EnergyPlus/NodeInputManager.hh>
77 : #include <EnergyPlus/OutputProcessor.hh>
78 : #include <EnergyPlus/PhotovoltaicThermalCollectors.hh>
79 : #include <EnergyPlus/Plant/DataPlant.hh>
80 : #include <EnergyPlus/Plant/PlantLocation.hh>
81 : #include <EnergyPlus/PlantUtilities.hh>
82 : #include <EnergyPlus/Psychrometrics.hh>
83 : #include <EnergyPlus/ScheduleManager.hh>
84 : #include <EnergyPlus/SurfaceGeometry.hh>
85 : #include <EnergyPlus/UtilityRoutines.hh>
86 :
87 : namespace EnergyPlus {
88 :
89 : namespace PhotovoltaicThermalCollectors {
90 :
91 : // Module containing the routines dealing with the photovoltaic thermal collectors
92 :
93 : // MODULE INFORMATION:
94 : // AUTHOR Brent. Griffith
95 : // DATE WRITTEN June-August 2008
96 : // MODIFIED na
97 : // RE-ENGINEERED na
98 :
99 : // PURPOSE OF THIS MODULE:
100 : // collect models related to PVT or hybrid, photovoltaic - thermal solar collectors
101 :
102 : // METHODOLOGY EMPLOYED:
103 : // The approach is to have one PVT structure that works with different models.
104 : // the PVT model reuses photovoltaic modeling in Photovoltaics.cc for electricity generation.
105 : // the electric load center and "generator" is all accessed thru PV objects and models.
106 : // this module is for the thermal portion of PVT.
107 : // the first model is a "simple" or "ideal" model useful for sizing, early design, or policy analyses
108 : // Simple PV/T model just converts incoming solar to electricity and temperature rise of a working fluid.
109 :
110 0 : PlantComponent *PVTCollectorStruct::factory(EnergyPlusData &state, std::string_view objectName)
111 :
112 : {
113 0 : if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
114 0 : GetPVTcollectorsInput(state);
115 0 : state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
116 : }
117 :
118 0 : for (auto &thisComp : state.dataPhotovoltaicThermalCollector->PVT) {
119 0 : if (thisComp.Name == objectName) {
120 0 : return &thisComp;
121 : }
122 : }
123 :
124 : // If we didn't find it, fatal
125 0 : ShowFatalError(state, format("Solar Thermal Collector Factory: Error getting inputs for object named: {}", objectName));
126 : // Shut up the compiler
127 0 : return nullptr;
128 : }
129 :
130 1 : void PVTCollectorStruct::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation)
131 : {
132 1 : this->initialize(state, true);
133 1 : this->size(state);
134 1 : }
135 :
136 0 : void PVTCollectorStruct::simulate(EnergyPlusData &state,
137 : [[maybe_unused]] const PlantLocation &calledFromLocation,
138 : bool const FirstHVACIteration,
139 : [[maybe_unused]] Real64 &CurLoad,
140 : [[maybe_unused]] bool const RunFlag)
141 : {
142 :
143 0 : this->initialize(state, FirstHVACIteration);
144 0 : this->control(state);
145 0 : this->calculate(state);
146 0 : this->update(state);
147 0 : }
148 :
149 4 : void GetPVTcollectorsInput(EnergyPlusData &state)
150 : {
151 : // SUBROUTINE INFORMATION:
152 : // AUTHOR B. Griffith
153 : // DATE WRITTEN June 2008
154 : // RE-ENGINEERED na
155 :
156 : // PURPOSE OF THIS SUBROUTINE:
157 : // Get input for PVT and BIPVT objects
158 :
159 : // Object Data
160 4 : Array1D<SimplePVTModelStruct> tmpSimplePVTperf;
161 4 : Array1D<BIPVTModelStruct> tmpBIPVTperf;
162 :
163 : // first load the 'Simple' performance object info into temporary structure
164 4 : state.dataIPShortCut->cCurrentModuleObject = "SolarCollectorPerformance:PhotovoltaicThermal:Simple";
165 4 : int NumSimplePVTPerform = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject);
166 4 : if (NumSimplePVTPerform > 0) {
167 2 : GetPVTSimpleCollectorsInput(state, NumSimplePVTPerform, tmpSimplePVTperf);
168 : }
169 :
170 : // load the 'BIPVT' performance object info into temporary structure
171 4 : state.dataIPShortCut->cCurrentModuleObject = "SolarCollectorPerformance:PhotovoltaicThermal:BIPVT";
172 4 : int NumBIPVTPerform = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject);
173 4 : if (NumBIPVTPerform > 0) {
174 2 : GetBIPVTCollectorsInput(state, NumBIPVTPerform, tmpBIPVTperf);
175 : }
176 :
177 : // now get main PVT objects
178 4 : state.dataIPShortCut->cCurrentModuleObject = "SolarCollector:FlatPlate:PhotovoltaicThermal";
179 8 : state.dataPhotovoltaicThermalCollector->NumPVT =
180 4 : state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject);
181 4 : if (state.dataPhotovoltaicThermalCollector->NumPVT > 0) {
182 4 : GetMainPVTInput(
183 4 : state, state.dataPhotovoltaicThermalCollector->NumPVT, state.dataPhotovoltaicThermalCollector->PVT, tmpSimplePVTperf, tmpBIPVTperf);
184 : }
185 4 : if (allocated(tmpSimplePVTperf)) {
186 2 : tmpSimplePVTperf.deallocate();
187 : }
188 4 : if (allocated(tmpBIPVTperf)) {
189 2 : tmpBIPVTperf.deallocate();
190 : }
191 4 : }
192 :
193 2 : void GetPVTSimpleCollectorsInput(EnergyPlusData &state, int NumSimplePVTPerform, Array1D<SimplePVTModelStruct> &tmpSimplePVTperf)
194 : {
195 : // PURPOSE OF THIS SUBROUTINE:
196 : // Get input for PVT Simple objects
197 :
198 : static constexpr std::string_view routineName = "GetPVTSimpleCollectorsInput";
199 :
200 : int Item; // Item to be "gotten"
201 : int NumAlphas; // Number of Alphas for each GetObjectItem call
202 : int NumNumbers; // Number of Numbers for each GetObjectItem call
203 : int IOStatus; // Used in GetObjectItem
204 2 : bool ErrorsFound(false); // Set to true if errors in input, fatal at end of routine
205 :
206 2 : tmpSimplePVTperf.allocate(NumSimplePVTPerform);
207 4 : for (Item = 1; Item <= NumSimplePVTPerform; ++Item) {
208 6 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
209 2 : state.dataIPShortCut->cCurrentModuleObject,
210 : Item,
211 2 : state.dataIPShortCut->cAlphaArgs,
212 : NumAlphas,
213 2 : state.dataIPShortCut->rNumericArgs,
214 : NumNumbers,
215 : IOStatus,
216 : _,
217 2 : state.dataIPShortCut->lAlphaFieldBlanks,
218 2 : state.dataIPShortCut->cAlphaFieldNames,
219 2 : state.dataIPShortCut->cNumericFieldNames);
220 :
221 2 : ErrorObjectHeader eoh{routineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)};
222 :
223 2 : auto &thisTmpSimplePVTperf = tmpSimplePVTperf(Item);
224 2 : thisTmpSimplePVTperf.Name = state.dataIPShortCut->cAlphaArgs(1);
225 2 : thisTmpSimplePVTperf.ThermEfficMode =
226 2 : static_cast<ThermEfficEnum>(getEnumValue(ThermEfficTypeNamesUC, Util::makeUPPER(state.dataIPShortCut->cAlphaArgs(2))));
227 2 : thisTmpSimplePVTperf.ThermalActiveFract = state.dataIPShortCut->rNumericArgs(1);
228 2 : thisTmpSimplePVTperf.ThermEffic = state.dataIPShortCut->rNumericArgs(2);
229 :
230 2 : if (thisTmpSimplePVTperf.ThermEfficMode == ThermEfficEnum::SCHEDULED) {
231 0 : if (state.dataIPShortCut->lAlphaFieldBlanks(3)) {
232 0 : ShowSevereEmptyField(state, eoh, state.dataIPShortCut->cAlphaFieldNames(3));
233 0 : ErrorsFound = true;
234 0 : } else if ((thisTmpSimplePVTperf.thermEffSched = Sched::GetSchedule(state, state.dataIPShortCut->cAlphaArgs(3))) == nullptr) {
235 0 : ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(3), state.dataIPShortCut->cAlphaArgs(3));
236 0 : ErrorsFound = true;
237 : }
238 : }
239 2 : thisTmpSimplePVTperf.SurfEmissivity = state.dataIPShortCut->rNumericArgs(3);
240 : }
241 2 : }
242 :
243 2 : void GetBIPVTCollectorsInput(EnergyPlusData &state, int NumBIPVTPerform, Array1D<BIPVTModelStruct> &tmpBIPVTperf)
244 : {
245 : // PURPOSE OF THIS SUBROUTINE:
246 : // Get input for BIPVT objects
247 : static constexpr std::string_view routineName = "GetBIPVTCollectorsInput";
248 :
249 : int Item; // Item to be "gotten"
250 : int NumAlphas; // Number of Alphas for each GetObjectItem call
251 : int NumNumbers; // Number of Numbers for each GetObjectItem call
252 : int IOStatus; // Used in GetObjectItem
253 : using DataSurfaces::OSCMData;
254 :
255 2 : tmpBIPVTperf.allocate(NumBIPVTPerform);
256 4 : for (Item = 1; Item <= NumBIPVTPerform; ++Item) {
257 6 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
258 2 : state.dataIPShortCut->cCurrentModuleObject,
259 : Item,
260 2 : state.dataIPShortCut->cAlphaArgs,
261 : NumAlphas,
262 2 : state.dataIPShortCut->rNumericArgs,
263 : NumNumbers,
264 : IOStatus,
265 : _,
266 2 : state.dataIPShortCut->lAlphaFieldBlanks,
267 2 : state.dataIPShortCut->cAlphaFieldNames,
268 2 : state.dataIPShortCut->cNumericFieldNames);
269 :
270 2 : ErrorObjectHeader eoh{routineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)};
271 :
272 2 : auto &thisTmpBIPVTperf = tmpBIPVTperf(Item);
273 2 : thisTmpBIPVTperf.Name = state.dataIPShortCut->cAlphaArgs(1);
274 2 : thisTmpBIPVTperf.OSCMName = state.dataIPShortCut->cAlphaArgs(2);
275 2 : int Found = Util::FindItemInList(thisTmpBIPVTperf.OSCMName, state.dataSurface->OSCM);
276 2 : if (Found == 0) {
277 0 : ShowSevereError(state,
278 0 : format("GetBIPVTCollectorsInput: Invalid outside model name={}, object type={}, object name={}",
279 0 : thisTmpBIPVTperf.OSCMName,
280 0 : state.dataIPShortCut->cCurrentModuleObject,
281 0 : thisTmpBIPVTperf.Name));
282 : }
283 2 : thisTmpBIPVTperf.OSCMPtr = Found;
284 2 : thisTmpBIPVTperf.PVEffGapWidth = state.dataIPShortCut->rNumericArgs(1);
285 2 : thisTmpBIPVTperf.PVCellTransAbsProduct = state.dataIPShortCut->rNumericArgs(2);
286 2 : thisTmpBIPVTperf.BackMatTranAbsProduct = state.dataIPShortCut->rNumericArgs(3);
287 2 : thisTmpBIPVTperf.CladTranAbsProduct = state.dataIPShortCut->rNumericArgs(4);
288 2 : thisTmpBIPVTperf.PVAreaFract = state.dataIPShortCut->rNumericArgs(5);
289 2 : thisTmpBIPVTperf.PVCellAreaFract = state.dataIPShortCut->rNumericArgs(6);
290 2 : thisTmpBIPVTperf.PVRTop = state.dataIPShortCut->rNumericArgs(7);
291 2 : thisTmpBIPVTperf.PVRBot = state.dataIPShortCut->rNumericArgs(8);
292 2 : thisTmpBIPVTperf.PVGEmiss = state.dataIPShortCut->rNumericArgs(9);
293 2 : thisTmpBIPVTperf.BackMatEmiss = state.dataIPShortCut->rNumericArgs(10);
294 2 : thisTmpBIPVTperf.ThGlass = state.dataIPShortCut->rNumericArgs(11);
295 2 : thisTmpBIPVTperf.RIndGlass = state.dataIPShortCut->rNumericArgs(12);
296 2 : thisTmpBIPVTperf.ECoffGlass = state.dataIPShortCut->rNumericArgs(13);
297 :
298 2 : if (state.dataIPShortCut->lAlphaFieldBlanks(3)) {
299 2 : thisTmpBIPVTperf.availSched = Sched::GetScheduleAlwaysOn(state);
300 0 : } else if ((thisTmpBIPVTperf.availSched = Sched::GetSchedule(state, state.dataIPShortCut->cAlphaArgs(3))) == nullptr) {
301 0 : ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(3), state.dataIPShortCut->cAlphaArgs(3));
302 0 : continue;
303 : }
304 : }
305 2 : }
306 :
307 4 : void GetMainPVTInput(EnergyPlusData &state,
308 : int NumPVT,
309 : Array1D<PVTCollectorStruct> &PVT,
310 : Array1D<SimplePVTModelStruct> const &tmpSimplePVTperf,
311 : Array1D<BIPVTModelStruct> const &tmpBIPVTperf)
312 : {
313 : // SUBROUTINE INFORMATION:
314 : // AUTHOR B. Griffith
315 : // DATE WRITTEN June 2008
316 : // RE-ENGINEERED na
317 :
318 : // PURPOSE OF THIS SUBROUTINE:
319 : // Get input for main PVT objects
320 :
321 : int Item; // Item to be "gotten"
322 : int NumAlphas; // Number of Alphas for each GetObjectItem call
323 : int NumNumbers; // Number of Numbers for each GetObjectItem call
324 : int IOStatus; // Used in GetObjectItem
325 4 : bool ErrorsFound(false); // Set to true if errors in input, fatal at end of routine
326 :
327 4 : PVT.allocate(NumPVT);
328 8 : for (Item = 1; Item <= NumPVT; ++Item) {
329 12 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
330 4 : state.dataIPShortCut->cCurrentModuleObject,
331 : Item,
332 4 : state.dataIPShortCut->cAlphaArgs,
333 : NumAlphas,
334 4 : state.dataIPShortCut->rNumericArgs,
335 : NumNumbers,
336 : IOStatus,
337 : _,
338 4 : state.dataIPShortCut->lAlphaFieldBlanks,
339 4 : state.dataIPShortCut->cAlphaFieldNames,
340 4 : state.dataIPShortCut->cNumericFieldNames);
341 4 : auto &thisPVT = state.dataPhotovoltaicThermalCollector->PVT(Item);
342 4 : thisPVT.Name = state.dataIPShortCut->cAlphaArgs(1);
343 4 : thisPVT.Type = DataPlant::PlantEquipmentType::PVTSolarCollectorFlatPlate;
344 :
345 4 : thisPVT.SurfNum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(2), state.dataSurface->Surface);
346 : // check surface
347 4 : if (thisPVT.SurfNum == 0) {
348 0 : if (state.dataIPShortCut->lAlphaFieldBlanks(2)) {
349 0 : ShowSevereError(state, format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(2), state.dataIPShortCut->cAlphaArgs(2)));
350 0 : ShowContinueError(state,
351 0 : format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
352 :
353 0 : ShowContinueError(state, "Surface name cannot be blank.");
354 : } else {
355 0 : ShowSevereError(state, format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(2), state.dataIPShortCut->cAlphaArgs(2)));
356 0 : ShowContinueError(state,
357 0 : format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
358 0 : ShowContinueError(state, "Surface was not found.");
359 : }
360 0 : ErrorsFound = true;
361 : } else {
362 :
363 4 : if (!state.dataSurface->Surface(thisPVT.SurfNum).ExtSolar) {
364 0 : ShowSevereError(state, format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(2), state.dataIPShortCut->cAlphaArgs(2)));
365 0 : ShowContinueError(state,
366 0 : format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
367 0 : ShowContinueError(state, "Surface must be exposed to solar.");
368 0 : ErrorsFound = true;
369 : }
370 : // check surface orientation, warn if upside down
371 4 : if ((state.dataSurface->Surface(thisPVT.SurfNum).Tilt < -95.0) || (state.dataSurface->Surface(thisPVT.SurfNum).Tilt > 95.0)) {
372 0 : ShowWarningError(state,
373 0 : format("Suspected input problem with {} = {}",
374 0 : state.dataIPShortCut->cAlphaFieldNames(2),
375 0 : state.dataIPShortCut->cAlphaArgs(2)));
376 0 : ShowContinueError(state,
377 0 : format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
378 0 : ShowContinueError(state, "Surface used for solar collector faces down");
379 0 : ShowContinueError(
380 : state,
381 0 : format("Surface tilt angle (degrees from ground outward normal) = {:.2R}", state.dataSurface->Surface(thisPVT.SurfNum).Tilt));
382 : }
383 : } // check surface
384 :
385 4 : if (state.dataIPShortCut->lAlphaFieldBlanks(3)) {
386 0 : ShowSevereError(state, format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(3), state.dataIPShortCut->cAlphaArgs(3)));
387 0 : ShowContinueError(state,
388 0 : format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
389 0 : ShowContinueError(state, format("{}, name cannot be blank.", state.dataIPShortCut->cAlphaFieldNames(3)));
390 0 : ErrorsFound = true;
391 : } else {
392 4 : thisPVT.PVTModelName = state.dataIPShortCut->cAlphaArgs(3);
393 4 : int ThisParamObj = Util::FindItemInList(thisPVT.PVTModelName, tmpSimplePVTperf);
394 4 : if (ThisParamObj > 0) {
395 2 : thisPVT.Simple = tmpSimplePVTperf(ThisParamObj); // entire structure assigned
396 : // do one-time setups on input data
397 2 : thisPVT.AreaCol = state.dataSurface->Surface(thisPVT.SurfNum).Area * thisPVT.Simple.ThermalActiveFract;
398 2 : thisPVT.ModelType = PVTModelType::Simple;
399 : } else {
400 2 : ThisParamObj = Util::FindItemInList(PVT(Item).PVTModelName, tmpBIPVTperf);
401 2 : if (ThisParamObj > 0) {
402 2 : thisPVT.BIPVT = tmpBIPVTperf(ThisParamObj); // entire structure assigned
403 : // do one-time setups on input data
404 2 : thisPVT.AreaCol = state.dataSurface->Surface(thisPVT.SurfNum).Area;
405 2 : thisPVT.ModelType = PVTModelType::BIPVT;
406 : } else {
407 0 : ShowSevereError(state,
408 0 : format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(3), state.dataIPShortCut->cAlphaArgs(3)));
409 0 : ShowContinueError(
410 0 : state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
411 0 : ShowContinueError(state, format("{}, was not found.", state.dataIPShortCut->cAlphaFieldNames(3)));
412 0 : ErrorsFound = true;
413 : }
414 : }
415 :
416 4 : if (allocated(state.dataPhotovoltaic->PVarray)) { // then PV input gotten... but don't expect this to be true.
417 4 : thisPVT.PVnum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(4), state.dataPhotovoltaic->PVarray);
418 : // check PV
419 4 : if (thisPVT.PVnum == 0) {
420 0 : ShowSevereError(state,
421 0 : format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(4), state.dataIPShortCut->cAlphaArgs(4)));
422 0 : ShowContinueError(
423 0 : state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
424 0 : ErrorsFound = true;
425 : } else {
426 4 : thisPVT.PVname = state.dataIPShortCut->cAlphaArgs(4);
427 4 : thisPVT.PVfound = true;
428 : }
429 : } else { // no PV or not yet gotten.
430 0 : thisPVT.PVname = state.dataIPShortCut->cAlphaArgs(4);
431 0 : thisPVT.PVfound = false;
432 : }
433 :
434 4 : if (Util::SameString(state.dataIPShortCut->cAlphaArgs(5), "Water")) {
435 2 : thisPVT.WorkingFluidType = WorkingFluidEnum::LIQUID;
436 2 : } else if (Util::SameString(state.dataIPShortCut->cAlphaArgs(5), "Air")) {
437 2 : thisPVT.WorkingFluidType = WorkingFluidEnum::AIR;
438 : } else {
439 0 : if (state.dataIPShortCut->lAlphaFieldBlanks(5)) {
440 0 : ShowSevereError(state,
441 0 : format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(5), state.dataIPShortCut->cAlphaArgs(5)));
442 0 : ShowContinueError(
443 0 : state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
444 0 : ShowContinueError(state, format("{} field cannot be blank.", state.dataIPShortCut->cAlphaFieldNames(5)));
445 : } else {
446 0 : ShowSevereError(state,
447 0 : format("Invalid {} = {}", state.dataIPShortCut->cAlphaFieldNames(5), state.dataIPShortCut->cAlphaArgs(5)));
448 0 : ShowContinueError(
449 0 : state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
450 : }
451 0 : ErrorsFound = true;
452 : }
453 :
454 4 : if (thisPVT.WorkingFluidType == WorkingFluidEnum::LIQUID) {
455 2 : thisPVT.PlantInletNodeNum =
456 2 : NodeInputManager::GetOnlySingleNode(state,
457 2 : state.dataIPShortCut->cAlphaArgs(6),
458 : ErrorsFound,
459 : DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
460 2 : state.dataIPShortCut->cAlphaArgs(1),
461 : DataLoopNode::NodeFluidType::Water,
462 : DataLoopNode::ConnectionType::Inlet,
463 : NodeInputManager::CompFluidStream::Primary,
464 : DataLoopNode::ObjectIsNotParent);
465 2 : thisPVT.PlantOutletNodeNum =
466 4 : NodeInputManager::GetOnlySingleNode(state,
467 2 : state.dataIPShortCut->cAlphaArgs(7),
468 : ErrorsFound,
469 : DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
470 2 : state.dataIPShortCut->cAlphaArgs(1),
471 : DataLoopNode::NodeFluidType::Water,
472 : DataLoopNode::ConnectionType::Outlet,
473 : NodeInputManager::CompFluidStream::Primary,
474 : DataLoopNode::ObjectIsNotParent);
475 4 : BranchNodeConnections::TestCompSet(state,
476 2 : state.dataIPShortCut->cCurrentModuleObject,
477 2 : state.dataIPShortCut->cAlphaArgs(1),
478 2 : state.dataIPShortCut->cAlphaArgs(6),
479 2 : state.dataIPShortCut->cAlphaArgs(7),
480 : "Water Nodes");
481 :
482 2 : thisPVT.WPlantLoc.loopSideNum = DataPlant::LoopSideLocation::Invalid;
483 : }
484 4 : if (thisPVT.WorkingFluidType == WorkingFluidEnum::AIR) {
485 2 : thisPVT.HVACInletNodeNum =
486 2 : NodeInputManager::GetOnlySingleNode(state,
487 2 : state.dataIPShortCut->cAlphaArgs(8),
488 : ErrorsFound,
489 : DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
490 2 : state.dataIPShortCut->cAlphaArgs(1),
491 : DataLoopNode::NodeFluidType::Air,
492 : DataLoopNode::ConnectionType::Inlet,
493 : NodeInputManager::CompFluidStream::Primary,
494 : DataLoopNode::ObjectIsNotParent);
495 2 : thisPVT.HVACOutletNodeNum =
496 4 : NodeInputManager::GetOnlySingleNode(state,
497 2 : state.dataIPShortCut->cAlphaArgs(9),
498 : ErrorsFound,
499 : DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
500 2 : state.dataIPShortCut->cAlphaArgs(1),
501 : DataLoopNode::NodeFluidType::Air,
502 : DataLoopNode::ConnectionType::Outlet,
503 : NodeInputManager::CompFluidStream::Primary,
504 : DataLoopNode::ObjectIsNotParent);
505 :
506 6 : BranchNodeConnections::TestCompSet(state,
507 2 : state.dataIPShortCut->cCurrentModuleObject,
508 2 : state.dataIPShortCut->cAlphaArgs(1),
509 2 : state.dataIPShortCut->cAlphaArgs(8),
510 2 : state.dataIPShortCut->cAlphaArgs(9),
511 : "Air Nodes");
512 : }
513 :
514 4 : thisPVT.DesignVolFlowRate = state.dataIPShortCut->rNumericArgs(1);
515 4 : thisPVT.SizingInit = true;
516 4 : if (thisPVT.DesignVolFlowRate == DataSizing::AutoSize) {
517 4 : thisPVT.DesignVolFlowRateWasAutoSized = true;
518 : }
519 4 : if (thisPVT.DesignVolFlowRate != DataSizing::AutoSize) {
520 :
521 0 : if (thisPVT.WorkingFluidType == WorkingFluidEnum::LIQUID) {
522 0 : PlantUtilities::RegisterPlantCompDesignFlow(state, thisPVT.PlantInletNodeNum, thisPVT.DesignVolFlowRate);
523 0 : } else if (thisPVT.WorkingFluidType == WorkingFluidEnum::AIR) {
524 0 : thisPVT.MaxMassFlowRate = thisPVT.DesignVolFlowRate * state.dataEnvrn->StdRhoAir;
525 : }
526 0 : thisPVT.SizingInit = false;
527 : }
528 : }
529 :
530 4 : if (ErrorsFound) {
531 0 : ShowFatalError(state, "Errors found in processing input for photovoltaic thermal collectors");
532 : }
533 : }
534 4 : }
535 :
536 1 : void PVTCollectorStruct::setupReportVars(EnergyPlusData &state)
537 : {
538 2 : SetupOutputVariable(state,
539 : "Generator Produced Thermal Rate",
540 : Constant::Units::W,
541 1 : this->Report.ThermPower,
542 : OutputProcessor::TimeStepType::System,
543 : OutputProcessor::StoreType::Average,
544 1 : this->Name);
545 :
546 1 : if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
547 2 : SetupOutputVariable(state,
548 : "Generator Produced Thermal Energy",
549 : Constant::Units::J,
550 1 : this->Report.ThermEnergy,
551 : OutputProcessor::TimeStepType::System,
552 : OutputProcessor::StoreType::Sum,
553 1 : this->Name,
554 : Constant::eResource::SolarWater,
555 : OutputProcessor::Group::Plant,
556 : OutputProcessor::EndUseCat::HeatProduced);
557 :
558 0 : } else if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
559 0 : SetupOutputVariable(state,
560 : "Generator Produced Thermal Energy",
561 : Constant::Units::J,
562 0 : this->Report.ThermEnergy,
563 : OutputProcessor::TimeStepType::System,
564 : OutputProcessor::StoreType::Sum,
565 0 : this->Name,
566 : Constant::eResource::SolarAir,
567 : OutputProcessor::Group::HVAC,
568 : OutputProcessor::EndUseCat::HeatProduced);
569 :
570 0 : SetupOutputVariable(state,
571 : "Generator PVT Fluid Bypass Status",
572 : Constant::Units::None,
573 0 : this->Report.BypassStatus,
574 : OutputProcessor::TimeStepType::System,
575 : OutputProcessor::StoreType::Average,
576 0 : this->Name);
577 : }
578 :
579 2 : SetupOutputVariable(state,
580 : "Generator PVT Fluid Inlet Temperature",
581 : Constant::Units::C,
582 1 : this->Report.TinletWorkFluid,
583 : OutputProcessor::TimeStepType::System,
584 : OutputProcessor::StoreType::Average,
585 1 : this->Name);
586 :
587 2 : SetupOutputVariable(state,
588 : "Generator PVT Fluid Outlet Temperature",
589 : Constant::Units::C,
590 1 : this->Report.ToutletWorkFluid,
591 : OutputProcessor::TimeStepType::System,
592 : OutputProcessor::StoreType::Average,
593 1 : this->Name);
594 :
595 2 : SetupOutputVariable(state,
596 : "Generator PVT Fluid Mass Flow Rate",
597 : Constant::Units::kg_s,
598 1 : this->Report.MdotWorkFluid,
599 : OutputProcessor::TimeStepType::System,
600 : OutputProcessor::StoreType::Average,
601 1 : this->Name);
602 1 : }
603 :
604 1 : void PVTCollectorStruct::initialize(EnergyPlusData &state, bool const FirstHVACIteration)
605 : {
606 :
607 : // SUBROUTINE INFORMATION:
608 : // AUTHOR B. Griffith
609 : // DATE WRITTEN June 2008
610 : // MODIFIED B. Griffith, May 2009, EMS setpoint check
611 : // RE-ENGINEERED na
612 :
613 : // PURPOSE OF THIS SUBROUTINE:
614 : // init for PVT
615 :
616 : static constexpr std::string_view RoutineName("InitPVTcollectors");
617 :
618 : // Do the one time initializations
619 1 : this->oneTimeInit(state);
620 :
621 : // finish set up of PV, because PV get-input follows PVT's get input.
622 1 : if (!this->PVfound) {
623 0 : if (allocated(state.dataPhotovoltaic->PVarray)) {
624 0 : this->PVnum = Util::FindItemInList(this->PVname, state.dataPhotovoltaic->PVarray);
625 0 : if (this->PVnum == 0) {
626 0 : ShowSevereError(state, format("Invalid name for photovoltaic generator = {}", this->PVname));
627 0 : ShowContinueError(state, format("Entered in flat plate photovoltaic-thermal collector = {}", this->Name));
628 : } else {
629 0 : this->PVfound = true;
630 : }
631 : } else {
632 0 : if ((!state.dataGlobal->BeginEnvrnFlag) && (!FirstHVACIteration)) {
633 0 : ShowSevereError(state, "Photovoltaic generators are missing for Photovoltaic Thermal modeling");
634 0 : ShowContinueError(state, format("Needed for flat plate photovoltaic-thermal collector = {}", this->Name));
635 : }
636 : }
637 : }
638 :
639 1 : if (!state.dataGlobal->SysSizingCalc && this->MySetPointCheckFlag && state.dataHVACGlobal->DoSetPointTest) {
640 0 : for (int PVTindex = 1; PVTindex <= state.dataPhotovoltaicThermalCollector->NumPVT; ++PVTindex) {
641 0 : if (state.dataPhotovoltaicThermalCollector->PVT(PVTindex).WorkingFluidType == WorkingFluidEnum::AIR) {
642 0 : if (state.dataLoopNodes->Node(state.dataPhotovoltaicThermalCollector->PVT(PVTindex).HVACOutletNodeNum).TempSetPoint ==
643 : DataLoopNode::SensedNodeFlagValue) {
644 0 : if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
645 0 : ShowSevereError(state, "Missing temperature setpoint for PVT outlet node ");
646 0 : ShowContinueError(state,
647 0 : format("Add a setpoint manager to outlet node of PVT named {}",
648 0 : state.dataPhotovoltaicThermalCollector->PVT(PVTindex).Name));
649 0 : state.dataHVACGlobal->SetPointErrorFlag = true;
650 : } else {
651 : // need call to EMS to check node
652 0 : EMSManager::CheckIfNodeSetPointManagedByEMS(state,
653 0 : state.dataPhotovoltaicThermalCollector->PVT(PVTindex).HVACOutletNodeNum,
654 : HVAC::CtrlVarType::Temp,
655 0 : state.dataHVACGlobal->SetPointErrorFlag);
656 0 : if (state.dataHVACGlobal->SetPointErrorFlag) {
657 0 : ShowSevereError(state, "Missing temperature setpoint for PVT outlet node ");
658 0 : ShowContinueError(state,
659 0 : format("Add a setpoint manager to outlet node of PVT named {}",
660 0 : state.dataPhotovoltaicThermalCollector->PVT(PVTindex).Name));
661 0 : ShowContinueError(state, " or use an EMS actuator to establish a setpoint at the outlet node of PVT");
662 : }
663 : }
664 : }
665 : }
666 : }
667 0 : this->MySetPointCheckFlag = false;
668 : }
669 :
670 1 : if (!state.dataGlobal->SysSizingCalc && this->SizingInit && (this->WorkingFluidType == WorkingFluidEnum::AIR)) {
671 0 : this->size(state);
672 : }
673 :
674 1 : int InletNode = 0;
675 1 : int OutletNode = 0;
676 :
677 1 : switch (this->WorkingFluidType) {
678 1 : case WorkingFluidEnum::LIQUID: {
679 1 : InletNode = this->PlantInletNodeNum;
680 1 : OutletNode = this->PlantOutletNodeNum;
681 1 : } break;
682 0 : case WorkingFluidEnum::AIR: {
683 0 : InletNode = this->HVACInletNodeNum;
684 0 : OutletNode = this->HVACOutletNodeNum;
685 0 : } break;
686 0 : default: {
687 0 : assert(false);
688 : } break;
689 : }
690 :
691 1 : if (state.dataGlobal->BeginEnvrnFlag && this->EnvrnInit) {
692 :
693 0 : this->MassFlowRate = 0.0;
694 0 : this->BypassDamperOff = true;
695 0 : this->CoolingUseful = false;
696 0 : this->HeatingUseful = false;
697 0 : this->Simple.LastCollectorTemp = 0.0;
698 0 : this->BIPVT.LastCollectorTemp = 0.0;
699 0 : this->Report.ThermPower = 0.0;
700 0 : this->Report.ThermHeatGain = 0.0;
701 0 : this->Report.ThermHeatLoss = 0.0;
702 0 : this->Report.ThermEnergy = 0.0;
703 0 : this->Report.MdotWorkFluid = 0.0;
704 0 : this->Report.TinletWorkFluid = 0.0;
705 0 : this->Report.ToutletWorkFluid = 0.0;
706 0 : this->Report.BypassStatus = 0.0;
707 :
708 0 : switch (this->WorkingFluidType) {
709 0 : case WorkingFluidEnum::LIQUID: {
710 :
711 0 : Real64 rho = state.dataPlnt->PlantLoop(this->WPlantLoc.loopNum).glycol->getDensity(state, Constant::HWInitConvTemp, RoutineName);
712 :
713 0 : this->MaxMassFlowRate = this->DesignVolFlowRate * rho;
714 :
715 0 : PlantUtilities::InitComponentNodes(state, 0.0, this->MaxMassFlowRate, InletNode, OutletNode);
716 :
717 0 : this->Simple.LastCollectorTemp = 23.0;
718 0 : } break;
719 0 : case WorkingFluidEnum::AIR: {
720 0 : this->Simple.LastCollectorTemp = 23.0;
721 0 : this->BIPVT.LastCollectorTemp = 23.0;
722 0 : } break;
723 0 : default:
724 0 : break;
725 : }
726 :
727 0 : this->EnvrnInit = false;
728 : }
729 1 : if (!state.dataGlobal->BeginEnvrnFlag) {
730 1 : this->EnvrnInit = true;
731 : }
732 :
733 1 : switch (this->WorkingFluidType) {
734 1 : case WorkingFluidEnum::LIQUID: {
735 : // heating only right now, so control flow requests based on incident solar;
736 1 : if (state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) > DataPhotovoltaics::MinIrradiance) {
737 0 : this->MassFlowRate = this->MaxMassFlowRate;
738 : } else {
739 1 : this->MassFlowRate = 0.0;
740 : }
741 :
742 1 : PlantUtilities::SetComponentFlowRate(state, this->MassFlowRate, InletNode, OutletNode, this->WPlantLoc);
743 1 : } break;
744 0 : case WorkingFluidEnum::AIR: {
745 0 : this->MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRate;
746 0 : } break;
747 0 : default:
748 0 : break;
749 : }
750 1 : }
751 :
752 1 : void PVTCollectorStruct::size(EnergyPlusData &state)
753 : {
754 :
755 : // SUBROUTINE INFORMATION:
756 : // AUTHOR Brent Griffith
757 : // DATE WRITTEN August 2008
758 : // MODIFIED November 2013 Daeho Kang, add component sizing table entries
759 : // RE-ENGINEERED na
760 :
761 : // PURPOSE OF THIS SUBROUTINE:
762 : // This subroutine is for sizing PVT flow rates that
763 : // have not been specified in the input.
764 :
765 : // METHODOLOGY EMPLOYED:
766 : // Obtains hot water flow rate from the plant sizing array.
767 :
768 : bool SizingDesRunThisAirSys; // true if a particular air system had a Sizing:System object and system sizing done
769 :
770 : // Indicator to hardsize and no sizing run
771 1 : bool HardSizeNoDesRun = !(state.dataSize->SysSizingRunDone || state.dataSize->ZoneSizingRunDone);
772 :
773 1 : if (state.dataSize->CurSysNum > 0) {
774 0 : CheckThisAirSystemForSizing(state, state.dataSize->CurSysNum, SizingDesRunThisAirSys);
775 : } else {
776 1 : SizingDesRunThisAirSys = false;
777 : }
778 :
779 1 : Real64 DesignVolFlowRateDes = 0.0; // Autosize design volume flow for reporting
780 1 : bool ErrorsFound = false;
781 :
782 1 : if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
783 :
784 1 : if (!allocated(state.dataSize->PlantSizData)) {
785 1 : return;
786 : }
787 0 : if (!allocated(state.dataPlnt->PlantLoop)) {
788 0 : return;
789 : }
790 0 : int PltSizNum = 0; // Plant Sizing index corresponding to CurLoopNum
791 :
792 0 : if (this->WPlantLoc.loopNum > 0) {
793 0 : PltSizNum = state.dataPlnt->PlantLoop(this->WPlantLoc.loopNum).PlantSizNum;
794 : }
795 0 : if (this->WPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
796 0 : if (PltSizNum > 0) {
797 0 : if (state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate >= HVAC::SmallWaterVolFlow) {
798 0 : DesignVolFlowRateDes = state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate;
799 : } else {
800 0 : DesignVolFlowRateDes = 0.0;
801 : }
802 : } else {
803 0 : if (this->DesignVolFlowRateWasAutoSized) {
804 0 : if (state.dataPlnt->PlantFirstSizesOkayToFinalize) {
805 0 : ShowSevereError(state, "Autosizing of PVT solar collector design flow rate requires a Sizing:Plant object");
806 0 : ShowContinueError(state, format("Occurs in PVT object={}", this->Name));
807 0 : ErrorsFound = true;
808 : }
809 : } else { // Hardsized
810 0 : if (state.dataPlnt->PlantFinalSizesOkayToReport && this->DesignVolFlowRate > 0.0) {
811 0 : BaseSizer::reportSizerOutput(state,
812 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
813 : this->Name,
814 : "User-Specified Design Flow Rate [m3/s]",
815 : this->DesignVolFlowRate);
816 : }
817 : }
818 : }
819 0 : } else if (this->WPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Demand) {
820 0 : Real64 constexpr SimplePVTWaterSizeFactor(1.905e-5); // [ m3/s/m2 ] average of collectors in SolarCollectors.idf
821 0 : DesignVolFlowRateDes = this->AreaCol * SimplePVTWaterSizeFactor;
822 : }
823 0 : if (this->DesignVolFlowRateWasAutoSized) {
824 0 : this->DesignVolFlowRate = DesignVolFlowRateDes;
825 0 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
826 0 : BaseSizer::reportSizerOutput(state,
827 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
828 : this->Name,
829 : "Design Size Design Flow Rate [m3/s]",
830 : DesignVolFlowRateDes);
831 : }
832 0 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
833 0 : BaseSizer::reportSizerOutput(state,
834 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
835 : this->Name,
836 : "Initial Design Size Design Flow Rate [m3/s]",
837 : DesignVolFlowRateDes);
838 : }
839 0 : PlantUtilities::RegisterPlantCompDesignFlow(state, this->PlantInletNodeNum, this->DesignVolFlowRate);
840 :
841 : } else { // Hardsized with sizing data
842 0 : if (this->DesignVolFlowRate > 0.0 && DesignVolFlowRateDes > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport) {
843 0 : Real64 DesignVolFlowRateUser = this->DesignVolFlowRate;
844 0 : BaseSizer::reportSizerOutput(state,
845 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
846 : this->Name,
847 : "Design Size Design Flow Rate [m3/s]",
848 : DesignVolFlowRateDes,
849 : "User-Specified Design Flow Rate [m3/s]",
850 : DesignVolFlowRateUser);
851 0 : if (state.dataGlobal->DisplayExtraWarnings) {
852 0 : if ((std::abs(DesignVolFlowRateDes - DesignVolFlowRateUser) / DesignVolFlowRateUser) >
853 0 : state.dataSize->AutoVsHardSizingThreshold) {
854 0 : ShowMessage(state, format("SizeSolarCollector: Potential issue with equipment sizing for {}", this->Name));
855 0 : ShowContinueError(state, format("User-Specified Design Flow Rate of {:.5R} [W]", DesignVolFlowRateUser));
856 0 : ShowContinueError(state, format("differs from Design Size Design Flow Rate of {:.5R} [W]", DesignVolFlowRateDes));
857 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
858 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
859 : }
860 : }
861 : }
862 : }
863 : } // plant component
864 :
865 0 : if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
866 :
867 0 : if (state.dataSize->CurSysNum > 0) {
868 0 : if (!this->DesignVolFlowRateWasAutoSized && !SizingDesRunThisAirSys) { // Simulation continue
869 0 : HardSizeNoDesRun = true;
870 0 : if (this->DesignVolFlowRate > 0.0) {
871 0 : BaseSizer::reportSizerOutput(state,
872 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
873 : this->Name,
874 : "User-Specified Design Flow Rate [m3/s]",
875 : this->DesignVolFlowRate);
876 : }
877 : } else {
878 0 : CheckSysSizing(state, "SolarCollector:FlatPlate:PhotovoltaicThermal", this->Name);
879 0 : auto const &thisFinalSysSizing = state.dataSize->FinalSysSizing(state.dataSize->CurSysNum);
880 0 : if (state.dataSize->CurOASysNum > 0) {
881 0 : DesignVolFlowRateDes = thisFinalSysSizing.DesOutAirVolFlow;
882 : } else {
883 0 : switch (state.dataSize->CurDuctType) {
884 0 : case HVAC::AirDuctType::Main: {
885 0 : DesignVolFlowRateDes = thisFinalSysSizing.SysAirMinFlowRat * thisFinalSysSizing.DesMainVolFlow;
886 0 : } break;
887 0 : case HVAC::AirDuctType::Cooling: {
888 0 : DesignVolFlowRateDes = thisFinalSysSizing.SysAirMinFlowRat * thisFinalSysSizing.DesCoolVolFlow;
889 0 : } break;
890 0 : case HVAC::AirDuctType::Heating: {
891 0 : DesignVolFlowRateDes = thisFinalSysSizing.DesHeatVolFlow;
892 0 : } break;
893 0 : default: {
894 0 : DesignVolFlowRateDes = thisFinalSysSizing.DesMainVolFlow;
895 0 : } break;
896 : }
897 : }
898 0 : Real64 DesMassFlow = state.dataEnvrn->StdRhoAir * DesignVolFlowRateDes;
899 0 : this->MaxMassFlowRate = DesMassFlow;
900 : }
901 0 : if (!HardSizeNoDesRun) {
902 0 : if (this->DesignVolFlowRateWasAutoSized) {
903 0 : this->DesignVolFlowRate = DesignVolFlowRateDes;
904 0 : BaseSizer::reportSizerOutput(state,
905 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
906 : this->Name,
907 : "Design Size Design Flow Rate [m3/s]",
908 : DesignVolFlowRateDes);
909 0 : this->SizingInit = false;
910 : } else {
911 0 : if (this->DesignVolFlowRate > 0.0 && DesignVolFlowRateDes > 0.0) {
912 0 : Real64 DesignVolFlowRateUser = this->DesignVolFlowRate;
913 0 : BaseSizer::reportSizerOutput(state,
914 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
915 : this->Name,
916 : "Design Size Design Flow Rate [m3/s]",
917 : DesignVolFlowRateDes,
918 : "User-Specified Design Flow Rate [m3/s]",
919 : DesignVolFlowRateUser);
920 0 : if (state.dataGlobal->DisplayExtraWarnings) {
921 0 : if ((std::abs(DesignVolFlowRateDes - DesignVolFlowRateUser) / DesignVolFlowRateUser) >
922 0 : state.dataSize->AutoVsHardSizingThreshold) {
923 0 : ShowMessage(state, format("SizeSolarCollector: Potential issue with equipment sizing for {}", this->Name));
924 0 : ShowContinueError(state, format("User-Specified Design Flow Rate of {:.5R} [W]", DesignVolFlowRateUser));
925 0 : ShowContinueError(state, format("differs from Design Size Design Flow Rate of {:.5R} [W]", DesignVolFlowRateDes));
926 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
927 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
928 : }
929 : }
930 : }
931 : }
932 : }
933 0 : } else if (state.dataSize->CurZoneEqNum > 0) {
934 : // PVT is not currently for zone equipment, should not come here.
935 : }
936 : }
937 :
938 0 : if (ErrorsFound) {
939 0 : ShowFatalError(state, "Preceding sizing errors cause program termination");
940 : }
941 : }
942 :
943 0 : void PVTCollectorStruct::control(EnergyPlusData &state)
944 : {
945 :
946 : // SUBROUTINE INFORMATION:
947 : // AUTHOR Brent Griffith
948 : // DATE WRITTEN August 2008
949 : // RE-ENGINEERED na
950 :
951 : // PURPOSE OF THIS SUBROUTINE:
952 : // make control decisions for PVT collector
953 :
954 : // METHODOLOGY EMPLOYED:
955 : // decide if PVT should be in cooling or heat mode and if it should be bypassed or not
956 :
957 0 : if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
958 0 : if ((this->ModelType == PVTModelType::Simple) || (this->ModelType == PVTModelType::BIPVT)) {
959 0 : if (state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) > DataPhotovoltaics::MinIrradiance) {
960 : // is heating wanted?
961 : // Outlet node is required to have a setpoint.
962 0 : if (state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint > state.dataLoopNodes->Node(this->HVACInletNodeNum).Temp) {
963 0 : this->HeatingUseful = true;
964 0 : this->CoolingUseful = false;
965 0 : this->BypassDamperOff = true;
966 : } else {
967 0 : this->HeatingUseful = false;
968 0 : this->CoolingUseful = true;
969 0 : this->BypassDamperOff = false;
970 : }
971 : } else {
972 : // is cooling wanted?
973 0 : if (state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint < state.dataLoopNodes->Node(this->HVACInletNodeNum).Temp) {
974 0 : this->CoolingUseful = true;
975 0 : this->HeatingUseful = false;
976 0 : this->BypassDamperOff = true;
977 : } else {
978 0 : this->CoolingUseful = false;
979 0 : this->HeatingUseful = true;
980 0 : this->BypassDamperOff = false;
981 : }
982 : }
983 : }
984 :
985 0 : } else if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
986 0 : if (this->ModelType == PVTModelType::Simple) {
987 0 : if (state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) > DataPhotovoltaics::MinIrradiance) {
988 : // is heating wanted?
989 0 : this->HeatingUseful = true;
990 0 : this->BypassDamperOff = true;
991 : } else {
992 : // is cooling wanted?
993 0 : this->CoolingUseful = false;
994 0 : this->BypassDamperOff = false;
995 : }
996 : }
997 : }
998 0 : }
999 :
1000 0 : void PVTCollectorStruct::calculate(EnergyPlusData &state)
1001 : {
1002 :
1003 : // SUBROUTINE INFORMATION:
1004 : // AUTHOR Brent Griffith
1005 : // DATE WRITTEN August 2008
1006 : // RE-ENGINEERED na
1007 :
1008 : // PURPOSE OF THIS SUBROUTINE:
1009 : // Calculate PVT collector thermal performance
1010 :
1011 : // METHODOLOGY EMPLOYED:
1012 :
1013 0 : if (this->ModelType == PVTModelType::Simple) {
1014 0 : calculateSimplePVT(state);
1015 0 : } else if (this->ModelType == PVTModelType::BIPVT) {
1016 0 : calculateBIPVT(state);
1017 : }
1018 0 : }
1019 :
1020 0 : void PVTCollectorStruct::calculateSimplePVT(EnergyPlusData &state)
1021 : {
1022 :
1023 : // SUBROUTINE INFORMATION:
1024 : // AUTHOR Brent Griffith
1025 : // DATE WRITTEN August 2008
1026 : // RE-ENGINEERED na
1027 :
1028 : // PURPOSE OF THIS SUBROUTINE:
1029 : // Calculate PVT Simple collector thermal
1030 :
1031 : // METHODOLOGY EMPLOYED:
1032 : // Current model is "simple" fixed efficiency and simple night sky balance for cooling
1033 :
1034 : static constexpr std::string_view RoutineName("CalcSimplePVTcollectors");
1035 :
1036 0 : int InletNode(0);
1037 :
1038 0 : switch (this->WorkingFluidType) {
1039 0 : case WorkingFluidEnum::LIQUID: {
1040 0 : InletNode = this->PlantInletNodeNum;
1041 0 : } break;
1042 0 : case WorkingFluidEnum::AIR: {
1043 0 : InletNode = this->HVACInletNodeNum;
1044 0 : } break;
1045 0 : default:
1046 0 : break;
1047 : }
1048 :
1049 0 : Real64 mdot = this->MassFlowRate;
1050 0 : Real64 Tinlet = state.dataLoopNodes->Node(InletNode).Temp;
1051 :
1052 0 : Real64 BypassFraction(0.0);
1053 0 : Real64 PotentialOutletTemp(0.0);
1054 :
1055 0 : if (this->HeatingUseful && this->BypassDamperOff && (mdot > 0.0)) {
1056 :
1057 0 : Real64 Eff(0.0);
1058 :
1059 0 : switch (this->Simple.ThermEfficMode) {
1060 0 : case ThermEfficEnum::FIXED: {
1061 0 : Eff = this->Simple.ThermEffic;
1062 0 : } break;
1063 0 : case ThermEfficEnum::SCHEDULED: {
1064 0 : Eff = this->Simple.thermEffSched->getCurrentVal();
1065 0 : this->Simple.ThermEffic = Eff;
1066 0 : } break;
1067 0 : default:
1068 0 : break;
1069 : }
1070 :
1071 0 : Real64 PotentialHeatGain = state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) * Eff * this->AreaCol;
1072 :
1073 0 : if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
1074 0 : Real64 Winlet = state.dataLoopNodes->Node(InletNode).HumRat;
1075 0 : Real64 CpInlet = Psychrometrics::PsyCpAirFnW(Winlet);
1076 0 : if (mdot * CpInlet > 0.0) {
1077 0 : PotentialOutletTemp = Tinlet + PotentialHeatGain / (mdot * CpInlet);
1078 : } else {
1079 0 : PotentialOutletTemp = Tinlet;
1080 : }
1081 : // now compare heating potential to setpoint and figure bypass fraction
1082 0 : if (PotentialOutletTemp > state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint) { // need to modulate
1083 0 : if (Tinlet != PotentialOutletTemp) {
1084 0 : BypassFraction =
1085 0 : (state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint - PotentialOutletTemp) / (Tinlet - PotentialOutletTemp);
1086 : } else {
1087 0 : BypassFraction = 0.0;
1088 : }
1089 0 : BypassFraction = max(0.0, BypassFraction);
1090 0 : PotentialOutletTemp = state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint;
1091 0 : PotentialHeatGain = mdot * Psychrometrics::PsyCpAirFnW(Winlet) * (PotentialOutletTemp - Tinlet);
1092 :
1093 : } else {
1094 0 : BypassFraction = 0.0;
1095 : }
1096 0 : } else if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
1097 0 : Real64 CpInlet = Psychrometrics::CPHW(Tinlet);
1098 0 : if (mdot * CpInlet != 0.0) { // protect divide by zero
1099 0 : PotentialOutletTemp = Tinlet + PotentialHeatGain / (mdot * CpInlet);
1100 : } else {
1101 0 : PotentialOutletTemp = Tinlet;
1102 : }
1103 0 : BypassFraction = 0.0;
1104 : }
1105 :
1106 0 : this->Report.ThermHeatGain = PotentialHeatGain;
1107 0 : this->Report.ThermPower = this->Report.ThermHeatGain;
1108 0 : this->Report.ThermEnergy = this->Report.ThermPower * state.dataHVACGlobal->TimeStepSysSec;
1109 0 : this->Report.ThermHeatLoss = 0.0;
1110 0 : this->Report.TinletWorkFluid = Tinlet;
1111 0 : this->Report.MdotWorkFluid = mdot;
1112 0 : this->Report.ToutletWorkFluid = PotentialOutletTemp;
1113 0 : this->Report.BypassStatus = BypassFraction;
1114 :
1115 0 : } else if (this->CoolingUseful && this->BypassDamperOff && (mdot > 0.0)) {
1116 : // calculate cooling using energy balance
1117 :
1118 0 : Real64 HrGround(0.0);
1119 0 : Real64 HrAir(0.0);
1120 0 : Real64 HcExt(0.0);
1121 0 : Real64 HrSky(0.0);
1122 0 : Real64 HrSrdSurf(0.0);
1123 :
1124 0 : Convect::InitExtConvCoeff(state,
1125 : this->SurfNum,
1126 : 0.0,
1127 : Material::SurfaceRoughness::VerySmooth,
1128 : this->Simple.SurfEmissivity,
1129 : this->Simple.LastCollectorTemp,
1130 : HcExt,
1131 : HrSky,
1132 : HrGround,
1133 : HrAir,
1134 : HrSrdSurf);
1135 :
1136 0 : Real64 WetBulbInlet(0.0);
1137 0 : Real64 DewPointInlet(0.0);
1138 0 : Real64 CpInlet(0.0);
1139 :
1140 0 : if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
1141 0 : Real64 Winlet = state.dataLoopNodes->Node(InletNode).HumRat;
1142 0 : CpInlet = Psychrometrics::PsyCpAirFnW(Winlet);
1143 0 : WetBulbInlet = Psychrometrics::PsyTwbFnTdbWPb(state, Tinlet, Winlet, state.dataEnvrn->OutBaroPress, RoutineName);
1144 0 : DewPointInlet = Psychrometrics::PsyTdpFnTdbTwbPb(state, Tinlet, WetBulbInlet, state.dataEnvrn->OutBaroPress, RoutineName);
1145 0 : } else if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
1146 0 : CpInlet = Psychrometrics::CPHW(Tinlet);
1147 : }
1148 : Real64 Tcollector =
1149 0 : (2.0 * mdot * CpInlet * Tinlet + this->AreaCol * (HrGround * state.dataEnvrn->OutDryBulbTemp + HrSky * state.dataEnvrn->SkyTemp +
1150 0 : HrAir * state.dataSurface->SurfOutDryBulbTemp(this->SurfNum) +
1151 0 : HcExt * state.dataSurface->SurfOutDryBulbTemp(this->SurfNum))) /
1152 0 : (2.0 * mdot * CpInlet + this->AreaCol * (HrGround + HrSky + HrAir + HcExt));
1153 0 : PotentialOutletTemp = 2.0 * Tcollector - Tinlet;
1154 0 : this->Report.ToutletWorkFluid = PotentialOutletTemp;
1155 : // trap for air not being cooled below its wetbulb.
1156 0 : if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
1157 0 : if (PotentialOutletTemp < DewPointInlet) {
1158 : // water removal would be needed.. not going to allow that for now. limit cooling to dew point and model bypass
1159 0 : if (Tinlet != PotentialOutletTemp) {
1160 0 : BypassFraction = (DewPointInlet - PotentialOutletTemp) / (Tinlet - PotentialOutletTemp);
1161 :
1162 : } else {
1163 0 : BypassFraction = 0.0;
1164 : }
1165 0 : BypassFraction = max(0.0, BypassFraction);
1166 0 : PotentialOutletTemp = DewPointInlet;
1167 : }
1168 : }
1169 :
1170 0 : this->Report.MdotWorkFluid = mdot;
1171 0 : this->Report.TinletWorkFluid = Tinlet;
1172 0 : this->Report.ToutletWorkFluid = PotentialOutletTemp;
1173 0 : this->Report.ThermHeatLoss = mdot * CpInlet * (Tinlet - this->Report.ToutletWorkFluid);
1174 0 : this->Report.ThermHeatGain = 0.0;
1175 0 : this->Report.ThermPower = -1.0 * this->Report.ThermHeatLoss;
1176 0 : this->Report.ThermEnergy = this->Report.ThermPower * state.dataHVACGlobal->TimeStepSysSec;
1177 0 : this->Simple.LastCollectorTemp = Tcollector;
1178 0 : this->Report.BypassStatus = BypassFraction;
1179 :
1180 0 : } else {
1181 0 : this->Report.TinletWorkFluid = Tinlet;
1182 0 : this->Report.ToutletWorkFluid = Tinlet;
1183 0 : this->Report.ThermHeatLoss = 0.0;
1184 0 : this->Report.ThermHeatGain = 0.0;
1185 0 : this->Report.ThermPower = 0.0;
1186 0 : this->Report.ThermEnergy = 0.0;
1187 0 : this->Report.BypassStatus = 1.0;
1188 0 : this->Report.MdotWorkFluid = mdot;
1189 : }
1190 0 : }
1191 :
1192 0 : void PVTCollectorStruct::calculateBIPVT(EnergyPlusData &state)
1193 : {
1194 :
1195 : // PURPOSE OF THIS SUBROUTINE:
1196 : // Calculate BIPVT collector thermal peformancce
1197 :
1198 : // METHODOLOGY EMPLOYED:
1199 : // ???
1200 :
1201 0 : static std::string const RoutineName("CalcBIPVTcollectors");
1202 :
1203 0 : int InletNode = this->HVACInletNodeNum;
1204 0 : Real64 mdot = this->MassFlowRate;
1205 0 : Real64 Tinlet = state.dataLoopNodes->Node(InletNode).Temp;
1206 0 : Real64 BypassFraction(0.0);
1207 0 : Real64 PotentialOutletTemp(Tinlet);
1208 0 : Real64 PotentialHeatGain(0.0);
1209 0 : Real64 Eff(0.0);
1210 0 : Real64 Tcollector(Tinlet);
1211 0 : this->OperatingMode = PVTMode::Heating;
1212 :
1213 0 : if (this->HeatingUseful && this->BypassDamperOff && (this->BIPVT.availSched->getCurrentVal() > 0.0)) {
1214 :
1215 0 : if ((state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint - Tinlet) > 0.1) {
1216 0 : calculateBIPVTMaxHeatGain(state,
1217 0 : state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint,
1218 : BypassFraction,
1219 : PotentialHeatGain,
1220 : PotentialOutletTemp,
1221 : Eff,
1222 : Tcollector);
1223 0 : if (PotentialHeatGain < 0.0) {
1224 0 : BypassFraction = 1.0;
1225 0 : PotentialHeatGain = 0.0;
1226 0 : PotentialOutletTemp = Tinlet;
1227 : }
1228 : }
1229 :
1230 0 : this->Report.ThermHeatGain = PotentialHeatGain;
1231 0 : this->Report.ThermPower = this->Report.ThermHeatGain;
1232 0 : this->Report.ThermEnergy = this->Report.ThermPower * state.dataHVACGlobal->TimeStepSysSec;
1233 0 : this->Report.ThermHeatLoss = 0.0;
1234 0 : this->Report.TinletWorkFluid = Tinlet;
1235 0 : this->Report.MdotWorkFluid = mdot;
1236 0 : this->Report.ToutletWorkFluid = PotentialOutletTemp;
1237 0 : this->Report.BypassStatus = BypassFraction;
1238 0 : if (PotentialHeatGain > 0.0) {
1239 0 : this->BIPVT.LastCollectorTemp = Tcollector;
1240 : }
1241 :
1242 0 : } else if (this->CoolingUseful && this->BypassDamperOff && (this->BIPVT.availSched->getCurrentVal() > 0.0)) {
1243 :
1244 0 : this->OperatingMode = PVTMode::Cooling;
1245 0 : if ((Tinlet - state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint) > 0.1) {
1246 0 : calculateBIPVTMaxHeatGain(state,
1247 0 : state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint,
1248 : BypassFraction,
1249 : PotentialHeatGain,
1250 : PotentialOutletTemp,
1251 : Eff,
1252 : Tcollector);
1253 0 : if (PotentialHeatGain > 0.0) {
1254 0 : PotentialHeatGain = 0.0;
1255 0 : BypassFraction = 1.0;
1256 0 : PotentialOutletTemp = Tinlet;
1257 : } else {
1258 0 : Real64 WetBulbInlet(0.0);
1259 0 : Real64 DewPointInlet(0.0);
1260 0 : Real64 CpInlet(0.0);
1261 0 : Real64 Winlet = state.dataLoopNodes->Node(InletNode).HumRat;
1262 0 : CpInlet = Psychrometrics::PsyCpAirFnW(Winlet);
1263 0 : WetBulbInlet = Psychrometrics::PsyTwbFnTdbWPb(state, Tinlet, Winlet, state.dataEnvrn->OutBaroPress, RoutineName);
1264 0 : DewPointInlet = Psychrometrics::PsyTdpFnTdbTwbPb(state, Tinlet, WetBulbInlet, state.dataEnvrn->OutBaroPress, RoutineName);
1265 : // trap for air not being cooled below its dewpoint.
1266 0 : if ((PotentialOutletTemp < DewPointInlet) && ((Tinlet - DewPointInlet) > 0.1)) {
1267 : // water removal would be needed.. not going to allow that for now. limit cooling to dew point and model bypass
1268 0 : calculateBIPVTMaxHeatGain(state, DewPointInlet, BypassFraction, PotentialHeatGain, PotentialOutletTemp, Eff, Tcollector);
1269 0 : PotentialOutletTemp = DewPointInlet;
1270 : }
1271 : }
1272 : } else {
1273 0 : PotentialHeatGain = 0.0;
1274 0 : BypassFraction = 1.0;
1275 0 : PotentialOutletTemp = Tinlet;
1276 : }
1277 0 : this->Report.MdotWorkFluid = mdot;
1278 0 : this->Report.TinletWorkFluid = Tinlet;
1279 0 : this->Report.ToutletWorkFluid = PotentialOutletTemp;
1280 0 : this->Report.ThermHeatLoss = -PotentialHeatGain;
1281 0 : this->Report.ThermHeatGain = 0.0;
1282 0 : this->Report.ThermPower = -1.0 * this->Report.ThermHeatLoss;
1283 0 : this->Report.ThermEnergy = this->Report.ThermPower * state.dataHVACGlobal->TimeStepSysSec;
1284 0 : if (PotentialHeatGain < 0.0) {
1285 0 : this->BIPVT.LastCollectorTemp = Tcollector;
1286 : }
1287 0 : this->Report.BypassStatus = BypassFraction;
1288 : } else {
1289 0 : this->Report.TinletWorkFluid = Tinlet;
1290 0 : this->Report.ToutletWorkFluid = Tinlet;
1291 0 : this->Report.ThermHeatLoss = 0.0;
1292 0 : this->Report.ThermHeatGain = 0.0;
1293 0 : this->Report.ThermPower = 0.0;
1294 0 : this->Report.ThermEnergy = 0.0;
1295 0 : this->Report.BypassStatus = 1.0;
1296 0 : this->Report.MdotWorkFluid = mdot;
1297 : }
1298 0 : } // namespace PhotovoltaicThermalCollectors
1299 :
1300 11 : void PVTCollectorStruct::calculateBIPVTMaxHeatGain(
1301 : EnergyPlusData &state, Real64 tsp, Real64 &bfr, Real64 &q, Real64 &tmixed, Real64 &ThEff, Real64 &tpv)
1302 : {
1303 : // PURPOSE OF THIS SUBROUTINE:
1304 : // Calculate the maximum heat transfer from the BIPVT system to the air stream in the channel behind the PV module
1305 :
1306 : // METHODOLOGY EMPLOYED:
1307 : // Numerical & Analytical
1308 :
1309 11 : Real64 pi(Constant::Pi);
1310 : // BIPVT system geometry
1311 11 : Real64 l = state.dataSurface->Surface(this->SurfNum).Height;
1312 11 : Real64 w = state.dataSurface->Surface(this->SurfNum).Width;
1313 11 : Real64 depth_channel = this->BIPVT.PVEffGapWidth; // depth of air channel (m)
1314 11 : Real64 slope = (pi / 180.0) * state.dataSurface->Surface(this->SurfNum).Tilt; // surface tilt in rad
1315 11 : Real64 beta(0); // surface tilt for calculating internal convective coefficient for stagnation condition
1316 11 : Real64 surf_azimuth = state.dataSurface->Surface(this->SurfNum).Azimuth; // surface azimuth (deg)
1317 11 : Real64 fcell = this->BIPVT.PVCellAreaFract; // area fraction of cells on pv module
1318 11 : Real64 glass_thickness = this->BIPVT.ThGlass; // glass thickness
1319 11 : Real64 area_pv = w * l * this->BIPVT.PVAreaFract; // total area of pv modules
1320 11 : Real64 area_wall_total = w * l; // total area of wall
1321 11 : Real64 length_conv = l; // length for wind convection coefficient calc
1322 11 : DataSurfaces::SurfaceShape shape_bld_surf = state.dataSurface->Surface(this->SurfNum).Shape;
1323 :
1324 11 : if (shape_bld_surf != EnergyPlus::DataSurfaces::SurfaceShape::Rectangle) {
1325 0 : ShowFatalError(state,
1326 0 : "BIPVT is located on non-rectangular surface. Surface name = " + state.dataSurface->Surface(this->SurfNum).Name +
1327 : ". BIPVT model requires rectangular surface.");
1328 : }
1329 :
1330 : // BIPVT materials thermal properties
1331 11 : Real64 emiss_b = this->BIPVT.BackMatEmiss; // emissivity of backing surface
1332 11 : Real64 emiss_2(0.85); // emissivity of bldg surface
1333 11 : Real64 emiss_pvg = this->BIPVT.PVGEmiss; // emissivity of glass surface
1334 11 : Real64 rpvg_pv = this->BIPVT.PVRTop; // thermal resistance of glass (m2-K/W)
1335 11 : Real64 rpv_1 = this->BIPVT.PVRBot; // thermal resistance of backing layer (m2-K/W)
1336 11 : Real64 taoalpha_back = this->BIPVT.BackMatTranAbsProduct; // tao-alpha product normal back of PV panel
1337 11 : Real64 taoalpha_pv = this->BIPVT.PVCellTransAbsProduct; // tao-aplha product normal PV cells
1338 11 : Real64 taoaplha_cladding = this->BIPVT.CladTranAbsProduct; // tao-alpha product normal cladding
1339 11 : Real64 refrac_index_glass = this->BIPVT.RIndGlass; // glass refractive index
1340 11 : Real64 k_glass = this->BIPVT.ECoffGlass; // extinction coefficient pv glass
1341 :
1342 : // PV panel efficiency
1343 11 : Real64 eff_pv(0.0); // efficiency pv panel
1344 :
1345 : // Weather/thermodynamic state/air properties/heat transfer
1346 11 : Real64 g(0.0); // Solar incident on surface of BIPVT collector (W/m^2)
1347 : Real64 tsurr, tsurrK; // surrouding temperature (DegC, DegK)
1348 : Real64 t1, t1K, t1_new; // temperature of pv backing surface (DegC, DegK, DegC)
1349 : Real64 tpv_new; // temperature of pv surface (DegC, DegC)
1350 : Real64 tpvg, tpvgK, tpvg_new; // temperature of pv glass cover (DegC, DegK,DegC)
1351 11 : Real64 tfavg(18.0); // average fluid temperature (DegC)
1352 : Real64 tfout; // outlet fluid temperature from BIPVT channel (DegC)
1353 11 : Real64 hconvf1(100.0); // heat transfer coefficient between fluid and backing surface (W/m2-K)
1354 11 : Real64 hconvf2(100.0); // heat transfer coefficient between fluid and bldg surface (W/m2-K)
1355 11 : Real64 hconvt_nat(0.0); // htc external natural
1356 11 : Real64 hconvt_forced(0.0); // htc external forced
1357 11 : Real64 hconvt(0.0); // htc external total
1358 : Real64 hpvg_pv; // conductance of pv glass cover (W/m2-K)
1359 : Real64 hpv_1; // conductance of pv backing (W/m2-K)
1360 : Real64 hrad12; // radiative heat transfer coefficient between bldg surface and pv backing surface (W/m2-K)
1361 : Real64 hrad_surr; // radiative heat transfer coefficient between pv glass cover and surrounding (W/m2-K)
1362 11 : constexpr Real64 sigma(5.67e-8); // stephan bolzmann constant
1363 11 : Real64 reynolds(0.0); // Reynolds inside collector
1364 11 : Real64 nusselt(0.0); // Nusselt inside collector
1365 11 : Real64 vel(0.0); // flow velocity (m/s)
1366 11 : Real64 raleigh(0.0); // Raleigh number for stagnation calculations
1367 11 : Real64 dhyd(0.0); // Hydraulic diameter of channel (m)
1368 11 : constexpr Real64 gravity(9.81); // gravity (m/s^2)
1369 11 : Real64 mu_air(22.7e-6); // dynamic viscosity (kg/m-s)
1370 11 : Real64 k_air(0.026); // thermal conductivity (W/m-K)
1371 11 : Real64 prandtl_air(0.7); // Prandtl number
1372 11 : Real64 density_air(1.2); // density (kg/m^3)
1373 11 : Real64 diffusivity_air(0.0); // thermal diffusivity (m^2/s)
1374 11 : Real64 kin_viscosity_air(0.0); // kinematic viscosity (m^2/s)
1375 11 : Real64 extHTCcoeff(0.0); // coefficient for calculating external forced HTC
1376 11 : Real64 extHTCexp(0.0); // exponent for calculating external forced HTC
1377 11 : int const InletNode = this->HVACInletNodeNum; // HVAC node associated with inlet of BIPVT
1378 11 : Real64 tfin = state.dataLoopNodes->Node(InletNode).Temp; // inlet fluid temperature (DegC)
1379 11 : Real64 w_in = state.dataLoopNodes->Node(InletNode).HumRat; // inlet air humidity ratio (kgda/kg)
1380 11 : Real64 cp_in = Psychrometrics::PsyCpAirFnW(w_in); // inlet air specific heat (J/kg-K)
1381 11 : Real64 tamb = state.dataEnvrn->OutDryBulbTemp; // ambient temperature (DegC)
1382 11 : Real64 wamb = state.dataEnvrn->OutHumRat; // ambient humidity ratio (kg/kg)
1383 11 : Real64 cp_amb = Psychrometrics::PsyCpAirFnW(wamb); // ambient air specific heat (J/kg-K)
1384 11 : Real64 t_film(20.0); // film temperature to calculate htc at ambient/pv interface (DegC)
1385 11 : Real64 tsky = state.dataEnvrn->SkyTemp; // sky temperature (DegC)
1386 11 : Real64 v_wind = state.dataEnvrn->WindSpeed; // wind speed (m/s)
1387 11 : Real64 wind_dir = state.dataEnvrn->WindDir; // wind direction (deg)
1388 11 : Real64 t2 = state.dataHeatBalSurf->SurfTempOut(this->SurfNum), t2K; // temperature of bldg surface (DegC)
1389 11 : Real64 mdot = this->MassFlowRate; // fluid mass flow rate (kg/s)
1390 11 : Real64 mdot_bipvt(mdot), mdot_bipvt_new(mdot); // mass flow rate through the bipvt duct (kg/s)
1391 11 : Real64 s(0.0); // solar radiation gain at pv surface (W/m2)
1392 11 : Real64 s1(0.0); // solar radiation gain at pv backing surface (W/m2)
1393 11 : Real64 k_taoalpha_beam(0.0);
1394 11 : Real64 k_taoalpha_sky(0.0);
1395 11 : Real64 k_taoalpha_ground(0.0); // solar radiation gain at pv backing surface (W/m2)
1396 11 : Real64 iam_pv_beam(1.0); // incident angle modifier pv cells
1397 11 : Real64 iam_back_beam(1.0); // incident angle modifier back
1398 11 : Real64 iam_pv_sky(1.0);
1399 11 : Real64 iam_back_sky(1.0);
1400 11 : Real64 iam_pv_ground(1.0);
1401 11 : Real64 iam_back_ground(1.0);
1402 11 : Real64 theta_sky(0.0 * pi / 180.0); // incident angle sky
1403 11 : Real64 theta_ground(0.0 * pi / 180.0); // incident angle ground
1404 11 : Real64 theta_beam = std::acos(state.dataHeatBal->SurfCosIncidenceAngle(this->SurfNum)); // incident angle beam in rad
1405 11 : Real64 wind_incidence(0); // wind incidence angle on surface
1406 :
1407 : // other parameters
1408 11 : constexpr Real64 small_num(1.0e-10); // small real number
1409 11 : Real64 a(0), b(0), c(0), d(0), e(0); // variables used for solving average fluid temperature
1410 11 : Real64 err_tpvg(1.0), err_tpv(1.0), err_t1(1.0), err_mdot_bipvt(1.0); // convergence errors for temperatures
1411 11 : constexpr Real64 tol(1.0e-3); // temperature convergence tolerance
1412 11 : constexpr Real64 rf(0.75); // relaxation factor
1413 11 : constexpr Real64 degc_to_kelvin(273.15); // conversion constant degC to Kelvin
1414 : Real64 ebal1, ebal2, ebal3; // energy balances on 3 surfaces
1415 11 : std::array<Real64, 9> jj = {0.0}; // 3x3 array for coefficient matrix
1416 11 : std::array<Real64, 3> f = {0.0}; // 3 element array for constant term
1417 11 : std::array<Real64, 3> y = {0.0}; // solution array for tpvg,tpv, and t1
1418 11 : int m(3); // parameter for number of unknwons
1419 : int i; // index
1420 11 : int iter(0); // iteration counter
1421 :
1422 11 : emiss_2 = state.dataConstruction->Construct(state.dataSurface->Surface(this->SurfNum).Construction)
1423 : .OutsideAbsorpThermal; // get emissivity of interior surface of channel from building properties
1424 11 : theta_ground = (pi / 180) * (90 - 0.5788 * (slope * 180 / pi) + 0.002693 * std::pow((slope * 180 / pi), 2)); // incidence angle ground rad
1425 11 : theta_sky = (pi / 180) * (59.7 - 0.1388 * (slope * 180 / pi) + 0.001497 * std::pow((slope * 180 / pi), 2)); // incidence angle sky rad
1426 11 : t1 = (tamb + t2) / 2.0;
1427 11 : tpv = (tamb + t2) / 2.0;
1428 11 : tpvg = (tamb + t2) / 2.0;
1429 11 : hpvg_pv = 1.0 / rpvg_pv;
1430 11 : hpv_1 = 1.0 / rpv_1;
1431 :
1432 11 : k_taoalpha_beam = calc_k_taoalpha(theta_beam, glass_thickness, refrac_index_glass, k_glass);
1433 11 : iam_back_beam = k_taoalpha_beam;
1434 11 : iam_pv_beam = k_taoalpha_beam;
1435 :
1436 11 : k_taoalpha_sky = calc_k_taoalpha(theta_sky, glass_thickness, refrac_index_glass, k_glass);
1437 11 : iam_back_sky = k_taoalpha_sky;
1438 11 : iam_pv_sky = k_taoalpha_sky;
1439 :
1440 11 : k_taoalpha_ground = calc_k_taoalpha(theta_ground, glass_thickness, refrac_index_glass, k_glass);
1441 11 : iam_back_ground = k_taoalpha_sky;
1442 11 : iam_pv_ground = k_taoalpha_sky;
1443 :
1444 : tsurrK =
1445 11 : std::pow((std::pow((tamb + 273.15), 4) * 0.5 * (1 - std::cos(slope)) + std::pow((tsky + 273.15), 4) * 0.5 * (1 + std::cos(slope))), 0.25);
1446 11 : tsurr = tsurrK - degc_to_kelvin;
1447 11 : tpvgK = tpvg + degc_to_kelvin;
1448 11 : hrad_surr = sigma * emiss_pvg * (pow(tsurrK, 2) + pow(tpvgK, 2)) * (tsurrK + tpvgK);
1449 :
1450 11 : dhyd = 4 * w * l / (2 * (w + l));
1451 :
1452 11 : tmixed = tfin;
1453 11 : bfr = 0.0;
1454 11 : q = 0.0;
1455 120 : while ((err_t1 > tol) || (err_tpv > tol) || (err_tpvg > tol) || (err_mdot_bipvt > tol)) {
1456 : // Properties of air required for external convective heat transfer coefficient calculations - function of exterior film temperature
1457 109 : t_film = (tamb + tpvg) * 0.5;
1458 109 : mu_air =
1459 109 : 0.0000171 * (std::pow(((t_film + 273.15) / 273.0), 1.5)) *
1460 109 : ((273.0 + 110.4) /
1461 109 : ((t_film + 273.15) +
1462 : 110.4)); // Sutherland's formula https://www.grc.nasa.gov/www/k-12/airplane/viscosity.html Sutherland's constant = 198.72 R
1463 : // converted to K =>110.4. At 273.15, Viscosity is 1.71E-5 as per Incropera, et al 6th ed. Temp range approx 273K - 373K
1464 109 : k_air = 0.000000000015207 * std::pow(t_film + 273.15, 3.0) - 0.000000048574 * std::pow(t_film + 273.15, 2.0) +
1465 109 : 0.00010184 * (t_film + 273.15) - 0.00039333; // Dumas, A., and Trancossi, M., SAE Technical Papers, 2009
1466 109 : density_air = 101.3 / (0.287 * (t_film + 273.15)); // Ideal gas law
1467 109 : diffusivity_air = k_air / (cp_amb * density_air); // definition
1468 109 : kin_viscosity_air = mu_air / density_air; // definition
1469 :
1470 : // duffie and beckman correlation for nat convection - This is for exterior
1471 109 : raleigh = (gravity * (1.0 / (0.5 * (tamb + tpvg) + 273.15)) * (std::max((Real64)(0.000001), std::abs(tpvg - tamb))) * std::pow(dhyd, 3)) /
1472 109 : (diffusivity_air * kin_viscosity_air); // definition
1473 109 : hconvt_nat = 0.15 * std::pow(raleigh, 0.333) * k_air / dhyd; // Incropera et al. 6th ed.
1474 :
1475 109 : wind_incidence = std::abs(wind_dir - surf_azimuth);
1476 109 : if ((wind_incidence - 180.0) > 0.001) {
1477 0 : wind_incidence -= 360.0;
1478 : }
1479 :
1480 109 : if (slope > 75.0 * pi / 180.0) { // If slope of surface if greater than 75deg, assume it's a wall and use wall external htc
1481 0 : if (wind_incidence <= 45) {
1482 0 : extHTCcoeff = 10.9247; // Windward Vert
1483 0 : extHTCexp = 0.6434; // Windward Vert
1484 0 : length_conv = dhyd;
1485 0 : } else if (wind_incidence > 45.0 && wind_incidence <= 135.0) {
1486 0 : extHTCcoeff = 8.8505; // Sides
1487 0 : extHTCexp = 0.6765; // Sides
1488 0 : length_conv = w;
1489 : } else {
1490 0 : extHTCcoeff = 7.5141; // Leeward Vertical
1491 0 : extHTCexp = 0.6235; // Leeward Vertical
1492 0 : length_conv = dhyd;
1493 : }
1494 :
1495 : } else { // if not, it's a roof
1496 109 : if (wind_incidence <= 90.0) {
1497 :
1498 109 : extHTCcoeff = 7.7283; // Windward Roof
1499 109 : extHTCexp = 0.7586; // Windward Roof
1500 109 : length_conv = l;
1501 : } else {
1502 :
1503 0 : extHTCcoeff = 5.6217; // Leeward Roof
1504 0 : extHTCexp = 0.6569; // Leeward Roof;
1505 0 : length_conv = l;
1506 : }
1507 : }
1508 :
1509 : // forced conv htc derived from results from Gorman et al 2019 - Charact. lenght is: Roof - length along flow direction, windward and
1510 : // leeawrd vert - hydraulic perimeter of surface, vert sides - length of surface along flow direction
1511 109 : hconvt_forced = extHTCcoeff * std::pow((v_wind), extHTCexp) / (std::pow(l, 1.0 - extHTCexp)); // derived correlation for forced convection
1512 :
1513 : //"total" exterior htc is a combination of natural and forced htc - As described in Churchill and Usagi 1972, n=3 is a good value in most
1514 : // cases.
1515 109 : hconvt = std::pow((std::pow(hconvt_forced, 3.0) + std::pow(hconvt_nat, 3.0)), 1.0 / 3.0);
1516 :
1517 109 : if (state.dataPhotovoltaic->PVarray(this->PVnum).PVModelType == DataPhotovoltaics::PVModel::Simple) {
1518 0 : eff_pv = state.dataPhotovoltaic->PVarray(this->PVnum).SimplePVModule.PVEfficiency;
1519 109 : } else if (state.dataPhotovoltaic->PVarray(this->PVnum).PVModelType == DataPhotovoltaics::PVModel::Sandia) {
1520 0 : eff_pv = state.dataPhotovoltaic->PVarray(this->PVnum).SNLPVCalc.EffMax;
1521 109 : } else if (state.dataPhotovoltaic->PVarray(this->PVnum).PVModelType == DataPhotovoltaics::PVModel::TRNSYS) {
1522 109 : eff_pv = state.dataPhotovoltaic->PVarray(this->PVnum).TRNSYSPVcalc.ArrayEfficiency;
1523 : }
1524 :
1525 109 : g = state.dataHeatBal->SurfQRadSWOutIncidentBeam(SurfNum) * iam_pv_beam +
1526 109 : state.dataHeatBal->SurfQRadSWOutIncidentSkyDiffuse(SurfNum) * iam_pv_sky +
1527 109 : state.dataHeatBal->SurfQRadSWOutIncidentGndDiffuse(SurfNum) * iam_pv_ground;
1528 : // s1 = DataHeatBalance::QRadSWOutIncident(this->SurfNum) * (1.0 - this->BIPVT.PVAreaFract) * IAM_bs;
1529 109 : s = g * taoalpha_pv * fcell * area_pv / area_wall_total - g * eff_pv * area_pv / area_wall_total;
1530 109 : s1 = taoalpha_back * g * (1.0 - fcell) * (area_pv / area_wall_total) + taoaplha_cladding * g * (1 - area_pv / area_wall_total);
1531 :
1532 : // Below are properties of air required for convective heat transfer coefficient calculations inside channel - function of avg channel
1533 : // temperature
1534 :
1535 109 : mu_air =
1536 109 : 0.0000171 * (std::pow(((tfavg + 273.15) / 273.0), 1.5)) *
1537 109 : ((273.0 + 110.4) /
1538 109 : ((tfavg + 273.15) +
1539 : 110.4)); // Sutherland's formula https://www.grc.nasa.gov/www/k-12/airplane/viscosity.html Sutherland's constant = 198.72 R
1540 : // converted to K =>110.4. At 273.15, Viscosity is 1.71E-5 as per Incropera, et al 6th ed. Temp range approx 273K - 373K
1541 109 : k_air = 0.000000000015207 * std::pow(tfavg + 273.15, 3.0) - 0.000000048574 * std::pow(tfavg + 273.15, 2.0) +
1542 109 : 0.00010184 * (tfavg + 273.15) - 0.00039333; // Dumas, A., and Trancossi, M., SAE Technical Papers, 2009
1543 109 : prandtl_air = 0.680 + 0.000000469 * std::pow(tfavg + 273.15 - 540.0, 2.0); // The Schock Absorber Handbook, 2nd Ed. John C. Dixon 2007
1544 109 : density_air = 101.3 / (0.287 * (tfavg + 273.15)); // Ideal gas law
1545 109 : diffusivity_air = k_air / (cp_in * density_air); // definition
1546 109 : kin_viscosity_air = mu_air / density_air; // definition
1547 109 : t1K = t1 + degc_to_kelvin;
1548 109 : t2K = t2 + degc_to_kelvin;
1549 109 : tpvgK = tpvg + degc_to_kelvin;
1550 109 : hrad12 = sigma * (pow(t1K, 2) + pow(t2K, 2)) * (t1K + t2K) / (1 / emiss_b + 1 / emiss_2 - 1);
1551 109 : hrad_surr = sigma * emiss_pvg * (pow(tsurrK, 2) + pow(tpvgK, 2)) * (tsurrK + tpvgK);
1552 109 : if (mdot_bipvt > 0.0) // If there is a positive flow rate
1553 : {
1554 109 : vel = mdot_bipvt / (density_air * w * depth_channel);
1555 109 : reynolds = density_air * (vel) * (4 * w * depth_channel / (2 * (w + depth_channel))) / mu_air;
1556 109 : nusselt = 0.052 * (std::pow(reynolds, 0.78)) * (std::pow(prandtl_air, 0.4)); // Candanedo et al. 2011
1557 109 : hconvf1 = k_air * nusselt / (4 * w * depth_channel / (2 * (w + depth_channel)));
1558 109 : nusselt = 1.017 * (std::pow(reynolds, 0.471)) * (std::pow(prandtl_air, 0.4));
1559 109 : hconvf2 = k_air * nusselt / (4 * w * depth_channel / (2 * (w + depth_channel))); // Candanedo et al. 2011
1560 109 : a = -(w / (mdot_bipvt * cp_in)) * (hconvf1 + hconvf2);
1561 109 : b = (w / (mdot_bipvt * cp_in)) * (hconvf1 * t1 + hconvf2 * t2);
1562 109 : tfavg = (1.0 / (a * l)) * (tfin + b / a) * (std::exp(a * l) - 1.0) - b / a;
1563 : } else // if there is no flow rate (stagnation)
1564 : {
1565 0 : raleigh = (gravity * (1.0 / (tfavg + 273.15)) * (std::max((Real64)(0.000001), std::abs(t1 - t2))) * std::pow(depth_channel, 3)) /
1566 0 : (diffusivity_air * kin_viscosity_air);
1567 0 : if (slope > 75.0 * pi / 180.0) {
1568 0 : beta = 75.0 * pi / 180.0;
1569 : } else {
1570 0 : beta = slope;
1571 : }
1572 0 : nusselt =
1573 0 : 1.0 +
1574 0 : 1.44 * (1.0 - 1708.0 * (std::pow((std::sin(1.8 * beta)), 1.6)) / raleigh / std::cos(beta)) *
1575 0 : std::max(0.0, (1.0 - 1708.0 / raleigh / std::cos(beta))) +
1576 0 : std::max(0.0, ((std::pow((raleigh * std::cos(beta) / 5830.0), (1.0 / 3.0))) - 1.0)); // Hollands et al J. Heat transfer v.98, 1976
1577 0 : hconvf1 = 2.0 * k_air * nusselt / depth_channel; // cavity is split in two; hconvf1 and hconvf2. hconvf1 is assumed equal to hconvf2.
1578 : // Therefore, hconvf1 = 2*hcavity = hconvf2
1579 0 : hconvf2 = hconvf1;
1580 0 : c = s + s1 + hconvt * (tamb - tpvg) + hrad_surr * (tsurr - tpvg) + hrad12 * (t2 - t1);
1581 0 : d = c + hconvf2 * t2;
1582 0 : e = -hconvf2;
1583 0 : tfavg = -d / e;
1584 : }
1585 109 : tfavg = std::max(tfavg, -50.0);
1586 :
1587 436 : for (i = 0; i <= (m - 1); i++) {
1588 327 : f[i] = 0.0;
1589 327 : y[i] = 0.0;
1590 : }
1591 218 : for (i = 0; i <= ((m ^ 2) - 1); i++) {
1592 109 : jj[i] = 0.0;
1593 : }
1594 109 : jj[0] = hconvt + hrad_surr + hpvg_pv;
1595 109 : jj[1] = -hpvg_pv;
1596 109 : jj[2] = 0.0;
1597 109 : jj[3] = hpvg_pv;
1598 109 : jj[4] = -hpv_1 - hpvg_pv;
1599 109 : jj[5] = hpv_1;
1600 109 : jj[6] = 0.0;
1601 109 : jj[7] = hpv_1;
1602 109 : jj[8] = -hpv_1 - hconvf1 - hrad12;
1603 109 : f[0] = hconvt * tamb + hrad_surr * tsurr;
1604 109 : f[1] = -s;
1605 109 : f[2] = -s1 - hconvf1 * tfavg - hrad12 * t2;
1606 109 : solveLinSysBackSub(jj, f, y);
1607 109 : tpvg_new = y[0];
1608 109 : tpv_new = y[1];
1609 109 : t1_new = y[2];
1610 109 : if (mdot > 0.0) {
1611 109 : tfout = (tfin + b / a) * std::exp(a * l) - b / a; // air outlet temperature (DegC)
1612 109 : if (((this->OperatingMode == PVTMode::Heating) && (q > 0.0) && (tmixed > tsp) && (tfin < tsp)) ||
1613 108 : ((this->OperatingMode == PVTMode::Cooling) && (q < 0.0) && (tmixed < tsp) && (tfin > tsp))) {
1614 10 : bfr = (tsp - tfout) / (tfin - tfout); // bypass fraction
1615 10 : bfr = std::max(0.0, bfr);
1616 10 : bfr = std::min(1.0, bfr);
1617 99 : } else if (((this->OperatingMode == PVTMode::Heating) && (q > 0.0) && (tmixed > tsp) && (tfin >= tsp)) ||
1618 97 : ((this->OperatingMode == PVTMode::Cooling) && (q < 0.0) && (tmixed < tsp) && (tfin <= tsp))) {
1619 12 : bfr = 1.0;
1620 12 : tfout = tfin;
1621 : }
1622 : } else {
1623 0 : tfout = tfin;
1624 : }
1625 109 : tmixed = bfr * tfin + (1.0 - bfr) * tfout;
1626 109 : mdot_bipvt_new = (1.0 - bfr) * mdot;
1627 109 : err_tpvg = std::abs((tpvg_new - tpvg) / (tpvg + small_num));
1628 109 : err_tpv = std::abs((tpv_new - tpv) / (tpv + small_num));
1629 109 : err_t1 = std::abs((t1_new - t1) / (t1 + small_num));
1630 109 : err_mdot_bipvt = std::abs((mdot_bipvt_new - mdot_bipvt) / (mdot_bipvt + small_num));
1631 109 : tpvg = tpvg + rf * (tpvg_new - tpvg);
1632 109 : tpv = tpv + rf * (tpv_new - tpv);
1633 109 : t1 = t1 + rf * (t1_new - t1);
1634 109 : mdot_bipvt = mdot_bipvt + rf * (mdot_bipvt_new - mdot_bipvt);
1635 109 : q = mdot_bipvt * cp_in * (tfout - tfin); // heat transfer to the air
1636 109 : ebal1 = s1 + hpv_1 * (tpv - t1) + hconvf1 * (tfavg - t1) + hrad12 * (t2 - t1);
1637 109 : ebal2 = s + hpvg_pv * (tpvg - tpv) + hpv_1 * (t1 - tpv);
1638 109 : ebal3 = hconvt * (tpvg - tamb) + hrad_surr * (tpvg - tsurr) + hpvg_pv * (tpvg - tpv);
1639 109 : iter += 1;
1640 109 : if (iter == 50) {
1641 0 : ShowSevereError(state, "Function PVTCollectorStruct::calculateBIPVTMaxHeatGain: Maximum number of iterations 50 reached");
1642 0 : break;
1643 : }
1644 : }
1645 11 : ThEff = 0.0;
1646 11 : if ((q > small_num) && (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) > small_num)) {
1647 6 : ThEff = q / (area_wall_total * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) + small_num); // Thermal efficiency of BIPVT
1648 : }
1649 11 : this->BIPVT.Tcoll = t1;
1650 11 : this->BIPVT.HrPlen = hrad12;
1651 11 : this->BIPVT.Tplen = tfavg;
1652 11 : this->BIPVT.HcPlen = hconvf2;
1653 11 : }
1654 :
1655 109 : void PVTCollectorStruct::solveLinSysBackSub(std::array<Real64, 9> &jj, std::array<Real64, 3> &f, std::array<Real64, 3> &y)
1656 : {
1657 : // PURPOSE OF THIS SUBROUTINE:
1658 : // Solve a system of linear equations using Gaussian elimination and back substitution method.
1659 :
1660 : int p;
1661 109 : int constexpr m = 3;
1662 109 : Real64 constexpr small = 1.0e-10;
1663 :
1664 436 : for (int i = 0; i < m; i++) {
1665 327 : y[i] = 0.0;
1666 : }
1667 :
1668 327 : for (int i = 0; i <= (m - 2); i++) {
1669 218 : bool coeff_not_zero = false;
1670 218 : for (int j = i; j <= (m - 1); j++) {
1671 218 : if (std::abs(jj[j * m + i]) > small) {
1672 218 : coeff_not_zero = true;
1673 218 : p = j;
1674 218 : break;
1675 : }
1676 : }
1677 :
1678 218 : if (coeff_not_zero) {
1679 218 : if (p != i) {
1680 0 : Real64 dummy2 = f[i];
1681 0 : f[i] = f[p];
1682 0 : f[p] = dummy2;
1683 0 : for (int j = 0; j <= (m - 1); j++) {
1684 0 : Real64 dummy1 = jj[i * m + j];
1685 0 : jj[i * m + j] = jj[p * m + j];
1686 0 : jj[p * m + j] = dummy1;
1687 : }
1688 : }
1689 545 : for (int j = (i + 1); j <= (m - 1); j++) {
1690 327 : if (std::abs(jj[i * m + i]) < small) {
1691 0 : jj[i * m + i] = small;
1692 : }
1693 327 : Real64 mm = jj[j * m + i] / jj[i * m + i];
1694 327 : f[j] = f[j] - mm * f[i];
1695 1308 : for (int k = 0; k <= (m - 1); k++) {
1696 981 : jj[j * m + k] = jj[j * m + k] - mm * jj[i * m + k];
1697 : }
1698 : }
1699 : }
1700 : }
1701 109 : if (std::abs(jj[(m - 1) * m + m - 1]) < small) {
1702 0 : jj[(m - 1) * m + m - 1] = small;
1703 : }
1704 109 : y[m - 1] = f[m - 1] / jj[(m - 1) * m + m - 1];
1705 109 : Real64 sum = 0.0;
1706 327 : for (int i = 0; i <= (m - 2); i++) {
1707 218 : int ii = m - 2 - i;
1708 763 : for (int j = ii; j <= (m - 1); j++) {
1709 545 : sum = sum + jj[ii * m + j] * y[j];
1710 : }
1711 218 : if (std::abs(jj[ii * m + ii]) < small) {
1712 0 : jj[ii * m + ii] = small;
1713 : }
1714 218 : y[ii] = (f[ii] - sum) / jj[ii * m + ii];
1715 218 : sum = 0.0;
1716 : }
1717 109 : }
1718 :
1719 80 : Real64 PVTCollectorStruct::calc_taoalpha(Real64 theta,
1720 : Real64 glass_thickness,
1721 : Real64 refrac_index_glass,
1722 : Real64 k_glass) // typ refrac_index_glass is 1.526, k_glass typ 4 m^-1, glass_thickness typ 0.002 m
1723 : {
1724 : // PURPOSE OF THIS SUBROUTINE:
1725 : // calculates the transmissivity absorptance of a glass/air interface, assuming all transmitted is absorbed
1726 :
1727 80 : Real64 theta_r(0.0);
1728 80 : Real64 taoalpha(0.0);
1729 :
1730 80 : if (theta == 0.0) // if theta is zero, set to very small positive, otehrwise, taoalpha calculation causes division by zero
1731 : {
1732 42 : theta = 0.000000001;
1733 : }
1734 :
1735 80 : theta_r = std::asin(std::sin(theta) / refrac_index_glass);
1736 :
1737 160 : taoalpha = std::exp(-k_glass * glass_thickness / (std::cos(theta_r))) *
1738 80 : (1 - 0.5 * ((std::pow(std::sin(theta_r - theta), 2) / std::pow(std::sin(theta_r + theta), 2)) +
1739 80 : (std::pow(std::tan(theta_r - theta), 2) / std::pow(std::tan(theta_r + theta), 2))));
1740 :
1741 80 : return taoalpha;
1742 : }
1743 :
1744 40 : Real64 PVTCollectorStruct::calc_k_taoalpha(Real64 theta,
1745 : Real64 glass_thickness,
1746 : Real64 refrac_index_glass,
1747 : Real64 k_glass) // typ refrac_index_glass is 1.526, k_glass typ 4 m^-1, glass_thickness typ 0.002 m
1748 : {
1749 : // PURPOSE OF THIS SUBROUTINE:
1750 : // calculates the off-normal angle factor K for the tao-alpha product
1751 40 : Real64 taoalpha(0.0);
1752 40 : Real64 taoalpha_zero(0.0);
1753 40 : Real64 k_taoalpha(0.0);
1754 :
1755 40 : taoalpha = calc_taoalpha(theta, glass_thickness, refrac_index_glass, k_glass);
1756 40 : taoalpha_zero = calc_taoalpha(0.0, glass_thickness, refrac_index_glass, k_glass);
1757 40 : k_taoalpha = taoalpha / taoalpha_zero;
1758 :
1759 40 : return k_taoalpha;
1760 : }
1761 :
1762 0 : void PVTCollectorStruct::update(EnergyPlusData &state)
1763 : {
1764 :
1765 : // SUBROUTINE INFORMATION:
1766 : // AUTHOR Brent Griffith
1767 : // DATE WRITTEN August 2008
1768 :
1769 : {
1770 0 : switch (this->WorkingFluidType) {
1771 0 : case WorkingFluidEnum::LIQUID: {
1772 0 : int InletNode = this->PlantInletNodeNum;
1773 0 : int OutletNode = this->PlantOutletNodeNum;
1774 :
1775 0 : PlantUtilities::SafeCopyPlantNode(state, InletNode, OutletNode);
1776 0 : state.dataLoopNodes->Node(OutletNode).Temp = this->Report.ToutletWorkFluid;
1777 0 : } break;
1778 0 : case WorkingFluidEnum::AIR: {
1779 0 : int InletNode = this->HVACInletNodeNum;
1780 0 : int OutletNode = this->HVACOutletNodeNum;
1781 :
1782 : // Set the outlet nodes for properties that just pass through & not used
1783 0 : state.dataLoopNodes->Node(OutletNode).Quality = state.dataLoopNodes->Node(InletNode).Quality;
1784 0 : state.dataLoopNodes->Node(OutletNode).Press = state.dataLoopNodes->Node(InletNode).Press;
1785 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRate;
1786 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMin = state.dataLoopNodes->Node(InletNode).MassFlowRateMin;
1787 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMax = state.dataLoopNodes->Node(InletNode).MassFlowRateMax;
1788 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMinAvail = state.dataLoopNodes->Node(InletNode).MassFlowRateMinAvail;
1789 0 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMaxAvail = state.dataLoopNodes->Node(InletNode).MassFlowRateMaxAvail;
1790 :
1791 : // Set outlet node variables that are possibly changed
1792 0 : state.dataLoopNodes->Node(OutletNode).Temp = this->Report.ToutletWorkFluid;
1793 0 : state.dataLoopNodes->Node(OutletNode).HumRat = state.dataLoopNodes->Node(InletNode).HumRat; // assumes dewpoint bound on cooling ....
1794 0 : state.dataLoopNodes->Node(OutletNode).Enthalpy =
1795 0 : Psychrometrics::PsyHFnTdbW(this->Report.ToutletWorkFluid, state.dataLoopNodes->Node(OutletNode).HumRat);
1796 :
1797 : // update the OtherSideConditionsModel coefficients for BIPVT
1798 0 : if (this->ModelType == PVTModelType::BIPVT) {
1799 0 : int thisOSCM = this->BIPVT.OSCMPtr;
1800 0 : state.dataSurface->OSCM(thisOSCM).TConv = this->BIPVT.Tplen;
1801 0 : state.dataSurface->OSCM(thisOSCM).HConv = this->BIPVT.HcPlen;
1802 0 : state.dataSurface->OSCM(thisOSCM).TRad = this->BIPVT.Tcoll;
1803 0 : state.dataSurface->OSCM(thisOSCM).HRad = this->BIPVT.HrPlen;
1804 : }
1805 0 : } break;
1806 0 : default:
1807 0 : break;
1808 : }
1809 : }
1810 0 : }
1811 :
1812 1 : void PVTCollectorStruct::oneTimeInit(EnergyPlusData &state)
1813 : {
1814 :
1815 1 : if (this->MyOneTimeFlag) {
1816 1 : this->setupReportVars(state);
1817 1 : this->MyOneTimeFlag = false;
1818 : }
1819 :
1820 1 : if (this->SetLoopIndexFlag) {
1821 1 : if (allocated(state.dataPlnt->PlantLoop) && (this->PlantInletNodeNum > 0)) {
1822 1 : bool errFlag = false;
1823 1 : PlantUtilities::ScanPlantLoopsForObject(state, this->Name, this->Type, this->WPlantLoc, errFlag, _, _, _, _, _);
1824 1 : if (errFlag) {
1825 0 : ShowFatalError(state, "InitPVTcollectors: Program terminated for previous conditions.");
1826 : }
1827 1 : this->SetLoopIndexFlag = false;
1828 : }
1829 : }
1830 1 : }
1831 :
1832 0 : void GetPVTThermalPowerProduction(EnergyPlusData &state, int const PVindex, Real64 &ThermalPower, Real64 &ThermalEnergy)
1833 : {
1834 0 : int PVTnum(0);
1835 :
1836 : // first find PVT index that is associated with this PV generator
1837 0 : for (int loop = 1; loop <= state.dataPhotovoltaicThermalCollector->NumPVT; ++loop) {
1838 0 : if (!state.dataPhotovoltaicThermalCollector->PVT(loop).PVfound) {
1839 0 : continue;
1840 : }
1841 0 : if (state.dataPhotovoltaicThermalCollector->PVT(loop).PVnum == PVindex) { // we found it
1842 0 : PVTnum = loop;
1843 : }
1844 : }
1845 :
1846 0 : if (PVTnum > 0) {
1847 0 : ThermalPower = state.dataPhotovoltaicThermalCollector->PVT(PVTnum).Report.ThermPower;
1848 0 : ThermalEnergy = state.dataPhotovoltaicThermalCollector->PVT(PVTnum).Report.ThermEnergy;
1849 : } else {
1850 0 : ThermalPower = 0.0;
1851 0 : ThermalEnergy = 0.0;
1852 : }
1853 0 : }
1854 :
1855 0 : int GetAirInletNodeNum(EnergyPlusData &state, std::string_view PVTName, bool &ErrorsFound)
1856 : {
1857 : // FUNCTION INFORMATION:
1858 : // AUTHOR Lixing Gu
1859 : // DATE WRITTEN May 2019
1860 : // MODIFIED na
1861 : // RE-ENGINEERED na
1862 :
1863 : // PURPOSE OF THIS FUNCTION:
1864 : // This function looks up the given PVT and returns the air inlet node number.
1865 : // If incorrect PVT name is given, ErrorsFound is returned as true and node number as zero.
1866 :
1867 : int NodeNum; // node number returned
1868 : int WhichPVT;
1869 :
1870 0 : if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
1871 0 : GetPVTcollectorsInput(state);
1872 0 : state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
1873 : }
1874 :
1875 0 : WhichPVT = Util::FindItemInList(PVTName, state.dataPhotovoltaicThermalCollector->PVT);
1876 0 : if (WhichPVT != 0) {
1877 0 : NodeNum = state.dataPhotovoltaicThermalCollector->PVT(WhichPVT).HVACInletNodeNum;
1878 : } else {
1879 0 : ShowSevereError(state, format("GetAirInletNodeNum: Could not find SolarCollector FlatPlate PhotovoltaicThermal = \"{}\"", PVTName));
1880 0 : ErrorsFound = true;
1881 0 : NodeNum = 0;
1882 : }
1883 :
1884 0 : return NodeNum;
1885 : }
1886 0 : int GetAirOutletNodeNum(EnergyPlusData &state, std::string_view PVTName, bool &ErrorsFound)
1887 : {
1888 : // FUNCTION INFORMATION:
1889 : // AUTHOR Lixing Gu
1890 : // DATE WRITTEN May 2019
1891 : // MODIFIED na
1892 : // RE-ENGINEERED na
1893 :
1894 : // PURPOSE OF THIS FUNCTION:
1895 : // This function looks up the given PVT and returns the air outlet node number.
1896 : // If incorrect PVT name is given, ErrorsFound is returned as true and node number as zero.
1897 :
1898 : int NodeNum; // node number returned
1899 : int WhichPVT;
1900 :
1901 0 : if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
1902 0 : GetPVTcollectorsInput(state);
1903 0 : state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
1904 : }
1905 :
1906 0 : WhichPVT = Util::FindItemInList(PVTName, state.dataPhotovoltaicThermalCollector->PVT);
1907 0 : if (WhichPVT != 0) {
1908 0 : NodeNum = state.dataPhotovoltaicThermalCollector->PVT(WhichPVT).HVACOutletNodeNum;
1909 : } else {
1910 0 : ShowSevereError(state, format("GetAirInletNodeNum: Could not find SolarCollector FlatPlate PhotovoltaicThermal = \"{}\"", PVTName));
1911 0 : ErrorsFound = true;
1912 0 : NodeNum = 0;
1913 : }
1914 :
1915 0 : return NodeNum;
1916 : }
1917 :
1918 0 : int getPVTindexFromName(EnergyPlusData &state, std::string_view objectName)
1919 : {
1920 0 : if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
1921 0 : GetPVTcollectorsInput(state);
1922 0 : state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
1923 : }
1924 :
1925 0 : for (auto it = state.dataPhotovoltaicThermalCollector->PVT.begin(); it != state.dataPhotovoltaicThermalCollector->PVT.end(); ++it) {
1926 0 : if (it->Name == objectName) {
1927 0 : return static_cast<int>(std::distance(state.dataPhotovoltaicThermalCollector->PVT.begin(), it) + 1);
1928 : }
1929 : }
1930 :
1931 : // If we didn't find it, fatal
1932 0 : ShowFatalError(state, format("Solar Thermal Collector GetIndexFromName: Error getting inputs for object named: {}", objectName));
1933 0 : assert(false);
1934 : return 0; // Shutup compiler
1935 : }
1936 :
1937 0 : void simPVTfromOASys(EnergyPlusData &state, int const index, bool const FirstHVACIteration)
1938 : {
1939 0 : PlantLocation dummyLoc(0, DataPlant::LoopSideLocation::Invalid, 0, 0);
1940 0 : Real64 dummyCurLoad(0.0);
1941 0 : bool dummyRunFlag(true);
1942 :
1943 0 : state.dataPhotovoltaicThermalCollector->PVT(index).simulate(state, dummyLoc, FirstHVACIteration, dummyCurLoad, dummyRunFlag);
1944 0 : }
1945 :
1946 2 : int GetPVTmodelIndex(EnergyPlusData &state, int const SurfacePtr)
1947 : {
1948 : // PURPOSE OF THIS SUBROUTINE:
1949 : // object oriented "Get" routine for establishing correct integer index from outside this module
1950 :
1951 : // METHODOLOGY EMPLOYED:
1952 : // mine Surface derived type for correct index/number of surface
1953 : // mine PVT derived type that has the surface.
1954 :
1955 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1956 : int PVTNum; // temporary
1957 : int thisPVT;
1958 : bool Found;
1959 2 : int PVTIndex(0);
1960 :
1961 2 : if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
1962 2 : GetPVTcollectorsInput(state);
1963 2 : state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
1964 : }
1965 :
1966 2 : if (SurfacePtr == 0) {
1967 0 : ShowFatalError(state, "Invalid surface passed to GetPVTmodelIndex");
1968 : }
1969 :
1970 2 : PVTNum = 0;
1971 2 : Found = false;
1972 4 : for (thisPVT = 1; thisPVT <= state.dataPhotovoltaicThermalCollector->NumPVT; ++thisPVT) {
1973 2 : if (SurfacePtr == state.dataPhotovoltaicThermalCollector->PVT(thisPVT).SurfNum) {
1974 2 : Found = true;
1975 2 : PVTNum = thisPVT;
1976 : }
1977 : }
1978 :
1979 2 : if (!Found) {
1980 0 : ShowFatalError(
1981 0 : state, "Did not find surface in PVT description in GetPVTmodelIndex, Surface name = " + state.dataSurface->Surface(SurfacePtr).Name);
1982 : } else {
1983 :
1984 2 : PVTIndex = PVTNum;
1985 : }
1986 :
1987 2 : return PVTIndex;
1988 : }
1989 :
1990 0 : void SetPVTQdotSource(EnergyPlusData &state,
1991 : int const PVTNum,
1992 : Real64 const QSource // source term in Watts
1993 : )
1994 : {
1995 : // PURPOSE OF THIS SUBROUTINE:
1996 : // object oriented "Set" routine for updating sink term without exposing variables
1997 :
1998 : // METHODOLOGY EMPLOYED:
1999 : // update derived type with new data , turn power into W/m2
2000 :
2001 0 : state.dataPhotovoltaicThermalCollector->PVT(PVTNum).QdotSource = QSource / state.dataPhotovoltaicThermalCollector->PVT(PVTNum).AreaCol;
2002 0 : }
2003 :
2004 0 : void GetPVTTsColl(EnergyPlusData &state, int const PVTNum, Real64 &TsColl)
2005 : {
2006 : // PURPOSE OF THIS SUBROUTINE:
2007 : // object oriented "Get" routine for collector surface temperature
2008 :
2009 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
2010 0 : if (state.dataPhotovoltaicThermalCollector->PVT(PVTNum).ModelType == PVTModelType::BIPVT) {
2011 0 : TsColl = state.dataPhotovoltaicThermalCollector->PVT(PVTNum).BIPVT.LastCollectorTemp;
2012 : }
2013 0 : }
2014 :
2015 : } // namespace PhotovoltaicThermalCollectors
2016 :
2017 : } // namespace EnergyPlus
|