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