Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 : #include <cmath>
50 :
51 : // ObjexxFCL Headers
52 : #include <ObjexxFCL/Array.functions.hh>
53 :
54 : // EnergyPlus Headers
55 : #include <EnergyPlus/BranchNodeConnections.hh>
56 : #include <EnergyPlus/Data/EnergyPlusData.hh>
57 : #include <EnergyPlus/DataEnvironment.hh>
58 : #include <EnergyPlus/DataHVACGlobals.hh>
59 : #include <EnergyPlus/DataHeatBalance.hh>
60 : #include <EnergyPlus/DataIPShortCuts.hh>
61 : #include <EnergyPlus/DataLoopNode.hh>
62 : #include <EnergyPlus/DataSurfaces.hh>
63 : #include <EnergyPlus/FluidProperties.hh>
64 : #include <EnergyPlus/General.hh>
65 : #include <EnergyPlus/GlobalNames.hh>
66 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
67 : #include <EnergyPlus/NodeInputManager.hh>
68 : #include <EnergyPlus/OutputProcessor.hh>
69 : #include <EnergyPlus/Plant/DataPlant.hh>
70 : #include <EnergyPlus/PlantUtilities.hh>
71 : #include <EnergyPlus/Psychrometrics.hh>
72 : #include <EnergyPlus/SolarCollectors.hh>
73 : #include <EnergyPlus/UtilityRoutines.hh>
74 :
75 : namespace EnergyPlus {
76 :
77 : namespace SolarCollectors {
78 :
79 : // MODULE INFORMATION:
80 : // AUTHOR Peter Graham Ellis
81 : // DATE WRITTEN December 2003
82 : // MODIFIED B. Nigusse, FSEC/UCF, March 2012, added ICS Collector
83 : // RE-ENGINEERED Brent Griffith, for plant upgrade, general fluid props
84 :
85 : // PURPOSE OF THIS MODULE:
86 : // Simulates solar collectors as a component on the plant loop. Currently only flat-plate collectors (glazed and
87 : // unglazed) are implemented.
88 :
89 : // METHODOLOGY EMPLOYED:
90 : // Solar collectors are called as non-zone equipment on the demand side of the plant loop. The collector object
91 : // must be connected to a WATER HEATER object on the supply side of the plant loop. Water is assumed to be
92 : // the heat transfer fluid.
93 :
94 0 : PlantComponent *CollectorData::factory(EnergyPlusData &state, std::string const &objectName)
95 : {
96 : // Process the input data
97 0 : if (state.dataSolarCollectors->GetInputFlag) {
98 0 : GetSolarCollectorInput(state);
99 0 : state.dataSolarCollectors->GetInputFlag = false;
100 : }
101 : // Now look for this particular object
102 0 : for (auto &thisSC : state.dataSolarCollectors->Collector) {
103 0 : if (thisSC.Name == objectName) {
104 0 : return &thisSC;
105 : }
106 : }
107 : // If we didn't find it, fatal
108 : ShowFatalError(state, format("LocalSolarCollectorFactory: Error getting inputs for object named: {}", objectName)); // LCOV_EXCL_LINE
109 : // Shut up the compiler
110 : return nullptr; // LCOV_EXCL_LINE
111 : }
112 :
113 0 : void GetSolarCollectorInput(EnergyPlusData &state)
114 : {
115 :
116 : // SUBROUTINE INFORMATION:
117 : // AUTHOR Peter Graham Ellis
118 : // DATE WRITTEN December 2003
119 : // MODIFIED na
120 : // RE-ENGINEERED na
121 :
122 : // PURPOSE OF THIS SUBROUTINE:
123 : // Gets the solar collector input from the input file and sets up the parameters and collector objects.
124 :
125 0 : bool ErrorsFound(false); // Set to true if errors in input, fatal at end of routine
126 : int IOStatus; // Used in GetObjectItem
127 : int NumAlphas; // Number of Alphas for each GetObjectItem call
128 : int NumNumbers; // Number of Numbers for each GetObjectItem call
129 0 : std::string CurrentModuleObject; // for ease in renaming.
130 0 : std::string CurrentModuleParamObject; // for ease in renaming.
131 :
132 : int NumFields; // Total number of fields in object
133 : int MaxAlphas; // Maximum number of alpha fields in all objects
134 : int MaxNumbers; // Maximum number of numeric fields in all objects
135 :
136 0 : Array1D<Real64> Numbers; // Numeric data
137 0 : Array1D_string Alphas; // Alpha data
138 0 : Array1D_string cAlphaFields; // Alpha field names
139 0 : Array1D_string cNumericFields; // Numeric field names
140 0 : Array1D_bool lAlphaBlanks; // Logical array, alpha field input BLANK = .TRUE.
141 0 : Array1D_bool lNumericBlanks; // Logical array, numeric field input BLANK = .TRUE.
142 :
143 0 : MaxNumbers = 0;
144 0 : MaxAlphas = 0;
145 :
146 0 : CurrentModuleParamObject = "SolarCollectorPerformance:FlatPlate";
147 0 : int NumOfFlatPlateParam = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleParamObject);
148 0 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleParamObject, NumFields, NumAlphas, NumNumbers);
149 0 : MaxNumbers = max(MaxNumbers, NumNumbers);
150 0 : MaxAlphas = max(MaxAlphas, NumAlphas);
151 :
152 0 : CurrentModuleObject = "SolarCollector:FlatPlate:Water";
153 0 : int NumFlatPlateUnits = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
154 0 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleObject, NumFields, NumAlphas, NumNumbers);
155 0 : MaxNumbers = max(MaxNumbers, NumNumbers);
156 0 : MaxAlphas = max(MaxAlphas, NumAlphas);
157 :
158 0 : CurrentModuleParamObject = "SolarCollectorPerformance:IntegralCollectorStorage";
159 0 : int NumOfICSParam = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleParamObject);
160 0 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleParamObject, NumFields, NumAlphas, NumNumbers);
161 0 : MaxNumbers = max(MaxNumbers, NumNumbers);
162 0 : MaxAlphas = max(MaxAlphas, NumAlphas);
163 :
164 0 : CurrentModuleObject = "SolarCollector:IntegralCollectorStorage";
165 0 : int NumOfICSUnits = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, CurrentModuleObject);
166 0 : state.dataInputProcessing->inputProcessor->getObjectDefMaxArgs(state, CurrentModuleObject, NumFields, NumAlphas, NumNumbers);
167 0 : MaxNumbers = max(MaxNumbers, NumNumbers);
168 0 : MaxAlphas = max(MaxAlphas, NumAlphas);
169 :
170 0 : Alphas.allocate(MaxAlphas);
171 0 : Numbers.dimension(MaxNumbers, 0.0);
172 0 : cAlphaFields.allocate(MaxAlphas);
173 0 : cNumericFields.allocate(MaxNumbers);
174 0 : lAlphaBlanks.dimension(MaxAlphas, true);
175 0 : lNumericBlanks.dimension(MaxNumbers, true);
176 :
177 0 : state.dataSolarCollectors->NumOfCollectors = NumFlatPlateUnits + NumOfICSUnits;
178 0 : state.dataSolarCollectors->NumOfParameters = NumOfFlatPlateParam + NumOfICSParam;
179 :
180 0 : if (state.dataSolarCollectors->NumOfParameters > 0) {
181 0 : state.dataSolarCollectors->Parameters.allocate(state.dataSolarCollectors->NumOfParameters);
182 :
183 0 : CurrentModuleParamObject = "SolarCollectorPerformance:FlatPlate";
184 :
185 0 : for (int FlatPlateParamNum = 1; FlatPlateParamNum <= NumOfFlatPlateParam; ++FlatPlateParamNum) {
186 :
187 0 : int ParametersNum = FlatPlateParamNum;
188 0 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
189 : CurrentModuleParamObject,
190 : ParametersNum,
191 0 : state.dataIPShortCut->cAlphaArgs,
192 : NumAlphas,
193 0 : state.dataIPShortCut->rNumericArgs,
194 : NumNumbers,
195 : IOStatus,
196 0 : state.dataIPShortCut->lNumericFieldBlanks,
197 : _,
198 0 : state.dataIPShortCut->cAlphaFieldNames,
199 0 : state.dataIPShortCut->cNumericFieldNames);
200 :
201 : // Collector module parameters name
202 0 : GlobalNames::VerifyUniqueInterObjectName(state,
203 0 : state.dataSolarCollectors->UniqueParametersNames,
204 0 : state.dataIPShortCut->cAlphaArgs(1),
205 : CurrentModuleObject,
206 0 : state.dataIPShortCut->cAlphaFieldNames(1),
207 : ErrorsFound);
208 0 : state.dataSolarCollectors->Parameters(ParametersNum).Name = state.dataIPShortCut->cAlphaArgs(1);
209 :
210 : // NOTE: This values serves mainly as a reference. The area of the associated surface object is used in all calculations.
211 0 : state.dataSolarCollectors->Parameters(ParametersNum).Area = state.dataIPShortCut->rNumericArgs(1);
212 :
213 : // The TestFluid member variable was never accessed, so this input field seems to not do anything as of right now
214 : // if (state.dataIPShortCut->cAlphaArgs(2) == "WATER") {
215 : // state.dataSolarCollectors->Parameters(ParametersNum).TestFluid = FluidEnum::WATER;
216 : // // CASE('AIR')
217 : // // Parameters(ParametersNum)%TestFluid = AIR
218 : // } else {
219 : // ShowSevereError(state, format("{}{} = {}: {}{} is an unsupported Test Fluid for {}{}", //,
220 : // CurrentModuleParamObject, state.dataIPShortCut->cAlphaArgs(1), //, state.dataIPShortCut->cAlphaArgs(2), //,
221 : // state.dataIPShortCut->cAlphaFieldNames(2))); ErrorsFound = true;
222 : // }
223 :
224 0 : if (state.dataIPShortCut->rNumericArgs(2) > 0.0) {
225 0 : state.dataSolarCollectors->Parameters(ParametersNum).TestMassFlowRate =
226 0 : state.dataIPShortCut->rNumericArgs(2) * Psychrometrics::RhoH2O(Constant::InitConvTemp);
227 : } else {
228 0 : ShowSevereError(state,
229 0 : format("{} = {}: flow rate must be greater than zero for {}",
230 : CurrentModuleParamObject,
231 0 : state.dataIPShortCut->cAlphaArgs(1),
232 0 : state.dataIPShortCut->cNumericFieldNames(2)));
233 0 : ErrorsFound = true;
234 : }
235 :
236 0 : std::string_view const key = state.dataIPShortCut->cAlphaArgs(3);
237 0 : state.dataSolarCollectors->Parameters(ParametersNum).TestType = static_cast<TestTypeEnum>(getEnumValue(testTypesUC, key));
238 0 : if (state.dataSolarCollectors->Parameters(ParametersNum).TestType == TestTypeEnum::INVALID) {
239 0 : ShowSevereError(state,
240 0 : format("{} = {}: {} is not supported for {}",
241 : CurrentModuleParamObject,
242 0 : state.dataIPShortCut->cAlphaArgs(1),
243 : key,
244 0 : state.dataIPShortCut->cAlphaFieldNames(3)));
245 0 : ErrorsFound = true;
246 : }
247 :
248 : // Efficiency equation coefficients
249 0 : state.dataSolarCollectors->Parameters(ParametersNum).eff0 = state.dataIPShortCut->rNumericArgs(3);
250 0 : state.dataSolarCollectors->Parameters(ParametersNum).eff1 = state.dataIPShortCut->rNumericArgs(4);
251 :
252 0 : if (NumNumbers > 4) {
253 0 : state.dataSolarCollectors->Parameters(ParametersNum).eff2 = state.dataIPShortCut->rNumericArgs(5);
254 : } else {
255 0 : state.dataSolarCollectors->Parameters(ParametersNum).eff2 = 0.0;
256 : }
257 :
258 : // Incident angle modifier coefficients
259 0 : if (NumNumbers > 5) {
260 0 : state.dataSolarCollectors->Parameters(ParametersNum).iam1 = state.dataIPShortCut->rNumericArgs(6);
261 : } else {
262 0 : state.dataSolarCollectors->Parameters(ParametersNum).iam1 = 0.0;
263 : }
264 :
265 0 : if (NumNumbers > 6) {
266 0 : state.dataSolarCollectors->Parameters(FlatPlateParamNum).iam2 = state.dataIPShortCut->rNumericArgs(7);
267 : } else {
268 0 : state.dataSolarCollectors->Parameters(ParametersNum).iam2 = 0.0;
269 : }
270 : } // ParametersNum
271 :
272 0 : if (ErrorsFound) ShowFatalError(state, format("Errors in {} input.", CurrentModuleParamObject));
273 : }
274 :
275 0 : if (state.dataSolarCollectors->NumOfCollectors > 0) {
276 0 : state.dataSolarCollectors->Collector.allocate(state.dataSolarCollectors->NumOfCollectors);
277 :
278 0 : CurrentModuleObject = "SolarCollector:FlatPlate:Water";
279 :
280 0 : for (int FlatPlateUnitsNum = 1; FlatPlateUnitsNum <= NumFlatPlateUnits; ++FlatPlateUnitsNum) {
281 :
282 0 : int CollectorNum = FlatPlateUnitsNum;
283 :
284 0 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
285 : CurrentModuleObject,
286 : CollectorNum,
287 0 : state.dataIPShortCut->cAlphaArgs,
288 : NumAlphas,
289 0 : state.dataIPShortCut->rNumericArgs,
290 : NumNumbers,
291 : IOStatus);
292 :
293 : // Collector name
294 0 : GlobalNames::VerifyUniqueInterObjectName(
295 0 : state, state.dataSolarCollectors->UniqueCollectorNames, state.dataIPShortCut->cAlphaArgs(1), CurrentModuleObject, ErrorsFound);
296 0 : state.dataSolarCollectors->Collector(CollectorNum).Name = state.dataIPShortCut->cAlphaArgs(1);
297 0 : state.dataSolarCollectors->Collector(CollectorNum).Type =
298 : DataPlant::PlantEquipmentType::SolarCollectorFlatPlate; // parameter assigned in DataPlant
299 :
300 : // Get parameters object
301 0 : int ParametersNum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(2), state.dataSolarCollectors->Parameters);
302 :
303 0 : if (ParametersNum == 0) {
304 0 : ShowSevereError(state,
305 0 : format("{} = {}: {} object called {} not found.",
306 : CurrentModuleObject,
307 0 : state.dataIPShortCut->cAlphaArgs(1),
308 : CurrentModuleParamObject,
309 0 : state.dataIPShortCut->cAlphaArgs(2)));
310 0 : ErrorsFound = true;
311 : } else {
312 0 : state.dataSolarCollectors->Collector(CollectorNum).Parameters = ParametersNum;
313 : }
314 :
315 : // Get surface object
316 0 : int SurfNum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(3), state.dataSurface->Surface);
317 :
318 0 : if (SurfNum == 0) {
319 0 : ShowSevereError(state,
320 0 : format("{} = {}: Surface {} not found.",
321 : CurrentModuleObject,
322 0 : state.dataIPShortCut->cAlphaArgs(1),
323 0 : state.dataIPShortCut->cAlphaArgs(3)));
324 0 : ErrorsFound = true;
325 0 : continue; // avoid hard crash
326 : } else {
327 :
328 0 : if (!state.dataSurface->Surface(SurfNum).ExtSolar) {
329 0 : ShowWarningError(state,
330 0 : format("{} = {}: Surface {} is not exposed to exterior radiation.",
331 : CurrentModuleObject,
332 0 : state.dataIPShortCut->cAlphaArgs(1),
333 0 : state.dataIPShortCut->cAlphaArgs(3)));
334 : }
335 :
336 : // check surface orientation, warn if upside down
337 0 : if ((state.dataSurface->Surface(SurfNum).Tilt < -95.0) || (state.dataSurface->Surface(SurfNum).Tilt > 95.0)) {
338 0 : ShowWarningError(state,
339 0 : format("Suspected input problem with {} = {}",
340 0 : state.dataIPShortCut->cAlphaFieldNames(3),
341 0 : state.dataIPShortCut->cAlphaArgs(3)));
342 0 : ShowContinueError(
343 0 : state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
344 0 : ShowContinueError(state, "Surface used for solar collector faces down");
345 0 : ShowContinueError(
346 : state,
347 0 : format("Surface tilt angle (degrees from ground outward normal) = {:.2R}", state.dataSurface->Surface(SurfNum).Tilt));
348 : }
349 :
350 : // Check to make sure other solar collectors are not using the same surface
351 : // NOTE: Must search over all solar collector types
352 0 : for (int CollectorNum2 = 1; CollectorNum2 <= NumFlatPlateUnits; ++CollectorNum2) {
353 0 : if (state.dataSolarCollectors->Collector(CollectorNum2).Surface == SurfNum) {
354 0 : ShowSevereError(state,
355 0 : format("{} = {}: Surface {} is referenced by more than one {}",
356 : CurrentModuleObject,
357 0 : state.dataIPShortCut->cAlphaArgs(1),
358 0 : state.dataIPShortCut->cAlphaArgs(3),
359 : CurrentModuleObject));
360 0 : ErrorsFound = true;
361 0 : break;
362 : }
363 : } // CollectorNum2
364 :
365 0 : state.dataSolarCollectors->Collector(CollectorNum).Surface = SurfNum;
366 : }
367 :
368 : // Give warning if surface area and gross area do not match within tolerance
369 0 : if (SurfNum > 0 && ParametersNum > 0 && state.dataSolarCollectors->Parameters(ParametersNum).Area > 0.0 &&
370 0 : std::abs(state.dataSolarCollectors->Parameters(ParametersNum).Area - state.dataSurface->Surface(SurfNum).Area) /
371 0 : state.dataSurface->Surface(SurfNum).Area >
372 : 0.01) {
373 :
374 0 : ShowWarningError(state,
375 0 : format("{} = {}: Gross Area of solar collector parameters and surface object differ by more than 1%.",
376 : CurrentModuleObject,
377 0 : state.dataIPShortCut->cAlphaArgs(1)));
378 0 : ShowContinueError(state, "Area of surface object will be used in all calculations.");
379 : }
380 :
381 0 : state.dataSolarCollectors->Collector(CollectorNum).InletNode =
382 0 : NodeInputManager::GetOnlySingleNode(state,
383 0 : state.dataIPShortCut->cAlphaArgs(4),
384 : ErrorsFound,
385 : DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlateWater,
386 0 : state.dataIPShortCut->cAlphaArgs(1),
387 : DataLoopNode::NodeFluidType::Water,
388 : DataLoopNode::ConnectionType::Inlet,
389 : NodeInputManager::CompFluidStream::Primary,
390 : DataLoopNode::ObjectIsNotParent);
391 0 : state.dataSolarCollectors->Collector(CollectorNum).OutletNode =
392 0 : NodeInputManager::GetOnlySingleNode(state,
393 0 : state.dataIPShortCut->cAlphaArgs(5),
394 : ErrorsFound,
395 : DataLoopNode::ConnectionObjectType::SolarCollectorFlatPlateWater,
396 0 : state.dataIPShortCut->cAlphaArgs(1),
397 : DataLoopNode::NodeFluidType::Water,
398 : DataLoopNode::ConnectionType::Outlet,
399 : NodeInputManager::CompFluidStream::Primary,
400 : DataLoopNode::ObjectIsNotParent);
401 :
402 0 : if (NumNumbers > 0) {
403 0 : state.dataSolarCollectors->Collector(CollectorNum).VolFlowRateMax =
404 0 : state.dataIPShortCut->rNumericArgs(1); // Max volumetric flow rate used for plant sizing calculation
405 : } else {
406 0 : state.dataSolarCollectors->Collector(CollectorNum).VolFlowRateMax =
407 : 0.0; // Max vol flow rate is not specified; no flow for plant sizing calculation
408 0 : state.dataSolarCollectors->Collector(CollectorNum).MassFlowRateMax =
409 : 999999.9; // But...set a very high value so that it demands as much as possible
410 : }
411 :
412 0 : BranchNodeConnections::TestCompSet(state,
413 : CurrentModuleObject,
414 0 : state.dataIPShortCut->cAlphaArgs(1),
415 0 : state.dataIPShortCut->cAlphaArgs(4),
416 0 : state.dataIPShortCut->cAlphaArgs(5),
417 : "Water Nodes");
418 :
419 : } // FlatPlateUnitsNum
420 :
421 : // Get data for ICS collector
422 0 : CurrentModuleParamObject = "SolarCollectorPerformance:IntegralCollectorStorage";
423 :
424 0 : for (int ICSParamNum = 1; ICSParamNum <= NumOfICSParam; ++ICSParamNum) {
425 :
426 0 : int ParametersNum = ICSParamNum + NumOfFlatPlateParam;
427 :
428 0 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
429 : CurrentModuleParamObject,
430 : ICSParamNum,
431 0 : state.dataIPShortCut->cAlphaArgs,
432 : NumAlphas,
433 0 : state.dataIPShortCut->rNumericArgs,
434 : NumNumbers,
435 : IOStatus,
436 0 : state.dataIPShortCut->lNumericFieldBlanks,
437 : _,
438 0 : state.dataIPShortCut->cAlphaFieldNames,
439 0 : state.dataIPShortCut->cNumericFieldNames);
440 :
441 : // Collector module parameters name
442 0 : GlobalNames::VerifyUniqueInterObjectName(state,
443 0 : state.dataSolarCollectors->UniqueParametersNames,
444 0 : state.dataIPShortCut->cAlphaArgs(1),
445 : CurrentModuleObject,
446 0 : state.dataIPShortCut->cAlphaFieldNames(1),
447 : ErrorsFound);
448 0 : state.dataSolarCollectors->Parameters(ParametersNum).Name = state.dataIPShortCut->cAlphaArgs(1);
449 : // NOTE: currently the only available choice is RectangularTank. In the future progressive tube type will be
450 : // added
451 : // if (Util::SameString(state.dataIPShortCut->cAlphaArgs(2), "RectangularTank")) {
452 : // state.dataSolarCollectors->Parameters(ParametersNum).ICSType_Num = TankTypeEnum::ICSRectangularTank;
453 : // } else {
454 : // ShowSevereError(state, format("{}{} not found={}{} in {}{} ={}{}", //,
455 : // state.dataIPShortCut->cAlphaFieldNames(2), state.dataIPShortCut->cAlphaArgs(2), //, //,
456 : // CurrentModuleParamObject, //, state.dataSolarCollectors->Parameters(ParametersNum).Name)); ErrorsFound = true;
457 : // }
458 : // NOTE: This collector gross area is used in all the calculations.
459 0 : state.dataSolarCollectors->Parameters(ParametersNum).Area = state.dataIPShortCut->rNumericArgs(1);
460 0 : if (state.dataIPShortCut->rNumericArgs(1) <= 0.0) {
461 0 : ShowSevereError(state, format("{} = {}", CurrentModuleParamObject, state.dataIPShortCut->cAlphaArgs(1)));
462 0 : ShowContinueError(
463 0 : state, format("Illegal {} = {:.2R}", state.dataIPShortCut->cNumericFieldNames(1), state.dataIPShortCut->rNumericArgs(1)));
464 0 : ShowContinueError(state, " Collector gross area must be always greater than zero.");
465 0 : ErrorsFound = true;
466 : }
467 0 : state.dataSolarCollectors->Parameters(ParametersNum).Volume = state.dataIPShortCut->rNumericArgs(2);
468 0 : if (state.dataIPShortCut->rNumericArgs(2) <= 0.0) {
469 0 : ShowSevereError(state, format("{} = {}", CurrentModuleParamObject, state.dataIPShortCut->cAlphaArgs(1)));
470 0 : ShowContinueError(
471 0 : state, format("Illegal {} = {:.2R}", state.dataIPShortCut->cNumericFieldNames(2), state.dataIPShortCut->rNumericArgs(2)));
472 0 : ShowContinueError(state, " Collector water volume must be always greater than zero.");
473 0 : ErrorsFound = true;
474 : }
475 : // Note: this value is used to calculate the heat loss through the bottom and side of the collector
476 0 : state.dataSolarCollectors->Parameters(ParametersNum).ULossBottom = state.dataIPShortCut->rNumericArgs(3);
477 0 : state.dataSolarCollectors->Parameters(ParametersNum).ULossSide = state.dataIPShortCut->rNumericArgs(4);
478 0 : state.dataSolarCollectors->Parameters(ParametersNum).AspectRatio = state.dataIPShortCut->rNumericArgs(5);
479 0 : state.dataSolarCollectors->Parameters(ParametersNum).SideHeight = state.dataIPShortCut->rNumericArgs(6);
480 0 : state.dataSolarCollectors->Parameters(ParametersNum).ThermalMass = state.dataIPShortCut->rNumericArgs(7);
481 0 : state.dataSolarCollectors->Parameters(ParametersNum).NumOfCovers = state.dataIPShortCut->rNumericArgs(8);
482 0 : state.dataSolarCollectors->Parameters(ParametersNum).CoverSpacing = state.dataIPShortCut->rNumericArgs(9);
483 :
484 0 : if (state.dataSolarCollectors->Parameters(ParametersNum).NumOfCovers == 2) {
485 : // Outer cover refractive index
486 0 : state.dataSolarCollectors->Parameters(ParametersNum).RefractiveIndex[0] = state.dataIPShortCut->rNumericArgs(10);
487 : // Outer cover extinction coefficient times thickness of the cover
488 0 : state.dataSolarCollectors->Parameters(ParametersNum).ExtCoefTimesThickness[0] = state.dataIPShortCut->rNumericArgs(11);
489 : // Outer cover Emissivity
490 0 : state.dataSolarCollectors->Parameters(ParametersNum).EmissOfCover[0] = state.dataIPShortCut->rNumericArgs(12);
491 :
492 0 : if (!state.dataIPShortCut->lNumericFieldBlanks(13) || !state.dataIPShortCut->lNumericFieldBlanks(14) ||
493 0 : !state.dataIPShortCut->lNumericFieldBlanks(15)) {
494 0 : state.dataSolarCollectors->Parameters(ParametersNum).RefractiveIndex[1] = state.dataIPShortCut->rNumericArgs(13);
495 0 : state.dataSolarCollectors->Parameters(ParametersNum).ExtCoefTimesThickness[1] = state.dataIPShortCut->rNumericArgs(14);
496 0 : state.dataSolarCollectors->Parameters(ParametersNum).EmissOfCover[1] = state.dataIPShortCut->rNumericArgs(15);
497 : } else {
498 0 : ShowSevereError(state, format("{} = {}", CurrentModuleParamObject, state.dataIPShortCut->cAlphaArgs(1)));
499 0 : ShowContinueError(state, "Illegal input for one of the three inputs of the inner cover optical properties");
500 0 : ErrorsFound = true;
501 : }
502 0 : } else if (state.dataSolarCollectors->Parameters(ParametersNum).NumOfCovers == 1) {
503 : // Outer cover refractive index
504 0 : state.dataSolarCollectors->Parameters(ParametersNum).RefractiveIndex[0] = state.dataIPShortCut->rNumericArgs(10);
505 : // Outer cover extinction coefficient times thickness of the cover
506 0 : state.dataSolarCollectors->Parameters(ParametersNum).ExtCoefTimesThickness[0] = state.dataIPShortCut->rNumericArgs(11);
507 : // Outer cover emissivity
508 0 : state.dataSolarCollectors->Parameters(ParametersNum).EmissOfCover[0] = state.dataIPShortCut->rNumericArgs(12);
509 : } else {
510 0 : ShowSevereError(state, format("{} = {}", CurrentModuleParamObject, state.dataIPShortCut->cAlphaArgs(1)));
511 0 : ShowContinueError(
512 0 : state, format("Illegal {} = {:.2R}", state.dataIPShortCut->cNumericFieldNames(8), state.dataIPShortCut->rNumericArgs(8)));
513 0 : ErrorsFound = true;
514 : }
515 : // Solar absorptance of the absorber plate
516 0 : state.dataSolarCollectors->Parameters(ParametersNum).AbsorOfAbsPlate = state.dataIPShortCut->rNumericArgs(16);
517 : // thermal emissivity of the absorber plate
518 0 : state.dataSolarCollectors->Parameters(ParametersNum).EmissOfAbsPlate = state.dataIPShortCut->rNumericArgs(17);
519 :
520 : } // end of ParametersNum
521 :
522 0 : if (ErrorsFound) ShowFatalError(state, format("Errors in {} input.", CurrentModuleParamObject));
523 :
524 0 : CurrentModuleObject = "SolarCollector:IntegralCollectorStorage";
525 :
526 0 : for (int ICSUnitsNum = 1; ICSUnitsNum <= NumOfICSUnits; ++ICSUnitsNum) {
527 :
528 0 : int CollectorNum = ICSUnitsNum + NumFlatPlateUnits;
529 :
530 0 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
531 : CurrentModuleObject,
532 : ICSUnitsNum,
533 0 : state.dataIPShortCut->cAlphaArgs,
534 : NumAlphas,
535 0 : state.dataIPShortCut->rNumericArgs,
536 : NumNumbers,
537 : IOStatus,
538 0 : state.dataIPShortCut->lNumericFieldBlanks,
539 : lAlphaBlanks,
540 0 : state.dataIPShortCut->cAlphaFieldNames,
541 0 : state.dataIPShortCut->cNumericFieldNames);
542 :
543 : // Collector name
544 0 : GlobalNames::VerifyUniqueInterObjectName(state,
545 0 : state.dataSolarCollectors->UniqueCollectorNames,
546 0 : state.dataIPShortCut->cAlphaArgs(1),
547 : CurrentModuleObject,
548 0 : state.dataIPShortCut->cAlphaFieldNames(1),
549 : ErrorsFound);
550 0 : state.dataSolarCollectors->Collector(CollectorNum).Name = state.dataIPShortCut->cAlphaArgs(1);
551 0 : state.dataSolarCollectors->Collector(CollectorNum).Type =
552 : DataPlant::PlantEquipmentType::SolarCollectorICS; // parameter assigned in DataPlant
553 :
554 0 : state.dataSolarCollectors->Collector(CollectorNum).InitICS = true;
555 :
556 : // Get parameters object
557 0 : int ParametersNum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(2), state.dataSolarCollectors->Parameters);
558 :
559 0 : if (ParametersNum == 0) {
560 0 : ShowSevereError(state,
561 0 : format("{} = {}: {} object called {} not found.",
562 : CurrentModuleObject,
563 0 : state.dataIPShortCut->cAlphaArgs(1),
564 : CurrentModuleParamObject,
565 0 : state.dataIPShortCut->cAlphaArgs(2)));
566 0 : ErrorsFound = true;
567 : } else {
568 0 : state.dataSolarCollectors->Collector(CollectorNum).Parameters = ParametersNum;
569 : }
570 :
571 0 : if (ParametersNum > 0) {
572 : // Calculate constant collector parameters only once
573 0 : Real64 Perimeter = 2.0 * std::sqrt(state.dataSolarCollectors->Parameters(ParametersNum).Area) *
574 0 : (std::sqrt(state.dataSolarCollectors->Parameters(ParametersNum).AspectRatio) +
575 0 : 1.0 / std::sqrt(state.dataSolarCollectors->Parameters(ParametersNum).AspectRatio));
576 0 : state.dataSolarCollectors->Collector(CollectorNum).Length = std::sqrt(
577 0 : state.dataSolarCollectors->Parameters(ParametersNum).Area / state.dataSolarCollectors->Parameters(ParametersNum).AspectRatio);
578 :
579 : // calculate the collector side heat transfer area and loss coefficient
580 0 : state.dataSolarCollectors->Collector(CollectorNum).Area = state.dataSolarCollectors->Parameters(ParametersNum).Area;
581 0 : state.dataSolarCollectors->Collector(CollectorNum).Volume = state.dataSolarCollectors->Parameters(ParametersNum).Volume;
582 0 : state.dataSolarCollectors->Collector(CollectorNum).SideArea =
583 0 : Perimeter * state.dataSolarCollectors->Parameters(ParametersNum).SideHeight;
584 0 : state.dataSolarCollectors->Collector(CollectorNum).AreaRatio =
585 0 : state.dataSolarCollectors->Collector(CollectorNum).SideArea / state.dataSolarCollectors->Collector(CollectorNum).Area;
586 : }
587 : // Get surface object
588 0 : int SurfNum = Util::FindItemInList(state.dataIPShortCut->cAlphaArgs(3), state.dataSurface->Surface);
589 :
590 0 : if (SurfNum == 0) {
591 0 : ShowSevereError(state,
592 0 : format("{} = {}: Surface {} not found.",
593 : CurrentModuleObject,
594 0 : state.dataIPShortCut->cAlphaArgs(1),
595 0 : state.dataIPShortCut->cAlphaArgs(3)));
596 0 : ErrorsFound = true;
597 0 : continue; // avoid hard crash
598 : } else {
599 :
600 0 : if (!state.dataSurface->Surface(SurfNum).ExtSolar) {
601 0 : ShowWarningError(state,
602 0 : format("{} = {}: Surface {} is not exposed to exterior radiation.",
603 : CurrentModuleObject,
604 0 : state.dataIPShortCut->cAlphaArgs(1),
605 0 : state.dataIPShortCut->cAlphaArgs(3)));
606 : }
607 :
608 : // check surface orientation, warn if upside down
609 0 : if ((state.dataSurface->Surface(SurfNum).Tilt < -95.0) || (state.dataSurface->Surface(SurfNum).Tilt > 95.0)) {
610 0 : ShowWarningError(state,
611 0 : format("Suspected input problem with {} = {}",
612 0 : state.dataIPShortCut->cAlphaFieldNames(3),
613 0 : state.dataIPShortCut->cAlphaArgs(3)));
614 0 : ShowContinueError(
615 0 : state, format("Entered in {} = {}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
616 0 : ShowContinueError(state, "Surface used for solar collector faces down");
617 0 : ShowContinueError(
618 : state,
619 0 : format("Surface tilt angle (degrees from ground outward normal) = {:.2R}", state.dataSurface->Surface(SurfNum).Tilt));
620 : }
621 :
622 : // Check to make sure other solar collectors are not using the same surface
623 : // NOTE: Must search over all solar collector types
624 0 : for (int CollectorNum2 = 1; CollectorNum2 <= state.dataSolarCollectors->NumOfCollectors; ++CollectorNum2) {
625 0 : if (state.dataSolarCollectors->Collector(CollectorNum2).Surface == SurfNum) {
626 0 : ShowSevereError(state,
627 0 : format("{} = {}: Surface {} is referenced by more than one {}",
628 : CurrentModuleObject,
629 0 : state.dataIPShortCut->cAlphaArgs(1),
630 0 : state.dataIPShortCut->cAlphaArgs(3),
631 : CurrentModuleObject));
632 0 : ErrorsFound = true;
633 0 : break;
634 : }
635 : } // ICSNum2
636 :
637 0 : state.dataSolarCollectors->Collector(CollectorNum).Surface = SurfNum;
638 : }
639 :
640 : // Give warning if surface area and gross area do not match within tolerance
641 0 : if (SurfNum > 0 && ParametersNum > 0 && state.dataSolarCollectors->Parameters(ParametersNum).Area > 0.0 &&
642 0 : std::abs(state.dataSolarCollectors->Parameters(ParametersNum).Area - state.dataSurface->Surface(SurfNum).Area) /
643 0 : state.dataSurface->Surface(SurfNum).Area >
644 : 0.01) {
645 :
646 0 : ShowWarningError(state, format("{} = {}: ", CurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
647 0 : ShowContinueError(state, "Gross area of solar collector parameters and surface object differ by more than 1%.");
648 0 : ShowContinueError(state, "Gross collector area is always used in the calculation. Modify the surface ");
649 0 : ShowContinueError(state, "coordinates to match its area with collector gross area. Otherwise, the underlying ");
650 0 : ShowContinueError(state, "surface is assumed to be fully shaded when it is not.");
651 : }
652 :
653 0 : state.dataSolarCollectors->Collector(CollectorNum).BCType = state.dataIPShortCut->cAlphaArgs(4);
654 0 : if (Util::SameString(state.dataIPShortCut->cAlphaArgs(4), "AmbientAir")) {
655 0 : state.dataSolarCollectors->Collector(CollectorNum).OSCMName = "";
656 0 : } else if (Util::SameString(state.dataIPShortCut->cAlphaArgs(4), "OtherSideConditionsModel")) {
657 0 : state.dataSolarCollectors->Collector(CollectorNum).OSCMName = state.dataIPShortCut->cAlphaArgs(5);
658 0 : state.dataSolarCollectors->Collector(CollectorNum).OSCM_ON = true;
659 0 : int Found = Util::FindItemInList(state.dataSolarCollectors->Collector(CollectorNum).OSCMName, state.dataSurface->OSCM);
660 0 : if (Found == 0) {
661 0 : ShowSevereError(state,
662 0 : format("{} not found={} in {} ={}",
663 0 : state.dataIPShortCut->cAlphaFieldNames(5),
664 0 : state.dataSolarCollectors->Collector(CollectorNum).OSCMName,
665 : CurrentModuleObject,
666 0 : state.dataSolarCollectors->Collector(CollectorNum).Name));
667 0 : ErrorsFound = true;
668 : }
669 : } else {
670 0 : ShowSevereError(state,
671 0 : format("{} not found={} in {} ={}",
672 0 : state.dataIPShortCut->cAlphaFieldNames(5),
673 0 : state.dataSolarCollectors->Collector(CollectorNum).BCType,
674 : CurrentModuleObject,
675 0 : state.dataSolarCollectors->Collector(CollectorNum).Name));
676 0 : ErrorsFound = true;
677 : }
678 :
679 0 : if (state.dataSolarCollectors->Collector(CollectorNum).OSCM_ON) {
680 : // get index of ventilated cavity object
681 0 : int VentCavIndex = 0;
682 0 : SolarCollectors::CollectorData::GetExtVentedCavityIndex(state, SurfNum, VentCavIndex);
683 0 : state.dataSolarCollectors->Collector(CollectorNum).VentCavIndex = VentCavIndex;
684 : }
685 :
686 0 : state.dataSolarCollectors->Collector(CollectorNum).InletNode =
687 0 : NodeInputManager::GetOnlySingleNode(state,
688 0 : state.dataIPShortCut->cAlphaArgs(6),
689 : ErrorsFound,
690 : DataLoopNode::ConnectionObjectType::SolarCollectorIntegralCollectorStorage,
691 0 : state.dataIPShortCut->cAlphaArgs(1),
692 : DataLoopNode::NodeFluidType::Water,
693 : DataLoopNode::ConnectionType::Inlet,
694 : NodeInputManager::CompFluidStream::Primary,
695 : DataLoopNode::ObjectIsNotParent);
696 0 : state.dataSolarCollectors->Collector(CollectorNum).OutletNode =
697 0 : NodeInputManager::GetOnlySingleNode(state,
698 0 : state.dataIPShortCut->cAlphaArgs(7),
699 : ErrorsFound,
700 : DataLoopNode::ConnectionObjectType::SolarCollectorIntegralCollectorStorage,
701 0 : state.dataIPShortCut->cAlphaArgs(1),
702 : DataLoopNode::NodeFluidType::Water,
703 : DataLoopNode::ConnectionType::Outlet,
704 : NodeInputManager::CompFluidStream::Primary,
705 : DataLoopNode::ObjectIsNotParent);
706 :
707 0 : if (NumNumbers > 0) {
708 0 : state.dataSolarCollectors->Collector(CollectorNum).VolFlowRateMax =
709 0 : state.dataIPShortCut->rNumericArgs(1); // Max volumetric flow rate used for plant sizing calculation
710 : } else {
711 0 : state.dataSolarCollectors->Collector(CollectorNum).VolFlowRateMax =
712 : 0.0; // Max vol flow rate is not specified; no flow for plant sizing calculation
713 0 : state.dataSolarCollectors->Collector(CollectorNum).MassFlowRateMax =
714 : 999999.9; // But...set a very high value so that it demands as much as possible
715 : }
716 :
717 0 : BranchNodeConnections::TestCompSet(state,
718 : CurrentModuleObject,
719 0 : state.dataIPShortCut->cAlphaArgs(1),
720 0 : state.dataIPShortCut->cAlphaArgs(6),
721 0 : state.dataIPShortCut->cAlphaArgs(7),
722 : "Water Nodes");
723 :
724 : } // ICSNum
725 :
726 0 : if (ErrorsFound) ShowFatalError(state, format("Errors in {} input.", CurrentModuleObject));
727 : }
728 0 : }
729 :
730 0 : void CollectorData::setupOutputVars(EnergyPlusData &state)
731 : {
732 0 : if (this->Type == DataPlant::PlantEquipmentType::SolarCollectorFlatPlate) {
733 : // Setup report variables
734 0 : SetupOutputVariable(state,
735 : "Solar Collector Incident Angle Modifier",
736 : Constant::Units::None,
737 0 : this->IncidentAngleModifier,
738 : OutputProcessor::TimeStepType::System,
739 : OutputProcessor::StoreType::Average,
740 0 : this->Name);
741 :
742 0 : SetupOutputVariable(state,
743 : "Solar Collector Efficiency",
744 : Constant::Units::None,
745 0 : this->Efficiency,
746 : OutputProcessor::TimeStepType::System,
747 : OutputProcessor::StoreType::Average,
748 0 : this->Name);
749 :
750 0 : SetupOutputVariable(state,
751 : "Solar Collector Heat Transfer Rate",
752 : Constant::Units::W,
753 0 : this->Power,
754 : OutputProcessor::TimeStepType::System,
755 : OutputProcessor::StoreType::Average,
756 0 : this->Name);
757 :
758 0 : SetupOutputVariable(state,
759 : "Solar Collector Heat Gain Rate",
760 : Constant::Units::W,
761 0 : this->HeatGain,
762 : OutputProcessor::TimeStepType::System,
763 : OutputProcessor::StoreType::Average,
764 0 : this->Name);
765 :
766 0 : SetupOutputVariable(state,
767 : "Solar Collector Heat Loss Rate",
768 : Constant::Units::W,
769 0 : this->HeatLoss,
770 : OutputProcessor::TimeStepType::System,
771 : OutputProcessor::StoreType::Average,
772 0 : this->Name);
773 :
774 0 : SetupOutputVariable(state,
775 : "Solar Collector Heat Transfer Energy",
776 : Constant::Units::J,
777 0 : this->Energy,
778 : OutputProcessor::TimeStepType::System,
779 : OutputProcessor::StoreType::Sum,
780 0 : this->Name,
781 : Constant::eResource::SolarWater,
782 : OutputProcessor::Group::Plant,
783 : OutputProcessor::EndUseCat::HeatProduced);
784 0 : } else if (this->Type == DataPlant::PlantEquipmentType::SolarCollectorICS) {
785 :
786 0 : SetupOutputVariable(state,
787 : "Solar Collector Transmittance Absorptance Product",
788 : Constant::Units::None,
789 0 : this->TauAlpha,
790 : OutputProcessor::TimeStepType::System,
791 : OutputProcessor::StoreType::Average,
792 0 : this->Name);
793 :
794 0 : SetupOutputVariable(state,
795 : "Solar Collector Overall Top Heat Loss Coefficient",
796 : Constant::Units::W_m2C,
797 0 : this->UTopLoss,
798 : OutputProcessor::TimeStepType::System,
799 : OutputProcessor::StoreType::Average,
800 0 : this->Name);
801 :
802 0 : SetupOutputVariable(state,
803 : "Solar Collector Absorber Plate Temperature",
804 : Constant::Units::C,
805 0 : this->TempOfAbsPlate,
806 : OutputProcessor::TimeStepType::System,
807 : OutputProcessor::StoreType::Average,
808 0 : this->Name);
809 :
810 0 : SetupOutputVariable(state,
811 : "Solar Collector Storage Water Temperature",
812 : Constant::Units::C,
813 0 : this->TempOfWater,
814 : OutputProcessor::TimeStepType::System,
815 : OutputProcessor::StoreType::Average,
816 0 : this->Name);
817 :
818 0 : SetupOutputVariable(state,
819 : "Solar Collector Thermal Efficiency",
820 : Constant::Units::None,
821 0 : this->Efficiency,
822 : OutputProcessor::TimeStepType::System,
823 : OutputProcessor::StoreType::Average,
824 0 : this->Name);
825 :
826 0 : SetupOutputVariable(state,
827 : "Solar Collector Storage Heat Transfer Rate",
828 : Constant::Units::W,
829 0 : this->StoredHeatRate,
830 : OutputProcessor::TimeStepType::System,
831 : OutputProcessor::StoreType::Average,
832 0 : this->Name);
833 :
834 0 : SetupOutputVariable(state,
835 : "Solar Collector Storage Heat Transfer Energy",
836 : Constant::Units::J,
837 0 : this->StoredHeatEnergy,
838 : OutputProcessor::TimeStepType::System,
839 : OutputProcessor::StoreType::Sum,
840 0 : this->Name,
841 : Constant::eResource::SolarWater,
842 : OutputProcessor::Group::Plant,
843 : OutputProcessor::EndUseCat::HeatProduced);
844 :
845 0 : SetupOutputVariable(state,
846 : "Solar Collector Skin Heat Transfer Rate",
847 : Constant::Units::W,
848 0 : this->SkinHeatLossRate,
849 : OutputProcessor::TimeStepType::System,
850 : OutputProcessor::StoreType::Average,
851 0 : this->Name);
852 :
853 0 : SetupOutputVariable(state,
854 : "Solar Collector Skin Heat Transfer Energy",
855 : Constant::Units::J,
856 0 : this->CollHeatLossEnergy,
857 : OutputProcessor::TimeStepType::System,
858 : OutputProcessor::StoreType::Sum,
859 0 : this->Name,
860 : Constant::eResource::SolarWater,
861 : OutputProcessor::Group::Plant,
862 : OutputProcessor::EndUseCat::HeatProduced);
863 :
864 0 : SetupOutputVariable(state,
865 : "Solar Collector Heat Transfer Rate",
866 : Constant::Units::W,
867 0 : this->HeatRate,
868 : OutputProcessor::TimeStepType::System,
869 : OutputProcessor::StoreType::Average,
870 0 : this->Name);
871 :
872 0 : SetupOutputVariable(state,
873 : "Solar Collector Heat Transfer Energy",
874 : Constant::Units::J,
875 0 : this->HeatEnergy,
876 : OutputProcessor::TimeStepType::System,
877 : OutputProcessor::StoreType::Sum,
878 0 : this->Name,
879 : Constant::eResource::SolarWater,
880 : OutputProcessor::Group::Plant,
881 : OutputProcessor::EndUseCat::HeatProduced);
882 : }
883 0 : }
884 :
885 0 : void CollectorData::simulate(EnergyPlusData &state,
886 : [[maybe_unused]] const PlantLocation &calledFromLocation,
887 : [[maybe_unused]] bool const FirstHVACIteration,
888 : [[maybe_unused]] Real64 &CurLoad,
889 : [[maybe_unused]] bool const RunFlag)
890 : {
891 0 : this->initialize(state);
892 :
893 0 : switch (this->Type) {
894 : // Select and CALL models based on collector type
895 0 : case DataPlant::PlantEquipmentType::SolarCollectorFlatPlate: {
896 0 : this->CalcSolarCollector(state);
897 0 : } break;
898 0 : case DataPlant::PlantEquipmentType::SolarCollectorICS: {
899 0 : this->CalcICSSolarCollector(state);
900 0 : } break;
901 0 : default: {
902 : assert(false); // LCOV_EXCL_LINE
903 : } break;
904 : }
905 :
906 0 : this->update(state);
907 :
908 0 : this->report(state);
909 0 : }
910 :
911 0 : void CollectorData::initialize(EnergyPlusData &state)
912 : {
913 :
914 : // SUBROUTINE INFORMATION:
915 : // AUTHOR Peter Graham Ellis
916 : // DATE WRITTEN January 2004
917 : // MODIFIED na
918 : // RE-ENGINEERED na
919 :
920 : // PURPOSE OF THIS SUBROUTINE:
921 : // Initializes the solar collector object during the plant simulation.
922 :
923 : // METHODOLOGY EMPLOYED:
924 : // Inlet and outlet nodes are initialized. The maximum collector flow rate is requested.
925 :
926 : static constexpr std::string_view RoutineName("InitSolarCollector");
927 0 : Real64 constexpr BigNumber(9999.9); // Component desired mass flow rate
928 :
929 0 : if (!state.dataGlobal->SysSizingCalc && this->InitSizing) {
930 0 : PlantUtilities::RegisterPlantCompDesignFlow(state, this->InletNode, this->VolFlowRateMax);
931 0 : this->InitSizing = false;
932 : }
933 :
934 0 : if (state.dataGlobal->BeginEnvrnFlag && this->Init) {
935 : // Clear node initial conditions
936 0 : if (this->VolFlowRateMax > 0) {
937 0 : Real64 rho = this->plantLoc.loop->glycol->getDensity(state, Constant::InitConvTemp, RoutineName);
938 :
939 0 : this->MassFlowRateMax = this->VolFlowRateMax * rho;
940 : } else {
941 0 : this->MassFlowRateMax = BigNumber;
942 : }
943 :
944 0 : PlantUtilities::InitComponentNodes(state, 0.0, this->MassFlowRateMax, this->InletNode, this->OutletNode);
945 :
946 0 : this->Init = false;
947 :
948 0 : if (this->InitICS) {
949 0 : this->TempOfWater = 20.0;
950 0 : this->SavedTempOfWater = this->TempOfWater;
951 0 : this->SavedTempOfAbsPlate = this->TempOfWater;
952 0 : this->TempOfAbsPlate = this->TempOfWater;
953 0 : this->TempOfInnerCover = this->TempOfWater;
954 0 : this->TempOfOuterCover = this->TempOfWater;
955 0 : this->SavedTempOfInnerCover = this->TempOfWater;
956 0 : this->SavedTempOfOuterCover = this->TempOfWater;
957 0 : this->SavedTempCollectorOSCM = this->TempOfWater;
958 : }
959 : }
960 :
961 0 : if (!state.dataGlobal->BeginEnvrnFlag) this->Init = true;
962 :
963 0 : if (this->SetDiffRadFlag && this->InitICS) {
964 : // calculates the sky and ground reflective diffuse radiation optical properties (only one time)
965 0 : int SurfNum = this->Surface;
966 0 : int ParamNum = this->Parameters;
967 :
968 0 : this->Tilt = state.dataSurface->Surface(SurfNum).Tilt;
969 0 : this->TiltR2V = std::abs(90.0 - Tilt);
970 0 : this->CosTilt = std::cos(Tilt * Constant::DegToRad);
971 0 : this->SinTilt = std::sin(1.8 * Tilt * Constant::DegToRad);
972 :
973 : // Diffuse reflectance of the cover for solar radiation diffusely reflected back from the absorber
974 : // plate to the cover. The diffuse solar radiation reflected back from the absorber plate to the
975 : // cover is represented by the 60 degree equivalent incident angle. This diffuse reflectance is
976 : // used to calculate the transmittance - absorptance product (Duffie and Beckman, 1991)
977 0 : Real64 Theta = 60.0 * Constant::DegToRad;
978 0 : Real64 TransSys = 0.0;
979 0 : Real64 RefSys = 0.0;
980 0 : Real64 AbsCover1 = 0.0;
981 0 : Real64 AbsCover2 = 0.0;
982 0 : Real64 RefSysDiffuse = 0.0;
983 0 : this->CalcTransRefAbsOfCover(state, Theta, TransSys, RefSys, AbsCover1, AbsCover2, true, RefSysDiffuse);
984 0 : this->RefDiffInnerCover = RefSysDiffuse;
985 :
986 : // transmittance-absorptance product normal incident:
987 0 : Theta = 0.0;
988 0 : this->CalcTransRefAbsOfCover(state, Theta, TransSys, RefSys, AbsCover1, AbsCover2);
989 :
990 : // transmittance-absorptance product for sky diffuse radiation. Uses equivalent incident angle
991 : // of sky radiation (radians), and is calculated according to Brandemuehl and Beckman (1980):
992 0 : Theta = (59.68 - 0.1388 * Tilt + 0.001497 * pow_2(Tilt)) * Constant::DegToRad;
993 0 : this->CalcTransRefAbsOfCover(state, Theta, TransSys, RefSys, AbsCover1, AbsCover2);
994 0 : this->TauAlphaSkyDiffuse = TransSys * state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate /
995 0 : (1.0 - (1.0 - state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate) * this->RefDiffInnerCover);
996 0 : this->CoversAbsSkyDiffuse[0] = AbsCover1;
997 0 : this->CoversAbsSkyDiffuse[1] = AbsCover2;
998 :
999 : // transmittance-absorptance product for ground diffuse radiation. Uses equivalent incident angle
1000 : // of ground radiation (radians), and is calculated according to Brandemuehl and Beckman (1980):
1001 0 : Theta = (90.0 - 0.5788 * Tilt + 0.002693 * pow_2(Tilt)) * Constant::DegToRad;
1002 0 : this->CalcTransRefAbsOfCover(state, Theta, TransSys, RefSys, AbsCover1, AbsCover2);
1003 0 : this->TauAlphaGndDiffuse = TransSys * state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate /
1004 0 : (1.0 - (1.0 - state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate) * this->RefDiffInnerCover);
1005 0 : this->CoversAbsGndDiffuse[0] = AbsCover1;
1006 0 : this->CoversAbsGndDiffuse[1] = AbsCover2;
1007 :
1008 0 : this->SetDiffRadFlag = false;
1009 : }
1010 :
1011 0 : this->InletTemp = state.dataLoopNodes->Node(this->InletNode).Temp;
1012 :
1013 0 : this->MassFlowRate = this->MassFlowRateMax;
1014 :
1015 : // Request the mass flow rate from the plant component flow utility routine
1016 0 : PlantUtilities::SetComponentFlowRate(state, this->MassFlowRate, this->InletNode, this->OutletNode, this->plantLoc);
1017 :
1018 0 : if (this->InitICS) {
1019 :
1020 : Real64 timeElapsed =
1021 0 : state.dataGlobal->HourOfDay + state.dataGlobal->TimeStep * state.dataGlobal->TimeStepZone + state.dataHVACGlobal->SysTimeElapsed;
1022 :
1023 0 : if (this->TimeElapsed != timeElapsed) {
1024 : // The simulation has advanced to the next system timestep. Save conditions from the end of the previous
1025 : // system timestep for use as initial condition of each iteration that does not advance system timestep.
1026 0 : this->SavedTempOfWater = this->TempOfWater;
1027 0 : this->SavedTempOfAbsPlate = this->TempOfAbsPlate;
1028 0 : this->SavedTempOfInnerCover = this->TempOfInnerCover;
1029 0 : this->SavedTempOfOuterCover = this->TempOfOuterCover;
1030 0 : if (this->OSCM_ON) {
1031 0 : this->SavedTempCollectorOSCM = state.dataHeatBal->ExtVentedCavity(this->VentCavIndex).Tbaffle;
1032 : }
1033 0 : this->TimeElapsed = timeElapsed;
1034 : }
1035 : }
1036 0 : }
1037 :
1038 0 : void CollectorData::CalcSolarCollector(EnergyPlusData &state)
1039 : {
1040 :
1041 : // SUBROUTINE INFORMATION:
1042 : // AUTHOR Peter Graham Ellis
1043 : // DATE WRITTEN January 2004
1044 : // MODIFIED na
1045 : // RE-ENGINEERED na
1046 :
1047 : // PURPOSE OF THIS SUBROUTINE:
1048 : // Calculates the heat gain (or loss), outlet temperature, and solar energy conversion efficiency for a flat-plate
1049 : // solar collector when there is a fluid flow. For the no flow condition, the fluid stagnation temperature is
1050 : // calculated as the outlet temperature. Glazed and unglazed collectors are both handled.
1051 :
1052 : // METHODOLOGY EMPLOYED:
1053 : // Calculation is performed using the methodology described in the ASHRAE standards and references below. Measured
1054 : // collector performance coefficients (available from the Solar Rating & Certification Corporation, for example)
1055 : // are modified from the test conditions to match the actual optical (incident angle modifier) and thermal (flow rate
1056 : // modifier) conditions. Water is assumed to be the heat transfer fluid.
1057 :
1058 : // REFERENCES:
1059 : // ASHRAE Standard 93-1986 (RA 91), "Methods of Testing to Determine the Thermal Performance of Solar Collectors".
1060 : // ASHRAE Standard 96-1980 (RA 89), "Methods of Testing to Determine the Thermal Performance of Unglazed Flat-Plate
1061 : // Liquid-Type Solar Collectors".
1062 : // Duffie, J. A., and Beckman, W. A. Solar Engineering of Thermal Processes, Second Edition. Wiley-Interscience:
1063 : // New York (1991).
1064 :
1065 : // NOTES:
1066 : // This subroutine has been validated against the TRNSYS Type 1 flat-plate solar collector module. Results are
1067 : // identical except for slight differences at extreme incident angles (>80 degrees) and extreme surface tilts (<20
1068 : // degrees). The differences are due to the fact that Type 1 does not prevent the *component* incident angle
1069 : // modifiers from being less than zero. There is an effect on the net incident angle modifier if one or more
1070 : // components are less than zero but the net adds up to greater than zero. The EnergyPlus subroutine, on the other
1071 : // hand, requires each component incident angle modifier always to be greater than zero.
1072 :
1073 : static constexpr std::string_view RoutineName("CalcSolarCollector");
1074 0 : Real64 efficiency = 0.0; // Thermal efficiency of solar energy conversion
1075 :
1076 0 : int SurfNum = this->Surface;
1077 0 : int ParamNum = this->Parameters;
1078 : Real64 incidentAngleModifier; // Net incident angle modifier combining beam, sky, and ground radiation
1079 :
1080 : // Calculate incident angle modifier
1081 0 : if (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) > 0.0) {
1082 : // Equivalent incident angle of sky radiation (radians)
1083 0 : Real64 ThetaBeam = std::acos(state.dataHeatBal->SurfCosIncidenceAngle(SurfNum));
1084 :
1085 : // Calculate equivalent incident angles for sky and ground radiation according to Brandemuehl and Beckman (1980)
1086 : // Surface tilt angle (degrees)
1087 0 : Real64 tilt = state.dataSurface->Surface(SurfNum).Tilt;
1088 :
1089 : // Equivalent incident angle of sky radiation (radians)
1090 0 : Real64 ThetaSky = (59.68 - 0.1388 * tilt + 0.001497 * pow_2(tilt)) * Constant::DegToRad;
1091 :
1092 : // Equivalent incident angle of ground radiation (radians)
1093 0 : Real64 ThetaGnd = (90.0 - 0.5788 * tilt + 0.002693 * pow_2(tilt)) * Constant::DegToRad;
1094 :
1095 0 : incidentAngleModifier =
1096 0 : (state.dataHeatBal->SurfQRadSWOutIncidentBeam(SurfNum) * state.dataSolarCollectors->Parameters(ParamNum).IAM(state, ThetaBeam) +
1097 0 : state.dataHeatBal->SurfQRadSWOutIncidentSkyDiffuse(SurfNum) * state.dataSolarCollectors->Parameters(ParamNum).IAM(state, ThetaSky) +
1098 0 : state.dataHeatBal->SurfQRadSWOutIncidentGndDiffuse(SurfNum) * state.dataSolarCollectors->Parameters(ParamNum).IAM(state, ThetaGnd)) /
1099 0 : state.dataHeatBal->SurfQRadSWOutIncident(SurfNum);
1100 : } else {
1101 0 : incidentAngleModifier = 0.0;
1102 : }
1103 :
1104 : // Inlet temperature from plant (C)
1105 0 : Real64 inletTemp = this->InletTemp;
1106 :
1107 : // Mass flow rate through collector (kg/s)
1108 0 : Real64 massFlowRate = this->MassFlowRate;
1109 :
1110 : // Specific heat of collector fluid (J/kg-K)
1111 0 : Real64 Cp = this->plantLoc.loop->glycol->getSpecificHeat(state, inletTemp, RoutineName);
1112 :
1113 : // Gross area of collector (m2)
1114 0 : Real64 area = state.dataSurface->Surface(SurfNum).Area;
1115 :
1116 : // = MassFlowRate * Cp / Area
1117 0 : Real64 mCpA = massFlowRate * Cp / area;
1118 :
1119 : // = MassFlowRateTest * Cp / Area (tested area)
1120 : Real64 mCpATest =
1121 0 : state.dataSolarCollectors->Parameters(ParamNum).TestMassFlowRate * Cp / state.dataSolarCollectors->Parameters(this->Parameters).Area;
1122 :
1123 0 : int Iteration = 1;
1124 :
1125 : // Outlet temperature or stagnation temperature in the collector (C)
1126 0 : Real64 outletTemp = 0.0;
1127 :
1128 : // Outlet temperature saved from previous iteration for convergence check (C)
1129 0 : Real64 OutletTempPrev = 999.9; // Set to a ridiculous number so that DO loop runs at least once
1130 :
1131 : // Heat gain or loss to collector fluid (W)
1132 0 : Real64 Q = 0.0;
1133 :
1134 0 : while (std::abs(outletTemp - OutletTempPrev) > state.dataHeatBal->TempConvergTol) { // Check for temperature convergence
1135 :
1136 0 : OutletTempPrev = outletTemp; // Save previous outlet temperature
1137 :
1138 : // Modifier for test correlation type: INLET, AVERAGE, or OUTLET
1139 0 : Real64 TestTypeMod = 0.0;
1140 :
1141 : // FR * ULoss "prime" for test conditions = (eff1 + eff2 * deltaT)
1142 0 : Real64 FRULpTest = 0.0;
1143 :
1144 : // Modify coefficients depending on test correlation type
1145 0 : switch (state.dataSolarCollectors->Parameters(ParamNum).TestType) {
1146 0 : case TestTypeEnum::INLET: {
1147 0 : FRULpTest = state.dataSolarCollectors->Parameters(ParamNum).eff1 +
1148 0 : state.dataSolarCollectors->Parameters(ParamNum).eff2 * (inletTemp - state.dataSurface->SurfOutDryBulbTemp(SurfNum));
1149 0 : TestTypeMod = 1.0;
1150 0 : } break;
1151 0 : case TestTypeEnum::AVERAGE: {
1152 0 : FRULpTest = state.dataSolarCollectors->Parameters(ParamNum).eff1 +
1153 0 : state.dataSolarCollectors->Parameters(ParamNum).eff2 *
1154 0 : ((inletTemp + outletTemp) * 0.5 - state.dataSurface->SurfOutDryBulbTemp(SurfNum));
1155 0 : TestTypeMod = 1.0 / (1.0 - FRULpTest / (2.0 * mCpATest));
1156 0 : } break;
1157 0 : case TestTypeEnum::OUTLET: {
1158 0 : FRULpTest = state.dataSolarCollectors->Parameters(ParamNum).eff1 +
1159 0 : state.dataSolarCollectors->Parameters(ParamNum).eff2 * (outletTemp - state.dataSurface->SurfOutDryBulbTemp(SurfNum));
1160 0 : TestTypeMod = 1.0 / (1.0 - FRULpTest / mCpATest);
1161 0 : } break;
1162 0 : default:
1163 0 : break;
1164 : }
1165 :
1166 : // FR * tau * alpha at normal incidence = Y-intercept of collector efficiency
1167 0 : Real64 FRTAN = state.dataSolarCollectors->Parameters(ParamNum).eff0 * TestTypeMod;
1168 :
1169 : // FR * ULoss = 1st order coefficient of collector efficiency
1170 0 : Real64 FRUL = state.dataSolarCollectors->Parameters(ParamNum).eff1 * TestTypeMod;
1171 :
1172 : // FR * ULoss / T = 2nd order coefficient of collector efficiency
1173 0 : Real64 FRULT = state.dataSolarCollectors->Parameters(ParamNum).eff2 * TestTypeMod;
1174 0 : FRULpTest *= TestTypeMod;
1175 :
1176 0 : if (massFlowRate > 0.0) { // Calculate efficiency and heat transfer with flow
1177 :
1178 : // Modifier for flow rate different from test flow rate
1179 0 : Real64 FlowMod = 0.0;
1180 :
1181 : // F prime * ULoss for test conditions = collector efficiency factor * overall loss coefficient
1182 : Real64 FpULTest;
1183 :
1184 0 : if ((1.0 + FRULpTest / mCpATest) > 0.0) {
1185 0 : FpULTest = -mCpATest * std::log(1.0 + FRULpTest / mCpATest);
1186 : } else {
1187 0 : FpULTest = FRULpTest; // Avoid LOG( <0 )
1188 : }
1189 :
1190 0 : if ((-FpULTest / mCpA) < 700.0) {
1191 0 : FlowMod = mCpA * (1.0 - std::exp(-FpULTest / mCpA));
1192 : } else { // avoid EXP(too large #)
1193 : // FlowMod = FlowMod; // Self-assignment commented out
1194 : }
1195 0 : if ((-FpULTest / mCpATest) < 700.0) {
1196 0 : FlowMod /= (mCpATest * (1.0 - std::exp(-FpULTest / mCpATest)));
1197 : } else {
1198 : // FlowMod = FlowMod; // Self-assignment commented out
1199 : }
1200 :
1201 : // Calculate fluid heat gain (or loss)
1202 : // Heat loss is possible if there is no incident radiation and fluid is still flowing.
1203 0 : Q = (FRTAN * incidentAngleModifier * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) +
1204 0 : FRULpTest * (inletTemp - state.dataSurface->SurfOutDryBulbTemp(SurfNum))) *
1205 : area * FlowMod;
1206 :
1207 0 : outletTemp = inletTemp + Q / (massFlowRate * Cp);
1208 :
1209 : // CR 7877 bound unreasonable result
1210 0 : if (outletTemp < -100) {
1211 0 : outletTemp = -100.0;
1212 0 : Q = massFlowRate * Cp * (outletTemp - inletTemp);
1213 : }
1214 0 : if (outletTemp > 200) {
1215 0 : outletTemp = 200.0;
1216 0 : Q = massFlowRate * Cp * (outletTemp - inletTemp);
1217 : }
1218 :
1219 0 : if (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) > 0.0) { // Calculate thermal efficiency
1220 : // NOTE: Efficiency can be > 1 if Q > SurfQRadSWOutIncident because of favorable delta T, i.e. warm outdoor temperature
1221 0 : efficiency =
1222 0 : Q / (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) * area); // Q has units of W; SurfQRadSWOutIncident has units of W/m2
1223 : } else {
1224 0 : efficiency = 0.0;
1225 : }
1226 :
1227 : } else { // Calculate stagnation temperature of fluid in collector (no flow)
1228 0 : Q = 0.0;
1229 0 : efficiency = 0.0;
1230 :
1231 : // Calculate temperature of stagnant fluid in collector
1232 0 : Real64 A = -FRULT;
1233 0 : Real64 B = -FRUL + 2.0 * FRULT * state.dataSurface->SurfOutDryBulbTemp(SurfNum);
1234 0 : Real64 C = -FRULT * pow_2(state.dataSurface->SurfOutDryBulbTemp(SurfNum)) + FRUL * state.dataSurface->SurfOutDryBulbTemp(SurfNum) -
1235 0 : FRTAN * incidentAngleModifier * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum);
1236 0 : Real64 qEquation = (pow_2(B) - 4.0 * A * C);
1237 0 : if (qEquation < 0.0) {
1238 0 : if (this->ErrIndex == 0) {
1239 0 : ShowSevereMessage(state,
1240 0 : format("CalcSolarCollector: {}=\"{}\", possible bad input coefficients.",
1241 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->Type)],
1242 0 : this->Name));
1243 0 : ShowContinueError(state,
1244 : "...coefficients cause negative quadratic equation part in calculating temperature of stagnant fluid.");
1245 0 : ShowContinueError(state, "...examine input coefficients for accuracy. Calculation will be treated as linear.");
1246 : }
1247 0 : ShowRecurringSevereErrorAtEnd(state,
1248 0 : format("CalcSolarCollector: {}=\"{}\", coefficient error continues.",
1249 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->Type)],
1250 0 : this->Name),
1251 0 : this->ErrIndex,
1252 : qEquation,
1253 : qEquation);
1254 : }
1255 0 : if (FRULT == 0.0 || qEquation < 0.0) { // Linear, 1st order solution
1256 0 : outletTemp = state.dataSurface->SurfOutDryBulbTemp(SurfNum) -
1257 0 : FRTAN * incidentAngleModifier * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) / FRUL;
1258 : } else { // Quadratic, 2nd order solution
1259 0 : outletTemp = (-B + std::sqrt(qEquation)) / (2.0 * A);
1260 : }
1261 : }
1262 :
1263 0 : if (state.dataSolarCollectors->Parameters(ParamNum).TestType == TestTypeEnum::INLET)
1264 0 : break; // Inlet temperature test correlations do not need to iterate
1265 :
1266 0 : if (Iteration > 100) {
1267 0 : if (this->IterErrIndex == 0) {
1268 0 : ShowWarningMessage(state,
1269 0 : format("CalcSolarCollector: {}=\"{}\": Solution did not converge.",
1270 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->Type)],
1271 0 : this->Name));
1272 : }
1273 0 : ShowRecurringWarningErrorAtEnd(state,
1274 0 : format("CalcSolarCollector: {}=\"{}\", solution not converge error continues.",
1275 0 : DataPlant::PlantEquipTypeNames[static_cast<int>(this->Type)],
1276 0 : this->Name),
1277 0 : this->IterErrIndex);
1278 0 : break;
1279 : } else {
1280 0 : ++Iteration;
1281 : }
1282 :
1283 : } // Check for temperature convergence
1284 :
1285 0 : this->IncidentAngleModifier = incidentAngleModifier;
1286 0 : this->Power = Q;
1287 0 : this->HeatGain = max(Q, 0.0);
1288 0 : this->HeatLoss = min(Q, 0.0);
1289 0 : this->OutletTemp = outletTemp;
1290 0 : this->Efficiency = efficiency;
1291 0 : }
1292 :
1293 0 : Real64 ParametersData::IAM(EnergyPlusData &state, Real64 const IncidentAngle)
1294 : {
1295 :
1296 : // SUBROUTINE INFORMATION:
1297 : // AUTHOR Peter Graham Ellis
1298 : // DATE WRITTEN December 2003
1299 : // MODIFIED Sept 2008, BG cut off IAM beyond 60 degrees.
1300 : // RE-ENGINEERED na
1301 :
1302 : // PURPOSE OF THIS SUBROUTINE:
1303 : // Calculates the incident angle modifier based on the solar collector parameters. Both first and second order
1304 : // correlations are allowed.
1305 :
1306 : // METHODOLOGY EMPLOYED:
1307 : // A simple function.
1308 :
1309 : // REFERENCES:
1310 : // ASHRAE Standard 93-1986 (RA 91), "Methods of Testing to Determine the Thermal Performance of Solar Collectors".
1311 : // ASHRAE Standard 96-1980 (RA 89), "Methods of Testing to Determine the Thermal Performance of Unglazed Flat-Plate
1312 : // Liquid-Type Solar Collectors".
1313 : // Duffie, J. A., and Beckman, W. A. Solar Engineering of Thermal Processes, Second Edition. Wiley-Interscience:
1314 : // New York (1991).
1315 :
1316 : Real64 IAM;
1317 :
1318 : // cut off IAM for angles greater than 60 degrees. (CR 7534)
1319 0 : Real64 CutoffAngle = 60.0 * Constant::DegToRad;
1320 0 : if (std::abs(IncidentAngle) > CutoffAngle) { // cut off, model curves not robust beyond cutoff
1321 : // curves from FSEC/SRCC testing are only certified to 60 degrees, larger angles can cause numerical problems in curves
1322 0 : IAM = 0.0;
1323 : } else {
1324 :
1325 0 : Real64 s = (1.0 / std::cos(IncidentAngle)) - 1.0;
1326 :
1327 0 : IAM = 1.0 + this->iam1 * s + this->iam2 * pow_2(s);
1328 0 : IAM = max(IAM, 0.0); // Never allow to be less than zero, but greater than one is a possibility
1329 :
1330 0 : if (IAM > 10.0) { // Greater than 10 is probably not a possibility
1331 0 : ShowSevereError(
1332 : state,
1333 0 : format(
1334 : "IAM Function: SolarCollectorPerformance:FlatPlate = {}: Incident Angle Modifier is out of bounds due to bad coefficients.",
1335 0 : this->Name));
1336 0 : ShowContinueError(state, format("Coefficient 2 of Incident Angle Modifier = {}", this->iam1));
1337 0 : ShowContinueError(state, format("Coefficient 3 of Incident Angle Modifier = {}", this->iam2));
1338 0 : ShowContinueError(state, format("Calculated Incident Angle Modifier = {}", IAM));
1339 0 : ShowContinueError(state, "Expected Incident Angle Modifier should be approximately 1.5 or less.");
1340 0 : ShowFatalError(state, "Errors in SolarCollectorPerformance:FlatPlate input.");
1341 : }
1342 :
1343 : } // not greater than cut off angle
1344 :
1345 0 : return IAM;
1346 : }
1347 :
1348 0 : void CollectorData::CalcICSSolarCollector(EnergyPlusData &state)
1349 : {
1350 :
1351 : // SUBROUTINE INFORMATION:
1352 : // AUTHOR Bereket Nigusse, FSEC/UCF
1353 : // DATE WRITTEN February 2012
1354 : // MODIFIED na
1355 : // RE-ENGINEERED na
1356 :
1357 : // PURPOSE OF THIS SUBROUTINE:
1358 : // Calculates the heat transferred (gain or loss), energy stored, skin heat loss, outlet temperature, solar energy
1359 : // conversion efficiency, and transmittance-absorptance product of an ICS solar collector.
1360 :
1361 : // METHODOLOGY EMPLOYED:
1362 : // The governing equations for the absorber and collector water heat balance equations are solved simultaneously.
1363 : // The two coupled first ODE are solved analytically.
1364 : // The transmittance-absorptance product of the collector cover-absorber system is calculated using ray tracing
1365 : // method according to Duffie and Beckman(1991).
1366 : // REFERENCES:
1367 : // Duffie, J. A., and Beckman, W. A. Solar Engineering of Thermal Processes, 2nd. Edition. Wiley-Interscience:
1368 : // New York (1991).
1369 : // NOTES:
1370 :
1371 : static constexpr std::string_view RoutineName("CalcICSSolarCollector");
1372 :
1373 0 : int SurfNum = this->Surface;
1374 0 : int ParamNum = this->Parameters;
1375 0 : Real64 SecInTimeStep = state.dataHVACGlobal->TimeStepSysSec;
1376 0 : Real64 TempWater = this->SavedTempOfWater;
1377 0 : Real64 TempAbsPlate = this->SavedTempOfAbsPlate;
1378 0 : Real64 TempOutdoorAir = state.dataSurface->SurfOutDryBulbTemp(SurfNum);
1379 :
1380 : Real64 TempOSCM; // Otherside condition model temperature [C]
1381 0 : if (this->OSCM_ON) {
1382 0 : TempOSCM = this->SavedTempCollectorOSCM;
1383 : } else {
1384 0 : TempOSCM = TempOutdoorAir;
1385 : }
1386 :
1387 : // Calculate transmittance-absorptance product of the system
1388 : // Incident angle of beam radiation (radians)
1389 0 : Real64 ThetaBeam = std::acos(state.dataHeatBal->SurfCosIncidenceAngle(SurfNum));
1390 0 : this->CalcTransAbsorProduct(state, ThetaBeam);
1391 :
1392 0 : Real64 inletTemp = this->InletTemp;
1393 :
1394 0 : Real64 massFlowRate = this->MassFlowRate;
1395 :
1396 : // Specific heat of collector fluid (J/kg-K)
1397 0 : Real64 Cpw = this->plantLoc.loop->glycol->getSpecificHeat(state, inletTemp, RoutineName);
1398 :
1399 : // density of collector fluid (kg/m3)
1400 0 : Real64 Rhow = this->plantLoc.loop->glycol->getDensity(state, inletTemp, RoutineName);
1401 :
1402 : // calculate heat transfer coefficients and covers temperature:
1403 0 : this->CalcHeatTransCoeffAndCoverTemp(state);
1404 :
1405 : // Calc convection heat transfer coefficient between the absorber plate and water:
1406 :
1407 : // convection coeff between absorber plate and water [W/m2K]
1408 : Real64 hConvCoefA2W =
1409 0 : EnergyPlus::SolarCollectors::CollectorData::CalcConvCoeffAbsPlateAndWater(state, TempAbsPlate, TempWater, this->Length, this->TiltR2V);
1410 0 : Real64 TempWaterOld = TempWater;
1411 0 : Real64 TempAbsPlateOld = TempAbsPlate;
1412 :
1413 : // flag if the absorber has thermal mass or not
1414 : bool AbsPlateMassFlag;
1415 :
1416 : Real64 a1; // coefficient of ODE for absorber temperature Tp
1417 : Real64 a2; // coefficient of ODE for absorber temperature Tw
1418 : Real64 a3; // constant term of ODE for absorber temperature
1419 :
1420 : // Gross area of collector (m2)
1421 0 : Real64 area = state.dataSolarCollectors->Parameters(ParamNum).Area;
1422 :
1423 0 : if (state.dataSolarCollectors->Parameters(ParamNum).ThermalMass > 0.0) {
1424 0 : AbsPlateMassFlag = true;
1425 :
1426 : // thermal mass of the absorber plate [J/K]
1427 0 : Real64 ap = state.dataSolarCollectors->Parameters(ParamNum).ThermalMass * area;
1428 0 : a1 = -area * (hConvCoefA2W + this->UTopLoss) / ap;
1429 0 : a2 = area * hConvCoefA2W / ap;
1430 0 : a3 = area * (this->TauAlpha * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) + this->UTopLoss * TempOutdoorAir) / ap;
1431 : } else {
1432 0 : AbsPlateMassFlag = false;
1433 0 : a1 = -area * (hConvCoefA2W + this->UTopLoss);
1434 0 : a2 = area * hConvCoefA2W;
1435 0 : a3 = area * (this->TauAlpha * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) + this->UTopLoss * TempOutdoorAir);
1436 : }
1437 :
1438 : // thermal mass of the collector water [J/K]
1439 0 : Real64 aw = state.dataSolarCollectors->Parameters(ParamNum).Volume * Rhow * Cpw;
1440 :
1441 : // coefficient of ODE for water temperature Tp
1442 0 : Real64 b1 = area * hConvCoefA2W / aw;
1443 :
1444 : // coefficient of ODE for water temperature Tw
1445 0 : Real64 b2 = -(area * (hConvCoefA2W + this->UbLoss + this->UsLoss) + massFlowRate * Cpw) / aw;
1446 :
1447 : // constant term of ODE for water temperature
1448 0 : Real64 b3 = (area * (this->UbLoss * TempOSCM + this->UsLoss * TempOutdoorAir) + massFlowRate * Cpw * inletTemp) / aw;
1449 :
1450 0 : EnergyPlus::SolarCollectors::CollectorData::ICSCollectorAnalyticalSolution(
1451 : state, SecInTimeStep, a1, a2, a3, b1, b2, b3, TempAbsPlateOld, TempWaterOld, TempAbsPlate, TempWater, AbsPlateMassFlag);
1452 :
1453 0 : this->SkinHeatLossRate = area * (this->UTopLoss * (TempOutdoorAir - TempAbsPlate) + this->UsLoss * (TempOutdoorAir - TempWater) +
1454 0 : this->UbLoss * (TempOSCM - TempWater));
1455 0 : this->StoredHeatRate = aw * (TempWater - TempWaterOld) / SecInTimeStep;
1456 :
1457 : // heat gain rate (W)
1458 0 : Real64 QHeatRate = massFlowRate * Cpw * (TempWater - inletTemp);
1459 0 : this->HeatRate = QHeatRate;
1460 0 : this->HeatGainRate = max(0.0, QHeatRate);
1461 :
1462 0 : Real64 outletTemp = TempWater;
1463 0 : this->OutletTemp = outletTemp;
1464 0 : this->TempOfWater = TempWater;
1465 0 : this->TempOfAbsPlate = TempAbsPlate;
1466 :
1467 0 : Real64 efficiency = 0.0; // Thermal efficiency of solar energy conversion
1468 0 : if (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) > 0.0) {
1469 0 : efficiency = (this->HeatGainRate + this->StoredHeatRate) / (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) * area);
1470 0 : if (efficiency < 0.0) efficiency = 0.0;
1471 : }
1472 0 : this->Efficiency = efficiency;
1473 0 : }
1474 :
1475 0 : void CollectorData::ICSCollectorAnalyticalSolution(EnergyPlusData &state,
1476 : Real64 const SecInTimeStep, // seconds in a time step
1477 : Real64 const a1, // coefficient of ODE for Tp
1478 : Real64 const a2, // coefficient of ODE for Tp
1479 : Real64 const a3, // coefficient of ODE for Tp
1480 : Real64 const b1, // coefficient of ODE for TW
1481 : Real64 const b2, // coefficient of ODE for TW
1482 : Real64 const b3, // coefficient of ODE for TW
1483 : Real64 const TempAbsPlateOld, // absorber plate temperature at previous time step [C]
1484 : Real64 const TempWaterOld, // collector water temperature at previous time step [C]
1485 : Real64 &TempAbsPlate, // absorber plate temperature at current time step [C]
1486 : Real64 &TempWater, // collector water temperature at current time step [C]
1487 : bool const AbsorberPlateHasMass // flag for absorber thermal mass
1488 : )
1489 : {
1490 :
1491 : // SUBROUTINE INFORMATION:
1492 : // AUTHOR Bereket Nigusse, FSEC/UCF
1493 : // DATE WRITTEN February 2012
1494 : // MODIFIED na
1495 : // RE-ENGINEERED na
1496 :
1497 : // PURPOSE OF THIS SUBROUTINE:
1498 : // Calculates the absorber plate and collector water temperatures.
1499 : // METHODOLOGY EMPLOYED:
1500 : // Analytical method: Solves the coupled absorber plate and collector water energy balance
1501 : // equations. The two non-homogeneous ordinary differential equations of the form.
1502 : // Tp' = a1*Tp + a2*Tw + a3.
1503 : // Tw' = b1*Tp + b2*Tw + b3.
1504 : // The general solution of these coupled equation with real routes has the following form:
1505 : // Tp = ConstantC1*exp(lamda1*t) + ConstantC2*exp(lamda2*t) + ConstOfTpSln
1506 : // Tw = r1*ConstantC2*exp(lamda1*t) + r2*ConstantC2*exp(lamda2*t) + ConstOfTwSln
1507 :
1508 0 : if (AbsorberPlateHasMass) {
1509 :
1510 : // coefficients of quadratic equation a*m2+b*m+c=0
1511 0 : Real64 a = 1.0;
1512 0 : Real64 b = -(a1 + b2);
1513 0 : Real64 c = a1 * b2 - a2 * b1;
1514 0 : Real64 BSquareM4TimesATimesC = pow_2(b) - 4.0 * a * c;
1515 :
1516 0 : if (BSquareM4TimesATimesC > 0.0) {
1517 :
1518 : // the real roots of the quadratic equation
1519 0 : Real64 lamda1 = (-b + std::sqrt(BSquareM4TimesATimesC)) / (2.0 * a);
1520 0 : Real64 lamda2 = (-b - std::sqrt(BSquareM4TimesATimesC)) / (2.0 * a);
1521 :
1522 : // the particular solution for the ODE
1523 0 : Real64 ConstOfTpSln = (-a3 * b2 + b3 * a2) / c;
1524 0 : Real64 ConstOfTwSln = (-a1 * b3 + b1 * a3) / c;
1525 :
1526 : // ratio of the ODE solution constant coefficients
1527 0 : Real64 r1 = (lamda1 - a1) / a2;
1528 0 : Real64 r2 = (lamda2 - a1) / a2;
1529 :
1530 : // coefficients of the ODE solution
1531 0 : Real64 ConstantC2 = (TempWaterOld + r1 * ConstOfTpSln - r1 * TempAbsPlateOld - ConstOfTwSln) / (r2 - r1);
1532 0 : Real64 ConstantC1 = (TempAbsPlateOld - ConstOfTpSln - ConstantC2);
1533 :
1534 0 : TempAbsPlate = ConstantC1 * std::exp(lamda1 * SecInTimeStep) + ConstantC2 * std::exp(lamda2 * SecInTimeStep) + ConstOfTpSln;
1535 0 : TempWater = r1 * ConstantC1 * std::exp(lamda1 * SecInTimeStep) + r2 * ConstantC2 * std::exp(lamda2 * SecInTimeStep) + ConstOfTwSln;
1536 :
1537 : } else { // this should never occur
1538 0 : ShowSevereError(
1539 : state, "ICSCollectorAnalyticalSoluton: Unanticipated differential equation coefficient - report to EnergyPlus Development Team");
1540 0 : ShowFatalError(state, "Program terminates due to above conditions.");
1541 : }
1542 : } else {
1543 : // In the absence of absorber plate thermal mass, only the collector water heat balance has a
1544 : // differential equation of the form: Tw' = b1*Tp + b2*Tw + b3. The absorber plate energy balance
1545 : // equation in the absence of thermal mass is a steady state form: b1*Tp + b2*Tw + b3 = 0
1546 0 : Real64 b = b2 - b1 * (a2 / a1);
1547 0 : Real64 c = b3 - b1 * (a3 / a1);
1548 0 : TempWater = (TempWaterOld + c / b) * std::exp(b * SecInTimeStep) - c / b;
1549 0 : TempAbsPlate = -(a2 * TempWater + a3) / a1;
1550 : }
1551 0 : }
1552 :
1553 0 : void CollectorData::CalcTransAbsorProduct(EnergyPlusData &state, Real64 const IncidAngle)
1554 : {
1555 :
1556 : // SUBROUTINE INFORMATION:
1557 : // AUTHOR Bereket A Nigusse
1558 : // DATE WRITTEN February 2012
1559 : // MODIFIED na
1560 : // RE-ENGINEERED na
1561 :
1562 : // PURPOSE OF THIS SUBROUTINE:
1563 : // Calculates transmittance-absorptance product and the fraction of total solar radiation
1564 : // absorbed by each cover of a multicover ICS solar collector.
1565 :
1566 : // METHODOLOGY EMPLOYED:
1567 : // Uses a ray tracing method.
1568 :
1569 : // REFERENCES:
1570 : // Duffie, J. A., and Beckman, W. A. Solar Engineering of Thermal Processes, Second Edition.
1571 : // Wiley-Interscience: New York (1991).
1572 :
1573 0 : Real64 TransSys = 1.0; // cover system solar transmittance
1574 0 : Real64 ReflSys = 0.0; // cover system solar reflectance
1575 0 : Real64 AbsCover1 = 0.0; // Inner cover solar absorbtance
1576 0 : Real64 AbsCover2 = 0.0; // Outer cover solar absorbtance
1577 : Real64 TuaAlpha; // weighted trans-abs product of system
1578 : Real64 TuaAlphaBeam; // trans-abs product of beam radiation
1579 0 : this->CoverAbs[0] = 0.0;
1580 0 : this->CoverAbs[1] = 0.0;
1581 :
1582 0 : int SurfNum = this->Surface;
1583 0 : int ParamNum = this->Parameters;
1584 :
1585 0 : if (state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) > 0.0) {
1586 :
1587 : // cover system transmittance and reflectance from outer to inner cover
1588 0 : this->CalcTransRefAbsOfCover(state, IncidAngle, TransSys, ReflSys, AbsCover1, AbsCover2);
1589 :
1590 0 : TuaAlphaBeam = TransSys * state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate /
1591 0 : (1.0 - (1.0 - state.dataSolarCollectors->Parameters(ParamNum).AbsorOfAbsPlate) * this->RefDiffInnerCover);
1592 :
1593 0 : this->TauAlphaBeam = max(0.0, TuaAlphaBeam);
1594 :
1595 0 : Array1D<Real64> CoversAbsBeam(2); // Inner and Outer Cover absorptance
1596 0 : CoversAbsBeam(1) = AbsCover1;
1597 0 : CoversAbsBeam(2) = AbsCover2;
1598 :
1599 : // calc total solar radiation weighted transmittance-absorptance product
1600 0 : TuaAlpha = (state.dataHeatBal->SurfQRadSWOutIncidentBeam(SurfNum) * this->TauAlphaBeam +
1601 0 : state.dataHeatBal->SurfQRadSWOutIncidentSkyDiffuse(SurfNum) * this->TauAlphaSkyDiffuse +
1602 0 : state.dataHeatBal->SurfQRadSWOutIncidentGndDiffuse(SurfNum) * this->TauAlphaGndDiffuse) /
1603 0 : state.dataHeatBal->SurfQRadSWOutIncident(SurfNum);
1604 :
1605 0 : if (state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers == 1) {
1606 : // calc total solar radiation weighted cover absorptance
1607 0 : this->CoverAbs[0] = (state.dataHeatBal->SurfQRadSWOutIncidentBeam(SurfNum) * CoversAbsBeam(1) +
1608 0 : state.dataHeatBal->SurfQRadSWOutIncidentSkyDiffuse(SurfNum) * this->CoversAbsSkyDiffuse[0] +
1609 0 : state.dataHeatBal->SurfQRadSWOutIncidentGndDiffuse(SurfNum) * this->CoversAbsGndDiffuse[0]) /
1610 0 : state.dataHeatBal->SurfQRadSWOutIncident(SurfNum);
1611 :
1612 0 : } else if (state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers == 2) {
1613 : // Num = 1 represents outer cover and Num = 2 represents inner cover
1614 0 : for (int Num = 0; Num < state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers; ++Num) {
1615 0 : this->CoverAbs[Num] = (state.dataHeatBal->SurfQRadSWOutIncidentBeam(SurfNum) * CoversAbsBeam(Num + 1) +
1616 0 : state.dataHeatBal->SurfQRadSWOutIncidentSkyDiffuse(SurfNum) * this->CoversAbsSkyDiffuse[Num] +
1617 0 : state.dataHeatBal->SurfQRadSWOutIncidentGndDiffuse(SurfNum) * this->CoversAbsGndDiffuse[Num]) /
1618 0 : state.dataHeatBal->SurfQRadSWOutIncident(SurfNum);
1619 : }
1620 : }
1621 :
1622 0 : } else {
1623 0 : TuaAlpha = 0.0;
1624 : }
1625 0 : this->TauAlpha = TuaAlpha;
1626 0 : }
1627 :
1628 0 : void CollectorData::CalcTransRefAbsOfCover(EnergyPlusData &state,
1629 : Real64 const IncidentAngle, // Angle of incidence (radians)
1630 : Real64 &TransSys, // cover system solar transmittance
1631 : Real64 &ReflSys, // cover system solar reflectance
1632 : Real64 &AbsCover1, // Inner cover solar absorbtance
1633 : Real64 &AbsCover2, // Outer cover solar absorbtance
1634 : ObjexxFCL::Optional_bool_const InOUTFlag, // flag for calc. diffuse solar refl of cover from inside out
1635 : ObjexxFCL::Optional<Real64> RefSysDiffuse // cover system solar reflectance from inner to outer cover
1636 : ) const
1637 : {
1638 :
1639 : // SUBROUTINE INFORMATION:
1640 : // AUTHOR Bereket A Nigusse
1641 : // DATE WRITTEN February 2012
1642 :
1643 : // PURPOSE OF THIS SUBROUTINE:
1644 : // Calculates the transmittance, reflectance, and absorptance of the collector covers based on
1645 : // solar collector optical parameters specified.
1646 :
1647 : // METHODOLOGY EMPLOYED:
1648 : // Uses a ray tracing method.
1649 :
1650 : // REFERENCES:
1651 : // Duffie, J. A., and Beckman, W. A. Solar Engineering of Thermal Processes, Second Edition.
1652 : // Wiley-Interscience: New York (1991).
1653 :
1654 0 : Real64 constexpr AirRefIndex(1.0003); // refractive index of air
1655 :
1656 0 : Array1D<Real64> TransPara(2); // cover transmittance parallel component
1657 0 : Array1D<Real64> TransPerp(2); // cover transmittance perpendicular component
1658 0 : Array1D<Real64> ReflPara(2); // cover reflectance parallel component
1659 0 : Array1D<Real64> ReflPerp(2); // cover reflectance Perpendicular component
1660 0 : Array1D<Real64> AbsorPara(2); // cover absorbtance parallel component
1661 0 : Array1D<Real64> AbsorPerp(2); // cover absorbtance Perpendicular component
1662 0 : Array1D<Real64> TransAbsOnly(2); // cover transmittance with absorptance only considered
1663 :
1664 0 : TransPerp = 1.0;
1665 0 : TransPara = 1.0;
1666 0 : ReflPerp = 0.0;
1667 0 : ReflPara = 0.0;
1668 0 : AbsorPerp = 0.0;
1669 0 : AbsorPara = 0.0;
1670 0 : TransAbsOnly = 1.0;
1671 0 : TransSys = 0.0;
1672 0 : ReflSys = 0.0;
1673 0 : AbsCover1 = 0.0;
1674 0 : AbsCover2 = 0.0;
1675 :
1676 : bool DiffRefFlag; // flag for calc. diffuse refl of cover from inside to outside
1677 0 : if (present(InOUTFlag)) {
1678 0 : DiffRefFlag = InOUTFlag;
1679 : } else {
1680 0 : DiffRefFlag = false;
1681 : }
1682 :
1683 : // get the incidence and refraction angles
1684 0 : int ParamNum = this->Parameters;
1685 0 : Real64 const sin_IncAngle(std::sin(IncidentAngle));
1686 :
1687 0 : for (int nCover = 1; nCover <= state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers; ++nCover) {
1688 :
1689 : // refractive index of collector cover
1690 0 : Real64 CoverRefrIndex = state.dataSolarCollectors->Parameters(ParamNum).RefractiveIndex[nCover - 1];
1691 :
1692 : // angle of refraction
1693 0 : Real64 RefrAngle = std::asin(sin_IncAngle * AirRefIndex / CoverRefrIndex);
1694 :
1695 : // transmitted component with absorption only considered:
1696 0 : TransAbsOnly(nCover) = std::exp(-state.dataSolarCollectors->Parameters(ParamNum).ExtCoefTimesThickness[nCover - 1] / std::cos(RefrAngle));
1697 :
1698 : // parallel reflected component of unpolarized solar radiation
1699 : Real64 ParaRad;
1700 :
1701 : // Perpendicular reflected component of unpolarized solar radiation
1702 : Real64 PerpRad;
1703 :
1704 : // parallel and perpendicular reflection components:
1705 0 : if (IncidentAngle == 0.0) {
1706 0 : ParaRad = pow_2((CoverRefrIndex - AirRefIndex) / (CoverRefrIndex + AirRefIndex));
1707 0 : PerpRad = pow_2((CoverRefrIndex - AirRefIndex) / (CoverRefrIndex + AirRefIndex));
1708 : } else {
1709 0 : ParaRad = pow_2(std::tan(RefrAngle - IncidentAngle)) / pow_2(std::tan(RefrAngle + IncidentAngle));
1710 0 : PerpRad = pow_2(std::sin(RefrAngle - IncidentAngle)) / pow_2(std::sin(RefrAngle + IncidentAngle));
1711 : }
1712 :
1713 : // parallel and perpendicular transmitted components:
1714 0 : TransPerp(nCover) =
1715 0 : TransAbsOnly(nCover) * ((1.0 - PerpRad) / (1.0 + PerpRad)) * ((1.0 - pow_2(PerpRad)) / (1.0 - pow_2(PerpRad * TransAbsOnly(nCover))));
1716 0 : TransPara(nCover) =
1717 0 : TransAbsOnly(nCover) * ((1.0 - ParaRad) / (1.0 + ParaRad)) * ((1.0 - pow_2(ParaRad)) / (1.0 - pow_2(ParaRad * TransAbsOnly(nCover))));
1718 :
1719 0 : ReflPerp(nCover) =
1720 0 : (PerpRad + (pow_2(1.0 - PerpRad) * pow_2(TransAbsOnly(nCover)) * PerpRad) / (1.0 - pow_2(PerpRad * TransAbsOnly(nCover))));
1721 0 : ReflPara(nCover) =
1722 0 : (ParaRad + (pow_2(1.0 - ParaRad) * pow_2(TransAbsOnly(nCover)) * ParaRad) / (1.0 - pow_2(ParaRad * TransAbsOnly(nCover))));
1723 :
1724 0 : AbsorPerp(nCover) = 1.0 - TransPerp(nCover) - ReflPerp(nCover);
1725 0 : AbsorPara(nCover) = 1.0 - TransPara(nCover) - ReflPara(nCover);
1726 : }
1727 :
1728 : // solar absorptance of the individual cover
1729 0 : AbsCover1 = 0.5 * (AbsorPerp(1) + AbsorPara(1));
1730 0 : if (state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers == 2) AbsCover2 = 0.5 * (AbsorPerp(2) + AbsorPara(2));
1731 :
1732 : // calculate from outer to inner cover:
1733 0 : TransSys =
1734 0 : 0.5 * (TransPerp(1) * TransPerp(2) / (1.0 - ReflPerp(1) * ReflPerp(2)) + TransPara(1) * TransPara(2) / (1.0 - ReflPara(1) * ReflPara(2)));
1735 0 : ReflSys = 0.5 * (ReflPerp(1) + TransSys * ReflPerp(2) * TransPerp(1) / TransPerp(2) + ReflPara(1) +
1736 0 : TransSys * ReflPara(2) * TransPara(1) / TransPara(2));
1737 0 : if (DiffRefFlag) {
1738 : // calculate from inner to outer cover:
1739 :
1740 : // cover system solar transmittance from inner to outer cover
1741 0 : Real64 TransSysDiff = 0.5 * (TransPerp(2) * TransPerp(1) / (1.0 - ReflPerp(2) * ReflPerp(1)) +
1742 0 : TransPara(2) * TransPara(1) / (1.0 - ReflPara(2) * ReflPara(1)));
1743 0 : RefSysDiffuse = 0.5 * (ReflPerp(2) + TransSysDiff * ReflPerp(1) * TransPerp(2) / TransPerp(1) + ReflPara(2) +
1744 0 : TransSysDiff * ReflPara(1) * TransPara(2) / TransPara(1));
1745 : }
1746 0 : }
1747 :
1748 0 : void CollectorData::CalcHeatTransCoeffAndCoverTemp(EnergyPlusData &state) // Collector object number
1749 : {
1750 : // SUBROUTINE INFORMATION:
1751 : // AUTHOR Bereket A Nigusse, FSEC/UCF
1752 : // DATE WRITTEN February 2012
1753 :
1754 : // PURPOSE OF THIS SUBROUTINE:
1755 : // Calculates the various heat transfer coefficients, and collector cover temperatures.
1756 :
1757 : // METHODOLOGY EMPLOYED:
1758 :
1759 : // REFERENCES:
1760 : // Duffie, J. A., and Beckman, W. A. Solar Engineering of Thermal Processes, Second Edition.
1761 : // Wiley-Interscience: New York (1991).
1762 :
1763 : Real64 tempnom; // intermediate variable
1764 : Real64 tempdenom; // intermediate variable
1765 : Real64 hRadCoefC2Sky; // radiation coeff from collector to the sky [W/m2C]
1766 0 : Real64 hRadCoefC2Gnd = 0.0; // radiation coeff from collector to the ground [W/m2C]
1767 0 : Real64 hConvCoefA2C = 0.0; // convection coeff. between abs plate and cover [W/m2C]
1768 0 : Real64 hConvCoefC2C = 0.0; // convection coeff. between covers [W/m2C]
1769 0 : Real64 hConvCoefC2O = 0.0; // convection coeff. between outer cover and the ambient [W/m2C]
1770 0 : Real64 hRadCoefA2C = 0.0; // radiation coeff. between abs plate and cover [W/m2C]
1771 0 : Real64 hRadCoefC2C = 0.0; // radiation coeff. between covers [W/m2C]
1772 0 : Real64 hRadCoefC2O = 0.0; // radiation coeff. between outer covers and the ambient [W/m2C]
1773 :
1774 0 : int ParamNum = this->Parameters;
1775 0 : int NumCovers = state.dataSolarCollectors->Parameters(ParamNum).NumOfCovers;
1776 0 : int SurfNum = this->Surface;
1777 :
1778 0 : Real64 TempAbsPlate = this->SavedTempOfAbsPlate; // absorber plate average temperature [C]
1779 0 : Real64 TempInnerCover = this->SavedTempOfInnerCover; // inner cover average temperature [C]
1780 0 : Real64 TempOuterCover = this->SavedTempOfOuterCover; // outer cover average temperature [C]
1781 0 : Real64 TempOutdoorAir = state.dataSurface->SurfOutDryBulbTemp(SurfNum); // outdoor air temperature [C]
1782 :
1783 0 : Real64 EmissOfAbsPlate = state.dataSolarCollectors->Parameters(ParamNum).EmissOfAbsPlate; // emissivity of absorber plate
1784 0 : Real64 EmissOfOuterCover = state.dataSolarCollectors->Parameters(ParamNum).EmissOfCover[0]; // emissivity of outer cover
1785 0 : Real64 EmissOfInnerCover = state.dataSolarCollectors->Parameters(ParamNum).EmissOfCover[1]; // emissivity of inner cover
1786 0 : Real64 AirGapDepth = state.dataSolarCollectors->Parameters(ParamNum).CoverSpacing; // characteristic length [m]
1787 :
1788 0 : if (NumCovers == 1) {
1789 : // calc linearized radiation coefficient
1790 0 : tempnom = Constant::StefanBoltzmann * ((TempAbsPlate + Constant::Kelvin) + (TempOuterCover + Constant::Kelvin)) *
1791 0 : (pow_2(TempAbsPlate + Constant::Kelvin) + pow_2(TempOuterCover + Constant::Kelvin));
1792 0 : tempdenom = 1.0 / EmissOfAbsPlate + 1.0 / EmissOfOuterCover - 1.0;
1793 0 : hRadCoefA2C = tempnom / tempdenom;
1794 0 : hRadCoefC2C = 0.0;
1795 0 : hConvCoefC2C = 0.0;
1796 : // Calc convection heat transfer coefficient:
1797 0 : hConvCoefA2C = EnergyPlus::SolarCollectors::CollectorData::CalcConvCoeffBetweenPlates(
1798 : TempAbsPlate, TempOuterCover, AirGapDepth, this->CosTilt, this->SinTilt);
1799 0 : } else if (NumCovers == 2) {
1800 0 : for (int CoverNum = 1; CoverNum <= NumCovers; ++CoverNum) {
1801 0 : if (CoverNum == 1) {
1802 : // calc linearized radiation coefficient
1803 0 : tempnom = Constant::StefanBoltzmann * ((TempAbsPlate + Constant::Kelvin) + (TempInnerCover + Constant::Kelvin)) *
1804 0 : (pow_2(TempAbsPlate + Constant::Kelvin) + pow_2(TempInnerCover + Constant::Kelvin));
1805 0 : tempdenom = 1.0 / EmissOfAbsPlate + 1.0 / EmissOfInnerCover - 1.0;
1806 0 : hRadCoefA2C = tempnom / tempdenom;
1807 : // Calc convection heat transfer coefficient:
1808 0 : hConvCoefA2C = EnergyPlus::SolarCollectors::CollectorData::CalcConvCoeffBetweenPlates(
1809 : TempAbsPlate, TempOuterCover, AirGapDepth, this->CosTilt, this->SinTilt);
1810 : } else {
1811 : // calculate the linearized radiation coeff.
1812 0 : tempnom = Constant::StefanBoltzmann * ((TempInnerCover + Constant::Kelvin) + (TempOuterCover + Constant::Kelvin)) *
1813 0 : (pow_2(TempInnerCover + Constant::Kelvin) + pow_2(TempOuterCover + Constant::Kelvin));
1814 0 : tempdenom = 1.0 / EmissOfInnerCover + 1.0 / EmissOfOuterCover - 1.0;
1815 0 : hRadCoefC2C = tempnom / tempdenom;
1816 : // Calc convection heat transfer coefficient:
1817 0 : hConvCoefC2C = EnergyPlus::SolarCollectors::CollectorData::CalcConvCoeffBetweenPlates(
1818 : TempInnerCover, TempOuterCover, AirGapDepth, this->CosTilt, this->SinTilt);
1819 : }
1820 : }
1821 : }
1822 :
1823 : // Calc collector outside surface convection heat transfer coefficient:
1824 0 : hConvCoefC2O = 2.8 + 3.0 * state.dataSurface->SurfOutWindSpeed(SurfNum);
1825 :
1826 : // Calc linearized radiation coefficient between outer cover and the surrounding:
1827 0 : tempnom = state.dataSurface->Surface(SurfNum).ViewFactorSky * EmissOfOuterCover * Constant::StefanBoltzmann *
1828 0 : ((TempOuterCover + Constant::Kelvin) + state.dataEnvrn->SkyTempKelvin) *
1829 0 : (pow_2(TempOuterCover + Constant::Kelvin) + pow_2(state.dataEnvrn->SkyTempKelvin));
1830 0 : tempdenom = (TempOuterCover - TempOutdoorAir) / (TempOuterCover - state.dataEnvrn->SkyTemp);
1831 0 : if (tempdenom < 0.0) {
1832 : // use approximate linearized radiation coefficient
1833 0 : hRadCoefC2Sky = tempnom;
1834 0 : } else if (tempdenom == 0.0) {
1835 : // if temperature difference is zero, no radiation exchange
1836 0 : hRadCoefC2Sky = 0.0;
1837 : } else {
1838 0 : hRadCoefC2Sky = tempnom / tempdenom;
1839 : }
1840 :
1841 0 : tempnom = state.dataSurface->Surface(SurfNum).ViewFactorGround * EmissOfOuterCover * Constant::StefanBoltzmann *
1842 0 : ((TempOuterCover + Constant::Kelvin) + state.dataEnvrn->GroundTempKelvin) *
1843 0 : (pow_2(TempOuterCover + Constant::Kelvin) + pow_2(state.dataEnvrn->GroundTempKelvin));
1844 0 : tempdenom =
1845 0 : (TempOuterCover - TempOutdoorAir) / (TempOuterCover - state.dataEnvrn->GroundTemp[(int)DataEnvironment::GroundTempType::BuildingSurface]);
1846 0 : if (tempdenom < 0.0) {
1847 : // use approximate linearized radiation coefficient
1848 0 : hRadCoefC2Gnd = tempnom;
1849 0 : } else if (tempdenom == 0.0) {
1850 : // if temperature difference is zero, no radiation exchange
1851 0 : hRadCoefC2Gnd = 0.0;
1852 : } else {
1853 0 : hRadCoefC2Gnd = tempnom / tempdenom;
1854 : }
1855 :
1856 : // combine the radiation coefficients
1857 0 : hRadCoefC2O = hRadCoefC2Sky + hRadCoefC2Gnd;
1858 :
1859 : // calculate the overall top heat loss coefficient:
1860 :
1861 0 : if (NumCovers == 1) {
1862 0 : this->UTopLoss = 1.0 / (1.0 / (hRadCoefA2C + hConvCoefA2C) + 1.0 / (hRadCoefC2O + hConvCoefC2O));
1863 : } else {
1864 0 : this->UTopLoss = 1.0 / (1.0 / (hRadCoefA2C + hConvCoefA2C) + 1.0 / (hRadCoefC2C + hConvCoefC2C) + 1.0 / (hRadCoefC2O + hConvCoefC2O));
1865 : }
1866 :
1867 : // calculate the side loss coefficient. Adds the insulation resistance and the combined
1868 : // convection-radiation coefficients in series.
1869 0 : Real64 hRadConvOut = 5.7 + 3.8 * state.dataSurface->SurfOutWindSpeed(SurfNum);
1870 0 : this->UsLoss =
1871 0 : 1.0 / (1.0 / (state.dataSolarCollectors->Parameters(ParamNum).ULossSide * this->AreaRatio) + 1.0 / (hRadConvOut * this->AreaRatio));
1872 :
1873 : // the bottom loss coefficient calculation depends on the boundary condition
1874 0 : if (this->OSCM_ON) { // OtherSideConditionsModel
1875 0 : this->UbLoss = state.dataSolarCollectors->Parameters(ParamNum).ULossBottom;
1876 : } else { // AmbientAir
1877 0 : this->UbLoss = 1.0 / (1.0 / state.dataSolarCollectors->Parameters(ParamNum).ULossBottom + 1.0 / hRadConvOut);
1878 : }
1879 :
1880 : // Calculate current timestep covers temperature
1881 0 : if (NumCovers == 1) {
1882 0 : tempnom = this->CoverAbs[0] * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) + TempOutdoorAir * (hConvCoefC2O + hRadCoefC2O) +
1883 0 : TempAbsPlate * (hConvCoefA2C + hRadCoefA2C);
1884 0 : tempdenom = (hConvCoefC2O + hRadCoefC2O) + (hConvCoefA2C + hRadCoefA2C);
1885 0 : TempOuterCover = tempnom / tempdenom;
1886 0 : } else if (NumCovers == 2) {
1887 0 : for (int Num = 0; Num < NumCovers; ++Num) {
1888 0 : if (Num == 0) {
1889 0 : tempnom = this->CoverAbs[Num] * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) +
1890 0 : TempOutdoorAir * (hConvCoefC2O + hRadCoefC2O) + TempInnerCover * (hConvCoefC2C + hRadCoefC2C);
1891 0 : tempdenom = (hConvCoefC2O + hRadCoefC2O) + (hConvCoefC2C + hRadCoefC2C);
1892 0 : TempOuterCover = tempnom / tempdenom;
1893 0 : } else if (Num == 1) {
1894 0 : tempnom = this->CoverAbs[Num] * state.dataHeatBal->SurfQRadSWOutIncident(SurfNum) + TempAbsPlate * (hConvCoefA2C + hRadCoefA2C) +
1895 0 : TempOuterCover * (hConvCoefC2C + hRadCoefC2C);
1896 0 : tempdenom = (hConvCoefC2C + hRadCoefC2C + hConvCoefA2C + hRadCoefA2C);
1897 0 : TempInnerCover = tempnom / tempdenom;
1898 : }
1899 : }
1900 : }
1901 0 : this->TempOfInnerCover = TempInnerCover;
1902 0 : this->TempOfOuterCover = TempOuterCover;
1903 0 : }
1904 :
1905 1 : Real64 CollectorData::CalcConvCoeffBetweenPlates(Real64 const TempSurf1, // temperature of surface 1
1906 : Real64 const TempSurf2, // temperature of surface 1
1907 : Real64 const AirGap, // characteristic length [m]
1908 : Real64 const CosTilt, // cosine of surface tilt angle relative to the horizontal
1909 : Real64 const SinTilt // sine of surface tilt angle relative to the horizontal
1910 : )
1911 : {
1912 :
1913 : // FUNCTION INFORMATION:
1914 : // AUTHOR Bereket Nigusse, FSEC/UCF
1915 : // DATE WRITTEN February 2012
1916 :
1917 : // PURPOSE OF THIS FUNCTION:
1918 : // Calculates the convection coefficient for an enclosure between two parallel surfaces
1919 : // at different temperatures.
1920 : // METHODOLOGY EMPLOYED:
1921 : // Uses empirical correlation by Holands et al (1976) to determine free convection between
1922 : // inclined parallel plates at different temperature.
1923 : // REFERENCES:
1924 : // Duffie, J. A., and Beckman, W. A. Solar Engineering of Thermal Processes, 2nd. Edition.
1925 : // Wiley-Interscience: New York (1991).
1926 : // Property data for air at atmospheric pressure were taken from Table A-11, Yunus A Cengel
1927 : // Heat Transfer: A Practical Approach, McGraw-Hill, Boston, MA, 1998.
1928 :
1929 1 : Real64 constexpr gravity(9.806); // gravitational constant [m/s^2]
1930 :
1931 1 : int constexpr NumOfPropDivisions(11);
1932 : static constexpr std::array<Real64, NumOfPropDivisions> Temps = {
1933 : -23.15, 6.85, 16.85, 24.85, 26.85, 36.85, 46.85, 56.85, 66.85, 76.85, 126.85}; // Temperature, in C
1934 : static constexpr std::array<Real64, NumOfPropDivisions> Mu = {
1935 : 0.0000161, 0.0000175, 0.000018, 0.0000184, 0.0000185, 0.000019, 0.0000194, 0.0000199, 0.0000203, 0.0000208, 0.0000229}; // Viscosity, in
1936 : // kg/(m.s)
1937 : static constexpr std::array<Real64, NumOfPropDivisions> Conductivity = {
1938 : 0.0223, 0.0246, 0.0253, 0.0259, 0.0261, 0.0268, 0.0275, 0.0283, 0.0290, 0.0297, 0.0331}; // Conductivity, in W/mK
1939 : static constexpr std::array<Real64, NumOfPropDivisions> Pr = {
1940 : 0.724, 0.717, 0.714, 0.712, 0.712, 0.711, 0.71, 0.708, 0.707, 0.706, 0.703}; // Prandtl number (dimensionless)
1941 : static constexpr std::array<Real64, NumOfPropDivisions> Density = {
1942 : 1.413, 1.271, 1.224, 1.186, 1.177, 1.143, 1.110, 1.076, 1.043, 1.009, 0.883}; // Density, in kg/m3
1943 :
1944 : Real64 CondOfAir; // thermal conductivity of air [W/mK]
1945 : Real64 VisDOfAir; // dynamic viscosity of air [kg/m.s]
1946 : Real64 DensOfAir; // density of air [W/mK]
1947 : Real64 PrOfAir; // Prandtl number of air [W/mK]
1948 : Real64 VolExpAir; // volumetric expansion of air [1/K]
1949 :
1950 1 : Real64 DeltaT = std::abs(TempSurf1 - TempSurf2);
1951 1 : Real64 Tref = 0.5 * (TempSurf1 + TempSurf2);
1952 1 : int Index = 0;
1953 12 : while (Index < NumOfPropDivisions) {
1954 11 : if (Tref < Temps[Index]) break; // DO loop
1955 11 : ++Index;
1956 : }
1957 :
1958 : // Initialize thermal properties of air
1959 1 : if (Index == 0) {
1960 0 : VisDOfAir = Mu[Index];
1961 0 : CondOfAir = Conductivity[Index];
1962 0 : PrOfAir = Pr[Index];
1963 0 : DensOfAir = Density[Index];
1964 1 : } else if (Index >= NumOfPropDivisions) { // 0-index, hence MaxIndex = NumOfPropDivisions - 1
1965 1 : Index = NumOfPropDivisions - 1;
1966 1 : VisDOfAir = Mu[Index];
1967 1 : CondOfAir = Conductivity[Index];
1968 1 : PrOfAir = Pr[Index];
1969 1 : DensOfAir = Density[Index];
1970 : } else {
1971 0 : Real64 InterpFrac = (Tref - Temps[Index - 1]) / (Temps[Index] - Temps[Index - 1]);
1972 0 : VisDOfAir = Mu[Index - 1] + InterpFrac * (Mu[Index] - Mu[Index - 1]);
1973 0 : CondOfAir = Conductivity[Index - 1] + InterpFrac * (Conductivity[Index] - Conductivity[Index - 1]);
1974 0 : PrOfAir = Pr[Index - 1] + InterpFrac * (Pr[Index] - Pr[Index - 1]);
1975 0 : DensOfAir = Density[Index - 1] + InterpFrac * (Density[Index] - Density[Index - 1]);
1976 : }
1977 :
1978 1 : VolExpAir = 1.0 / (Tref + Constant::Kelvin);
1979 :
1980 : // Rayleigh number
1981 1 : Real64 RaNum = gravity * pow_2(DensOfAir) * VolExpAir * PrOfAir * DeltaT * pow_3(AirGap) / pow_2(VisDOfAir);
1982 :
1983 : // Rayleigh number of air times cosine of collector tilt []
1984 1 : Real64 RaNumCosTilt = RaNum * CosTilt;
1985 :
1986 : Real64 NuL; // Nusselt number
1987 1 : if (RaNum == 0.0) {
1988 0 : NuL = 0.0;
1989 : } else {
1990 1 : if (RaNumCosTilt > 1708.0) {
1991 1 : NuL = 1.44 * (1.0 - 1708.0 * std::pow(SinTilt, 1.6) / (RaNum * CosTilt)) * (1.0 - 1708.0 / RaNumCosTilt);
1992 : } else {
1993 0 : NuL = 0.0;
1994 : }
1995 : }
1996 1 : if (RaNumCosTilt > 5830.0) {
1997 1 : NuL += std::pow(RaNumCosTilt / 5830.0 - 1.0, 1.0 / 3.0);
1998 : }
1999 1 : ++NuL;
2000 1 : Real64 hConvCoef = NuL * CondOfAir / AirGap;
2001 :
2002 1 : return hConvCoef;
2003 : }
2004 :
2005 0 : Real64 CollectorData::CalcConvCoeffAbsPlateAndWater(EnergyPlusData &state,
2006 : Real64 const TAbsorber, // temperature of absorber plate [C]
2007 : Real64 const TWater, // temperature of water [C]
2008 : Real64 const Lc, // characteristic length [m]
2009 : Real64 const TiltR2V // collector tilt angle relative to the vertical [degree]
2010 : )
2011 : {
2012 :
2013 : // FUNCTION INFORMATION:
2014 : // AUTHOR Bereket Nigusse, FSEC/UCF
2015 : // DATE WRITTEN February 2012
2016 :
2017 : // PURPOSE OF THIS FUNCTION:
2018 : // Calculates the free convection coefficient between the absorber plate and water.
2019 : // METHODOLOGY EMPLOYED:
2020 : // The convection coefficient calculation were based on the Fujii and Imura empirical correlations
2021 : // REFERENCES:
2022 : // T.Fujii, and H.Imura,Natural convection heat transfer from a plate with arbitrary inclination.
2023 : // International Journal of Heat and Mass Transfer: 15(4), (1972), 755-764.
2024 :
2025 : Real64 hConvA2W; // convection coefficient, [W/m2K]
2026 :
2027 0 : Real64 constexpr gravity(9.806); // gravitational constant [m/s^2]
2028 : static constexpr std::string_view CalledFrom("SolarCollectors:CalcConvCoeffAbsPlateAndWater");
2029 :
2030 0 : Real64 DeltaT = std::abs(TAbsorber - TWater);
2031 0 : Real64 TReference = TAbsorber - 0.25 * (TAbsorber - TWater);
2032 : // record fluid prop index for water
2033 :
2034 0 : auto *water = Fluid::GetWater(state);
2035 : // find properties of water - always assume water
2036 0 : Real64 WaterSpecHeat = water->getSpecificHeat(state, max(TReference, 0.0), CalledFrom);
2037 0 : Real64 CondOfWater = water->getConductivity(state, max(TReference, 0.0), CalledFrom);
2038 0 : Real64 VisOfWater = water->getViscosity(state, max(TReference, 0.0), CalledFrom);
2039 0 : Real64 DensOfWater = water->getDensity(state, max(TReference, 0.0), CalledFrom);
2040 0 : Real64 PrOfWater = VisOfWater * WaterSpecHeat / CondOfWater;
2041 : // Requires a different reference temperature for volumetric expansion coefficient
2042 0 : TReference = TWater - 0.25 * (TWater - TAbsorber);
2043 : Real64 VolExpWater =
2044 0 : -(water->getDensity(state, max(TReference, 10.0) + 5.0, CalledFrom) - water->getDensity(state, max(TReference, 10.0) - 5.0, CalledFrom)) /
2045 0 : (10.0 * DensOfWater);
2046 :
2047 : // Grashof number
2048 0 : Real64 GrNum = gravity * VolExpWater * DensOfWater * DensOfWater * PrOfWater * DeltaT * pow_3(Lc) / pow_2(VisOfWater);
2049 0 : Real64 CosTilt = std::cos(TiltR2V * Constant::DegToRad);
2050 :
2051 : Real64 RaNum; // Raleigh number
2052 : Real64 NuL; // Nusselt number
2053 0 : if (TAbsorber > TWater) {
2054 : // hot absorber plate facing down
2055 0 : if (std::abs(TiltR2V - 90.0) < 1.0) {
2056 : // It is a horizontal surface
2057 0 : RaNum = GrNum * PrOfWater;
2058 0 : if (RaNum <= 1708.0) {
2059 0 : NuL = 1.0;
2060 : } else {
2061 0 : NuL = 0.58 * std::pow(RaNum, 0.20);
2062 : }
2063 : } else {
2064 0 : RaNum = GrNum * PrOfWater * CosTilt;
2065 0 : if (RaNum <= 1708.0) {
2066 0 : NuL = 1.0;
2067 : } else {
2068 0 : NuL = 0.56 * root_4(RaNum);
2069 : }
2070 : }
2071 : } else {
2072 : // cold plate facing down or hot plate facing up
2073 0 : RaNum = GrNum * PrOfWater;
2074 0 : if (RaNum > 5.0e8) {
2075 0 : NuL = 0.13 * std::pow(RaNum, 1.0 / 3.0);
2076 : } else {
2077 0 : NuL = 0.16 * std::pow(RaNum, 1.0 / 3.0);
2078 0 : if (RaNum <= 1708.0) NuL = 1.0;
2079 : }
2080 : }
2081 0 : hConvA2W = NuL * CondOfWater / Lc;
2082 :
2083 0 : return hConvA2W;
2084 : }
2085 :
2086 0 : void CollectorData::update(EnergyPlusData &state) // NOLINT(readability-make-member-function-const)
2087 : {
2088 :
2089 : // SUBROUTINE INFORMATION:
2090 : // AUTHOR Peter Graham Ellis
2091 : // DATE WRITTEN January 2004
2092 :
2093 : // PURPOSE OF THIS SUBROUTINE:
2094 : // Updates the node variables with local variables.
2095 :
2096 : static constexpr std::string_view RoutineName("UpdateSolarCollector");
2097 :
2098 0 : PlantUtilities::SafeCopyPlantNode(state, this->InletNode, this->OutletNode);
2099 : // Set outlet node variables that are possibly changed
2100 0 : state.dataLoopNodes->Node(this->OutletNode).Temp = this->OutletTemp;
2101 0 : Real64 Cp = this->plantLoc.loop->glycol->getSpecificHeat(state, this->OutletTemp, RoutineName);
2102 0 : state.dataLoopNodes->Node(this->OutletNode).Enthalpy = Cp * state.dataLoopNodes->Node(this->OutletNode).Temp;
2103 0 : }
2104 :
2105 0 : void CollectorData::report(EnergyPlusData &state)
2106 : {
2107 :
2108 : // SUBROUTINE INFORMATION:
2109 : // AUTHOR Peter Graham Ellis
2110 : // DATE WRITTEN January 2004
2111 :
2112 : // PURPOSE OF THIS SUBROUTINE:
2113 : // Calculates report variables.
2114 :
2115 0 : Real64 TimeStepInSecond = state.dataHVACGlobal->TimeStepSysSec;
2116 :
2117 0 : this->Energy = this->Power * TimeStepInSecond;
2118 0 : this->HeatEnergy = this->HeatRate * TimeStepInSecond;
2119 0 : this->CollHeatLossEnergy = this->SkinHeatLossRate * TimeStepInSecond;
2120 0 : this->StoredHeatEnergy = this->StoredHeatRate * TimeStepInSecond;
2121 0 : }
2122 :
2123 0 : void CollectorData::GetExtVentedCavityIndex(EnergyPlusData &state, int const SurfacePtr, int &VentCavIndex)
2124 : {
2125 :
2126 : // SUBROUTINE INFORMATION:
2127 : // AUTHOR B. Nigusse, FSEC. Adopted from Photovoltaics module
2128 : // DATE WRITTEN February 2012
2129 :
2130 : // PURPOSE OF THIS SUBROUTINE:
2131 : // object oriented "Get" routine for establishing correct integer index from outside this module
2132 :
2133 : // METHODOLOGY EMPLOYED:
2134 : // mine Surface derived type for correct index/number of surface
2135 : // mine ExtVentedCavity derived type that has the surface.
2136 : // Adapted from Photovoltaics module, originally developed by Brent G. (2004)
2137 :
2138 : bool Found;
2139 :
2140 0 : if (SurfacePtr == 0) {
2141 : // should be trapped already
2142 0 : ShowFatalError(state, "Invalid surface passed to GetExtVentedCavityIndex");
2143 : }
2144 :
2145 0 : int CavNum = 0;
2146 0 : Found = false;
2147 0 : for (int thisCav = 1; thisCav <= state.dataSurface->TotExtVentCav; ++thisCav) {
2148 0 : for (int ThisSurf = 1; ThisSurf <= state.dataHeatBal->ExtVentedCavity(thisCav).NumSurfs; ++ThisSurf) {
2149 0 : if (SurfacePtr == state.dataHeatBal->ExtVentedCavity(thisCav).SurfPtrs(ThisSurf)) {
2150 0 : Found = true;
2151 0 : CavNum = thisCav;
2152 : }
2153 : }
2154 : }
2155 :
2156 0 : if (!Found) {
2157 0 : ShowFatalError(state,
2158 0 : format("Did not find surface in Exterior Vented Cavity description in GetExtVentedCavityIndex, Surface name = {}",
2159 0 : state.dataSurface->Surface(SurfacePtr).Name));
2160 : } else {
2161 :
2162 0 : VentCavIndex = CavNum;
2163 : }
2164 0 : }
2165 0 : void CollectorData::oneTimeInit_new(EnergyPlusData &state)
2166 : {
2167 :
2168 0 : this->setupOutputVars(state);
2169 :
2170 0 : if (this->SetLoopIndexFlag) {
2171 0 : if (allocated(state.dataPlnt->PlantLoop)) {
2172 0 : bool errFlag = false;
2173 0 : PlantUtilities::ScanPlantLoopsForObject(state, this->Name, this->Type, this->plantLoc, errFlag, _, _, _, _, _);
2174 0 : if (errFlag) {
2175 0 : ShowFatalError(state, "InitSolarCollector: Program terminated due to previous condition(s).");
2176 : }
2177 0 : this->SetLoopIndexFlag = false;
2178 : }
2179 : }
2180 0 : }
2181 0 : void CollectorData::oneTimeInit([[maybe_unused]] EnergyPlusData &state)
2182 : {
2183 0 : }
2184 :
2185 : } // namespace SolarCollectors
2186 :
2187 : } // namespace EnergyPlus
|