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