Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2023, 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 :
51 : // ObjexxFCL Headers
52 : #include <ObjexxFCL/Array.functions.hh>
53 : #include <ObjexxFCL/Fmath.hh>
54 :
55 : // EnergyPlus Headers
56 : #include <EnergyPlus/Autosizing/Base.hh>
57 : #include <EnergyPlus/BranchNodeConnections.hh>
58 : #include <EnergyPlus/ConvectionCoefficients.hh>
59 : #include <EnergyPlus/Data/EnergyPlusData.hh>
60 : #include <EnergyPlus/DataEnvironment.hh>
61 : #include <EnergyPlus/DataHVACGlobals.hh>
62 : #include <EnergyPlus/DataHeatBalance.hh>
63 : #include <EnergyPlus/DataIPShortCuts.hh>
64 : #include <EnergyPlus/DataLoopNode.hh>
65 : #include <EnergyPlus/DataPhotovoltaics.hh>
66 : #include <EnergyPlus/DataSizing.hh>
67 : #include <EnergyPlus/DataSurfaces.hh>
68 : #include <EnergyPlus/EMSManager.hh>
69 : #include <EnergyPlus/FluidProperties.hh>
70 : #include <EnergyPlus/General.hh>
71 : #include <EnergyPlus/GeneralRoutines.hh>
72 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
73 : #include <EnergyPlus/NodeInputManager.hh>
74 : #include <EnergyPlus/OutputProcessor.hh>
75 : #include <EnergyPlus/PhotovoltaicThermalCollectors.hh>
76 : #include <EnergyPlus/Plant/DataPlant.hh>
77 : #include <EnergyPlus/Plant/PlantLocation.hh>
78 : #include <EnergyPlus/PlantUtilities.hh>
79 : #include <EnergyPlus/Psychrometrics.hh>
80 : #include <EnergyPlus/ScheduleManager.hh>
81 : #include <EnergyPlus/UtilityRoutines.hh>
82 :
83 : namespace EnergyPlus {
84 :
85 : namespace PhotovoltaicThermalCollectors {
86 :
87 : // Module containing the routines dealing with the photovoltaic thermal collectors
88 :
89 : // MODULE INFORMATION:
90 : // AUTHOR Brent. Griffith
91 : // DATE WRITTEN June-August 2008
92 : // MODIFIED na
93 : // RE-ENGINEERED na
94 :
95 : // PURPOSE OF THIS MODULE:
96 : // collect models related to PVT or hybrid, photovoltaic - thermal solar collectors
97 :
98 : // METHODOLOGY EMPLOYED:
99 : // The approach is to have one PVT structure that works with different models.
100 : // the PVT model reuses photovoltaic modeling in Photovoltaics.cc for electricity generation.
101 : // the electric load center and "generator" is all accessed thru PV objects and models.
102 : // this module is for the thermal portion of PVT.
103 : // the first model is a "simple" or "ideal" model useful for sizing, early design, or policy analyses
104 : // Simple PV/T model just converts incoming solar to electricity and temperature rise of a working fluid.
105 :
106 : int constexpr SimplePVTmodel(1001);
107 :
108 : Real64 constexpr SimplePVTWaterSizeFactor(1.905e-5); // [ m3/s/m2 ] average of collectors in SolarCollectors.idf
109 :
110 5 : PlantComponent *PVTCollectorStruct::factory(EnergyPlusData &state, std::string_view objectName)
111 : {
112 5 : if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
113 1 : GetPVTcollectorsInput(state);
114 1 : state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
115 : }
116 :
117 40 : for (auto &thisComp : state.dataPhotovoltaicThermalCollector->PVT) {
118 40 : if (thisComp.Name == objectName) {
119 5 : return &thisComp;
120 : }
121 : }
122 :
123 : // If we didn't find it, fatal
124 0 : ShowFatalError(state, "Solar Thermal Collector Factory: Error getting inputs for object named: " + std::string{objectName});
125 : // Shut up the compiler
126 0 : return nullptr;
127 : }
128 :
129 25 : void PVTCollectorStruct::onInitLoopEquip(EnergyPlusData &state, [[maybe_unused]] const PlantLocation &calledFromLocation)
130 : {
131 25 : this->initialize(state, true);
132 25 : this->size(state);
133 25 : }
134 :
135 197268 : void PVTCollectorStruct::simulate(EnergyPlusData &state,
136 : [[maybe_unused]] const PlantLocation &calledFromLocation,
137 : bool const FirstHVACIteration,
138 : [[maybe_unused]] Real64 &CurLoad,
139 : [[maybe_unused]] bool const RunFlag)
140 : {
141 :
142 197268 : this->initialize(state, FirstHVACIteration);
143 197268 : this->control(state);
144 197268 : this->calculate(state);
145 197268 : this->update(state);
146 197268 : }
147 :
148 1 : void GetPVTcollectorsInput(EnergyPlusData &state)
149 : {
150 :
151 : // SUBROUTINE INFORMATION:
152 : // AUTHOR B. Griffith
153 : // DATE WRITTEN June 2008
154 : // MODIFIED na
155 : // RE-ENGINEERED na
156 :
157 : // PURPOSE OF THIS SUBROUTINE:
158 : // Get input for PVT objects
159 :
160 : int Item; // Item to be "gotten"
161 : int NumAlphas; // Number of Alphas for each GetObjectItem call
162 : int NumNumbers; // Number of Numbers for each GetObjectItem call
163 : int IOStatus; // Used in GetObjectItem
164 1 : bool ErrorsFound(false); // Set to true if errors in input, fatal at end of routine
165 :
166 : // Object Data
167 2 : Array1D<SimplePVTModelStruct> tmpSimplePVTperf;
168 :
169 : // first load the performance object info into temporary structure
170 1 : state.dataIPShortCut->cCurrentModuleObject = "SolarCollectorPerformance:PhotovoltaicThermal:Simple";
171 1 : int NumSimplePVTPerform = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject);
172 1 : if (NumSimplePVTPerform > 0) {
173 1 : tmpSimplePVTperf.allocate(NumSimplePVTPerform);
174 3 : for (Item = 1; Item <= NumSimplePVTPerform; ++Item) {
175 14 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
176 2 : state.dataIPShortCut->cCurrentModuleObject,
177 : Item,
178 2 : state.dataIPShortCut->cAlphaArgs,
179 : NumAlphas,
180 2 : state.dataIPShortCut->rNumericArgs,
181 : NumNumbers,
182 : IOStatus,
183 : _,
184 2 : state.dataIPShortCut->lAlphaFieldBlanks,
185 2 : state.dataIPShortCut->cAlphaFieldNames,
186 2 : state.dataIPShortCut->cNumericFieldNames);
187 2 : if (UtilityRoutines::IsNameEmpty(state, state.dataIPShortCut->cAlphaArgs(1), state.dataIPShortCut->cCurrentModuleObject, ErrorsFound))
188 0 : continue;
189 :
190 2 : tmpSimplePVTperf(Item).Name = state.dataIPShortCut->cAlphaArgs(1);
191 2 : if (UtilityRoutines::SameString(state.dataIPShortCut->cAlphaArgs(2), "Fixed")) {
192 2 : tmpSimplePVTperf(Item).ThermEfficMode = ThermEfficEnum::FIXED;
193 0 : } else if (UtilityRoutines::SameString(state.dataIPShortCut->cAlphaArgs(2), "Scheduled")) {
194 0 : tmpSimplePVTperf(Item).ThermEfficMode = ThermEfficEnum::SCHEDULED;
195 : } else {
196 0 : ShowSevereError(state, "Invalid " + state.dataIPShortCut->cAlphaFieldNames(2) + " = " + state.dataIPShortCut->cAlphaArgs(2));
197 0 : ShowContinueError(state,
198 0 : "Entered in " + state.dataIPShortCut->cCurrentModuleObject + " = " + state.dataIPShortCut->cAlphaArgs(1));
199 0 : ErrorsFound = true;
200 : }
201 2 : tmpSimplePVTperf(Item).ThermalActiveFract = state.dataIPShortCut->rNumericArgs(1);
202 2 : tmpSimplePVTperf(Item).ThermEffic = state.dataIPShortCut->rNumericArgs(2);
203 :
204 2 : tmpSimplePVTperf(Item).ThermEffSchedNum = ScheduleManager::GetScheduleIndex(state, state.dataIPShortCut->cAlphaArgs(3));
205 2 : if ((tmpSimplePVTperf(Item).ThermEffSchedNum == 0) && (tmpSimplePVTperf(Item).ThermEfficMode == ThermEfficEnum::SCHEDULED)) {
206 0 : ShowSevereError(state, "Invalid " + state.dataIPShortCut->cAlphaFieldNames(3) + " = " + state.dataIPShortCut->cAlphaArgs(3));
207 0 : ShowContinueError(state,
208 0 : "Entered in " + state.dataIPShortCut->cCurrentModuleObject + " = " + state.dataIPShortCut->cAlphaArgs(1));
209 0 : ErrorsFound = true;
210 : }
211 2 : tmpSimplePVTperf(Item).SurfEmissivity = state.dataIPShortCut->rNumericArgs(3);
212 : }
213 : } // NumSimplePVTPerform > 0
214 :
215 : // now get main PVT objects
216 1 : state.dataIPShortCut->cCurrentModuleObject = "SolarCollector:FlatPlate:PhotovoltaicThermal";
217 1 : state.dataPhotovoltaicThermalCollector->NumPVT =
218 1 : state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, state.dataIPShortCut->cCurrentModuleObject);
219 1 : state.dataPhotovoltaicThermalCollector->PVT.allocate(state.dataPhotovoltaicThermalCollector->NumPVT);
220 :
221 11 : for (Item = 1; Item <= state.dataPhotovoltaicThermalCollector->NumPVT; ++Item) {
222 70 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
223 10 : state.dataIPShortCut->cCurrentModuleObject,
224 : Item,
225 10 : state.dataIPShortCut->cAlphaArgs,
226 : NumAlphas,
227 10 : state.dataIPShortCut->rNumericArgs,
228 : NumNumbers,
229 : IOStatus,
230 : _,
231 10 : state.dataIPShortCut->lAlphaFieldBlanks,
232 10 : state.dataIPShortCut->cAlphaFieldNames,
233 10 : state.dataIPShortCut->cNumericFieldNames);
234 10 : if (UtilityRoutines::IsNameEmpty(state, state.dataIPShortCut->cAlphaArgs(1), state.dataIPShortCut->cCurrentModuleObject, ErrorsFound))
235 0 : continue;
236 :
237 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).Name = state.dataIPShortCut->cAlphaArgs(1);
238 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).Type = DataPlant::PlantEquipmentType::PVTSolarCollectorFlatPlate;
239 :
240 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).SurfNum =
241 10 : UtilityRoutines::FindItemInList(state.dataIPShortCut->cAlphaArgs(2), state.dataSurface->Surface);
242 : // check surface
243 10 : if (state.dataPhotovoltaicThermalCollector->PVT(Item).SurfNum == 0) {
244 0 : if (state.dataIPShortCut->lAlphaFieldBlanks(2)) {
245 0 : ShowSevereError(state, "Invalid " + state.dataIPShortCut->cAlphaFieldNames(2) + " = " + state.dataIPShortCut->cAlphaArgs(2));
246 0 : ShowContinueError(state,
247 0 : "Entered in " + state.dataIPShortCut->cCurrentModuleObject + " = " + state.dataIPShortCut->cAlphaArgs(1));
248 :
249 0 : ShowContinueError(state, "Surface name cannot be blank.");
250 : } else {
251 0 : ShowSevereError(state, "Invalid " + state.dataIPShortCut->cAlphaFieldNames(2) + " = " + state.dataIPShortCut->cAlphaArgs(2));
252 0 : ShowContinueError(state,
253 0 : "Entered in " + state.dataIPShortCut->cCurrentModuleObject + " = " + state.dataIPShortCut->cAlphaArgs(1));
254 0 : ShowContinueError(state, "Surface was not found.");
255 : }
256 0 : ErrorsFound = true;
257 : } else {
258 :
259 10 : if (!state.dataSurface->Surface(state.dataPhotovoltaicThermalCollector->PVT(Item).SurfNum).ExtSolar) {
260 0 : ShowSevereError(state, "Invalid " + state.dataIPShortCut->cAlphaFieldNames(2) + " = " + state.dataIPShortCut->cAlphaArgs(2));
261 0 : ShowContinueError(state,
262 0 : "Entered in " + state.dataIPShortCut->cCurrentModuleObject + " = " + state.dataIPShortCut->cAlphaArgs(1));
263 0 : ShowContinueError(state, "Surface must be exposed to solar.");
264 0 : ErrorsFound = true;
265 : }
266 : // check surface orientation, warn if upside down
267 20 : if ((state.dataSurface->Surface(state.dataPhotovoltaicThermalCollector->PVT(Item).SurfNum).Tilt < -95.0) ||
268 10 : (state.dataSurface->Surface(state.dataPhotovoltaicThermalCollector->PVT(Item).SurfNum).Tilt > 95.0)) {
269 0 : ShowWarningError(state,
270 0 : "Suspected input problem with " + state.dataIPShortCut->cAlphaFieldNames(2) + " = " +
271 0 : state.dataIPShortCut->cAlphaArgs(2));
272 0 : ShowContinueError(state,
273 0 : "Entered in " + state.dataIPShortCut->cCurrentModuleObject + " = " + state.dataIPShortCut->cAlphaArgs(1));
274 0 : ShowContinueError(state, "Surface used for solar collector faces down");
275 0 : ShowContinueError(state,
276 0 : format("Surface tilt angle (degrees from ground outward normal) = {:.2R}",
277 0 : state.dataSurface->Surface(state.dataPhotovoltaicThermalCollector->PVT(Item).SurfNum).Tilt));
278 : }
279 :
280 : } // check surface
281 :
282 10 : if (state.dataIPShortCut->lAlphaFieldBlanks(3)) {
283 0 : ShowSevereError(state, "Invalid " + state.dataIPShortCut->cAlphaFieldNames(3) + " = " + state.dataIPShortCut->cAlphaArgs(3));
284 0 : ShowContinueError(state, "Entered in " + state.dataIPShortCut->cCurrentModuleObject + " = " + state.dataIPShortCut->cAlphaArgs(1));
285 0 : ShowContinueError(state, state.dataIPShortCut->cAlphaFieldNames(3) + ", name cannot be blank.");
286 0 : ErrorsFound = true;
287 : } else {
288 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).PVTModelName = state.dataIPShortCut->cAlphaArgs(3);
289 10 : int ThisParamObj = UtilityRoutines::FindItemInList(state.dataPhotovoltaicThermalCollector->PVT(Item).PVTModelName, tmpSimplePVTperf);
290 10 : if (ThisParamObj > 0) {
291 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).Simple = tmpSimplePVTperf(ThisParamObj); // entire structure assigned
292 : // do one-time setups on input data
293 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).AreaCol =
294 20 : state.dataSurface->Surface(state.dataPhotovoltaicThermalCollector->PVT(Item).SurfNum).Area *
295 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).Simple.ThermalActiveFract;
296 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).PVTModelType = SimplePVTmodel;
297 : } else {
298 0 : ShowSevereError(state, "Invalid " + state.dataIPShortCut->cAlphaFieldNames(3) + " = " + state.dataIPShortCut->cAlphaArgs(3));
299 0 : ShowContinueError(state,
300 0 : "Entered in " + state.dataIPShortCut->cCurrentModuleObject + " = " + state.dataIPShortCut->cAlphaArgs(1));
301 0 : ShowContinueError(state, state.dataIPShortCut->cAlphaFieldNames(3) + ", was not found.");
302 0 : ErrorsFound = true;
303 : }
304 : }
305 10 : if (allocated(state.dataPhotovoltaic->PVarray)) { // then PV input gotten... but don't expect this to be true.
306 0 : state.dataPhotovoltaicThermalCollector->PVT(Item).PVnum =
307 0 : UtilityRoutines::FindItemInList(state.dataIPShortCut->cAlphaArgs(4), state.dataPhotovoltaic->PVarray);
308 : // check PV
309 0 : if (state.dataPhotovoltaicThermalCollector->PVT(Item).PVnum == 0) {
310 0 : ShowSevereError(state, "Invalid " + state.dataIPShortCut->cAlphaFieldNames(4) + " = " + state.dataIPShortCut->cAlphaArgs(4));
311 0 : ShowContinueError(state,
312 0 : "Entered in " + state.dataIPShortCut->cCurrentModuleObject + " = " + state.dataIPShortCut->cAlphaArgs(1));
313 0 : ErrorsFound = true;
314 : } else {
315 0 : state.dataPhotovoltaicThermalCollector->PVT(Item).PVname = state.dataIPShortCut->cAlphaArgs(4);
316 0 : state.dataPhotovoltaicThermalCollector->PVT(Item).PVfound = true;
317 : }
318 : } else { // no PV or not yet gotten.
319 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).PVname = state.dataIPShortCut->cAlphaArgs(4);
320 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).PVfound = false;
321 : }
322 :
323 10 : if (UtilityRoutines::SameString(state.dataIPShortCut->cAlphaArgs(5), "Water")) {
324 5 : state.dataPhotovoltaicThermalCollector->PVT(Item).WorkingFluidType = WorkingFluidEnum::LIQUID;
325 5 : } else if (UtilityRoutines::SameString(state.dataIPShortCut->cAlphaArgs(5), "Air")) {
326 5 : state.dataPhotovoltaicThermalCollector->PVT(Item).WorkingFluidType = WorkingFluidEnum::AIR;
327 : } else {
328 0 : if (state.dataIPShortCut->lAlphaFieldBlanks(5)) {
329 0 : ShowSevereError(state, "Invalid " + state.dataIPShortCut->cAlphaFieldNames(5) + " = " + state.dataIPShortCut->cAlphaArgs(5));
330 0 : ShowContinueError(state,
331 0 : "Entered in " + state.dataIPShortCut->cCurrentModuleObject + " = " + state.dataIPShortCut->cAlphaArgs(1));
332 0 : ShowContinueError(state, state.dataIPShortCut->cAlphaFieldNames(5) + " field cannot be blank.");
333 : } else {
334 0 : ShowSevereError(state, "Invalid " + state.dataIPShortCut->cAlphaFieldNames(5) + " = " + state.dataIPShortCut->cAlphaArgs(5));
335 0 : ShowContinueError(state,
336 0 : "Entered in " + state.dataIPShortCut->cCurrentModuleObject + " = " + state.dataIPShortCut->cAlphaArgs(1));
337 : }
338 0 : ErrorsFound = true;
339 : }
340 :
341 10 : if (state.dataPhotovoltaicThermalCollector->PVT(Item).WorkingFluidType == WorkingFluidEnum::LIQUID) {
342 5 : state.dataPhotovoltaicThermalCollector->PVT(Item).PlantInletNodeNum =
343 10 : NodeInputManager::GetOnlySingleNode(state,
344 5 : state.dataIPShortCut->cAlphaArgs(6),
345 : ErrorsFound,
346 : DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
347 5 : state.dataIPShortCut->cAlphaArgs(1),
348 : DataLoopNode::NodeFluidType::Water,
349 : DataLoopNode::ConnectionType::Inlet,
350 : NodeInputManager::CompFluidStream::Primary,
351 5 : DataLoopNode::ObjectIsNotParent);
352 5 : state.dataPhotovoltaicThermalCollector->PVT(Item).PlantOutletNodeNum =
353 10 : NodeInputManager::GetOnlySingleNode(state,
354 5 : state.dataIPShortCut->cAlphaArgs(7),
355 : ErrorsFound,
356 : DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
357 5 : state.dataIPShortCut->cAlphaArgs(1),
358 : DataLoopNode::NodeFluidType::Water,
359 : DataLoopNode::ConnectionType::Outlet,
360 : NodeInputManager::CompFluidStream::Primary,
361 5 : DataLoopNode::ObjectIsNotParent);
362 :
363 15 : BranchNodeConnections::TestCompSet(state,
364 5 : state.dataIPShortCut->cCurrentModuleObject,
365 5 : state.dataIPShortCut->cAlphaArgs(1),
366 5 : state.dataIPShortCut->cAlphaArgs(6),
367 5 : state.dataIPShortCut->cAlphaArgs(7),
368 : "Water Nodes");
369 :
370 5 : state.dataPhotovoltaicThermalCollector->PVT(Item).WPlantLoc.loopSideNum = DataPlant::LoopSideLocation::Invalid;
371 : }
372 :
373 10 : if (state.dataPhotovoltaicThermalCollector->PVT(Item).WorkingFluidType == WorkingFluidEnum::AIR) {
374 5 : state.dataPhotovoltaicThermalCollector->PVT(Item).HVACInletNodeNum =
375 10 : NodeInputManager::GetOnlySingleNode(state,
376 5 : state.dataIPShortCut->cAlphaArgs(8),
377 : ErrorsFound,
378 : DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
379 5 : state.dataIPShortCut->cAlphaArgs(1),
380 : DataLoopNode::NodeFluidType::Air,
381 : DataLoopNode::ConnectionType::Inlet,
382 : NodeInputManager::CompFluidStream::Primary,
383 5 : DataLoopNode::ObjectIsNotParent);
384 5 : state.dataPhotovoltaicThermalCollector->PVT(Item).HVACOutletNodeNum =
385 10 : NodeInputManager::GetOnlySingleNode(state,
386 5 : state.dataIPShortCut->cAlphaArgs(9),
387 : ErrorsFound,
388 : DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlatePhotovoltaicThermal,
389 5 : state.dataIPShortCut->cAlphaArgs(1),
390 : DataLoopNode::NodeFluidType::Air,
391 : DataLoopNode::ConnectionType::Outlet,
392 : NodeInputManager::CompFluidStream::Primary,
393 5 : DataLoopNode::ObjectIsNotParent);
394 :
395 15 : BranchNodeConnections::TestCompSet(state,
396 5 : state.dataIPShortCut->cCurrentModuleObject,
397 5 : state.dataIPShortCut->cAlphaArgs(1),
398 5 : state.dataIPShortCut->cAlphaArgs(8),
399 5 : state.dataIPShortCut->cAlphaArgs(9),
400 : "Air Nodes");
401 : }
402 :
403 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).DesignVolFlowRate = state.dataIPShortCut->rNumericArgs(1);
404 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).SizingInit = true;
405 10 : if (state.dataPhotovoltaicThermalCollector->PVT(Item).DesignVolFlowRate == DataSizing::AutoSize) {
406 10 : state.dataPhotovoltaicThermalCollector->PVT(Item).DesignVolFlowRateWasAutoSized = true;
407 : }
408 10 : if (state.dataPhotovoltaicThermalCollector->PVT(Item).DesignVolFlowRate != DataSizing::AutoSize) {
409 :
410 0 : if (state.dataPhotovoltaicThermalCollector->PVT(Item).WorkingFluidType == WorkingFluidEnum::LIQUID) {
411 0 : PlantUtilities::RegisterPlantCompDesignFlow(state,
412 0 : state.dataPhotovoltaicThermalCollector->PVT(Item).PlantInletNodeNum,
413 0 : state.dataPhotovoltaicThermalCollector->PVT(Item).DesignVolFlowRate);
414 0 : } else if (state.dataPhotovoltaicThermalCollector->PVT(Item).WorkingFluidType == WorkingFluidEnum::AIR) {
415 0 : state.dataPhotovoltaicThermalCollector->PVT(Item).MaxMassFlowRate =
416 0 : state.dataPhotovoltaicThermalCollector->PVT(Item).DesignVolFlowRate * state.dataEnvrn->StdRhoAir;
417 : }
418 0 : state.dataPhotovoltaicThermalCollector->PVT(Item).SizingInit = false;
419 : }
420 : }
421 :
422 1 : if (ErrorsFound) {
423 0 : ShowFatalError(state, "Errors found in processing input for photovoltaic thermal collectors");
424 : }
425 :
426 1 : if (allocated(tmpSimplePVTperf)) tmpSimplePVTperf.deallocate();
427 1 : }
428 :
429 10 : void PVTCollectorStruct::setupReportVars(EnergyPlusData &state)
430 : {
431 20 : SetupOutputVariable(state,
432 : "Generator Produced Thermal Rate",
433 : OutputProcessor::Unit::W,
434 : this->Report.ThermPower,
435 : OutputProcessor::SOVTimeStepType::System,
436 : OutputProcessor::SOVStoreType::Average,
437 10 : this->Name);
438 :
439 10 : if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
440 10 : SetupOutputVariable(state,
441 : "Generator Produced Thermal Energy",
442 : OutputProcessor::Unit::J,
443 : this->Report.ThermEnergy,
444 : OutputProcessor::SOVTimeStepType::System,
445 : OutputProcessor::SOVStoreType::Summed,
446 : this->Name,
447 : _,
448 : "SolarWater",
449 : "HeatProduced",
450 : _,
451 5 : "Plant");
452 :
453 5 : } else if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
454 10 : SetupOutputVariable(state,
455 : "Generator Produced Thermal Energy",
456 : OutputProcessor::Unit::J,
457 : this->Report.ThermEnergy,
458 : OutputProcessor::SOVTimeStepType::System,
459 : OutputProcessor::SOVStoreType::Summed,
460 : this->Name,
461 : _,
462 : "SolarAir",
463 : "HeatProduced",
464 : _,
465 5 : "System");
466 :
467 10 : SetupOutputVariable(state,
468 : "Generator PVT Fluid Bypass Status",
469 : OutputProcessor::Unit::None,
470 : this->Report.BypassStatus,
471 : OutputProcessor::SOVTimeStepType::System,
472 : OutputProcessor::SOVStoreType::Average,
473 5 : this->Name);
474 : }
475 :
476 20 : SetupOutputVariable(state,
477 : "Generator PVT Fluid Inlet Temperature",
478 : OutputProcessor::Unit::C,
479 : this->Report.TinletWorkFluid,
480 : OutputProcessor::SOVTimeStepType::System,
481 : OutputProcessor::SOVStoreType::Average,
482 10 : this->Name);
483 :
484 20 : SetupOutputVariable(state,
485 : "Generator PVT Fluid Outlet Temperature",
486 : OutputProcessor::Unit::C,
487 : this->Report.ToutletWorkFluid,
488 : OutputProcessor::SOVTimeStepType::System,
489 : OutputProcessor::SOVStoreType::Average,
490 10 : this->Name);
491 :
492 20 : SetupOutputVariable(state,
493 : "Generator PVT Fluid Mass Flow Rate",
494 : OutputProcessor::Unit::kg_s,
495 : this->Report.MdotWorkFluid,
496 : OutputProcessor::SOVTimeStepType::System,
497 : OutputProcessor::SOVStoreType::Average,
498 10 : this->Name);
499 10 : }
500 :
501 197293 : void PVTCollectorStruct::initialize(EnergyPlusData &state, bool const FirstHVACIteration)
502 : {
503 :
504 : // SUBROUTINE INFORMATION:
505 : // AUTHOR B. Griffith
506 : // DATE WRITTEN June 2008
507 : // MODIFIED B. Griffith, May 2009, EMS setpoint check
508 : // RE-ENGINEERED na
509 :
510 : // PURPOSE OF THIS SUBROUTINE:
511 : // init for PVT
512 :
513 : static constexpr std::string_view RoutineName("InitPVTcollectors");
514 :
515 : // Do the one time initializations
516 197293 : this->oneTimeInit(state);
517 :
518 : // finish set up of PV, because PV get-input follows PVT's get input.
519 197293 : if (!this->PVfound) {
520 10 : if (allocated(state.dataPhotovoltaic->PVarray)) {
521 10 : this->PVnum = UtilityRoutines::FindItemInList(this->PVname, state.dataPhotovoltaic->PVarray);
522 10 : if (this->PVnum == 0) {
523 0 : ShowSevereError(state, "Invalid name for photovoltaic generator = " + this->PVname);
524 0 : ShowContinueError(state, "Entered in flat plate photovoltaic-thermal collector = " + this->Name);
525 : } else {
526 10 : this->PVfound = true;
527 : }
528 : } else {
529 0 : if ((!state.dataGlobal->BeginEnvrnFlag) && (!FirstHVACIteration)) {
530 0 : ShowSevereError(state, "Photovoltaic generators are missing for Photovoltaic Thermal modeling");
531 0 : ShowContinueError(state, "Needed for flat plate photovoltaic-thermal collector = " + this->Name);
532 : }
533 : }
534 : }
535 :
536 197293 : if (!state.dataGlobal->SysSizingCalc && this->MySetPointCheckFlag && state.dataHVACGlobal->DoSetPointTest) {
537 110 : for (int PVTindex = 1; PVTindex <= state.dataPhotovoltaicThermalCollector->NumPVT; ++PVTindex) {
538 100 : if (state.dataPhotovoltaicThermalCollector->PVT(PVTindex).WorkingFluidType == WorkingFluidEnum::AIR) {
539 50 : if (state.dataLoopNodes->Node(state.dataPhotovoltaicThermalCollector->PVT(PVTindex).HVACOutletNodeNum).TempSetPoint ==
540 : DataLoopNode::SensedNodeFlagValue) {
541 0 : if (!state.dataGlobal->AnyEnergyManagementSystemInModel) {
542 0 : ShowSevereError(state, "Missing temperature setpoint for PVT outlet node ");
543 0 : ShowContinueError(state,
544 0 : "Add a setpoint manager to outlet node of PVT named " +
545 0 : state.dataPhotovoltaicThermalCollector->PVT(PVTindex).Name);
546 0 : state.dataHVACGlobal->SetPointErrorFlag = true;
547 : } else {
548 : // need call to EMS to check node
549 0 : EMSManager::CheckIfNodeSetPointManagedByEMS(state,
550 0 : state.dataPhotovoltaicThermalCollector->PVT(PVTindex).HVACOutletNodeNum,
551 : EMSManager::SPControlType::TemperatureSetPoint,
552 0 : state.dataHVACGlobal->SetPointErrorFlag);
553 0 : if (state.dataHVACGlobal->SetPointErrorFlag) {
554 0 : ShowSevereError(state, "Missing temperature setpoint for PVT outlet node ");
555 0 : ShowContinueError(state,
556 0 : "Add a setpoint manager to outlet node of PVT named " +
557 0 : state.dataPhotovoltaicThermalCollector->PVT(PVTindex).Name);
558 0 : ShowContinueError(state, " or use an EMS actuator to establish a setpoint at the outlet node of PVT");
559 : }
560 : }
561 : }
562 : }
563 : }
564 10 : this->MySetPointCheckFlag = false;
565 : }
566 :
567 197293 : if (!state.dataGlobal->SysSizingCalc && this->SizingInit && (this->WorkingFluidType == WorkingFluidEnum::AIR)) {
568 5 : this->size(state);
569 : }
570 :
571 197293 : int InletNode = 0;
572 197293 : int OutletNode = 0;
573 :
574 197293 : switch (this->WorkingFluidType) {
575 151650 : case WorkingFluidEnum::LIQUID: {
576 151650 : InletNode = this->PlantInletNodeNum;
577 151650 : OutletNode = this->PlantOutletNodeNum;
578 151650 : } break;
579 45643 : case WorkingFluidEnum::AIR: {
580 45643 : InletNode = this->HVACInletNodeNum;
581 45643 : OutletNode = this->HVACOutletNodeNum;
582 45643 : } break;
583 0 : default: {
584 0 : assert(false);
585 : } break;
586 : }
587 :
588 197293 : if (state.dataGlobal->BeginEnvrnFlag && this->EnvrnInit) {
589 :
590 60 : this->MassFlowRate = 0.0;
591 60 : this->BypassDamperOff = true;
592 60 : this->CoolingUseful = false;
593 60 : this->HeatingUseful = false;
594 60 : this->Simple.LastCollectorTemp = 0.0;
595 60 : this->Simple.CollectorTemp = 0.0;
596 60 : this->Report.ThermEfficiency = 0.0;
597 60 : this->Report.ThermPower = 0.0;
598 60 : this->Report.ThermHeatGain = 0.0;
599 60 : this->Report.ThermHeatLoss = 0.0;
600 60 : this->Report.ThermEnergy = 0.0;
601 60 : this->Report.MdotWorkFluid = 0.0;
602 60 : this->Report.TinletWorkFluid = 0.0;
603 60 : this->Report.ToutletWorkFluid = 0.0;
604 60 : this->Report.BypassStatus = 0.0;
605 :
606 60 : switch (this->WorkingFluidType) {
607 30 : case WorkingFluidEnum::LIQUID: {
608 :
609 60 : Real64 rho = FluidProperties::GetDensityGlycol(state,
610 30 : state.dataPlnt->PlantLoop(this->WPlantLoc.loopNum).FluidName,
611 : DataGlobalConstants::HWInitConvTemp,
612 30 : state.dataPlnt->PlantLoop(this->WPlantLoc.loopNum).FluidIndex,
613 30 : RoutineName);
614 :
615 30 : this->MaxMassFlowRate = this->DesignVolFlowRate * rho;
616 :
617 30 : PlantUtilities::InitComponentNodes(state, 0.0, this->MaxMassFlowRate, InletNode, OutletNode);
618 :
619 30 : this->Simple.LastCollectorTemp = 23.0;
620 :
621 30 : } break;
622 30 : case WorkingFluidEnum::AIR: {
623 30 : this->Simple.LastCollectorTemp = 23.0;
624 30 : } break;
625 0 : default:
626 0 : break;
627 : }
628 :
629 60 : this->EnvrnInit = false;
630 : }
631 197293 : if (!state.dataGlobal->BeginEnvrnFlag) this->EnvrnInit = true;
632 :
633 197293 : switch (this->WorkingFluidType) {
634 151650 : case WorkingFluidEnum::LIQUID: {
635 : // heating only right now, so control flow requests based on incident solar;
636 151650 : if (state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) > DataPhotovoltaics::MinIrradiance) {
637 31280 : this->MassFlowRate = this->MaxMassFlowRate;
638 : } else {
639 120370 : this->MassFlowRate = 0.0;
640 : }
641 :
642 151650 : PlantUtilities::SetComponentFlowRate(state, this->MassFlowRate, InletNode, OutletNode, this->WPlantLoc);
643 151650 : } break;
644 45643 : case WorkingFluidEnum::AIR: {
645 45643 : this->MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRate;
646 45643 : } break;
647 0 : default:
648 0 : break;
649 : }
650 197293 : }
651 :
652 30 : void PVTCollectorStruct::size(EnergyPlusData &state)
653 : {
654 :
655 : // SUBROUTINE INFORMATION:
656 : // AUTHOR Brent Griffith
657 : // DATE WRITTEN August 2008
658 : // MODIFIED November 2013 Daeho Kang, add component sizing table entries
659 : // RE-ENGINEERED na
660 :
661 : // PURPOSE OF THIS SUBROUTINE:
662 : // This subroutine is for sizing PVT flow rates that
663 : // have not been specified in the input.
664 :
665 : // METHODOLOGY EMPLOYED:
666 : // Obtains hot water flow rate from the plant sizing array.
667 :
668 : bool SizingDesRunThisAirSys; // true if a particular air system had a Sizing:System object and system sizing done
669 :
670 : // Indicator to hardsize and no sizing run
671 30 : bool HardSizeNoDesRun = !(state.dataSize->SysSizingRunDone || state.dataSize->ZoneSizingRunDone);
672 :
673 30 : if (state.dataSize->CurSysNum > 0) {
674 5 : CheckThisAirSystemForSizing(state, state.dataSize->CurSysNum, SizingDesRunThisAirSys);
675 : } else {
676 25 : SizingDesRunThisAirSys = false;
677 : }
678 :
679 30 : Real64 DesignVolFlowRateDes = 0.0; // Autosize design volume flow for reporting
680 30 : int PltSizNum = 0; // Plant Sizing index corresponding to CurLoopNum
681 30 : bool ErrorsFound = false;
682 :
683 30 : if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
684 :
685 25 : if (!allocated(state.dataSize->PlantSizData)) return;
686 25 : if (!allocated(state.dataPlnt->PlantLoop)) return;
687 :
688 25 : if (this->WPlantLoc.loopNum > 0) {
689 25 : PltSizNum = state.dataPlnt->PlantLoop(this->WPlantLoc.loopNum).PlantSizNum;
690 : }
691 25 : if (this->WPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply) {
692 0 : if (PltSizNum > 0) {
693 0 : if (state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate >= DataHVACGlobals::SmallWaterVolFlow) {
694 0 : DesignVolFlowRateDes = state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate;
695 : } else {
696 0 : DesignVolFlowRateDes = 0.0;
697 : }
698 : } else {
699 0 : if (this->DesignVolFlowRateWasAutoSized) {
700 0 : if (state.dataPlnt->PlantFirstSizesOkayToFinalize) {
701 0 : ShowSevereError(state, "Autosizing of PVT solar collector design flow rate requires a Sizing:Plant object");
702 0 : ShowContinueError(state, "Occurs in PVT object=" + this->Name);
703 0 : ErrorsFound = true;
704 : }
705 : } else { // Hardsized
706 0 : if (state.dataPlnt->PlantFinalSizesOkayToReport && this->DesignVolFlowRate > 0.0) {
707 0 : BaseSizer::reportSizerOutput(state,
708 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
709 : this->Name,
710 : "User-Specified Design Flow Rate [m3/s]",
711 0 : this->DesignVolFlowRate);
712 : }
713 : }
714 : }
715 25 : } else if (this->WPlantLoc.loopSideNum == DataPlant::LoopSideLocation::Demand) {
716 25 : DesignVolFlowRateDes = this->AreaCol * SimplePVTWaterSizeFactor;
717 : }
718 25 : if (this->DesignVolFlowRateWasAutoSized) {
719 25 : this->DesignVolFlowRate = DesignVolFlowRateDes;
720 25 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
721 15 : BaseSizer::reportSizerOutput(state,
722 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
723 : this->Name,
724 : "Design Size Design Flow Rate [m3/s]",
725 10 : DesignVolFlowRateDes);
726 : }
727 25 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
728 0 : BaseSizer::reportSizerOutput(state,
729 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
730 : this->Name,
731 : "Initial Design Size Design Flow Rate [m3/s]",
732 0 : DesignVolFlowRateDes);
733 : }
734 25 : PlantUtilities::RegisterPlantCompDesignFlow(state, this->PlantInletNodeNum, this->DesignVolFlowRate);
735 :
736 : } else { // Hardsized with sizing data
737 0 : if (this->DesignVolFlowRate > 0.0 && DesignVolFlowRateDes > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport) {
738 0 : Real64 DesignVolFlowRateUser = this->DesignVolFlowRate;
739 0 : BaseSizer::reportSizerOutput(state,
740 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
741 : this->Name,
742 : "Design Size Design Flow Rate [m3/s]",
743 : DesignVolFlowRateDes,
744 : "User-Specified Design Flow Rate [m3/s]",
745 0 : DesignVolFlowRateUser);
746 0 : if (state.dataGlobal->DisplayExtraWarnings) {
747 0 : if ((std::abs(DesignVolFlowRateDes - DesignVolFlowRateUser) / DesignVolFlowRateUser) >
748 0 : state.dataSize->AutoVsHardSizingThreshold) {
749 0 : ShowMessage(state, "SizeSolarCollector: Potential issue with equipment sizing for " + this->Name);
750 0 : ShowContinueError(state, format("User-Specified Design Flow Rate of {:.5R} [W]", DesignVolFlowRateUser));
751 0 : ShowContinueError(state, format("differs from Design Size Design Flow Rate of {:.5R} [W]", DesignVolFlowRateDes));
752 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
753 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
754 : }
755 : }
756 : }
757 : }
758 : } // plant component
759 :
760 30 : if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
761 :
762 5 : if (state.dataSize->CurSysNum > 0) {
763 5 : if (!this->DesignVolFlowRateWasAutoSized && !SizingDesRunThisAirSys) { // Simulation continue
764 0 : HardSizeNoDesRun = true;
765 0 : if (this->DesignVolFlowRate > 0.0) {
766 0 : BaseSizer::reportSizerOutput(state,
767 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
768 : this->Name,
769 : "User-Specified Design Flow Rate [m3/s]",
770 0 : this->DesignVolFlowRate);
771 : }
772 : } else {
773 5 : CheckSysSizing(state, "SolarCollector:FlatPlate:PhotovoltaicThermal", this->Name);
774 5 : auto &thisFinalSysSizing(state.dataSize->FinalSysSizing(state.dataSize->CurSysNum));
775 5 : if (state.dataSize->CurOASysNum > 0) {
776 5 : DesignVolFlowRateDes = thisFinalSysSizing.DesOutAirVolFlow;
777 : } else {
778 0 : switch (state.dataSize->CurDuctType) {
779 0 : case DataHVACGlobals::AirDuctType::Main: {
780 0 : DesignVolFlowRateDes = thisFinalSysSizing.SysAirMinFlowRat * thisFinalSysSizing.DesMainVolFlow;
781 0 : } break;
782 0 : case DataHVACGlobals::AirDuctType::Cooling: {
783 0 : DesignVolFlowRateDes = thisFinalSysSizing.SysAirMinFlowRat * thisFinalSysSizing.DesCoolVolFlow;
784 0 : } break;
785 0 : case DataHVACGlobals::AirDuctType::Heating: {
786 0 : DesignVolFlowRateDes = thisFinalSysSizing.DesHeatVolFlow;
787 0 : } break;
788 0 : default: {
789 0 : DesignVolFlowRateDes = thisFinalSysSizing.DesMainVolFlow;
790 0 : } break;
791 : }
792 : }
793 5 : Real64 DesMassFlow = state.dataEnvrn->StdRhoAir * DesignVolFlowRateDes;
794 5 : this->MaxMassFlowRate = DesMassFlow;
795 : }
796 5 : if (!HardSizeNoDesRun) {
797 5 : if (this->DesignVolFlowRateWasAutoSized) {
798 5 : this->DesignVolFlowRate = DesignVolFlowRateDes;
799 15 : BaseSizer::reportSizerOutput(state,
800 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
801 : this->Name,
802 : "Design Size Design Flow Rate [m3/s]",
803 10 : DesignVolFlowRateDes);
804 5 : this->SizingInit = false;
805 : } else {
806 0 : if (this->DesignVolFlowRate > 0.0 && DesignVolFlowRateDes > 0.0) {
807 0 : Real64 DesignVolFlowRateUser = this->DesignVolFlowRate;
808 0 : BaseSizer::reportSizerOutput(state,
809 : "SolarCollector:FlatPlate:PhotovoltaicThermal",
810 : this->Name,
811 : "Design Size Design Flow Rate [m3/s]",
812 : DesignVolFlowRateDes,
813 : "User-Specified Design Flow Rate [m3/s]",
814 0 : DesignVolFlowRateUser);
815 0 : if (state.dataGlobal->DisplayExtraWarnings) {
816 0 : if ((std::abs(DesignVolFlowRateDes - DesignVolFlowRateUser) / DesignVolFlowRateUser) >
817 0 : state.dataSize->AutoVsHardSizingThreshold) {
818 0 : ShowMessage(state, "SizeSolarCollector: Potential issue with equipment sizing for " + this->Name);
819 0 : ShowContinueError(state, format("User-Specified Design Flow Rate of {:.5R} [W]", DesignVolFlowRateUser));
820 0 : ShowContinueError(state, format("differs from Design Size Design Flow Rate of {:.5R} [W]", DesignVolFlowRateDes));
821 0 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
822 0 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
823 : }
824 : }
825 : }
826 : }
827 : }
828 0 : } else if (state.dataSize->CurZoneEqNum > 0) {
829 : // PVT is not currently for zone equipment, should not come here.
830 : }
831 : }
832 :
833 30 : if (ErrorsFound) {
834 0 : ShowFatalError(state, "Preceding sizing errors cause program termination");
835 : }
836 : }
837 :
838 197268 : void PVTCollectorStruct::control(EnergyPlusData &state)
839 : {
840 :
841 : // SUBROUTINE INFORMATION:
842 : // AUTHOR Brent Griffith
843 : // DATE WRITTEN August 2008
844 : // MODIFIED na
845 : // RE-ENGINEERED na
846 :
847 : // PURPOSE OF THIS SUBROUTINE:
848 : // make control decisions for PVT collector
849 :
850 : // METHODOLOGY EMPLOYED:
851 : // decide if PVT should be in cooling or heat mode and if it should be bypassed or not
852 :
853 197268 : if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
854 :
855 45643 : if (this->PVTModelType == SimplePVTmodel) {
856 45643 : if (state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) > DataPhotovoltaics::MinIrradiance) {
857 : // is heating wanted?
858 : // Outlet node is required to have a setpoint.
859 9298 : if (state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint > state.dataLoopNodes->Node(this->HVACInletNodeNum).Temp) {
860 1306 : this->HeatingUseful = true;
861 1306 : this->CoolingUseful = false;
862 1306 : this->BypassDamperOff = true;
863 : } else {
864 7992 : this->HeatingUseful = false;
865 7992 : this->CoolingUseful = true;
866 7992 : this->BypassDamperOff = false;
867 : }
868 : } else {
869 : // is cooling wanted?
870 36345 : if (state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint < state.dataLoopNodes->Node(this->HVACInletNodeNum).Temp) {
871 3806 : this->CoolingUseful = true;
872 3806 : this->HeatingUseful = false;
873 3806 : this->BypassDamperOff = true;
874 : } else {
875 32539 : this->CoolingUseful = false;
876 32539 : this->HeatingUseful = true;
877 32539 : this->BypassDamperOff = false;
878 : }
879 : }
880 : }
881 :
882 151625 : } else if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
883 151625 : if (this->PVTModelType == SimplePVTmodel) {
884 151625 : if (state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) > DataPhotovoltaics::MinIrradiance) {
885 : // is heating wanted?
886 31280 : this->HeatingUseful = true;
887 31280 : this->BypassDamperOff = true;
888 : } else {
889 : // is cooling wanted?
890 120345 : this->CoolingUseful = false;
891 120345 : this->BypassDamperOff = false;
892 : }
893 : }
894 : }
895 197268 : }
896 :
897 197268 : void PVTCollectorStruct::calculate(EnergyPlusData &state)
898 : {
899 :
900 : // SUBROUTINE INFORMATION:
901 : // AUTHOR Brent Griffith
902 : // DATE WRITTEN August 2008
903 : // MODIFIED na
904 : // RE-ENGINEERED na
905 :
906 : // PURPOSE OF THIS SUBROUTINE:
907 : // Calculate PVT collector thermal
908 :
909 : // METHODOLOGY EMPLOYED:
910 : // Current model is "simple" fixed efficiency and simple night sky balance for cooling
911 :
912 : static constexpr std::string_view RoutineName("CalcPVTcollectors");
913 :
914 197268 : int InletNode(0);
915 :
916 197268 : switch (this->WorkingFluidType) {
917 151625 : case WorkingFluidEnum::LIQUID: {
918 151625 : InletNode = this->PlantInletNodeNum;
919 151625 : } break;
920 45643 : case WorkingFluidEnum::AIR: {
921 45643 : InletNode = this->HVACInletNodeNum;
922 45643 : } break;
923 0 : default:
924 0 : break;
925 : }
926 :
927 197268 : Real64 mdot = this->MassFlowRate;
928 197268 : Real64 Tinlet = state.dataLoopNodes->Node(InletNode).Temp;
929 :
930 197268 : if (this->PVTModelType == SimplePVTmodel) {
931 :
932 197268 : Real64 BypassFraction(0.0);
933 197268 : Real64 PotentialOutletTemp(0.0);
934 :
935 197268 : if (this->HeatingUseful && this->BypassDamperOff && (mdot > 0.0)) {
936 :
937 27860 : Real64 Eff(0.0);
938 :
939 27860 : switch (this->Simple.ThermEfficMode) {
940 27860 : case ThermEfficEnum::FIXED: {
941 27860 : Eff = this->Simple.ThermEffic;
942 27860 : } break;
943 0 : case ThermEfficEnum::SCHEDULED: {
944 0 : Eff = ScheduleManager::GetCurrentScheduleValue(state, this->Simple.ThermEffSchedNum);
945 0 : this->Simple.ThermEffic = Eff;
946 0 : } break;
947 0 : default:
948 0 : break;
949 : }
950 :
951 27860 : Real64 PotentialHeatGain = state.dataHeatBal->SurfQRadSWOutIncident(this->SurfNum) * Eff * this->AreaCol;
952 :
953 27860 : if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
954 490 : Real64 Winlet = state.dataLoopNodes->Node(InletNode).HumRat;
955 490 : Real64 CpInlet = Psychrometrics::PsyCpAirFnW(Winlet);
956 490 : if (mdot * CpInlet > 0.0) {
957 490 : PotentialOutletTemp = Tinlet + PotentialHeatGain / (mdot * CpInlet);
958 : } else {
959 0 : PotentialOutletTemp = Tinlet;
960 : }
961 : // now compare heating potential to setpoint and figure bypass fraction
962 490 : if (PotentialOutletTemp > state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint) { // need to modulate
963 455 : if (Tinlet != PotentialOutletTemp) {
964 910 : BypassFraction = (state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint - PotentialOutletTemp) /
965 455 : (Tinlet - PotentialOutletTemp);
966 : } else {
967 0 : BypassFraction = 0.0;
968 : }
969 455 : BypassFraction = max(0.0, BypassFraction);
970 455 : PotentialOutletTemp = state.dataLoopNodes->Node(this->HVACOutletNodeNum).TempSetPoint;
971 455 : PotentialHeatGain = mdot * Psychrometrics::PsyCpAirFnW(Winlet) * (PotentialOutletTemp - Tinlet);
972 :
973 : } else {
974 35 : BypassFraction = 0.0;
975 : }
976 27370 : } else if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
977 27370 : Real64 CpInlet = Psychrometrics::CPHW(Tinlet);
978 27370 : if (mdot * CpInlet != 0.0) { // protect divide by zero
979 27370 : PotentialOutletTemp = Tinlet + PotentialHeatGain / (mdot * CpInlet);
980 : } else {
981 0 : PotentialOutletTemp = Tinlet;
982 : }
983 27370 : BypassFraction = 0.0;
984 : }
985 :
986 27860 : this->Report.ThermEfficiency = Eff;
987 27860 : this->Report.ThermHeatGain = PotentialHeatGain;
988 27860 : this->Report.ThermPower = this->Report.ThermHeatGain;
989 27860 : this->Report.ThermEnergy = this->Report.ThermPower * state.dataHVACGlobal->TimeStepSys * DataGlobalConstants::SecInHour;
990 27860 : this->Report.ThermHeatLoss = 0.0;
991 27860 : this->Report.TinletWorkFluid = Tinlet;
992 27860 : this->Report.MdotWorkFluid = mdot;
993 27860 : this->Report.ToutletWorkFluid = PotentialOutletTemp;
994 27860 : this->Report.BypassStatus = BypassFraction;
995 :
996 169408 : } else if (this->CoolingUseful && this->BypassDamperOff && (mdot > 0.0)) {
997 : // calculate cooling using energy balance
998 2898 : Real64 HrGround(0.0);
999 2898 : Real64 HrAir(0.0);
1000 2898 : Real64 HcExt(0.0);
1001 2898 : Real64 HrSky(0.0);
1002 :
1003 2898 : ConvectionCoefficients::InitExteriorConvectionCoeff(state,
1004 : this->SurfNum,
1005 : 0.0,
1006 : DataSurfaces::SurfaceRoughness::VerySmooth,
1007 : this->Simple.SurfEmissivity,
1008 : this->Simple.LastCollectorTemp,
1009 : HcExt,
1010 : HrSky,
1011 : HrGround,
1012 : HrAir);
1013 :
1014 2898 : Real64 WetBulbInlet(0.0);
1015 2898 : Real64 DewPointInlet(0.0);
1016 2898 : Real64 CpInlet(0.0);
1017 :
1018 2898 : if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
1019 2898 : Real64 Winlet = state.dataLoopNodes->Node(InletNode).HumRat;
1020 2898 : CpInlet = Psychrometrics::PsyCpAirFnW(Winlet);
1021 2898 : WetBulbInlet = Psychrometrics::PsyTwbFnTdbWPb(state, Tinlet, Winlet, state.dataEnvrn->OutBaroPress, RoutineName);
1022 2898 : DewPointInlet = Psychrometrics::PsyTdpFnTdbTwbPb(state, Tinlet, WetBulbInlet, state.dataEnvrn->OutBaroPress, RoutineName);
1023 0 : } else if (this->WorkingFluidType == WorkingFluidEnum::LIQUID) {
1024 0 : CpInlet = Psychrometrics::CPHW(Tinlet);
1025 : }
1026 :
1027 : Real64 Tcollector =
1028 8694 : (2.0 * mdot * CpInlet * Tinlet + this->AreaCol * (HrGround * state.dataEnvrn->OutDryBulbTemp + HrSky * state.dataEnvrn->SkyTemp +
1029 5796 : HrAir * state.dataSurface->SurfOutDryBulbTemp(this->SurfNum) +
1030 2898 : HcExt * state.dataSurface->SurfOutDryBulbTemp(this->SurfNum))) /
1031 2898 : (2.0 * mdot * CpInlet + this->AreaCol * (HrGround + HrSky + HrAir + HcExt));
1032 :
1033 2898 : PotentialOutletTemp = 2.0 * Tcollector - Tinlet;
1034 2898 : this->Report.ToutletWorkFluid = PotentialOutletTemp;
1035 : // trap for air not being cooled below its wetbulb.
1036 2898 : if (this->WorkingFluidType == WorkingFluidEnum::AIR) {
1037 2898 : if (PotentialOutletTemp < DewPointInlet) {
1038 : // water removal would be needed.. not going to allow that for now. limit cooling to dew point and model bypass
1039 2883 : if (Tinlet != PotentialOutletTemp) {
1040 2883 : BypassFraction = (DewPointInlet - PotentialOutletTemp) / (Tinlet - PotentialOutletTemp);
1041 : } else {
1042 0 : BypassFraction = 0.0;
1043 : }
1044 2883 : BypassFraction = max(0.0, BypassFraction);
1045 2883 : PotentialOutletTemp = DewPointInlet;
1046 : }
1047 : }
1048 :
1049 2898 : this->Report.MdotWorkFluid = mdot;
1050 2898 : this->Report.TinletWorkFluid = Tinlet;
1051 2898 : this->Report.ToutletWorkFluid = PotentialOutletTemp;
1052 2898 : this->Report.ThermHeatLoss = mdot * CpInlet * (Tinlet - this->Report.ToutletWorkFluid);
1053 2898 : this->Report.ThermHeatGain = 0.0;
1054 2898 : this->Report.ThermPower = -1.0 * this->Report.ThermHeatLoss;
1055 2898 : this->Report.ThermEnergy = this->Report.ThermPower * state.dataHVACGlobal->TimeStepSys * DataGlobalConstants::SecInHour;
1056 2898 : this->Report.ThermEfficiency = 0.0;
1057 2898 : this->Simple.LastCollectorTemp = Tcollector;
1058 2898 : this->Report.BypassStatus = BypassFraction;
1059 :
1060 : } else {
1061 166510 : this->Report.TinletWorkFluid = Tinlet;
1062 166510 : this->Report.ToutletWorkFluid = Tinlet;
1063 166510 : this->Report.ThermHeatLoss = 0.0;
1064 166510 : this->Report.ThermHeatGain = 0.0;
1065 166510 : this->Report.ThermPower = 0.0;
1066 166510 : this->Report.ThermEfficiency = 0.0;
1067 166510 : this->Report.ThermEnergy = 0.0;
1068 166510 : this->Report.BypassStatus = 1.0;
1069 166510 : this->Report.MdotWorkFluid = mdot;
1070 : }
1071 : }
1072 197268 : }
1073 :
1074 197268 : void PVTCollectorStruct::update(EnergyPlusData &state)
1075 : {
1076 :
1077 : // SUBROUTINE INFORMATION:
1078 : // AUTHOR Brent Griffith
1079 : // DATE WRITTEN August 2008
1080 : // MODIFIED na
1081 : // RE-ENGINEERED na
1082 :
1083 : int InletNode;
1084 : int OutletNode;
1085 :
1086 197268 : switch (this->WorkingFluidType) {
1087 151625 : case WorkingFluidEnum::LIQUID: {
1088 151625 : InletNode = this->PlantInletNodeNum;
1089 151625 : OutletNode = this->PlantOutletNodeNum;
1090 :
1091 151625 : PlantUtilities::SafeCopyPlantNode(state, InletNode, OutletNode);
1092 151625 : state.dataLoopNodes->Node(OutletNode).Temp = this->Report.ToutletWorkFluid;
1093 151625 : } break;
1094 45643 : case WorkingFluidEnum::AIR: {
1095 45643 : InletNode = this->HVACInletNodeNum;
1096 45643 : OutletNode = this->HVACOutletNodeNum;
1097 :
1098 : // Set the outlet nodes for properties that just pass through & not used
1099 45643 : state.dataLoopNodes->Node(OutletNode).Quality = state.dataLoopNodes->Node(InletNode).Quality;
1100 45643 : state.dataLoopNodes->Node(OutletNode).Press = state.dataLoopNodes->Node(InletNode).Press;
1101 45643 : state.dataLoopNodes->Node(OutletNode).MassFlowRate = state.dataLoopNodes->Node(InletNode).MassFlowRate;
1102 45643 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMin = state.dataLoopNodes->Node(InletNode).MassFlowRateMin;
1103 45643 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMax = state.dataLoopNodes->Node(InletNode).MassFlowRateMax;
1104 45643 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMinAvail = state.dataLoopNodes->Node(InletNode).MassFlowRateMinAvail;
1105 45643 : state.dataLoopNodes->Node(OutletNode).MassFlowRateMaxAvail = state.dataLoopNodes->Node(InletNode).MassFlowRateMaxAvail;
1106 :
1107 : // Set outlet node variables that are possibly changed
1108 45643 : state.dataLoopNodes->Node(OutletNode).Temp = this->Report.ToutletWorkFluid;
1109 45643 : state.dataLoopNodes->Node(OutletNode).HumRat = state.dataLoopNodes->Node(InletNode).HumRat; // assumes dewpoint bound on cooling ....
1110 45643 : state.dataLoopNodes->Node(OutletNode).Enthalpy =
1111 45643 : Psychrometrics::PsyHFnTdbW(this->Report.ToutletWorkFluid, state.dataLoopNodes->Node(OutletNode).HumRat);
1112 45643 : } break;
1113 0 : default:
1114 0 : break;
1115 : }
1116 197268 : }
1117 197293 : void PVTCollectorStruct::oneTimeInit(EnergyPlusData &state)
1118 : {
1119 :
1120 197293 : if (this->MyOneTimeFlag) {
1121 10 : this->setupReportVars(state);
1122 10 : this->MyOneTimeFlag = false;
1123 : }
1124 :
1125 197293 : if (this->SetLoopIndexFlag) {
1126 45648 : if (allocated(state.dataPlnt->PlantLoop) && (this->PlantInletNodeNum > 0)) {
1127 5 : bool errFlag = false;
1128 5 : PlantUtilities::ScanPlantLoopsForObject(state, this->Name, this->Type, this->WPlantLoc, errFlag, _, _, _, _, _);
1129 5 : if (errFlag) {
1130 0 : ShowFatalError(state, "InitPVTcollectors: Program terminated for previous conditions.");
1131 : }
1132 5 : this->SetLoopIndexFlag = false;
1133 : }
1134 : }
1135 197293 : }
1136 :
1137 80592 : void GetPVTThermalPowerProduction(EnergyPlusData &state, int const PVindex, Real64 &ThermalPower, Real64 &ThermalEnergy)
1138 : {
1139 :
1140 : // SUBROUTINE INFORMATION:
1141 : // AUTHOR <author>
1142 : // DATE WRITTEN <date_written>
1143 : // MODIFIED na
1144 : // RE-ENGINEERED na
1145 :
1146 80592 : int PVTnum(0);
1147 :
1148 : // first find PVT index that is associated with this PV generator
1149 886512 : for (int loop = 1; loop <= state.dataPhotovoltaicThermalCollector->NumPVT; ++loop) {
1150 805920 : if (!state.dataPhotovoltaicThermalCollector->PVT(loop).PVfound) continue;
1151 682170 : if (state.dataPhotovoltaicThermalCollector->PVT(loop).PVnum == PVindex) { // we found it
1152 68215 : PVTnum = loop;
1153 : }
1154 : }
1155 :
1156 80592 : if (PVTnum > 0) {
1157 68215 : ThermalPower = state.dataPhotovoltaicThermalCollector->PVT(PVTnum).Report.ThermPower;
1158 68215 : ThermalEnergy = state.dataPhotovoltaicThermalCollector->PVT(PVTnum).Report.ThermEnergy;
1159 : } else {
1160 12377 : ThermalPower = 0.0;
1161 12377 : ThermalEnergy = 0.0;
1162 : }
1163 80592 : }
1164 :
1165 0 : int GetAirInletNodeNum(EnergyPlusData &state, std::string_view PVTName, bool &ErrorsFound)
1166 : {
1167 : // FUNCTION INFORMATION:
1168 : // AUTHOR Lixing Gu
1169 : // DATE WRITTEN May 2019
1170 : // MODIFIED na
1171 : // RE-ENGINEERED na
1172 :
1173 : // PURPOSE OF THIS FUNCTION:
1174 : // This function looks up the given PVT and returns the air inlet node number.
1175 : // If incorrect PVT name is given, ErrorsFound is returned as true and node number as zero.
1176 :
1177 : int NodeNum; // node number returned
1178 : int WhichPVT;
1179 :
1180 0 : if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
1181 0 : GetPVTcollectorsInput(state);
1182 0 : state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
1183 : }
1184 :
1185 0 : WhichPVT = UtilityRoutines::FindItemInList(PVTName, state.dataPhotovoltaicThermalCollector->PVT);
1186 0 : if (WhichPVT != 0) {
1187 0 : NodeNum = state.dataPhotovoltaicThermalCollector->PVT(WhichPVT).HVACInletNodeNum;
1188 : } else {
1189 0 : ShowSevereError(state,
1190 0 : "GetAirInletNodeNum: Could not find SolarCollector FlatPlate PhotovoltaicThermal = \"" + std::string{PVTName} + "\"");
1191 0 : ErrorsFound = true;
1192 0 : NodeNum = 0;
1193 : }
1194 :
1195 0 : return NodeNum;
1196 : }
1197 0 : int GetAirOutletNodeNum(EnergyPlusData &state, std::string_view PVTName, bool &ErrorsFound)
1198 : {
1199 : // FUNCTION INFORMATION:
1200 : // AUTHOR Lixing Gu
1201 : // DATE WRITTEN May 2019
1202 : // MODIFIED na
1203 : // RE-ENGINEERED na
1204 :
1205 : // PURPOSE OF THIS FUNCTION:
1206 : // This function looks up the given PVT and returns the air outlet node number.
1207 : // If incorrect PVT name is given, ErrorsFound is returned as true and node number as zero.
1208 :
1209 : int NodeNum; // node number returned
1210 : int WhichPVT;
1211 :
1212 0 : if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
1213 0 : GetPVTcollectorsInput(state);
1214 0 : state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
1215 : }
1216 :
1217 0 : WhichPVT = UtilityRoutines::FindItemInList(PVTName, state.dataPhotovoltaicThermalCollector->PVT);
1218 0 : if (WhichPVT != 0) {
1219 0 : NodeNum = state.dataPhotovoltaicThermalCollector->PVT(WhichPVT).HVACOutletNodeNum;
1220 : } else {
1221 0 : ShowSevereError(state,
1222 0 : "GetAirInletNodeNum: Could not find SolarCollector FlatPlate PhotovoltaicThermal = \"" + std::string{PVTName} + "\"");
1223 0 : ErrorsFound = true;
1224 0 : NodeNum = 0;
1225 : }
1226 :
1227 0 : return NodeNum;
1228 : }
1229 :
1230 5 : int getPVTindexFromName(EnergyPlusData &state, std::string_view objectName)
1231 : {
1232 5 : if (state.dataPhotovoltaicThermalCollector->GetInputFlag) {
1233 0 : GetPVTcollectorsInput(state);
1234 0 : state.dataPhotovoltaicThermalCollector->GetInputFlag = false;
1235 : }
1236 :
1237 15 : for (auto it = state.dataPhotovoltaicThermalCollector->PVT.begin(); it != state.dataPhotovoltaicThermalCollector->PVT.end(); ++it) {
1238 15 : if (it->Name == objectName) {
1239 5 : return static_cast<int>(std::distance(state.dataPhotovoltaicThermalCollector->PVT.begin(), it) + 1);
1240 : }
1241 : }
1242 :
1243 : // If we didn't find it, fatal
1244 0 : ShowFatalError(state, "Solar Thermal Collector GetIndexFromName: Error getting inputs for object named: " + std::string{objectName});
1245 0 : assert(false);
1246 : return 0; // Shutup compiler
1247 : }
1248 :
1249 45643 : void simPVTfromOASys(EnergyPlusData &state, int const index, bool const FirstHVACIteration)
1250 : {
1251 45643 : PlantLocation dummyLoc(0, DataPlant::LoopSideLocation::Invalid, 0, 0);
1252 45643 : Real64 dummyCurLoad(0.0);
1253 45643 : bool dummyRunFlag(true);
1254 :
1255 45643 : state.dataPhotovoltaicThermalCollector->PVT(index).simulate(state, dummyLoc, FirstHVACIteration, dummyCurLoad, dummyRunFlag);
1256 45643 : }
1257 :
1258 : } // namespace PhotovoltaicThermalCollectors
1259 :
1260 2313 : } // namespace EnergyPlus
|