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