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