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