Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2025, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 : #include <cmath>
50 : #include <memory>
51 :
52 : // ObjexxFCL Headers
53 : #include <ObjexxFCL/Array.functions.hh>
54 : #include <ObjexxFCL/Array3D.hh>
55 : #include <ObjexxFCL/Fmath.hh>
56 :
57 : // EnergyPlus Headers
58 : #include <EnergyPlus/BranchNodeConnections.hh>
59 : #include <EnergyPlus/Construction.hh>
60 : #include <EnergyPlus/ConvectionCoefficients.hh>
61 : #include <EnergyPlus/Data/EnergyPlusData.hh>
62 : #include <EnergyPlus/DataEnvironment.hh>
63 : #include <EnergyPlus/DataHVACGlobals.hh>
64 : #include <EnergyPlus/DataHeatBalance.hh>
65 : #include <EnergyPlus/DataIPShortCuts.hh>
66 : #include <EnergyPlus/DataLoopNode.hh>
67 : #include <EnergyPlus/FluidProperties.hh>
68 : #include <EnergyPlus/General.hh>
69 : #include <EnergyPlus/GlobalNames.hh>
70 : #include <EnergyPlus/GroundTemperatureModeling/GroundTemperatureModelManager.hh>
71 : #include <EnergyPlus/HeatBalanceInternalHeatGains.hh>
72 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
73 : #include <EnergyPlus/Material.hh>
74 : #include <EnergyPlus/NodeInputManager.hh>
75 : #include <EnergyPlus/OutAirNodeManager.hh>
76 : #include <EnergyPlus/OutputProcessor.hh>
77 : #include <EnergyPlus/PipeHeatTransfer.hh>
78 : #include <EnergyPlus/Plant/DataPlant.hh>
79 : #include <EnergyPlus/PlantUtilities.hh>
80 : #include <EnergyPlus/ScheduleManager.hh>
81 : #include <EnergyPlus/UtilityRoutines.hh>
82 : #include <EnergyPlus/ZoneTempPredictorCorrector.hh>
83 :
84 : namespace EnergyPlus::PipeHeatTransfer {
85 :
86 : // Module containing the routines dealing with pipes with transport delay
87 : // and heat transfer.
88 :
89 : // MODULE INFORMATION:
90 : // AUTHOR Simon Rees
91 : // DATE WRITTEN July 2007
92 : // MODIFIED May 2008
93 : // RE-ENGINEERED na
94 :
95 : // PURPOSE OF THIS MODULE:
96 : // The purpose of this module is to simulate a pipe with heat transfer
97 :
98 : // METHODOLOGY EMPLOYED:
99 : // An implicit finite difference method is used to solve the temperature distribution of the
100 : // fluid in the pipe as a result of the transport delay and heat transfer to the environment.
101 : // For buried pipes, the simulation involves an implicit finite difference model of the soil,
102 : // which was originally based on Piechowski's thesis (below). Equation numbers for
103 : // pipe:underground calculations are from Piechowski's thesis. In Piechowski, the near-pipe
104 : // region is solved with a detailed finite difference grid, this current model makes use of
105 : // the Hanby model to simulate the actual pipe.
106 :
107 : // Kusuda, T. & Achenbach, P. (1965), 'Earth temperature and thermal diffusivity at
108 : // selected stations in the united states', ASHRAE Transactions 71(1), 61-75.
109 : // Piechowski, M. (1996), A Ground Coupled Heat Pump System with Energy Storage,
110 : // PhD thesis, University of Melbourne.
111 :
112 : // OTHER NOTES: Equation Numbers listed in buried pipe routines are from Piechowski's thesis
113 :
114 : enum class PipeIndoorBoundaryType
115 : {
116 : Invalid = -1,
117 : Zone,
118 : Schedule,
119 : Num
120 : };
121 : constexpr std::array<std::string_view, static_cast<int>(PipeIndoorBoundaryType::Num)> pipeIndoorBoundaryTypeNamesUC = {"ZONE", "SCHEDULE"};
122 :
123 : // Functions
124 :
125 4 : PlantComponent *PipeHTData::factory(EnergyPlusData &state, DataPlant::PlantEquipmentType objectType, std::string const &objectName)
126 : {
127 : // Process the input data for pipes if it hasn't been done already
128 4 : if (state.dataPipeHT->GetPipeInputFlag) {
129 4 : GetPipesHeatTransfer(state);
130 4 : state.dataPipeHT->GetPipeInputFlag = false;
131 : }
132 : // Now look for this particular pipe in the list
133 4 : auto thisObj = std::find_if(state.dataPipeHT->PipeHT.begin(),
134 4 : state.dataPipeHT->PipeHT.end(),
135 4 : [&objectType, &objectName](const PipeHTData &myObj) { return myObj.Type == objectType && myObj.Name == objectName; });
136 4 : if (thisObj != state.dataPipeHT->PipeHT.end()) {
137 4 : return thisObj;
138 : }
139 : // If we didn't find it, fatal
140 0 : ShowFatalError(state, format("PipeHTFactory: Error getting inputs for pipe named: {}", objectName));
141 : // Shut up the compiler
142 0 : return nullptr;
143 : }
144 :
145 58100 : void PipeHTData::simulate(EnergyPlusData &state,
146 : [[maybe_unused]] const PlantLocation &calledFromLocation,
147 : bool const FirstHVACIteration,
148 : [[maybe_unused]] Real64 &CurLoad,
149 : [[maybe_unused]] bool const RunFlag)
150 : {
151 58100 : this->InitPipesHeatTransfer(state, FirstHVACIteration);
152 : // make the calculations
153 802816 : for (int InnerTimeStepCtr = 1; InnerTimeStepCtr <= state.dataPipeHT->nsvNumInnerTimeSteps; ++InnerTimeStepCtr) {
154 744716 : switch (this->EnvironmentPtr) {
155 186179 : case EnvrnPtr::GroundEnv: {
156 186179 : this->CalcBuriedPipeSoil(state);
157 186179 : } break;
158 558537 : default: {
159 558537 : this->CalcPipesHeatTransfer(state);
160 558537 : } break;
161 : }
162 744716 : this->PushInnerTimeStepArrays();
163 : }
164 : // update variables
165 58100 : this->UpdatePipesHeatTransfer(state);
166 : // update report variables
167 58100 : this->ReportPipesHeatTransfer(state);
168 58100 : }
169 :
170 744716 : void PipeHTData::PushInnerTimeStepArrays()
171 : {
172 744716 : if (this->EnvironmentPtr == EnvrnPtr::GroundEnv) {
173 3723580 : for (int LengthIndex = 2; LengthIndex <= this->NumSections; ++LengthIndex) {
174 31836609 : for (int DepthIndex = 1; DepthIndex <= this->NumDepthNodes; ++DepthIndex) {
175 113196832 : for (int WidthIndex = 2; WidthIndex <= this->PipeNodeWidth; ++WidthIndex) {
176 : // This will store the old 'current' values as the new 'previous values' This allows
177 : // us to use the previous time array as history terms in the equations
178 84897624 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Previous) =
179 84897624 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Current);
180 : }
181 : }
182 : }
183 : }
184 : // Then update the Hanby near pipe model temperatures
185 744716 : this->PreviousFluidTemp = this->FluidTemp;
186 744716 : this->PreviousPipeTemp = this->PipeTemp;
187 744716 : }
188 :
189 4 : void GetPipesHeatTransfer(EnergyPlusData &state)
190 : {
191 :
192 : // SUBROUTINE INFORMATION:
193 : // AUTHOR Simon Rees
194 : // DATE WRITTEN July 2007
195 : // MODIFIED na
196 : // RE-ENGINEERED na
197 : // PURPOSE OF THIS SUBROUTINE:
198 : // This subroutine reads the input for hydronic Pipe Heat Transfers
199 : // from the user input file. This will contain all of the information
200 : // needed to define and simulate the surface.
201 :
202 : // Using/Aliasing
203 : using BranchNodeConnections::TestCompSet;
204 :
205 : using NodeInputManager::GetOnlySingleNode;
206 : using namespace DataLoopNode;
207 : using OutAirNodeManager::CheckOutAirNodeNumber;
208 :
209 : static constexpr std::string_view routineName = "GetPipeHeatTransfer";
210 :
211 : // SUBROUTINE PARAMETER DEFINITIONS:
212 4 : int constexpr NumPipeSections(20);
213 4 : int constexpr NumberOfDepthNodes(8); // Number of nodes in the cartesian grid-Should be an even # for now
214 :
215 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
216 4 : bool ErrorsFound(false); // Set to true if errors in input,
217 :
218 : // fatal at end of routine
219 : int IOStatus; // Used in GetObjectItem
220 : int NumAlphas; // Number of Alphas for each GetObjectItem call
221 : int NumNumbers; // Number of Numbers for each GetObjectItem call
222 : int NumOfPipeHTInt; // Number of Pipe Heat Transfer objects
223 : int NumOfPipeHTExt; // Number of Pipe Heat Transfer objects
224 : int NumOfPipeHTUG; // Number of Pipe Heat Transfer objects
225 :
226 4 : auto &s_ipsc = state.dataIPShortCut;
227 4 : auto &s_mat = state.dataMaterial;
228 : // Initializations and allocations
229 4 : s_ipsc->cCurrentModuleObject = "Pipe:Indoor";
230 4 : NumOfPipeHTInt = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, s_ipsc->cCurrentModuleObject);
231 4 : s_ipsc->cCurrentModuleObject = "Pipe:Outdoor";
232 4 : NumOfPipeHTExt = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, s_ipsc->cCurrentModuleObject);
233 4 : s_ipsc->cCurrentModuleObject = "Pipe:Underground";
234 4 : NumOfPipeHTUG = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, s_ipsc->cCurrentModuleObject);
235 :
236 4 : state.dataPipeHT->nsvNumOfPipeHT = NumOfPipeHTInt + NumOfPipeHTExt + NumOfPipeHTUG;
237 : // allocate data structures
238 4 : if (allocated(state.dataPipeHT->PipeHT)) {
239 0 : state.dataPipeHT->PipeHT.deallocate();
240 : }
241 :
242 4 : state.dataPipeHT->PipeHT.allocate(state.dataPipeHT->nsvNumOfPipeHT);
243 4 : state.dataPipeHT->PipeHTUniqueNames.reserve(static_cast<unsigned>(state.dataPipeHT->nsvNumOfPipeHT));
244 4 : int Item = 0;
245 :
246 4 : s_ipsc->cCurrentModuleObject = "Pipe:Indoor";
247 6 : for (int PipeItem = 1; PipeItem <= NumOfPipeHTInt; ++PipeItem) {
248 2 : ++Item;
249 : // get the object name
250 4 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
251 2 : s_ipsc->cCurrentModuleObject,
252 : PipeItem,
253 2 : s_ipsc->cAlphaArgs,
254 : NumAlphas,
255 2 : s_ipsc->rNumericArgs,
256 : NumNumbers,
257 : IOStatus,
258 2 : s_ipsc->lNumericFieldBlanks,
259 2 : s_ipsc->lAlphaFieldBlanks,
260 2 : s_ipsc->cAlphaFieldNames,
261 2 : s_ipsc->cNumericFieldNames);
262 :
263 2 : GlobalNames::VerifyUniqueInterObjectName(state,
264 2 : state.dataPipeHT->PipeHTUniqueNames,
265 2 : s_ipsc->cAlphaArgs(1),
266 2 : s_ipsc->cCurrentModuleObject,
267 2 : s_ipsc->cAlphaFieldNames(1),
268 : ErrorsFound);
269 2 : state.dataPipeHT->PipeHT(Item).Name = s_ipsc->cAlphaArgs(1);
270 2 : state.dataPipeHT->PipeHT(Item).Type = DataPlant::PlantEquipmentType::PipeInterior;
271 :
272 : // General user input data
273 2 : state.dataPipeHT->PipeHT(Item).Construction = s_ipsc->cAlphaArgs(2);
274 2 : state.dataPipeHT->PipeHT(Item).ConstructionNum = Util::FindItemInList(s_ipsc->cAlphaArgs(2), state.dataConstruction->Construct);
275 :
276 2 : if (state.dataPipeHT->PipeHT(Item).ConstructionNum == 0) {
277 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(2), s_ipsc->cAlphaArgs(2)));
278 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
279 0 : ErrorsFound = true;
280 : }
281 :
282 : // get inlet node data
283 2 : state.dataPipeHT->PipeHT(Item).InletNode = s_ipsc->cAlphaArgs(3);
284 4 : state.dataPipeHT->PipeHT(Item).InletNodeNum = GetOnlySingleNode(state,
285 2 : s_ipsc->cAlphaArgs(3),
286 : ErrorsFound,
287 : DataLoopNode::ConnectionObjectType::PipeIndoor,
288 2 : s_ipsc->cAlphaArgs(1),
289 : DataLoopNode::NodeFluidType::Water,
290 : DataLoopNode::ConnectionType::Inlet,
291 : NodeInputManager::CompFluidStream::Primary,
292 : ObjectIsNotParent);
293 2 : if (state.dataPipeHT->PipeHT(Item).InletNodeNum == 0) {
294 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3)));
295 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
296 0 : ErrorsFound = true;
297 : }
298 :
299 : // get outlet node data
300 2 : state.dataPipeHT->PipeHT(Item).OutletNode = s_ipsc->cAlphaArgs(4);
301 4 : state.dataPipeHT->PipeHT(Item).OutletNodeNum = GetOnlySingleNode(state,
302 2 : s_ipsc->cAlphaArgs(4),
303 : ErrorsFound,
304 : DataLoopNode::ConnectionObjectType::PipeIndoor,
305 2 : s_ipsc->cAlphaArgs(1),
306 : DataLoopNode::NodeFluidType::Water,
307 : DataLoopNode::ConnectionType::Outlet,
308 : NodeInputManager::CompFluidStream::Primary,
309 : ObjectIsNotParent);
310 2 : if (state.dataPipeHT->PipeHT(Item).OutletNodeNum == 0) {
311 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(4), s_ipsc->cAlphaArgs(4)));
312 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
313 0 : ErrorsFound = true;
314 : }
315 :
316 2 : TestCompSet(state, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1), s_ipsc->cAlphaArgs(3), s_ipsc->cAlphaArgs(4), "Pipe Nodes");
317 :
318 : // get environmental boundary condition type
319 :
320 2 : if (s_ipsc->lAlphaFieldBlanks(5)) {
321 0 : s_ipsc->cAlphaArgs(5) = "ZONE";
322 : }
323 :
324 2 : PipeIndoorBoundaryType indoorType = static_cast<PipeIndoorBoundaryType>(getEnumValue(pipeIndoorBoundaryTypeNamesUC, s_ipsc->cAlphaArgs(5)));
325 2 : switch (indoorType) {
326 1 : case PipeIndoorBoundaryType::Zone:
327 1 : state.dataPipeHT->PipeHT(Item).EnvironmentPtr = EnvrnPtr::ZoneEnv;
328 1 : state.dataPipeHT->PipeHT(Item).EnvrZonePtr = Util::FindItemInList(s_ipsc->cAlphaArgs(6), state.dataHeatBal->Zone);
329 1 : if (state.dataPipeHT->PipeHT(Item).EnvrZonePtr == 0) {
330 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6)));
331 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
332 0 : ErrorsFound = true;
333 : }
334 1 : break;
335 :
336 1 : case PipeIndoorBoundaryType::Schedule:
337 1 : state.dataPipeHT->PipeHT(Item).EnvironmentPtr = EnvrnPtr::ScheduleEnv;
338 :
339 1 : state.dataPipeHT->PipeHT(Item).envrSched = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(7));
340 1 : state.dataPipeHT->PipeHT(Item).envrVelSched = Sched::GetSchedule(state, s_ipsc->cAlphaArgs(8));
341 1 : if (state.dataPipeHT->PipeHT(Item).envrSched == nullptr) {
342 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaArgs(7)));
343 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
344 0 : ErrorsFound = true;
345 : }
346 1 : if (state.dataPipeHT->PipeHT(Item).envrVelSched == nullptr) {
347 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(8), s_ipsc->cAlphaArgs(8)));
348 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
349 0 : ErrorsFound = true;
350 : }
351 1 : break;
352 :
353 0 : default:
354 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(5), s_ipsc->cAlphaArgs(5)));
355 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
356 0 : ShowContinueError(state, R"(Should be "ZONE" or "SCHEDULE")"); // TODO rename point
357 0 : ErrorsFound = true;
358 : }
359 :
360 : // dimensions
361 2 : state.dataPipeHT->PipeHT(Item).PipeID = s_ipsc->rNumericArgs(1);
362 2 : if (s_ipsc->rNumericArgs(1) <= 0.0) { // not really necessary because idd field has "minimum> 0"
363 0 : ShowSevereError(state, format("GetPipesHeatTransfer: invalid {} of {:.4R}", s_ipsc->cNumericFieldNames(1), s_ipsc->rNumericArgs(1)));
364 0 : ShowContinueError(state, format("{} must be > 0.0", s_ipsc->cNumericFieldNames(1)));
365 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
366 :
367 0 : ErrorsFound = true;
368 : }
369 :
370 2 : state.dataPipeHT->PipeHT(Item).Length = s_ipsc->rNumericArgs(2);
371 2 : if (s_ipsc->rNumericArgs(2) <= 0.0) { // not really necessary because idd field has "minimum> 0"
372 0 : ShowSevereError(state, format("GetPipesHeatTransfer: invalid {} of {:.4R}", s_ipsc->cNumericFieldNames(2), s_ipsc->rNumericArgs(2)));
373 0 : ShowContinueError(state, format("{} must be > 0.0", s_ipsc->cNumericFieldNames(2)));
374 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
375 0 : ErrorsFound = true;
376 : }
377 :
378 2 : if (state.dataPipeHT->PipeHT(Item).ConstructionNum != 0) {
379 2 : state.dataPipeHT->PipeHT(Item).ValidatePipeConstruction(state,
380 2 : s_ipsc->cCurrentModuleObject,
381 2 : s_ipsc->cAlphaArgs(2),
382 2 : s_ipsc->cAlphaFieldNames(2),
383 2 : state.dataPipeHT->PipeHT(Item).ConstructionNum,
384 : ErrorsFound);
385 : }
386 :
387 : } // end of input loop
388 :
389 4 : s_ipsc->cCurrentModuleObject = "Pipe:Outdoor";
390 5 : for (int PipeItem = 1; PipeItem <= NumOfPipeHTExt; ++PipeItem) {
391 1 : ++Item;
392 : // get the object name
393 2 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
394 1 : s_ipsc->cCurrentModuleObject,
395 : PipeItem,
396 1 : s_ipsc->cAlphaArgs,
397 : NumAlphas,
398 1 : s_ipsc->rNumericArgs,
399 : NumNumbers,
400 : IOStatus,
401 1 : s_ipsc->lNumericFieldBlanks,
402 1 : s_ipsc->lAlphaFieldBlanks,
403 1 : s_ipsc->cAlphaFieldNames,
404 1 : s_ipsc->cNumericFieldNames);
405 :
406 1 : GlobalNames::VerifyUniqueInterObjectName(state,
407 1 : state.dataPipeHT->PipeHTUniqueNames,
408 1 : s_ipsc->cAlphaArgs(1),
409 1 : s_ipsc->cCurrentModuleObject,
410 1 : s_ipsc->cAlphaFieldNames(1),
411 : ErrorsFound);
412 1 : state.dataPipeHT->PipeHT(Item).Name = s_ipsc->cAlphaArgs(1);
413 1 : state.dataPipeHT->PipeHT(Item).Type = DataPlant::PlantEquipmentType::PipeExterior;
414 :
415 : // General user input data
416 1 : state.dataPipeHT->PipeHT(Item).Construction = s_ipsc->cAlphaArgs(2);
417 1 : state.dataPipeHT->PipeHT(Item).ConstructionNum = Util::FindItemInList(s_ipsc->cAlphaArgs(2), state.dataConstruction->Construct);
418 :
419 1 : if (state.dataPipeHT->PipeHT(Item).ConstructionNum == 0) {
420 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(2), s_ipsc->cAlphaArgs(2)));
421 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
422 0 : ErrorsFound = true;
423 : }
424 :
425 : // get inlet node data
426 1 : state.dataPipeHT->PipeHT(Item).InletNode = s_ipsc->cAlphaArgs(3);
427 2 : state.dataPipeHT->PipeHT(Item).InletNodeNum = GetOnlySingleNode(state,
428 1 : s_ipsc->cAlphaArgs(3),
429 : ErrorsFound,
430 : DataLoopNode::ConnectionObjectType::PipeOutdoor,
431 1 : s_ipsc->cAlphaArgs(1),
432 : DataLoopNode::NodeFluidType::Water,
433 : DataLoopNode::ConnectionType::Inlet,
434 : NodeInputManager::CompFluidStream::Primary,
435 : ObjectIsNotParent);
436 1 : if (state.dataPipeHT->PipeHT(Item).InletNodeNum == 0) {
437 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3)));
438 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
439 0 : ErrorsFound = true;
440 : }
441 :
442 : // get outlet node data
443 1 : state.dataPipeHT->PipeHT(Item).OutletNode = s_ipsc->cAlphaArgs(4);
444 2 : state.dataPipeHT->PipeHT(Item).OutletNodeNum = GetOnlySingleNode(state,
445 1 : s_ipsc->cAlphaArgs(4),
446 : ErrorsFound,
447 : DataLoopNode::ConnectionObjectType::PipeOutdoor,
448 1 : s_ipsc->cAlphaArgs(1),
449 : DataLoopNode::NodeFluidType::Water,
450 : DataLoopNode::ConnectionType::Outlet,
451 : NodeInputManager::CompFluidStream::Primary,
452 : ObjectIsNotParent);
453 1 : if (state.dataPipeHT->PipeHT(Item).OutletNodeNum == 0) {
454 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(4), s_ipsc->cAlphaArgs(4)));
455 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
456 0 : ErrorsFound = true;
457 : }
458 :
459 1 : TestCompSet(state, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1), s_ipsc->cAlphaArgs(3), s_ipsc->cAlphaArgs(4), "Pipe Nodes");
460 :
461 : // get environmental boundary condition type
462 : // PipeHT(Item)%Environment = 'OutdoorAir'
463 1 : state.dataPipeHT->PipeHT(Item).EnvironmentPtr = EnvrnPtr::OutsideAirEnv;
464 :
465 1 : state.dataPipeHT->PipeHT(Item).EnvrAirNode = s_ipsc->cAlphaArgs(5);
466 2 : state.dataPipeHT->PipeHT(Item).EnvrAirNodeNum = GetOnlySingleNode(state,
467 1 : s_ipsc->cAlphaArgs(5),
468 : ErrorsFound,
469 : DataLoopNode::ConnectionObjectType::PipeOutdoor,
470 1 : s_ipsc->cAlphaArgs(1),
471 : DataLoopNode::NodeFluidType::Air,
472 : DataLoopNode::ConnectionType::OutsideAirReference,
473 : NodeInputManager::CompFluidStream::Primary,
474 : ObjectIsNotParent);
475 1 : if (!s_ipsc->lAlphaFieldBlanks(5)) {
476 1 : if (!CheckOutAirNodeNumber(state, state.dataPipeHT->PipeHT(Item).EnvrAirNodeNum)) {
477 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(5), s_ipsc->cAlphaArgs(5)));
478 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
479 0 : ShowContinueError(state, "Outdoor Air Node not on OutdoorAir:NodeList or OutdoorAir:Node");
480 0 : ErrorsFound = true;
481 : }
482 : } else {
483 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(5), s_ipsc->cAlphaArgs(5)));
484 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
485 0 : ShowContinueError(state, format("An {} must be used ", s_ipsc->cAlphaFieldNames(5)));
486 0 : ErrorsFound = true;
487 : }
488 :
489 : // dimensions
490 1 : state.dataPipeHT->PipeHT(Item).PipeID = s_ipsc->rNumericArgs(1);
491 1 : if (s_ipsc->rNumericArgs(1) <= 0.0) { // not really necessary because idd field has "minimum> 0"
492 0 : ShowSevereError(state, format("Invalid {} of {:.4R}", s_ipsc->cNumericFieldNames(1), s_ipsc->rNumericArgs(1)));
493 0 : ShowContinueError(state, format("{} must be > 0.0", s_ipsc->cNumericFieldNames(1)));
494 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
495 0 : ErrorsFound = true;
496 : }
497 :
498 1 : state.dataPipeHT->PipeHT(Item).Length = s_ipsc->rNumericArgs(2);
499 1 : if (s_ipsc->rNumericArgs(2) <= 0.0) { // not really necessary because idd field has "minimum> 0"
500 0 : ShowSevereError(state, format("Invalid {} of {:.4R}", s_ipsc->cNumericFieldNames(2), s_ipsc->rNumericArgs(2)));
501 0 : ShowContinueError(state, format("{} must be > 0.0", s_ipsc->cNumericFieldNames(2)));
502 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
503 0 : ErrorsFound = true;
504 : }
505 :
506 1 : if (state.dataPipeHT->PipeHT(Item).ConstructionNum != 0) {
507 1 : state.dataPipeHT->PipeHT(Item).ValidatePipeConstruction(state,
508 1 : s_ipsc->cCurrentModuleObject,
509 1 : s_ipsc->cAlphaArgs(2),
510 1 : s_ipsc->cAlphaFieldNames(2),
511 1 : state.dataPipeHT->PipeHT(Item).ConstructionNum,
512 : ErrorsFound);
513 : }
514 :
515 : } // end of input loop
516 :
517 4 : s_ipsc->cCurrentModuleObject = "Pipe:Underground";
518 5 : for (int PipeItem = 1; PipeItem <= NumOfPipeHTUG; ++PipeItem) {
519 1 : ++Item;
520 : // get the object name
521 2 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
522 1 : s_ipsc->cCurrentModuleObject,
523 : PipeItem,
524 1 : s_ipsc->cAlphaArgs,
525 : NumAlphas,
526 1 : s_ipsc->rNumericArgs,
527 : NumNumbers,
528 : IOStatus,
529 1 : s_ipsc->lNumericFieldBlanks,
530 1 : s_ipsc->lAlphaFieldBlanks,
531 1 : s_ipsc->cAlphaFieldNames,
532 1 : s_ipsc->cNumericFieldNames);
533 :
534 1 : ErrorObjectHeader eoh{routineName, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)};
535 :
536 1 : GlobalNames::VerifyUniqueInterObjectName(state,
537 1 : state.dataPipeHT->PipeHTUniqueNames,
538 1 : s_ipsc->cAlphaArgs(1),
539 1 : s_ipsc->cCurrentModuleObject,
540 1 : s_ipsc->cAlphaFieldNames(1),
541 : ErrorsFound);
542 1 : state.dataPipeHT->PipeHT(Item).Name = s_ipsc->cAlphaArgs(1);
543 1 : state.dataPipeHT->PipeHT(Item).Type = DataPlant::PlantEquipmentType::PipeUnderground;
544 :
545 : // General user input data
546 1 : state.dataPipeHT->PipeHT(Item).Construction = s_ipsc->cAlphaArgs(2);
547 1 : state.dataPipeHT->PipeHT(Item).ConstructionNum = Util::FindItemInList(s_ipsc->cAlphaArgs(2), state.dataConstruction->Construct);
548 :
549 1 : if (state.dataPipeHT->PipeHT(Item).ConstructionNum == 0) {
550 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(2), s_ipsc->cAlphaArgs(2)));
551 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
552 0 : ErrorsFound = true;
553 : }
554 :
555 : // get inlet node data
556 1 : state.dataPipeHT->PipeHT(Item).InletNode = s_ipsc->cAlphaArgs(3);
557 2 : state.dataPipeHT->PipeHT(Item).InletNodeNum = GetOnlySingleNode(state,
558 1 : s_ipsc->cAlphaArgs(3),
559 : ErrorsFound,
560 : DataLoopNode::ConnectionObjectType::PipeUnderground,
561 1 : s_ipsc->cAlphaArgs(1),
562 : DataLoopNode::NodeFluidType::Water,
563 : DataLoopNode::ConnectionType::Inlet,
564 : NodeInputManager::CompFluidStream::Primary,
565 : ObjectIsNotParent);
566 1 : if (state.dataPipeHT->PipeHT(Item).InletNodeNum == 0) {
567 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(3), s_ipsc->cAlphaArgs(3)));
568 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
569 0 : ErrorsFound = true;
570 : }
571 :
572 : // get outlet node data
573 1 : state.dataPipeHT->PipeHT(Item).OutletNode = s_ipsc->cAlphaArgs(4);
574 2 : state.dataPipeHT->PipeHT(Item).OutletNodeNum = GetOnlySingleNode(state,
575 1 : s_ipsc->cAlphaArgs(4),
576 : ErrorsFound,
577 : DataLoopNode::ConnectionObjectType::PipeUnderground,
578 1 : s_ipsc->cAlphaArgs(1),
579 : DataLoopNode::NodeFluidType::Water,
580 : DataLoopNode::ConnectionType::Outlet,
581 : NodeInputManager::CompFluidStream::Primary,
582 : ObjectIsNotParent);
583 1 : if (state.dataPipeHT->PipeHT(Item).OutletNodeNum == 0) {
584 0 : ShowSevereError(state, format("Invalid {}={}", s_ipsc->cAlphaFieldNames(4), s_ipsc->cAlphaArgs(4)));
585 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
586 0 : ErrorsFound = true;
587 : }
588 :
589 1 : TestCompSet(state, s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1), s_ipsc->cAlphaArgs(3), s_ipsc->cAlphaArgs(4), "Pipe Nodes");
590 :
591 1 : state.dataPipeHT->PipeHT(Item).EnvironmentPtr = EnvrnPtr::GroundEnv;
592 :
593 : // Solar inclusion flag
594 : // A6, \field Sun Exposure
595 1 : if (Util::SameString(s_ipsc->cAlphaArgs(5), "SUNEXPOSED")) {
596 1 : state.dataPipeHT->PipeHT(Item).SolarExposed = true;
597 0 : } else if (Util::SameString(s_ipsc->cAlphaArgs(5), "NOSUN")) {
598 0 : state.dataPipeHT->PipeHT(Item).SolarExposed = false;
599 : } else {
600 0 : ShowSevereError(state, format("GetPipesHeatTransfer: invalid key for sun exposure flag for {}", s_ipsc->cAlphaArgs(1)));
601 0 : ShowContinueError(state, format("Key should be either SunExposed or NoSun. Entered Key: {}", s_ipsc->cAlphaArgs(5)));
602 0 : ErrorsFound = true;
603 : }
604 :
605 : // dimensions
606 1 : state.dataPipeHT->PipeHT(Item).PipeID = s_ipsc->rNumericArgs(1);
607 1 : if (s_ipsc->rNumericArgs(1) <= 0.0) { // not really necessary because idd field has "minimum> 0"
608 0 : ShowSevereError(state, format("Invalid {} of {:.4R}", s_ipsc->cNumericFieldNames(1), s_ipsc->rNumericArgs(1)));
609 0 : ShowContinueError(state, format("{} must be > 0.0", s_ipsc->cNumericFieldNames(1)));
610 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
611 0 : ErrorsFound = true;
612 : }
613 :
614 1 : state.dataPipeHT->PipeHT(Item).Length = s_ipsc->rNumericArgs(2);
615 1 : if (s_ipsc->rNumericArgs(2) <= 0.0) { // not really necessary because idd field has "minimum> 0"
616 0 : ShowSevereError(state, format("Invalid {} of {:.4R}", s_ipsc->cNumericFieldNames(2), s_ipsc->rNumericArgs(2)));
617 0 : ShowContinueError(state, format("{} must be > 0.0", s_ipsc->cNumericFieldNames(2)));
618 0 : ShowContinueError(state, format("Entered in {}={}", s_ipsc->cCurrentModuleObject, s_ipsc->cAlphaArgs(1)));
619 0 : ErrorsFound = true;
620 : }
621 :
622 : // Also get the soil material name
623 : // A7, \field Soil Material
624 1 : state.dataPipeHT->PipeHT(Item).SoilMaterial = s_ipsc->cAlphaArgs(6);
625 1 : state.dataPipeHT->PipeHT(Item).SoilMaterialNum = Material::GetMaterialNum(state, s_ipsc->cAlphaArgs(6));
626 1 : if (state.dataPipeHT->PipeHT(Item).SoilMaterialNum == 0) {
627 0 : ShowSevereItemNotFound(state, eoh, s_ipsc->cAlphaFieldNames(6), s_ipsc->cAlphaArgs(6));
628 0 : ErrorsFound = true;
629 : } else {
630 1 : auto const *matSoil = s_mat->materials(state.dataPipeHT->PipeHT(Item).SoilMaterialNum);
631 :
632 1 : state.dataPipeHT->PipeHT(Item).SoilDensity = matSoil->Density;
633 1 : state.dataPipeHT->PipeHT(Item).SoilDepth = matSoil->Thickness;
634 1 : state.dataPipeHT->PipeHT(Item).SoilCp = matSoil->SpecHeat;
635 1 : state.dataPipeHT->PipeHT(Item).SoilConductivity = matSoil->Conductivity;
636 1 : state.dataPipeHT->PipeHT(Item).SoilThermAbs = matSoil->AbsorpThermal;
637 1 : state.dataPipeHT->PipeHT(Item).SoilSolarAbs = matSoil->AbsorpSolar;
638 1 : state.dataPipeHT->PipeHT(Item).SoilRoughness = matSoil->Roughness;
639 1 : state.dataPipeHT->PipeHT(Item).PipeDepth = state.dataPipeHT->PipeHT(Item).SoilDepth + state.dataPipeHT->PipeHT(Item).PipeID / 2.0;
640 1 : state.dataPipeHT->PipeHT(Item).DomainDepth = state.dataPipeHT->PipeHT(Item).PipeDepth * 2.0;
641 2 : state.dataPipeHT->PipeHT(Item).SoilDiffusivity = state.dataPipeHT->PipeHT(Item).SoilConductivity /
642 1 : (state.dataPipeHT->PipeHT(Item).SoilDensity * state.dataPipeHT->PipeHT(Item).SoilCp);
643 1 : state.dataPipeHT->PipeHT(Item).SoilDiffusivityPerDay = state.dataPipeHT->PipeHT(Item).SoilDiffusivity * Constant::rSecsInDay;
644 :
645 : // Mesh the cartesian domain
646 1 : state.dataPipeHT->PipeHT(Item).NumDepthNodes = NumberOfDepthNodes;
647 1 : state.dataPipeHT->PipeHT(Item).PipeNodeDepth = state.dataPipeHT->PipeHT(Item).NumDepthNodes / 2;
648 1 : state.dataPipeHT->PipeHT(Item).PipeNodeWidth = state.dataPipeHT->PipeHT(Item).NumDepthNodes / 2;
649 1 : state.dataPipeHT->PipeHT(Item).DomainDepth = state.dataPipeHT->PipeHT(Item).PipeDepth * 2.0;
650 1 : state.dataPipeHT->PipeHT(Item).dSregular =
651 1 : state.dataPipeHT->PipeHT(Item).DomainDepth / (state.dataPipeHT->PipeHT(Item).NumDepthNodes - 1);
652 : }
653 :
654 1 : if (state.dataPipeHT->PipeHT(Item).ConstructionNum != 0) {
655 1 : state.dataPipeHT->PipeHT(Item).ValidatePipeConstruction(state,
656 1 : s_ipsc->cCurrentModuleObject,
657 1 : s_ipsc->cAlphaArgs(2),
658 1 : s_ipsc->cAlphaFieldNames(2),
659 1 : state.dataPipeHT->PipeHT(Item).ConstructionNum,
660 : ErrorsFound);
661 : }
662 :
663 : // Get ground temperature model
664 1 : GroundTemp::ModelType gtmType = static_cast<GroundTemp::ModelType>(getEnumValue(GroundTemp::modelTypeNamesUC, s_ipsc->cAlphaArgs(7)));
665 1 : if (gtmType == GroundTemp::ModelType::Invalid) {
666 0 : ShowSevereInvalidKey(state, eoh, s_ipsc->cAlphaFieldNames(7), s_ipsc->cAlphaArgs(7));
667 0 : ErrorsFound = true;
668 : }
669 :
670 1 : state.dataPipeHT->PipeHT(Item).groundTempModel = GroundTemp::GetGroundTempModelAndInit(state, gtmType, s_ipsc->cAlphaArgs(8));
671 :
672 : // Select number of pipe sections. Hanby's optimal number of 20 section is selected.
673 1 : state.dataPipeHT->PipeHT(Item).NumSections = NumPipeSections;
674 :
675 : // For buried pipes, we need to allocate the cartesian finite difference array
676 2 : state.dataPipeHT->PipeHT(Item).T.allocate(state.dataPipeHT->PipeHT(Item).PipeNodeWidth,
677 1 : state.dataPipeHT->PipeHT(Item).NumDepthNodes,
678 1 : state.dataPipeHT->PipeHT(Item).NumSections,
679 : TimeIndex::Tentative);
680 1 : state.dataPipeHT->PipeHT(Item).T = 0.0;
681 :
682 : } // PipeUG input loop
683 :
684 8 : for (Item = 1; Item <= state.dataPipeHT->nsvNumOfPipeHT; ++Item) {
685 : // Select number of pipe sections. Hanby's optimal number of 20 section is selected.
686 4 : int NumSections = NumPipeSections;
687 4 : state.dataPipeHT->PipeHT(Item).NumSections = NumPipeSections;
688 :
689 : // We need to allocate the Hanby model arrays for all pipes, including buried
690 4 : state.dataPipeHT->PipeHT(Item).TentativeFluidTemp.allocate({0, NumSections});
691 4 : state.dataPipeHT->PipeHT(Item).TentativePipeTemp.allocate({0, NumSections});
692 4 : state.dataPipeHT->PipeHT(Item).FluidTemp.allocate({0, NumSections});
693 4 : state.dataPipeHT->PipeHT(Item).PreviousFluidTemp.allocate({0, NumSections});
694 4 : state.dataPipeHT->PipeHT(Item).PipeTemp.allocate({0, NumSections});
695 4 : state.dataPipeHT->PipeHT(Item).PreviousPipeTemp.allocate({0, NumSections});
696 :
697 4 : state.dataPipeHT->PipeHT(Item).TentativeFluidTemp = 0.0;
698 4 : state.dataPipeHT->PipeHT(Item).FluidTemp = 0.0;
699 4 : state.dataPipeHT->PipeHT(Item).PreviousFluidTemp = 0.0;
700 4 : state.dataPipeHT->PipeHT(Item).TentativePipeTemp = 0.0;
701 4 : state.dataPipeHT->PipeHT(Item).PipeTemp = 0.0;
702 4 : state.dataPipeHT->PipeHT(Item).PreviousPipeTemp = 0.0;
703 :
704 : // work out heat transfer areas (area per section)
705 4 : state.dataPipeHT->PipeHT(Item).InsideArea =
706 4 : Constant::Pi * state.dataPipeHT->PipeHT(Item).PipeID * state.dataPipeHT->PipeHT(Item).Length / NumSections;
707 4 : state.dataPipeHT->PipeHT(Item).OutsideArea =
708 4 : Constant::Pi * (state.dataPipeHT->PipeHT(Item).PipeOD + 2 * state.dataPipeHT->PipeHT(Item).InsulationThickness) *
709 4 : state.dataPipeHT->PipeHT(Item).Length / NumSections;
710 :
711 : // cross sectional area
712 4 : state.dataPipeHT->PipeHT(Item).SectionArea = Constant::Pi * 0.25 * pow_2(state.dataPipeHT->PipeHT(Item).PipeID);
713 :
714 : // pipe & insulation mass
715 4 : state.dataPipeHT->PipeHT(Item).PipeHeatCapacity =
716 4 : state.dataPipeHT->PipeHT(Item).PipeCp * state.dataPipeHT->PipeHT(Item).PipeDensity *
717 4 : (Constant::Pi * 0.25 * pow_2(state.dataPipeHT->PipeHT(Item).PipeOD) - state.dataPipeHT->PipeHT(Item).SectionArea); // the metal component
718 : }
719 :
720 : // final error check
721 4 : if (ErrorsFound) {
722 0 : ShowFatalError(state, "GetPipesHeatTransfer: Errors found in input. Preceding conditions cause termination.");
723 : }
724 :
725 : // Set up the output variables CurrentModuleObject='Pipe:Indoor/Outdoor/Underground'
726 8 : for (Item = 1; Item <= state.dataPipeHT->nsvNumOfPipeHT; ++Item) {
727 :
728 8 : SetupOutputVariable(state,
729 : "Pipe Fluid Heat Transfer Rate",
730 : Constant::Units::W,
731 4 : state.dataPipeHT->PipeHT(Item).FluidHeatLossRate,
732 : OutputProcessor::TimeStepType::System,
733 : OutputProcessor::StoreType::Average,
734 4 : state.dataPipeHT->PipeHT(Item).Name);
735 8 : SetupOutputVariable(state,
736 : "Pipe Fluid Heat Transfer Energy",
737 : Constant::Units::J,
738 4 : state.dataPipeHT->PipeHT(Item).FluidHeatLossEnergy,
739 : OutputProcessor::TimeStepType::System,
740 : OutputProcessor::StoreType::Sum,
741 4 : state.dataPipeHT->PipeHT(Item).Name);
742 :
743 4 : if (state.dataPipeHT->PipeHT(Item).EnvironmentPtr == EnvrnPtr::ZoneEnv) {
744 2 : SetupOutputVariable(state,
745 : "Pipe Ambient Heat Transfer Rate",
746 : Constant::Units::W,
747 1 : state.dataPipeHT->PipeHT(Item).EnvironmentHeatLossRate,
748 : OutputProcessor::TimeStepType::System,
749 : OutputProcessor::StoreType::Average,
750 1 : state.dataPipeHT->PipeHT(Item).Name);
751 2 : SetupOutputVariable(state,
752 : "Pipe Ambient Heat Transfer Energy",
753 : Constant::Units::J,
754 1 : state.dataPipeHT->PipeHT(Item).EnvHeatLossEnergy,
755 : OutputProcessor::TimeStepType::System,
756 : OutputProcessor::StoreType::Sum,
757 1 : state.dataPipeHT->PipeHT(Item).Name);
758 :
759 3 : SetupZoneInternalGain(state,
760 1 : state.dataPipeHT->PipeHT(Item).EnvrZonePtr,
761 1 : state.dataPipeHT->PipeHT(Item).Name,
762 : DataHeatBalance::IntGainType::PipeIndoor,
763 1 : &state.dataPipeHT->PipeHT(Item).ZoneHeatGainRate);
764 : }
765 :
766 8 : SetupOutputVariable(state,
767 : "Pipe Mass Flow Rate",
768 : Constant::Units::kg_s,
769 4 : state.dataPipeHT->PipeHT(Item).MassFlowRate,
770 : OutputProcessor::TimeStepType::System,
771 : OutputProcessor::StoreType::Average,
772 4 : state.dataPipeHT->PipeHT(Item).Name);
773 8 : SetupOutputVariable(state,
774 : "Pipe Volume Flow Rate",
775 : Constant::Units::m3_s,
776 4 : state.dataPipeHT->PipeHT(Item).VolumeFlowRate,
777 : OutputProcessor::TimeStepType::System,
778 : OutputProcessor::StoreType::Average,
779 4 : state.dataPipeHT->PipeHT(Item).Name);
780 8 : SetupOutputVariable(state,
781 : "Pipe Inlet Temperature",
782 : Constant::Units::C,
783 4 : state.dataPipeHT->PipeHT(Item).FluidInletTemp,
784 : OutputProcessor::TimeStepType::System,
785 : OutputProcessor::StoreType::Average,
786 4 : state.dataPipeHT->PipeHT(Item).Name);
787 8 : SetupOutputVariable(state,
788 : "Pipe Outlet Temperature",
789 : Constant::Units::C,
790 4 : state.dataPipeHT->PipeHT(Item).FluidOutletTemp,
791 : OutputProcessor::TimeStepType::System,
792 : OutputProcessor::StoreType::Average,
793 4 : state.dataPipeHT->PipeHT(Item).Name);
794 : }
795 4 : }
796 :
797 4 : void PipeHTData::ValidatePipeConstruction(EnergyPlusData &state,
798 : std::string const &PipeType, // module object of pipe (error messages)
799 : std::string const &ConstructionName, // construction name of pipe (error messages)
800 : std::string_view FieldName, // fieldname of pipe (error messages)
801 : int const ConstructionNum, // pointer into construction data
802 : bool &ErrorsFound // set to true if errors found here
803 : )
804 : {
805 :
806 : // SUBROUTINE INFORMATION:
807 : // AUTHOR Linda Lawrie
808 : // DATE WRITTEN August 2008
809 : // MODIFIED na
810 : // RE-ENGINEERED na
811 :
812 : // PURPOSE OF THIS SUBROUTINE:
813 : // This routine, called from GetInput, validates the pipe construction usage.
814 :
815 : // METHODOLOGY EMPLOYED:
816 : // na
817 :
818 : // REFERENCES:
819 : // na
820 :
821 : // Using/Aliasing
822 :
823 : // Locals
824 : // SUBROUTINE ARGUMENT DEFINITIONS:
825 :
826 : // SUBROUTINE PARAMETER DEFINITIONS:
827 : // na
828 :
829 : // INTERFACE BLOCK SPECIFICATIONS:
830 : // na
831 :
832 : // DERIVED TYPE DEFINITIONS:
833 : // na
834 :
835 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
836 : Real64 Density; // average density [kg/m^3]
837 : Real64 SpHeat; // average specific heat [J/kg.K]
838 4 : Real64 Resistance = 0.0;
839 4 : Real64 TotThickness = 0.0;
840 :
841 4 : auto const &s_mat = state.dataMaterial;
842 :
843 : // CTF stuff
844 4 : int TotalLayers = state.dataConstruction->Construct(ConstructionNum).TotLayers;
845 : // get pipe properties
846 4 : if (TotalLayers == 1) { // no insulation layer
847 0 : auto const *mat = s_mat->materials(state.dataConstruction->Construct(ConstructionNum).LayerPoint(1));
848 :
849 0 : this->PipeConductivity = mat->Conductivity;
850 0 : this->PipeDensity = mat->Density;
851 0 : this->PipeCp = mat->SpecHeat;
852 0 : this->PipeOD = this->PipeID + 2.0 * mat->Thickness;
853 0 : this->InsulationOD = this->PipeOD;
854 0 : this->SumTK = mat->Thickness / mat->Conductivity;
855 :
856 4 : } else if (TotalLayers >= 2) { // first layers are insulation, last layer is pipe
857 :
858 8 : for (int LayerNum = 1; LayerNum <= TotalLayers - 1; ++LayerNum) {
859 4 : auto const *mat = state.dataMaterial->materials(state.dataConstruction->Construct(ConstructionNum).LayerPoint(LayerNum));
860 4 : Resistance += mat->Thickness / mat->Conductivity;
861 4 : Density = mat->Density * mat->Thickness;
862 4 : TotThickness += mat->Thickness;
863 4 : SpHeat = mat->SpecHeat * mat->Thickness;
864 4 : this->InsulationThickness = mat->Thickness;
865 4 : this->SumTK += mat->Thickness / mat->Conductivity;
866 : }
867 :
868 4 : this->InsulationResistance = Resistance;
869 4 : this->InsulationConductivity = TotThickness / Resistance;
870 4 : this->InsulationDensity = Density / TotThickness;
871 4 : this->InsulationCp = SpHeat / TotThickness;
872 4 : this->InsulationThickness = TotThickness;
873 :
874 4 : auto const *mat = state.dataMaterial->materials(state.dataConstruction->Construct(ConstructionNum).LayerPoint(TotalLayers));
875 4 : this->PipeConductivity = mat->Conductivity;
876 4 : this->PipeDensity = mat->Density;
877 4 : this->PipeCp = mat->SpecHeat;
878 :
879 4 : this->PipeOD = this->PipeID + 2.0 * mat->Thickness;
880 4 : this->InsulationOD = this->PipeOD + 2.0 * this->InsulationThickness;
881 :
882 : } else {
883 0 : ShowSevereError(
884 0 : state, format("{}: invalid {}=\"{}\", too many layers=[{}], only 1 or 2 allowed.", PipeType, FieldName, ConstructionName, TotalLayers));
885 0 : ErrorsFound = true;
886 : }
887 4 : }
888 :
889 4 : void PipeHTData::oneTimeInit_new(EnergyPlusData &state)
890 : {
891 4 : bool errFlag = false;
892 4 : PlantUtilities::ScanPlantLoopsForObject(state, this->Name, this->Type, this->plantLoc, errFlag, _, _, _, _, _);
893 4 : if (errFlag) {
894 0 : ShowFatalError(state, "InitPipesHeatTransfer: Program terminated due to previous condition(s).");
895 : }
896 4 : }
897 :
898 58100 : void PipeHTData::InitPipesHeatTransfer(EnergyPlusData &state, bool const FirstHVACIteration // component number
899 : )
900 : {
901 :
902 : // SUBROUTINE INFORMATION:
903 : // AUTHOR Simon Rees
904 : // DATE WRITTEN July 2007
905 : // MODIFIED L. Gu, 6/19/08, pipe wall heat capacity has metal layer only
906 : // RE-ENGINEERED na
907 :
908 : // PURPOSE OF THIS SUBROUTINE:
909 : // This subroutine Resets the elements of the data structure as necessary
910 : // at the first step, and start of each call to simulated
911 :
912 : // METHODOLOGY EMPLOYED:
913 : // Check flags and update data structure
914 :
915 : // Using/Aliasing
916 58100 : Real64 SysTimeElapsed = state.dataHVACGlobal->SysTimeElapsed;
917 58100 : Real64 TimeStepSysSec = state.dataHVACGlobal->TimeStepSysSec;
918 :
919 : // SUBROUTINE PARAMETER DEFINITIONS:
920 : static constexpr std::string_view RoutineName("InitPipesHeatTransfer");
921 :
922 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
923 :
924 : Real64 FirstTemperatures; // initial temperature of every node in pipe (set to inlet temp) [C]
925 : int TimeIndex;
926 : int LengthIndex;
927 : int DepthIndex;
928 : int WidthIndex;
929 : Real64 CurrentDepth;
930 : Real64 CurTemp;
931 : Real64 CurSimDay;
932 : bool PushArrays;
933 :
934 : // Assign variable
935 58100 : CurSimDay = double(state.dataGlobal->DayOfSim);
936 :
937 : // some useful module variables
938 58100 : state.dataPipeHT->nsvInletNodeNum = this->InletNodeNum;
939 58100 : state.dataPipeHT->nsvOutletNodeNum = this->OutletNodeNum;
940 58100 : state.dataPipeHT->nsvMassFlowRate = state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).MassFlowRate;
941 58100 : state.dataPipeHT->nsvInletTemp = state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).Temp;
942 :
943 : // initialize temperatures by inlet node temp
944 58100 : if ((state.dataGlobal->BeginSimFlag && this->BeginSimInit) || (state.dataGlobal->BeginEnvrnFlag && this->BeginSimEnvrn)) {
945 :
946 20 : if (this->EnvironmentPtr == EnvrnPtr::GroundEnv) {
947 20 : for (TimeIndex = TimeIndex::Previous; TimeIndex <= TimeIndex::Tentative; ++TimeIndex) {
948 : // Loop through all length, depth, and width of pipe to init soil temperature
949 315 : for (LengthIndex = 1; LengthIndex <= this->NumSections; ++LengthIndex) {
950 2700 : for (DepthIndex = 1; DepthIndex <= this->NumDepthNodes; ++DepthIndex) {
951 12000 : for (WidthIndex = 1; WidthIndex <= this->PipeNodeWidth; ++WidthIndex) {
952 9600 : CurrentDepth = (DepthIndex - 1) * this->dSregular;
953 9600 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex) = this->TBND(state, CurrentDepth);
954 : }
955 : }
956 : }
957 : }
958 : }
959 :
960 : // We also need to re-init the Hanby arrays for all pipes, including buried
961 20 : FirstTemperatures = 21.0; // Node(InletNodeNum)%Temp
962 20 : this->TentativeFluidTemp = FirstTemperatures;
963 20 : this->FluidTemp = FirstTemperatures;
964 20 : this->PreviousFluidTemp = FirstTemperatures;
965 20 : this->TentativePipeTemp = FirstTemperatures;
966 20 : this->PipeTemp = FirstTemperatures;
967 20 : this->PreviousPipeTemp = FirstTemperatures;
968 20 : this->PreviousSimTime = 0.0;
969 20 : state.dataPipeHT->nsvDeltaTime = 0.0;
970 20 : state.dataPipeHT->nsvOutletTemp = 0.0;
971 20 : state.dataPipeHT->nsvEnvironmentTemp = 0.0;
972 20 : state.dataPipeHT->nsvEnvHeatLossRate = 0.0;
973 20 : state.dataPipeHT->nsvFluidHeatLossRate = 0.0;
974 :
975 20 : this->BeginSimInit = false;
976 20 : this->BeginSimEnvrn = false;
977 : }
978 :
979 58100 : if (!state.dataGlobal->BeginSimFlag) {
980 58100 : this->BeginSimInit = true;
981 : }
982 58100 : if (!state.dataGlobal->BeginEnvrnFlag) {
983 57696 : this->BeginSimEnvrn = true;
984 : }
985 :
986 : // time step in seconds
987 58100 : state.dataPipeHT->nsvDeltaTime = TimeStepSysSec;
988 58100 : state.dataPipeHT->nsvNumInnerTimeSteps = int(state.dataPipeHT->nsvDeltaTime / InnerDeltaTime);
989 :
990 : // previous temps are updated if necessary at start of timestep rather than end
991 58100 : if ((FirstHVACIteration && this->FirstHVACupdateFlag) || (state.dataGlobal->BeginEnvrnFlag && this->BeginEnvrnupdateFlag)) {
992 :
993 : // We need to update boundary conditions here, as well as updating the arrays
994 7260 : if (this->EnvironmentPtr == EnvrnPtr::GroundEnv) {
995 :
996 : // And then update Ground Boundary Conditions
997 7260 : for (TimeIndex = 1; TimeIndex <= TimeIndex::Tentative; ++TimeIndex) {
998 114345 : for (LengthIndex = 1; LengthIndex <= this->NumSections; ++LengthIndex) {
999 980100 : for (DepthIndex = 1; DepthIndex <= this->NumDepthNodes; ++DepthIndex) {
1000 : // Farfield boundary
1001 871200 : CurrentDepth = (DepthIndex - 1) * this->dSregular;
1002 871200 : CurTemp = this->TBND(state, CurrentDepth);
1003 871200 : this->T(1, DepthIndex, LengthIndex, TimeIndex) = CurTemp;
1004 : }
1005 544500 : for (WidthIndex = 1; WidthIndex <= this->PipeNodeWidth; ++WidthIndex) {
1006 : // Bottom side of boundary
1007 435600 : CurrentDepth = this->DomainDepth;
1008 435600 : CurTemp = this->TBND(state, CurrentDepth);
1009 435600 : this->T(WidthIndex, this->NumDepthNodes, LengthIndex, TimeIndex) = CurTemp;
1010 : }
1011 : }
1012 : }
1013 : }
1014 :
1015 : // should next choose environment temperature according to coupled with air or ground
1016 7260 : switch (this->EnvironmentPtr) {
1017 1815 : case EnvrnPtr::GroundEnv: {
1018 : // EnvironmentTemp = GroundTemp
1019 1815 : } break;
1020 1815 : case EnvrnPtr::OutsideAirEnv: {
1021 1815 : state.dataPipeHT->nsvEnvironmentTemp = state.dataEnvrn->OutDryBulbTemp;
1022 1815 : } break;
1023 1815 : case EnvrnPtr::ZoneEnv: {
1024 1815 : state.dataPipeHT->nsvEnvironmentTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(this->EnvrZonePtr).MAT;
1025 1815 : } break;
1026 1815 : case EnvrnPtr::ScheduleEnv: {
1027 1815 : state.dataPipeHT->nsvEnvironmentTemp = this->envrSched->getCurrentVal();
1028 1815 : } break;
1029 0 : case EnvrnPtr::None: { // default to outside temp
1030 0 : state.dataPipeHT->nsvEnvironmentTemp = state.dataEnvrn->OutDryBulbTemp;
1031 0 : } break;
1032 0 : default:
1033 0 : break;
1034 : }
1035 :
1036 7260 : this->BeginEnvrnupdateFlag = false;
1037 7260 : this->FirstHVACupdateFlag = false;
1038 : }
1039 :
1040 58100 : if (!state.dataGlobal->BeginEnvrnFlag) {
1041 57696 : this->BeginEnvrnupdateFlag = true;
1042 : }
1043 58100 : if (!FirstHVACIteration) {
1044 29040 : this->FirstHVACupdateFlag = true;
1045 : }
1046 :
1047 : // Calculate the current sim time for this pipe (not necessarily structure variable, but it is ok for consistency)
1048 58100 : this->CurrentSimTime = (state.dataGlobal->DayOfSim - 1) * 24 + state.dataGlobal->HourOfDay - 1 +
1049 58100 : (state.dataGlobal->TimeStep - 1) * state.dataGlobal->TimeStepZone + SysTimeElapsed;
1050 58100 : if (std::abs(this->CurrentSimTime - this->PreviousSimTime) > 1.0e-6) {
1051 6372 : PushArrays = true;
1052 6372 : this->PreviousSimTime = this->CurrentSimTime;
1053 : } else {
1054 51728 : PushArrays = false; // Time hasn't passed, don't accept the tentative values yet!
1055 : }
1056 :
1057 58100 : if (PushArrays) {
1058 :
1059 : // If sim time has changed all values from previous runs should have been acceptable.
1060 : // Thus we will now shift the arrays from 2>1 and 3>2 so we can then begin
1061 : // to update 2 and 3 again.
1062 6372 : if (this->EnvironmentPtr == EnvrnPtr::GroundEnv) {
1063 31860 : for (LengthIndex = 2; LengthIndex <= this->NumSections; ++LengthIndex) {
1064 272403 : for (DepthIndex = 1; DepthIndex <= this->NumDepthNodes; ++DepthIndex) {
1065 968544 : for (WidthIndex = 2; WidthIndex <= this->PipeNodeWidth; ++WidthIndex) {
1066 : // This will essentially 'accept' the tentative values that were calculated last iteration
1067 : // as the new officially 'current' values
1068 726408 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Current) =
1069 726408 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Tentative);
1070 : }
1071 : }
1072 : }
1073 : }
1074 :
1075 : // Then update the Hanby near pipe model temperatures
1076 6372 : this->FluidTemp = this->TentativeFluidTemp;
1077 6372 : this->PipeTemp = this->TentativePipeTemp;
1078 :
1079 : } else { // IF(.NOT. FirstHVACIteration)THEN
1080 :
1081 : // If we don't have FirstHVAC, the last iteration values were not accepted, and we should
1082 : // not step through time. Thus we will revert our T(3,:,:,:) array back to T(2,:,:,:) to
1083 : // start over with the same values as last time.
1084 1034560 : for (LengthIndex = 2; LengthIndex <= this->NumSections; ++LengthIndex) {
1085 2948496 : for (DepthIndex = 1; DepthIndex <= this->NumDepthNodes; ++DepthIndex) {
1086 7862656 : for (WidthIndex = 2; WidthIndex <= this->PipeNodeWidth; ++WidthIndex) {
1087 : // This will essentially erase the past iterations and revert back to the correct values
1088 5896992 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Tentative) =
1089 5896992 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Current);
1090 : }
1091 : }
1092 : }
1093 :
1094 : // Similarly for Hanby model arrays
1095 51728 : this->TentativeFluidTemp = this->FluidTemp;
1096 51728 : this->TentativePipeTemp = this->PipeTemp;
1097 : }
1098 :
1099 : // This still catches even in winter design day
1100 : // Even though the loop eventually has no flow rate, it appears it initializes to a value, then converges to OFF
1101 : // Thus, this is called at the beginning of every time step once.
1102 :
1103 58100 : this->FluidSpecHeat =
1104 58100 : state.dataPlnt->PlantLoop(this->plantLoc.loopNum).glycol->getSpecificHeat(state, state.dataPipeHT->nsvInletTemp, RoutineName);
1105 58100 : this->FluidDensity = state.dataPlnt->PlantLoop(this->plantLoc.loopNum).glycol->getDensity(state, state.dataPipeHT->nsvInletTemp, RoutineName);
1106 :
1107 : // At this point, for all Pipe:Interior objects we should zero out the energy and rate arrays
1108 58100 : this->FluidHeatLossRate = 0.0;
1109 58100 : this->FluidHeatLossEnergy = 0.0;
1110 58100 : this->EnvironmentHeatLossRate = 0.0;
1111 58100 : this->EnvHeatLossEnergy = 0.0;
1112 58100 : this->ZoneHeatGainRate = 0.0;
1113 58100 : state.dataPipeHT->nsvFluidHeatLossRate = 0.0;
1114 58100 : state.dataPipeHT->nsvEnvHeatLossRate = 0.0;
1115 58100 : state.dataPipeHT->nsvOutletTemp = 0.0;
1116 :
1117 58100 : if (this->FluidDensity > 0.0) {
1118 : // The density will only be zero the first time through, which will be a warmup day, and not reported
1119 58100 : state.dataPipeHT->nsvVolumeFlowRate = state.dataPipeHT->nsvMassFlowRate / this->FluidDensity;
1120 : }
1121 58100 : }
1122 :
1123 : //==============================================================================
1124 :
1125 4467317 : void PipeHTData::CalcPipesHeatTransfer(EnergyPlusData &state, ObjexxFCL::Optional_int_const LengthIndex)
1126 : {
1127 :
1128 : // AUTHOR Simon Rees
1129 : // DATE WRITTEN July 2007
1130 : // MODIFIED na
1131 : // RE-ENGINEERED na
1132 :
1133 : // PURPOSE OF THIS SUBROUTINE:
1134 : // This subroutine does all of the stuff that is necessary to simulate
1135 : // a Pipe Heat Transfer. Calls are made to appropriate routines
1136 : // for heat transfer coefficients
1137 :
1138 : // METHODOLOGY EMPLOYED:
1139 : // Differential equations for pipe and fluid nodes along the pipe are solved
1140 : // taking backward differences in time.
1141 : // The heat loss/gain calculations are run continuously, even when the loop is off.
1142 : // Fluid temps will drift according to environmental conditions when there is zero flow.
1143 :
1144 : // REFERENCES:
1145 :
1146 : // Using/Aliasing
1147 : using namespace DataEnvironment;
1148 :
1149 : // fluid node heat balance (see engineering doc).
1150 4467317 : Real64 A1(0.0); // sum of the heat balance terms
1151 4467317 : Real64 A2(0.0); // mass flow term
1152 4467317 : Real64 A3(0.0); // inside pipe wall convection term
1153 4467317 : Real64 A4(0.0); // fluid node heat capacity term
1154 : // pipe wall node heat balance (see engineering doc).
1155 4467317 : Real64 B1(0.0); // sum of the heat balance terms
1156 4467317 : Real64 B2(0.0); // inside pipe wall convection term
1157 4467317 : Real64 B3(0.0); // outside pipe wall convection term
1158 4467317 : Real64 B4(0.0); // fluid node heat capacity term
1159 :
1160 4467317 : Real64 AirConvCoef(0.0); // air-pipe convection coefficient
1161 4467317 : Real64 FluidConvCoef(0.0); // fluid-pipe convection coefficient
1162 4467317 : Real64 EnvHeatTransCoef(0.0); // external convection coefficient (outside pipe)
1163 4467317 : Real64 FluidNodeHeatCapacity(0.0); // local var for MCp for single node of pipe
1164 :
1165 : Real64 TempBelow;
1166 : Real64 TempBeside;
1167 : Real64 TempAbove;
1168 : Real64 Numerator;
1169 : Real64 Denominator;
1170 : Real64 SurfaceTemp;
1171 :
1172 : // traps fluid properties problems such as freezing conditions
1173 4467317 : if (this->FluidSpecHeat <= 0.0 || this->FluidDensity <= 0.0) {
1174 : // leave the state of the pipe as it was
1175 0 : state.dataPipeHT->nsvOutletTemp = this->TentativeFluidTemp(this->NumSections);
1176 : // set heat transfer rates to zero for consistency
1177 0 : state.dataPipeHT->nsvEnvHeatLossRate = 0.0;
1178 0 : state.dataPipeHT->nsvFluidHeatLossRate = 0.0;
1179 0 : return;
1180 : }
1181 :
1182 : // AirConvCoef = OutsidePipeHeatTransCoef(PipeHTNum)
1183 : // Revised by L. Gu by including insulation conductance 6/19/08
1184 :
1185 4467317 : if (this->EnvironmentPtr != EnvrnPtr::GroundEnv) {
1186 558537 : AirConvCoef = 1.0 / (1.0 / this->OutsidePipeHeatTransCoef(state) + this->InsulationResistance);
1187 : }
1188 :
1189 4467317 : FluidConvCoef = this->CalcPipeHeatTransCoef(state, state.dataPipeHT->nsvInletTemp, state.dataPipeHT->nsvMassFlowRate, this->PipeID);
1190 :
1191 : // heat transfer to air or ground
1192 4467317 : switch (this->EnvironmentPtr) {
1193 3908780 : case EnvrnPtr::GroundEnv: {
1194 : // Approximate conductance using ground conductivity, (h=k/L), where L is grid spacing
1195 : // between pipe wall and next closest node.
1196 3908780 : EnvHeatTransCoef = this->SoilConductivity / (this->dSregular - (this->PipeID / 2.0));
1197 3908780 : } break;
1198 186179 : case EnvrnPtr::OutsideAirEnv: {
1199 186179 : EnvHeatTransCoef = AirConvCoef;
1200 186179 : } break;
1201 186179 : case EnvrnPtr::ZoneEnv: {
1202 186179 : EnvHeatTransCoef = AirConvCoef;
1203 186179 : } break;
1204 186179 : case EnvrnPtr::ScheduleEnv: {
1205 186179 : EnvHeatTransCoef = AirConvCoef;
1206 186179 : } break;
1207 0 : case EnvrnPtr::None: {
1208 0 : EnvHeatTransCoef = 0.0;
1209 0 : } break;
1210 0 : default: {
1211 0 : EnvHeatTransCoef = 0.0;
1212 0 : } break;
1213 : }
1214 :
1215 : // work out the coefficients
1216 4467317 : FluidNodeHeatCapacity =
1217 4467317 : this->SectionArea * this->Length / this->NumSections * this->FluidSpecHeat * this->FluidDensity; // Mass of Node x Specific heat
1218 :
1219 : // coef of fluid heat balance
1220 4467317 : A1 = FluidNodeHeatCapacity + state.dataPipeHT->nsvMassFlowRate * this->FluidSpecHeat * state.dataPipeHT->nsvDeltaTime +
1221 4467317 : FluidConvCoef * this->InsideArea * state.dataPipeHT->nsvDeltaTime;
1222 :
1223 4467317 : A2 = state.dataPipeHT->nsvMassFlowRate * this->FluidSpecHeat * state.dataPipeHT->nsvDeltaTime;
1224 :
1225 4467317 : A3 = FluidConvCoef * this->InsideArea * state.dataPipeHT->nsvDeltaTime;
1226 :
1227 4467317 : A4 = FluidNodeHeatCapacity;
1228 :
1229 : // coef of pipe heat balance
1230 4467317 : B1 = this->PipeHeatCapacity + FluidConvCoef * this->InsideArea * state.dataPipeHT->nsvDeltaTime +
1231 4467317 : EnvHeatTransCoef * this->OutsideArea * state.dataPipeHT->nsvDeltaTime;
1232 :
1233 4467317 : B2 = A3;
1234 :
1235 4467317 : B3 = EnvHeatTransCoef * this->OutsideArea * state.dataPipeHT->nsvDeltaTime;
1236 :
1237 4467317 : B4 = this->PipeHeatCapacity;
1238 :
1239 4467317 : this->TentativeFluidTemp(0) = state.dataPipeHT->nsvInletTemp;
1240 :
1241 4467317 : this->TentativePipeTemp(0) = this->PipeTemp(1); // for convenience
1242 :
1243 4467317 : if (present(LengthIndex)) { // Just simulate the single section if being called from Pipe:Underground
1244 :
1245 3908780 : int PipeDepth = this->PipeNodeDepth;
1246 3908780 : int PipeWidth = this->PipeNodeWidth;
1247 3908780 : TempBelow = this->T(PipeWidth, PipeDepth + 1, LengthIndex, TimeIndex::Current);
1248 3908780 : TempBeside = this->T(PipeWidth - 1, PipeDepth, LengthIndex, TimeIndex::Current);
1249 3908780 : TempAbove = this->T(PipeWidth, PipeDepth - 1, LengthIndex, TimeIndex::Current);
1250 3908780 : state.dataPipeHT->nsvEnvironmentTemp = (TempBelow + TempBeside + TempAbove) / 3.0;
1251 :
1252 7817560 : this->TentativeFluidTemp(LengthIndex) = (A2 * this->TentativeFluidTemp(LengthIndex - 1) +
1253 3908780 : A3 / B1 * (B3 * state.dataPipeHT->nsvEnvironmentTemp + B4 * this->PreviousPipeTemp(LengthIndex)) +
1254 3908780 : A4 * this->PreviousFluidTemp(LengthIndex)) /
1255 3908780 : (A1 - A3 * B2 / B1);
1256 :
1257 3908780 : this->TentativePipeTemp(LengthIndex) =
1258 3908780 : (B2 * this->TentativeFluidTemp(LengthIndex) + B3 * state.dataPipeHT->nsvEnvironmentTemp + B4 * this->PreviousPipeTemp(LengthIndex)) / B1;
1259 :
1260 : // Get exterior surface temperature from energy balance at the surface
1261 3908780 : Numerator = state.dataPipeHT->nsvEnvironmentTemp - this->TentativeFluidTemp(LengthIndex);
1262 3908780 : Denominator = EnvHeatTransCoef * ((1 / EnvHeatTransCoef) + this->SumTK);
1263 3908780 : SurfaceTemp = state.dataPipeHT->nsvEnvironmentTemp - Numerator / Denominator;
1264 :
1265 : // keep track of environmental heat loss rate - not same as fluid loss at same time
1266 3908780 : state.dataPipeHT->nsvEnvHeatLossRate += EnvHeatTransCoef * this->OutsideArea * (SurfaceTemp - state.dataPipeHT->nsvEnvironmentTemp);
1267 :
1268 : } else { // Simulate all sections at once if not pipe:underground
1269 :
1270 : // start loop along pipe
1271 : // b1 must not be zero but this should have been checked on input
1272 11729277 : for (int curnode = 1; curnode <= this->NumSections; ++curnode) {
1273 11170740 : this->TentativeFluidTemp(curnode) = (A2 * this->TentativeFluidTemp(curnode - 1) +
1274 11170740 : A3 / B1 * (B3 * state.dataPipeHT->nsvEnvironmentTemp + B4 * this->PreviousPipeTemp(curnode)) +
1275 11170740 : A4 * this->PreviousFluidTemp(curnode)) /
1276 11170740 : (A1 - A3 * B2 / B1);
1277 :
1278 22341480 : this->TentativePipeTemp(curnode) =
1279 11170740 : (B2 * this->TentativeFluidTemp(curnode) + B3 * state.dataPipeHT->nsvEnvironmentTemp + B4 * this->PreviousPipeTemp(curnode)) / B1;
1280 :
1281 : // Get exterior surface temperature from energy balance at the surface
1282 11170740 : Numerator = state.dataPipeHT->nsvEnvironmentTemp - this->TentativeFluidTemp(curnode);
1283 11170740 : Denominator = EnvHeatTransCoef * ((1 / EnvHeatTransCoef) + this->SumTK);
1284 11170740 : SurfaceTemp = state.dataPipeHT->nsvEnvironmentTemp - Numerator / Denominator;
1285 :
1286 : // Keep track of environmental heat loss
1287 11170740 : state.dataPipeHT->nsvEnvHeatLossRate += EnvHeatTransCoef * this->OutsideArea * (SurfaceTemp - state.dataPipeHT->nsvEnvironmentTemp);
1288 : }
1289 : }
1290 :
1291 8934634 : state.dataPipeHT->nsvFluidHeatLossRate =
1292 4467317 : state.dataPipeHT->nsvMassFlowRate * this->FluidSpecHeat * (this->TentativeFluidTemp(0) - this->TentativeFluidTemp(this->NumSections));
1293 :
1294 4467317 : state.dataPipeHT->nsvOutletTemp = this->TentativeFluidTemp(this->NumSections);
1295 : }
1296 :
1297 : //==============================================================================
1298 :
1299 186179 : void PipeHTData::CalcBuriedPipeSoil(EnergyPlusData &state) // Current Simulation Pipe Number
1300 : {
1301 :
1302 : // AUTHOR Edwin Lee
1303 : // DATE WRITTEN May 2008
1304 : // MODIFIED na
1305 : // RE-ENGINEERED na
1306 :
1307 : // PURPOSE OF THIS SUBROUTINE:
1308 : // This subroutine does all of the stuff that is necessary to simulate
1309 : // soil heat transfer with a Buried Pipe.
1310 :
1311 : // METHODOLOGY EMPLOYED:
1312 : // An implicit pseudo 3D finite difference grid
1313 : // is set up, which simulates transient behavior in the soil.
1314 : // This then interfaces with the Hanby model for near-pipe region
1315 :
1316 : // Using/Aliasing
1317 : using Convect::CalcASHRAESimpExtConvCoeff;
1318 :
1319 : // SUBROUTINE PARAMETER DEFINITIONS:
1320 186179 : int constexpr NumSections(20);
1321 186179 : Real64 constexpr ConvCrit(0.05);
1322 186179 : int constexpr MaxIterations(200);
1323 186179 : Real64 constexpr StefBoltzmann(5.6697e-08); // Stefan-Boltzmann constant
1324 :
1325 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1326 186179 : int IterationIndex(0); // Index when stepping through equations
1327 186179 : int DepthIndex(0); // Index for nodes in the depth direction
1328 186179 : int WidthIndex(0); // Index for nodes in the width direction
1329 186179 : Real64 ConvCoef(0.0); // Current convection coefficient = f(Wind Speed,Roughness)
1330 186179 : Real64 RadCoef(0.0); // Current radiation coefficient
1331 186179 : Real64 QSolAbsorbed(0.0); // Current total solar energy absorbed
1332 186179 : Array3D<Real64> T_O(this->PipeNodeWidth, this->NumDepthNodes, NumSections);
1333 :
1334 : // Local variable placeholders for code readability
1335 186179 : Real64 A1(0.0); // Placeholder for CoefA1
1336 186179 : Real64 A2(0.0); // Placeholder for CoefA2
1337 186179 : Real64 NodeBelow(0.0); // Placeholder for Node temp below current node
1338 186179 : Real64 NodeAbove(0.0); // Placeholder for Node temp above current node
1339 186179 : Real64 NodeRight(0.0); // Placeholder for Node temp to the right of current node
1340 186179 : Real64 NodeLeft(0.0); // Placeholder for Node temp to the left of current node
1341 186179 : Real64 NodePast(0.0); // Placeholder for Node temp at current node but previous time step
1342 186179 : Real64 PastNodeTempAbs(0.0); // Placeholder for absolute temperature (K) version of NodePast
1343 186179 : Real64 Ttemp(0.0); // Placeholder for a current temperature node in convergence check
1344 186179 : Real64 SkyTempAbs(0.0); // Placeholder for current sky temperature in Kelvin
1345 186179 : Material::SurfaceRoughness TopRoughness(Material::SurfaceRoughness::Invalid); // Placeholder for soil surface roughness
1346 186179 : Real64 TopThermAbs(0.0); // Placeholder for soil thermal radiation absorptivity
1347 186179 : Real64 TopSolarAbs(0.0); // Placeholder for soil solar radiation absorptivity
1348 186179 : Real64 kSoil(0.0); // Placeholder for soil conductivity
1349 186179 : Real64 dS(0.0); // Placeholder for soil grid spacing
1350 186179 : Real64 rho(0.0); // Placeholder for soil density
1351 186179 : Real64 Cp(0.0); // Placeholder for soil specific heat
1352 :
1353 : // There are a number of coefficients which change through the simulation, and they are updated here
1354 186179 : this->FourierDS = this->SoilDiffusivity * state.dataPipeHT->nsvDeltaTime / pow_2(this->dSregular); // Eq. D4
1355 186179 : this->CoefA1 = this->FourierDS / (1 + 4 * this->FourierDS); // Eq. D2
1356 186179 : this->CoefA2 = 1 / (1 + 4 * this->FourierDS); // Eq. D3
1357 :
1358 195439 : for (IterationIndex = 1; IterationIndex <= MaxIterations; ++IterationIndex) {
1359 195439 : if (IterationIndex == MaxIterations) {
1360 0 : ShowWarningError(state, format("BuriedPipeHeatTransfer: Large number of iterations detected in object: {}", this->Name));
1361 : }
1362 :
1363 : // Store computed values in T_O array
1364 3908780 : for (int LengthIndex = 2; LengthIndex <= this->NumSections; ++LengthIndex) {
1365 29706728 : for (DepthIndex = 1; DepthIndex <= this->NumDepthNodes - 1; ++DepthIndex) {
1366 103973548 : for (WidthIndex = 2; WidthIndex <= this->PipeNodeWidth; ++WidthIndex) {
1367 77980161 : T_O(WidthIndex, DepthIndex, LengthIndex) = this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Tentative);
1368 : }
1369 : }
1370 : }
1371 :
1372 : // Loop along entire length of pipe, analyzing cross sects
1373 4104219 : for (int LengthIndex = 1; LengthIndex <= this->NumSections; ++LengthIndex) {
1374 31270240 : for (DepthIndex = 1; DepthIndex <= this->NumDepthNodes - 1; ++DepthIndex) {
1375 109445840 : for (WidthIndex = 2; WidthIndex <= this->PipeNodeWidth; ++WidthIndex) {
1376 :
1377 82084380 : if (DepthIndex == 1) { // Soil Surface Boundary
1378 :
1379 : // If on soil boundary, load up local variables and perform calculations
1380 11726340 : NodePast = this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Previous);
1381 11726340 : PastNodeTempAbs = NodePast + Constant::Kelvin;
1382 11726340 : SkyTempAbs = state.dataEnvrn->SkyTemp + Constant::Kelvin;
1383 11726340 : TopRoughness = this->SoilRoughness;
1384 11726340 : TopThermAbs = this->SoilThermAbs;
1385 11726340 : TopSolarAbs = this->SoilSolarAbs;
1386 11726340 : kSoil = this->SoilConductivity;
1387 11726340 : dS = this->dSregular;
1388 11726340 : rho = this->SoilDensity;
1389 11726340 : Cp = this->SoilCp;
1390 :
1391 : // ASHRAE simple convection coefficient model for external surfaces.
1392 11726340 : this->OutdoorConvCoef = CalcASHRAESimpExtConvCoeff(TopRoughness, state.dataEnvrn->WindSpeed);
1393 11726340 : ConvCoef = this->OutdoorConvCoef;
1394 :
1395 : // thermal radiation coefficient using surf temp from past time step
1396 11726340 : if (std::abs(PastNodeTempAbs - SkyTempAbs) > Constant::rTinyValue) {
1397 11726340 : RadCoef = StefBoltzmann * TopThermAbs * (pow_4(PastNodeTempAbs) - pow_4(SkyTempAbs)) / (PastNodeTempAbs - SkyTempAbs);
1398 : } else {
1399 0 : RadCoef = 0.0;
1400 : }
1401 :
1402 : // total absorbed solar - no ground solar
1403 11726340 : QSolAbsorbed =
1404 11726340 : TopSolarAbs * (max(state.dataEnvrn->SOLCOS(3), 0.0) * state.dataEnvrn->BeamSolarRad + state.dataEnvrn->DifSolarRad);
1405 :
1406 : // If sun is not exposed, then turn off both solar and thermal radiation
1407 11726340 : if (!this->SolarExposed) {
1408 0 : RadCoef = 0.0;
1409 0 : QSolAbsorbed = 0.0;
1410 : }
1411 :
1412 11726340 : if (WidthIndex == this->PipeNodeWidth) { // Symmetric centerline boundary
1413 :
1414 : //-Coefficients and Temperatures
1415 3908780 : NodeBelow = this->T(WidthIndex, DepthIndex + 1, LengthIndex, TimeIndex::Current);
1416 3908780 : NodeLeft = this->T(WidthIndex - 1, DepthIndex, LengthIndex, TimeIndex::Current);
1417 :
1418 : //-Update Equation, basically a detailed energy balance at the surface
1419 3908780 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Tentative) =
1420 3908780 : (QSolAbsorbed + RadCoef * state.dataEnvrn->SkyTemp + ConvCoef * state.dataEnvrn->OutDryBulbTemp +
1421 3908780 : (kSoil / dS) * (NodeBelow + 2 * NodeLeft) + (rho * Cp / state.dataPipeHT->nsvDeltaTime) * NodePast) /
1422 3908780 : (RadCoef + ConvCoef + 3 * (kSoil / dS) + (rho * Cp / state.dataPipeHT->nsvDeltaTime));
1423 :
1424 : } else { // Soil surface, but not on centerline
1425 :
1426 : //-Coefficients and Temperatures
1427 7817560 : NodeBelow = this->T(WidthIndex, DepthIndex + 1, LengthIndex, TimeIndex::Current);
1428 7817560 : NodeLeft = this->T(WidthIndex - 1, DepthIndex, LengthIndex, TimeIndex::Current);
1429 7817560 : NodeRight = this->T(WidthIndex + 1, DepthIndex, LengthIndex, TimeIndex::Current);
1430 :
1431 : //-Update Equation
1432 7817560 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Tentative) =
1433 7817560 : (QSolAbsorbed + RadCoef * state.dataEnvrn->SkyTemp + ConvCoef * state.dataEnvrn->OutDryBulbTemp +
1434 7817560 : (kSoil / dS) * (NodeBelow + NodeLeft + NodeRight) + (rho * Cp / state.dataPipeHT->nsvDeltaTime) * NodePast) /
1435 7817560 : (RadCoef + ConvCoef + 3 * (kSoil / dS) + (rho * Cp / state.dataPipeHT->nsvDeltaTime));
1436 :
1437 : } // Soil-to-air surface node structure
1438 :
1439 70358040 : } else if (WidthIndex == this->PipeNodeWidth) { // On Symmetric centerline boundary
1440 :
1441 23452680 : if (DepthIndex == this->PipeNodeDepth) { // On the node containing the pipe
1442 :
1443 : //-Call to simulate a single pipe segment (by passing OPTIONAL LengthIndex argument)
1444 3908780 : this->CalcPipesHeatTransfer(state, LengthIndex);
1445 :
1446 : //-Update node for cartesian system
1447 3908780 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Tentative) = this->PipeTemp(LengthIndex);
1448 :
1449 : } else { // Not surface node
1450 :
1451 : //-Coefficients and Temperatures
1452 19543900 : NodeLeft = this->T(WidthIndex - 1, DepthIndex, LengthIndex, TimeIndex::Current);
1453 19543900 : NodeAbove = this->T(WidthIndex, DepthIndex - 1, LengthIndex, TimeIndex::Current);
1454 19543900 : NodeBelow = this->T(WidthIndex, DepthIndex + 1, LengthIndex, TimeIndex::Current);
1455 19543900 : NodePast = this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Current - 1);
1456 19543900 : A1 = this->CoefA1;
1457 19543900 : A2 = this->CoefA2;
1458 :
1459 : //-Update Equation
1460 19543900 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Tentative) =
1461 19543900 : A1 * (NodeBelow + NodeAbove + 2 * NodeLeft) + A2 * NodePast;
1462 :
1463 : } // Symmetric centerline node structure
1464 :
1465 : } else { // All Normal Interior Nodes
1466 :
1467 : //-Coefficients and Temperatures
1468 46905360 : A1 = this->CoefA1;
1469 46905360 : A2 = this->CoefA2;
1470 46905360 : NodeBelow = this->T(WidthIndex, DepthIndex + 1, LengthIndex, TimeIndex::Current);
1471 46905360 : NodeAbove = this->T(WidthIndex, DepthIndex - 1, LengthIndex, TimeIndex::Current);
1472 46905360 : NodeRight = this->T(WidthIndex + 1, DepthIndex, LengthIndex, TimeIndex::Current);
1473 46905360 : NodeLeft = this->T(WidthIndex - 1, DepthIndex, LengthIndex, TimeIndex::Current);
1474 46905360 : NodePast = this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Current - 1);
1475 :
1476 : //-Update Equation
1477 46905360 : this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Tentative) =
1478 46905360 : A1 * (NodeBelow + NodeAbove + NodeRight + NodeLeft) + A2 * NodePast; // Eq. D1
1479 : }
1480 : }
1481 : }
1482 : }
1483 :
1484 : // Check for convergence
1485 3739744 : for (int LengthIndex = 2; LengthIndex <= this->NumSections; ++LengthIndex) {
1486 28372256 : for (DepthIndex = 1; DepthIndex <= this->NumDepthNodes - 1; ++DepthIndex) {
1487 99289775 : for (WidthIndex = 2; WidthIndex <= this->PipeNodeWidth; ++WidthIndex) {
1488 74471084 : Ttemp = this->T(WidthIndex, DepthIndex, LengthIndex, TimeIndex::Tentative);
1489 74471084 : if (std::abs(T_O(WidthIndex, DepthIndex, LengthIndex) - Ttemp) > ConvCrit) {
1490 9260 : goto IterationLoop_loop;
1491 : }
1492 : }
1493 : }
1494 : }
1495 :
1496 : // If we didn't cycle back, then the system is converged
1497 : // PipeHT(PipeHTNum)%PipeUGIters=IterationIndex
1498 186179 : goto IterationLoop_exit;
1499 :
1500 9260 : IterationLoop_loop:;
1501 : }
1502 0 : IterationLoop_exit:;
1503 186179 : }
1504 :
1505 : //==============================================================================
1506 :
1507 58100 : void PipeHTData::UpdatePipesHeatTransfer(EnergyPlusData &state)
1508 : {
1509 :
1510 : // SUBROUTINE INFORMATION:
1511 : // AUTHOR Simon Rees
1512 : // DATE WRITTEN July 2007
1513 : // MODIFIED na
1514 : // RE-ENGINEERED na
1515 :
1516 : // PURPOSE OF THIS SUBROUTINE:
1517 : // This subroutine does any updating that needs to be done for
1518 : // Pipe Heat Transfers. This routine must also set the outlet water conditions.
1519 :
1520 : // METHODOLOGY EMPLOYED:
1521 :
1522 : // REFERENCES:
1523 : // na
1524 :
1525 : // SUBROUTINE ARGUMENT DEFINITIONS:
1526 : // INTEGER, INTENT(IN) :: PipeHTNum ! Index for the surface
1527 :
1528 : // SUBROUTINE PARAMETER DEFINITIONS:
1529 :
1530 : // INTERFACE BLOCK SPECIFICATIONS
1531 : // na
1532 :
1533 : // DERIVED TYPE DEFINITIONS
1534 : // na
1535 :
1536 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1537 :
1538 : // only outlet node temp should need updating
1539 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).Temp = state.dataPipeHT->nsvOutletTemp;
1540 :
1541 : // pass everything else through
1542 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).TempMin = state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).TempMin;
1543 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).TempMax = state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).TempMax;
1544 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).MassFlowRate =
1545 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).MassFlowRate;
1546 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).MassFlowRateMin =
1547 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).MassFlowRateMin;
1548 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).MassFlowRateMax =
1549 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).MassFlowRateMax;
1550 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).MassFlowRateMinAvail =
1551 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).MassFlowRateMinAvail;
1552 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).MassFlowRateMaxAvail =
1553 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).MassFlowRateMaxAvail;
1554 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).Quality = state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).Quality;
1555 : // Only pass pressure if we aren't doing a pressure simulation
1556 58100 : switch (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).PressureSimType) {
1557 58100 : case DataPlant::PressSimType::NoPressure:
1558 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).Press = state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).Press;
1559 58100 : break;
1560 0 : default:
1561 : // Don't do anything
1562 0 : break;
1563 : }
1564 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).Enthalpy = state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).Enthalpy;
1565 58100 : state.dataLoopNodes->Node(state.dataPipeHT->nsvOutletNodeNum).HumRat = state.dataLoopNodes->Node(state.dataPipeHT->nsvInletNodeNum).HumRat;
1566 58100 : }
1567 :
1568 : //==============================================================================
1569 :
1570 58100 : void PipeHTData::ReportPipesHeatTransfer(EnergyPlusData &state)
1571 : {
1572 :
1573 : // SUBROUTINE INFORMATION:
1574 : // AUTHOR Simon Rees
1575 : // DATE WRITTEN July 2007
1576 : // MODIFIED na
1577 : // RE-ENGINEERED na
1578 :
1579 : // PURPOSE OF THIS SUBROUTINE:
1580 : // This subroutine simply updates the report data
1581 :
1582 : // METHODOLOGY EMPLOYED:
1583 : // Standard EnergyPlus methodology.
1584 :
1585 : // REFERENCES:
1586 : // na
1587 :
1588 : // USE STATEMENTS:
1589 :
1590 : // Locals
1591 : // SUBROUTINE ARGUMENT DEFINITIONS:
1592 :
1593 : // SUBROUTINE PARAMETER DEFINITIONS:
1594 : // na
1595 :
1596 : // INTERFACE BLOCK SPECIFICATIONS
1597 : // na
1598 :
1599 : // DERIVED TYPE DEFINITIONS
1600 : // na
1601 :
1602 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1603 :
1604 : // update flows and temps from module variables
1605 58100 : this->FluidInletTemp = state.dataPipeHT->nsvInletTemp;
1606 58100 : this->FluidOutletTemp = state.dataPipeHT->nsvOutletTemp;
1607 58100 : this->MassFlowRate = state.dataPipeHT->nsvMassFlowRate;
1608 58100 : this->VolumeFlowRate = state.dataPipeHT->nsvVolumeFlowRate;
1609 :
1610 : // update other variables from module variables
1611 58100 : this->FluidHeatLossRate = state.dataPipeHT->nsvFluidHeatLossRate;
1612 58100 : this->FluidHeatLossEnergy = state.dataPipeHT->nsvFluidHeatLossRate * state.dataPipeHT->nsvDeltaTime; // DeltaTime is in seconds
1613 58100 : this->PipeInletTemp = this->PipeTemp(1);
1614 58100 : this->PipeOutletTemp = this->PipeTemp(this->NumSections);
1615 :
1616 : // need to average the heat rate because it is now summing over multiple inner time steps
1617 58100 : this->EnvironmentHeatLossRate = state.dataPipeHT->nsvEnvHeatLossRate / state.dataPipeHT->nsvNumInnerTimeSteps;
1618 58100 : this->EnvHeatLossEnergy = this->EnvironmentHeatLossRate * state.dataPipeHT->nsvDeltaTime;
1619 :
1620 : // for zone heat gains, we assign the averaged heat rate over all inner time steps
1621 58100 : if (this->EnvironmentPtr == EnvrnPtr::ZoneEnv) {
1622 14525 : this->ZoneHeatGainRate = this->EnvironmentHeatLossRate;
1623 : }
1624 58100 : }
1625 :
1626 : //==============================================================================
1627 :
1628 2828408 : void PipeHTData::CalcZonePipesHeatGain(EnergyPlusData &state)
1629 : {
1630 :
1631 : // SUBROUTINE INFORMATION:
1632 : // AUTHOR Edwin Lee
1633 : // DATE WRITTEN September 2008
1634 : // MODIFIED
1635 : // RE-ENGINEERED na
1636 :
1637 : // PURPOSE OF THIS SUBROUTINE:
1638 : // Calculates the zone internal gains due to pipe heat transfer objects.
1639 :
1640 : // METHODOLOGY EMPLOYED:
1641 : // Sums the heat losses from all of the water heaters in the zone to add as a gain to the zone.
1642 :
1643 : // Using/Aliasing
1644 2828408 : if (state.dataPipeHT->nsvNumOfPipeHT == 0) {
1645 2817600 : return;
1646 : }
1647 :
1648 10808 : if (state.dataGlobal->BeginEnvrnFlag && state.dataPipeHT->MyEnvrnFlag) {
1649 64 : for (auto &e : state.dataPipeHT->PipeHT) {
1650 32 : e.ZoneHeatGainRate = 0.0;
1651 : }
1652 32 : state.dataPipeHT->MyEnvrnFlag = false;
1653 : }
1654 :
1655 10808 : if (!state.dataGlobal->BeginEnvrnFlag) {
1656 10776 : state.dataPipeHT->MyEnvrnFlag = true;
1657 : }
1658 : }
1659 :
1660 : //==============================================================================
1661 :
1662 4467317 : Real64 PipeHTData::CalcPipeHeatTransCoef(EnergyPlusData &state,
1663 : Real64 const Temperature, // Temperature of water entering the surface, in C
1664 : Real64 const MassFlowRate, // Mass flow rate, in kg/s
1665 : Real64 const Diameter // Pipe diameter, m
1666 : )
1667 : {
1668 :
1669 : // FUNCTION INFORMATION:
1670 : // AUTHOR Simon Rees
1671 : // DATE WRITTEN July 2007
1672 : // MODIFIED na
1673 : // RE-ENGINEERED na
1674 :
1675 : // PURPOSE OF THIS SUBROUTINE:
1676 : // This subroutine calculates pipe/fluid heat transfer coefficients.
1677 : // This routine is adapted from that in the low temp radiant surface model.
1678 :
1679 : // METHODOLOGY EMPLOYED:
1680 : // Currently assumes water data when calculating Pr and Re
1681 :
1682 : // REFERENCES:
1683 : // See RadiantSystemLowTemp module.
1684 : // Property data for water shown below as parameters taken from
1685 : // Incropera and DeWitt, Introduction to Heat Transfer, Table A.6.
1686 : // Heat exchanger information also from Incropera and DeWitt.
1687 : // Code based loosely on code from IBLAST program (research version)
1688 :
1689 : // Return value
1690 : Real64 CalcPipeHeatTransCoef;
1691 :
1692 : // Locals
1693 : // SUBROUTINE ARGUMENT DEFINITIONS:
1694 :
1695 : // SUBROUTINE PARAMETER DEFINITIONS:
1696 : static constexpr std::string_view RoutineName("PipeHeatTransfer::CalcPipeHeatTransCoef: ");
1697 4467317 : Real64 constexpr MaxLaminarRe(2300.0); // Maximum Reynolds number for laminar flow
1698 4467317 : int constexpr NumOfPropDivisions(13); // intervals in property correlation
1699 : static constexpr std::array<Real64, NumOfPropDivisions> Temps = {
1700 : 1.85, 6.85, 11.85, 16.85, 21.85, 26.85, 31.85, 36.85, 41.85, 46.85, 51.85, 56.85, 61.85}; // Temperature, in C
1701 : static constexpr std::array<Real64, NumOfPropDivisions> Pr = {
1702 : 12.22, 10.26, 8.81, 7.56, 6.62, 5.83, 5.20, 4.62, 4.16, 3.77, 3.42, 3.15, 2.88}; // Prandtl number (dimensionless)
1703 :
1704 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1705 : Real64 InterpFrac;
1706 : Real64 NuD;
1707 : Real64 ReD;
1708 : Real64 Kactual;
1709 : Real64 MUactual;
1710 : Real64 PRactual;
1711 : int LoopNum;
1712 :
1713 : // retrieve loop index for this component so we can look up fluid properties
1714 4467317 : LoopNum = this->plantLoc.loopNum;
1715 :
1716 : // since the fluid properties routine doesn't have Prandtl, we'll just use water values
1717 4467317 : int idx = 0;
1718 20129911 : while (idx < NumOfPropDivisions) {
1719 20129911 : if (Temperature < Temps[idx]) {
1720 4467317 : break;
1721 : }
1722 15662594 : ++idx;
1723 : }
1724 :
1725 4467317 : if (idx == 0) {
1726 0 : PRactual = Pr[idx];
1727 4467317 : } else if (idx >= NumOfPropDivisions) {
1728 0 : PRactual = Pr[NumOfPropDivisions - 1];
1729 : } else {
1730 4467317 : InterpFrac = (Temperature - Temps[idx - 1]) / (Temps[idx] - Temps[idx - 1]);
1731 4467317 : PRactual = Pr[idx - 1] + InterpFrac * (Pr[idx] - Pr[idx - 1]);
1732 : }
1733 :
1734 : // look up conductivity and viscosity
1735 4467317 : Kactual = state.dataPlnt->PlantLoop(LoopNum).glycol->getConductivity(state, this->FluidTemp(0), RoutineName); // W/m-K
1736 4467317 : MUactual = state.dataPlnt->PlantLoop(LoopNum).glycol->getViscosity(state, this->FluidTemp(0), RoutineName) /
1737 : 1000.0; // Note fluid properties routine returns mPa-s, we need Pa-s
1738 :
1739 : // Calculate the Reynold's number from RE=(4*Mdot)/(Pi*Mu*Diameter) - as RadiantSysLowTemp
1740 4467317 : ReD = 4.0 * MassFlowRate / (Constant::Pi * MUactual * Diameter);
1741 :
1742 4467317 : if (ReD == 0.0) { // No flow
1743 :
1744 : // For now just leave it how it was doing it before
1745 1938541 : NuD = 3.66;
1746 : // Although later it would be nice to have a natural convection correlation
1747 :
1748 : } else { // Calculate the Nusselt number based on what flow regime one is in
1749 :
1750 2528776 : if (ReD >= MaxLaminarRe) { // Turbulent flow --> use Colburn equation
1751 2528776 : NuD = 0.023 * std::pow(ReD, 0.8) * std::pow(PRactual, 1.0 / 3.0);
1752 : } else { // Laminar flow --> use constant surface temperature relation
1753 0 : NuD = 3.66;
1754 : }
1755 : }
1756 :
1757 4467317 : CalcPipeHeatTransCoef = Kactual * NuD / Diameter;
1758 :
1759 4467317 : return CalcPipeHeatTransCoef;
1760 : }
1761 :
1762 : //==============================================================================
1763 :
1764 558537 : Real64 PipeHTData::OutsidePipeHeatTransCoef(EnergyPlusData &state)
1765 : {
1766 :
1767 : // FUNCTION INFORMATION:
1768 : // AUTHOR Dan Fisher
1769 : // DATE WRITTEN July 2007
1770 : // MODIFIED na
1771 : // RE-ENGINEERED na
1772 :
1773 : // PURPOSE OF THIS SUBROUTINE:
1774 : // This subroutine calculates the convection heat transfer
1775 : // coefficient for a cylinder in cross flow.
1776 :
1777 : // REFERENCES:
1778 : // Fundamentals of Heat and Mass Transfer: Incropera and DeWitt, 4th ed.
1779 : // p. 369-370 (Eq. 7:55b)
1780 :
1781 : // Return value
1782 : Real64 OutsidePipeHeatTransCoef;
1783 :
1784 : // SUBROUTINE PARAMETER DEFINITIONS:
1785 558537 : Real64 constexpr Pr(0.7); // Prandl number for air (assume constant)
1786 558537 : Real64 constexpr CondAir(0.025); // thermal conductivity of air (assume constant) [W/m.K]
1787 558537 : Real64 constexpr RoomAirVel(0.381); // room air velocity of 75 ft./min [m/s]
1788 558537 : Real64 constexpr NaturalConvNusselt(0.36);
1789 : // Nusselt for natural convection for horizontal cylinder
1790 : // from: Correlations for Convective Heat Transfer
1791 : // Dr. Bernhard Spang
1792 : // Chemical Engineers' Resource Page: http://www.cheresources.com/convection.pdf
1793 558537 : int constexpr NumOfParamDivisions(5); // intervals in property correlation
1794 558537 : int constexpr NumOfPropDivisions(12); // intervals in property correlation
1795 :
1796 : static constexpr std::array<Real64, NumOfParamDivisions> CCoef = {0.989, 0.911, 0.683, 0.193, 0.027}; // correlation coefficient
1797 : static constexpr std::array<Real64, NumOfParamDivisions> mExp = {0.33, 0.385, 0.466, 0.618, 0.805}; // exponent
1798 : static constexpr std::array<Real64, NumOfParamDivisions> UpperBound = {4.0, 40.0, 4000.0, 40000.0, 400000.0}; // upper bound of correlation range
1799 : static constexpr std::array<Real64, NumOfPropDivisions> Temperature = {
1800 : -73.0, -23.0, -10.0, 0.0, 10.0, 20.0, 27.0, 30.0, 40.0, 50.0, 76.85, 126.85}; // temperature [C]
1801 : static constexpr std::array<Real64, NumOfPropDivisions> DynVisc = {
1802 : 75.52e-7, 11.37e-6, 12.44e-6, 13.3e-6, 14.18e-6, 15.08e-6, 15.75e-6, 16e-6, 16.95e-6, 17.91e-6, 20.92e-6, 26.41e-6}; // dynamic
1803 : // viscosity
1804 : // [m^2/s]
1805 :
1806 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
1807 : int idx;
1808 : Real64 NuD;
1809 : Real64 ReD;
1810 : Real64 Coef;
1811 : Real64 rExp;
1812 : Real64 AirVisc;
1813 : Real64 AirVel;
1814 : Real64 AirTemp;
1815 : Real64 PipeOD;
1816 : bool ViscositySet;
1817 : bool CoefSet;
1818 :
1819 : // Set environmental variables
1820 558537 : switch (this->Type) {
1821 372358 : case DataPlant::PlantEquipmentType::PipeInterior: {
1822 372358 : switch (this->EnvironmentPtr) {
1823 186179 : case EnvrnPtr::ScheduleEnv: {
1824 186179 : AirTemp = this->envrSched->getCurrentVal();
1825 186179 : AirVel = this->envrVelSched->getCurrentVal();
1826 186179 : } break;
1827 186179 : case EnvrnPtr::ZoneEnv: {
1828 186179 : AirTemp = state.dataZoneTempPredictorCorrector->zoneHeatBalance(this->EnvrZonePtr).MAT;
1829 186179 : AirVel = RoomAirVel;
1830 186179 : } break;
1831 0 : default:
1832 0 : break;
1833 : }
1834 372358 : } break;
1835 186179 : case DataPlant::PlantEquipmentType::PipeExterior: {
1836 186179 : switch (this->EnvironmentPtr) {
1837 186179 : case EnvrnPtr::OutsideAirEnv: {
1838 186179 : AirTemp = state.dataLoopNodes->Node(this->EnvrAirNodeNum).Temp;
1839 186179 : AirVel = state.dataEnvrn->WindSpeed;
1840 186179 : } break;
1841 0 : default:
1842 0 : break;
1843 : }
1844 186179 : } break;
1845 0 : default:
1846 0 : break;
1847 : }
1848 :
1849 558537 : PipeOD = this->InsulationOD;
1850 :
1851 558537 : ViscositySet = false;
1852 3702653 : for (idx = 0; idx < NumOfPropDivisions; ++idx) {
1853 3702653 : if (AirTemp <= Temperature[idx]) {
1854 558537 : AirVisc = DynVisc[idx];
1855 558537 : ViscositySet = true;
1856 558537 : break;
1857 : }
1858 : }
1859 :
1860 558537 : if (!ViscositySet) {
1861 0 : AirVisc = DynVisc[NumOfPropDivisions - 1];
1862 0 : if (AirTemp > Temperature[NumOfPropDivisions - 1]) {
1863 0 : ShowWarningError(state,
1864 0 : format("Heat Transfer Pipe = {}Viscosity out of range, air temperature too high, setting to upper limit.", this->Name));
1865 : }
1866 : }
1867 :
1868 : // Calculate the Reynold's number
1869 558537 : CoefSet = false;
1870 558537 : if (AirVisc > 0.0) {
1871 558537 : ReD = AirVel * PipeOD / (AirVisc);
1872 : }
1873 :
1874 2212153 : for (idx = 0; idx < NumOfParamDivisions; ++idx) {
1875 2212153 : if (ReD <= UpperBound[idx]) {
1876 558537 : Coef = CCoef[idx];
1877 558537 : rExp = mExp[idx];
1878 558537 : CoefSet = true;
1879 558537 : break;
1880 : }
1881 : }
1882 :
1883 558537 : if (!CoefSet) {
1884 0 : Coef = CCoef[NumOfParamDivisions - 1];
1885 0 : rExp = mExp[NumOfParamDivisions - 1];
1886 0 : if (ReD > UpperBound[NumOfParamDivisions - 1]) {
1887 0 : ShowWarningError(state, format("Heat Transfer Pipe = {}Reynolds Number out of range, setting coefficients to upper limit.", this->Name));
1888 : }
1889 : }
1890 :
1891 : // Calculate the Nusselt number
1892 558537 : NuD = Coef * std::pow(ReD, rExp) * std::pow(Pr, 1.0 / 3.0);
1893 :
1894 : // If the wind speed is too small, we need to use natural convection behavior:
1895 558537 : NuD = max(NuD, NaturalConvNusselt);
1896 :
1897 : // h = (k)(Nu)/D
1898 558537 : OutsidePipeHeatTransCoef = CondAir * NuD / PipeOD;
1899 :
1900 558537 : return OutsidePipeHeatTransCoef;
1901 : }
1902 :
1903 : //==============================================================================
1904 :
1905 1316400 : Real64 PipeHTData::TBND(EnergyPlusData &state,
1906 : Real64 const z // Current Depth
1907 : )
1908 : {
1909 :
1910 : // AUTHOR Edwin Lee
1911 : // DATE WRITTEN December 2007
1912 : // MODIFIED na
1913 : // RE-ENGINEERED na
1914 :
1915 : // PURPOSE OF THIS FUNCTION:
1916 : // Returns a temperature to be used on the boundary of the buried pipe model domain
1917 :
1918 : // METHODOLOGY EMPLOYED:
1919 :
1920 : // REFERENCES: See Module Level Description
1921 :
1922 : // Using/Aliasing
1923 1316400 : Real64 curSimTime = state.dataGlobal->DayOfSim * Constant::rSecsInDay;
1924 1316400 : return this->groundTempModel->getGroundTempAtTimeInSeconds(state, z, curSimTime);
1925 : }
1926 :
1927 0 : void PipeHTData::oneTimeInit([[maybe_unused]] EnergyPlusData &state)
1928 : {
1929 0 : }
1930 :
1931 : //===============================================================================
1932 :
1933 : //===============================================================================
1934 :
1935 : } // namespace EnergyPlus::PipeHeatTransfer
|