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