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 <iostream>
51 : #include <string>
52 :
53 : // ObjexxFCL Headers
54 : #include <ObjexxFCL/Array.functions.hh>
55 : #include <ObjexxFCL/Fmath.hh>
56 :
57 : // EnergyPlus Headers
58 : #include <EnergyPlus/Autosizing/Base.hh>
59 : #include <EnergyPlus/BranchNodeConnections.hh>
60 : #include <EnergyPlus/Data/EnergyPlusData.hh>
61 : #include <EnergyPlus/DataEnvironment.hh>
62 : #include <EnergyPlus/DataHVACGlobals.hh>
63 : #include <EnergyPlus/DataIPShortCuts.hh>
64 : #include <EnergyPlus/DataLoopNode.hh>
65 : #include <EnergyPlus/DataSizing.hh>
66 : #include <EnergyPlus/FluidProperties.hh>
67 : #include <EnergyPlus/General.hh>
68 : #include <EnergyPlus/GlobalNames.hh>
69 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
70 : #include <EnergyPlus/NodeInputManager.hh>
71 : #include <EnergyPlus/OutputProcessor.hh>
72 : #include <EnergyPlus/OutsideEnergySources.hh>
73 : #include <EnergyPlus/Plant/DataPlant.hh>
74 : #include <EnergyPlus/PlantUtilities.hh>
75 : #include <EnergyPlus/ScheduleManager.hh>
76 : #include <EnergyPlus/UtilityRoutines.hh>
77 :
78 : namespace EnergyPlus::OutsideEnergySources {
79 :
80 : // MODULE INFORMATION:
81 : // AUTHOR Dan Fisher
82 : // DATE WRITTEN Unknown
83 : // MODIFIED na
84 : // RE-ENGINEERED Brent Griffith, Sept 2010, revised plant interactions.
85 :
86 : // PURPOSE OF THIS MODULE:
87 : // Module containing the routines dealing with the OutsideEnergySources
88 :
89 278 : PlantComponent *OutsideEnergySourceSpecs::factory(EnergyPlusData &state, DataPlant::PlantEquipmentType objectType, std::string_view objectName)
90 : {
91 : // Process the input data for outside energy sources if it hasn't been done already
92 278 : if (state.dataOutsideEnergySrcs->SimOutsideEnergyGetInputFlag) {
93 159 : GetOutsideEnergySourcesInput(state);
94 159 : state.dataOutsideEnergySrcs->SimOutsideEnergyGetInputFlag = false;
95 : }
96 : // Now look for this particular pipe in the list
97 397 : for (auto &source : state.dataOutsideEnergySrcs->EnergySource) {
98 397 : if (source.EnergyType == objectType && source.Name == objectName) {
99 278 : return &source;
100 : }
101 : }
102 : // If we didn't find it, fatal
103 : ShowFatalError(state, format("OutsideEnergySourceSpecsFactory: Error getting inputs for source named: {}", objectName)); // LCOV_EXCL_LINE
104 : // Shut up the compiler
105 : return nullptr; // LCOV_EXCL_LINE
106 : }
107 :
108 7034957 : void OutsideEnergySourceSpecs::simulate(EnergyPlusData &state,
109 : [[maybe_unused]] const PlantLocation &calledFromLocation,
110 : [[maybe_unused]] bool FirstHVACIteration,
111 : Real64 &CurLoad,
112 : bool RunFlag)
113 : {
114 7034957 : this->initialize(state, CurLoad);
115 7034957 : this->calculate(state, RunFlag, CurLoad);
116 7034957 : }
117 :
118 1392 : void OutsideEnergySourceSpecs::onInitLoopEquip(EnergyPlusData &state, const PlantLocation &)
119 : {
120 1392 : this->initialize(state, 0.0);
121 1392 : this->size(state);
122 1392 : }
123 :
124 1392 : void OutsideEnergySourceSpecs::getDesignCapacities([[maybe_unused]] EnergyPlusData &state,
125 : [[maybe_unused]] const PlantLocation &calledFromLocation,
126 : Real64 &MaxLoad,
127 : Real64 &MinLoad,
128 : Real64 &OptLoad)
129 : {
130 1392 : MinLoad = 0.0;
131 1392 : MaxLoad = this->NomCap;
132 1392 : OptLoad = this->NomCap;
133 1392 : }
134 :
135 159 : void GetOutsideEnergySourcesInput(EnergyPlusData &state)
136 : {
137 : // SUBROUTINE INFORMATION:
138 : // AUTHOR Dan Fisher
139 : // DATE WRITTEN April 1998
140 : // MODIFIED May 2010; Edwin Lee; Linda Lawrie (consolidation)
141 : // June 2022, Dareum Nam, Add DistrictHeatingSteam
142 : // RE-ENGINEERED na
143 :
144 : // PURPOSE OF THIS SUBROUTINE:
145 : // This routine obtains the input data puts it into the
146 : // component arrays. Data items in the component arrays
147 : // are initialized. Output variables are set up.
148 :
149 : static constexpr std::string_view routineName = "GetOutsideEnergySourcesInput";
150 :
151 : // GET NUMBER OF ALL EQUIPMENT TYPES
152 159 : int const NumDistrictUnitsHeatWater = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "DistrictHeating:Water");
153 159 : int const NumDistrictUnitsCool = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "DistrictCooling");
154 159 : int const NumDistrictUnitsHeatSteam = state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, "DistrictHeating:Steam");
155 159 : state.dataOutsideEnergySrcs->NumDistrictUnits = NumDistrictUnitsHeatWater + NumDistrictUnitsCool + NumDistrictUnitsHeatSteam;
156 :
157 159 : if (allocated(state.dataOutsideEnergySrcs->EnergySource)) {
158 0 : return;
159 : }
160 :
161 159 : state.dataOutsideEnergySrcs->EnergySource.allocate(state.dataOutsideEnergySrcs->NumDistrictUnits);
162 159 : state.dataOutsideEnergySrcs->EnergySourceUniqueNames.reserve(static_cast<unsigned>(state.dataOutsideEnergySrcs->NumDistrictUnits));
163 :
164 159 : bool ErrorsFound(false); // If errors detected in input
165 159 : int heatWaterIndex = 0;
166 159 : int coolIndex = 0;
167 159 : int heatSteamIndex = 0;
168 :
169 437 : for (int EnergySourceNum = 1; EnergySourceNum <= state.dataOutsideEnergySrcs->NumDistrictUnits; ++EnergySourceNum) {
170 :
171 278 : std::string nodeNames;
172 : DataPlant::PlantEquipmentType EnergyType;
173 : DataLoopNode::ConnectionObjectType objType;
174 : int thisIndex;
175 278 : if (EnergySourceNum <= NumDistrictUnitsHeatWater) {
176 144 : state.dataIPShortCut->cCurrentModuleObject = "DistrictHeating:Water";
177 144 : objType = DataLoopNode::ConnectionObjectType::DistrictHeatingWater;
178 144 : nodeNames = "Hot Water Nodes";
179 144 : EnergyType = DataPlant::PlantEquipmentType::PurchHotWater;
180 144 : heatWaterIndex++;
181 144 : thisIndex = heatWaterIndex;
182 134 : } else if (EnergySourceNum <= NumDistrictUnitsHeatWater + NumDistrictUnitsCool) {
183 133 : state.dataIPShortCut->cCurrentModuleObject = "DistrictCooling";
184 133 : objType = DataLoopNode::ConnectionObjectType::DistrictCooling;
185 133 : nodeNames = "Chilled Water Nodes";
186 133 : EnergyType = DataPlant::PlantEquipmentType::PurchChilledWater;
187 133 : coolIndex++;
188 133 : thisIndex = coolIndex;
189 : } else { // EnergySourceNum > NumDistrictUnitsHeatWater + NumDistrictUnitsCool
190 1 : state.dataIPShortCut->cCurrentModuleObject = "DistrictHeating:Steam";
191 1 : objType = DataLoopNode::ConnectionObjectType::DistrictHeatingSteam;
192 1 : nodeNames = "Steam Nodes";
193 1 : EnergyType = DataPlant::PlantEquipmentType::PurchSteam;
194 1 : heatSteamIndex++;
195 1 : thisIndex = heatSteamIndex;
196 : }
197 :
198 278 : int NumAlphas = 0, NumNums = 0, IOStat = 0;
199 834 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
200 278 : state.dataIPShortCut->cCurrentModuleObject,
201 : thisIndex,
202 278 : state.dataIPShortCut->cAlphaArgs,
203 : NumAlphas,
204 278 : state.dataIPShortCut->rNumericArgs,
205 : NumNums,
206 : IOStat,
207 : _,
208 278 : state.dataIPShortCut->lAlphaFieldBlanks,
209 278 : state.dataIPShortCut->cAlphaFieldNames);
210 :
211 278 : ErrorObjectHeader eoh{routineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)};
212 :
213 278 : if (EnergySourceNum > 1) {
214 119 : GlobalNames::VerifyUniqueInterObjectName(state,
215 119 : state.dataOutsideEnergySrcs->EnergySourceUniqueNames,
216 119 : state.dataIPShortCut->cAlphaArgs(1),
217 119 : state.dataIPShortCut->cCurrentModuleObject,
218 119 : state.dataIPShortCut->cAlphaFieldNames(1),
219 : ErrorsFound);
220 : }
221 278 : state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).Name = state.dataIPShortCut->cAlphaArgs(1);
222 278 : if (EnergySourceNum <= NumDistrictUnitsHeatWater + NumDistrictUnitsCool) {
223 277 : state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).InletNodeNum =
224 554 : NodeInputManager::GetOnlySingleNode(state,
225 277 : state.dataIPShortCut->cAlphaArgs(2),
226 : ErrorsFound,
227 : objType,
228 277 : state.dataIPShortCut->cAlphaArgs(1),
229 : DataLoopNode::NodeFluidType::Water,
230 : DataLoopNode::ConnectionType::Inlet,
231 : NodeInputManager::CompFluidStream::Primary,
232 : DataLoopNode::ObjectIsNotParent);
233 277 : state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).OutletNodeNum =
234 831 : NodeInputManager::GetOnlySingleNode(state,
235 277 : state.dataIPShortCut->cAlphaArgs(3),
236 : ErrorsFound,
237 : objType,
238 277 : state.dataIPShortCut->cAlphaArgs(1),
239 : DataLoopNode::NodeFluidType::Water,
240 : DataLoopNode::ConnectionType::Outlet,
241 : NodeInputManager::CompFluidStream::Primary,
242 : DataLoopNode::ObjectIsNotParent);
243 : } else {
244 1 : state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).InletNodeNum =
245 2 : NodeInputManager::GetOnlySingleNode(state,
246 1 : state.dataIPShortCut->cAlphaArgs(2),
247 : ErrorsFound,
248 : objType,
249 1 : state.dataIPShortCut->cAlphaArgs(1),
250 : DataLoopNode::NodeFluidType::Steam,
251 : DataLoopNode::ConnectionType::Inlet,
252 : NodeInputManager::CompFluidStream::Primary,
253 : DataLoopNode::ObjectIsNotParent);
254 1 : state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).OutletNodeNum =
255 3 : NodeInputManager::GetOnlySingleNode(state,
256 1 : state.dataIPShortCut->cAlphaArgs(3),
257 : ErrorsFound,
258 : objType,
259 1 : state.dataIPShortCut->cAlphaArgs(1),
260 : DataLoopNode::NodeFluidType::Steam,
261 : DataLoopNode::ConnectionType::Outlet,
262 : NodeInputManager::CompFluidStream::Primary,
263 : DataLoopNode::ObjectIsNotParent);
264 : }
265 278 : BranchNodeConnections::TestCompSet(state,
266 278 : state.dataIPShortCut->cCurrentModuleObject,
267 278 : state.dataIPShortCut->cAlphaArgs(1),
268 278 : state.dataIPShortCut->cAlphaArgs(2),
269 278 : state.dataIPShortCut->cAlphaArgs(3),
270 : nodeNames);
271 278 : state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).NomCap = state.dataIPShortCut->rNumericArgs(1);
272 278 : if (state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).NomCap == DataSizing::AutoSize) {
273 8 : state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).NomCapWasAutoSized = true;
274 : }
275 278 : state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).EnergyTransfer = 0.0;
276 278 : state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).EnergyRate = 0.0;
277 278 : state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).EnergyType = EnergyType;
278 :
279 278 : if (state.dataIPShortCut->lAlphaFieldBlanks(4)) {
280 278 : state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).capFractionSched =
281 278 : Sched::GetScheduleAlwaysOn(state); // Defaults to constant-1.0
282 0 : } else if ((state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).capFractionSched =
283 0 : Sched::GetSchedule(state, state.dataIPShortCut->cAlphaArgs(4))) == nullptr) {
284 0 : ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(4), state.dataIPShortCut->cAlphaArgs(4));
285 0 : ErrorsFound = true;
286 0 : } else if (!state.dataOutsideEnergySrcs->EnergySource(EnergySourceNum).capFractionSched->checkMinVal(state, Clusive::In, 0.0)) {
287 0 : Sched::ShowWarningBadMin(state,
288 : eoh,
289 0 : state.dataIPShortCut->cAlphaFieldNames(4),
290 0 : state.dataIPShortCut->cAlphaArgs(4),
291 : Clusive::In,
292 : 0.0,
293 : "Negative values will be treated as zero, and the simulation continues.");
294 : }
295 278 : }
296 :
297 159 : if (ErrorsFound) {
298 0 : ShowFatalError(
299 : state,
300 0 : format("Errors found in processing input for {}, Preceding condition caused termination.", state.dataIPShortCut->cCurrentModuleObject));
301 : }
302 : }
303 :
304 7036349 : void OutsideEnergySourceSpecs::initialize(EnergyPlusData &state, Real64 MyLoad)
305 : {
306 :
307 : // SUBROUTINE INFORMATION:
308 : // AUTHOR: Dan Fisher
309 : // DATE WRITTEN: October 1998
310 : // MODIFIED May 2010; Edwin Lee; Linda Lawrie (consolidation)
311 : // RE-ENGINEERED Sept 2010, Brent Griffith, plant rewrite
312 :
313 : // PURPOSE OF THIS SUBROUTINE:
314 : // This subroutine does one-time inits and sets the operating mass flow rate of this machine
315 :
316 : // METHODOLOGY EMPLOYED:
317 : // One time inits include validating source type (should happen in getinput?) and locating this
318 : // component on the PlantLoop topology.
319 : // The mass flow rate is determined based on component load, and making use of
320 : // the SetComponentFlowRate routine.
321 : // The mass flow rate could be an inter-connected-loop side trigger. This is not really the type of
322 : // interconnect that that routine was written for, but it is the clearest example of using it.
323 :
324 7036349 : auto &loop = state.dataPlnt->PlantLoop(this->plantLoc.loopNum);
325 :
326 : // begin environment inits
327 7036349 : if (state.dataGlobal->BeginEnvrnFlag && this->BeginEnvrnInitFlag) {
328 : // component model has not design flow rates, using data for overall plant loop
329 1619 : PlantUtilities::InitComponentNodes(state, loop.MinMassFlowRate, loop.MaxMassFlowRate, this->InletNodeNum, this->OutletNodeNum);
330 1619 : this->BeginEnvrnInitFlag = false;
331 : }
332 7036349 : if (!state.dataGlobal->BeginEnvrnFlag) {
333 6996856 : this->BeginEnvrnInitFlag = true;
334 : }
335 :
336 7036349 : Real64 TempPlantMassFlow(0.0);
337 7036349 : if (std::abs(MyLoad) > 0.0) {
338 1692062 : TempPlantMassFlow = loop.MaxMassFlowRate;
339 : }
340 :
341 : // get actual mass flow to use, hold in MassFlowRate variable
342 7036349 : PlantUtilities::SetComponentFlowRate(state, TempPlantMassFlow, this->InletNodeNum, this->OutletNodeNum, this->plantLoc);
343 :
344 7036349 : this->InletTemp = state.dataLoopNodes->Node(this->InletNodeNum).Temp;
345 7036349 : this->MassFlowRate = TempPlantMassFlow;
346 7036349 : }
347 :
348 1392 : void OutsideEnergySourceSpecs::size(EnergyPlusData &state)
349 : {
350 : // SUBROUTINE INFORMATION:
351 : // AUTHOR Daeho Kang
352 : // DATE WRITTEN April 2014
353 : // MODIFIED June 2021, Dareum Nam, Add DistrictHeatingSteam
354 : // RE-ENGINEERED na
355 :
356 : // PURPOSE OF THIS SUBROUTINE:
357 : // This subroutine is for sizing capacities of district cooling and heating objects.
358 :
359 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
360 1392 : bool ErrorsFound(false); // If errors detected in input
361 :
362 : // Type name string variable to collapse the sizing for cooling and heating into one block
363 1392 : std::string_view typeName = DataPlant::PlantEquipTypeNames[static_cast<int>(this->EnergyType)];
364 :
365 1392 : auto &loop = state.dataPlnt->PlantLoop(this->plantLoc.loopNum);
366 1392 : int const PltSizNum = loop.PlantSizNum;
367 1392 : if (PltSizNum > 0) {
368 : Real64 NomCapDes;
369 427 : if (this->EnergyType == DataPlant::PlantEquipmentType::PurchChilledWater ||
370 242 : this->EnergyType == DataPlant::PlantEquipmentType::PurchHotWater) {
371 422 : Real64 const rho = loop.glycol->getDensity(state, Constant::InitConvTemp, format("Size {}", typeName));
372 422 : Real64 const Cp = loop.glycol->getSpecificHeat(state, Constant::InitConvTemp, format("Size {}", typeName));
373 422 : NomCapDes = Cp * rho * state.dataSize->PlantSizData(PltSizNum).DeltaT * state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate;
374 422 : } else { // this->EnergyType == DataPlant::TypeOf_PurchSteam
375 5 : Real64 const tempSteam = loop.steam->getSatTemperature(state, state.dataEnvrn->StdBaroPress, format("Size {}", typeName));
376 5 : Real64 const rhoSteam = loop.steam->getSatDensity(state, tempSteam, 1.0, format("Size {}", typeName));
377 5 : Real64 const EnthSteamDry = loop.steam->getSatEnthalpy(state, tempSteam, 1.0, format("Size {}", typeName));
378 5 : Real64 const EnthSteamWet = loop.steam->getSatEnthalpy(state, tempSteam, 0.0, format("Size {}", typeName));
379 5 : Real64 const LatentHeatSteam = EnthSteamDry - EnthSteamWet;
380 5 : NomCapDes = rhoSteam * state.dataSize->PlantSizData(PltSizNum).DesVolFlowRate * LatentHeatSteam;
381 : }
382 427 : if (state.dataPlnt->PlantFirstSizesOkayToFinalize) {
383 87 : if (this->NomCapWasAutoSized) {
384 10 : this->NomCap = NomCapDes;
385 10 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
386 8 : BaseSizer::reportSizerOutput(state, typeName, this->Name, "Design Size Nominal Capacity [W]", NomCapDes);
387 : }
388 10 : if (state.dataPlnt->PlantFirstSizesOkayToReport) {
389 1 : BaseSizer::reportSizerOutput(state, typeName, this->Name, "Initial Design Size Nominal Capacity [W]", NomCapDes);
390 : }
391 : } else { // Hard-size with sizing data
392 77 : if (this->NomCap > 0.0 && NomCapDes > 0.0) {
393 75 : Real64 const NomCapUser = this->NomCap;
394 75 : if (state.dataPlnt->PlantFinalSizesOkayToReport) {
395 75 : BaseSizer::reportSizerOutput(state,
396 : typeName,
397 : this->Name,
398 : "Design Size Nominal Capacity [W]",
399 : NomCapDes,
400 : "User-Specified Nominal Capacity [W]",
401 : NomCapUser);
402 75 : if (state.dataGlobal->DisplayExtraWarnings) {
403 5 : if ((std::abs(NomCapDes - NomCapUser) / NomCapUser) > state.dataSize->AutoVsHardSizingThreshold) {
404 1 : ShowMessage(state, format("Size {}: Potential issue with equipment sizing for {}", typeName, this->Name));
405 1 : ShowContinueError(state, format("User-Specified Nominal Capacity of {:.2R} [W]", NomCapUser));
406 1 : ShowContinueError(state, format("differs from Design Size Nominal Capacity of {:.2R} [W]", NomCapDes));
407 2 : ShowContinueError(state, "This may, or may not, indicate mismatched component sizes.");
408 3 : ShowContinueError(state, "Verify that the value entered is intended and is consistent with other components.");
409 : }
410 : }
411 : }
412 : }
413 : }
414 : }
415 : } else {
416 965 : if (this->NomCapWasAutoSized && state.dataPlnt->PlantFirstSizesOkayToFinalize) {
417 0 : ShowSevereError(state, format("Autosizing of {} nominal capacity requires a loop Sizing:Plant object", typeName));
418 0 : ShowContinueError(state, format("Occurs in {} object={}", typeName, this->Name));
419 0 : ErrorsFound = true;
420 : }
421 965 : if (!this->NomCapWasAutoSized && this->NomCap > 0.0 && state.dataPlnt->PlantFinalSizesOkayToReport) {
422 193 : BaseSizer::reportSizerOutput(state, typeName, this->Name, "User-Specified Nominal Capacity [W]", this->NomCap);
423 : }
424 : }
425 1392 : if (ErrorsFound) {
426 0 : ShowFatalError(state, "Preceding sizing errors cause program termination");
427 : }
428 1392 : }
429 :
430 7034957 : void OutsideEnergySourceSpecs::calculate(EnergyPlusData &state, bool runFlag, Real64 MyLoad)
431 : {
432 : // SUBROUTINE INFORMATION:
433 : // AUTHOR Dan Fisher
434 : // DATE WRITTEN July 1998
435 : // MODIFIED May 2010; Edwin Lee; Linda Lawrie (consolidation)
436 : // June 2021, Dareum Nam, Add DistrictHeatingSteam
437 : // RE-ENGINEERED Sept 2010, Brent Griffith, plant rewrite
438 :
439 : // SUBROUTINE PARAMETER DEFINITIONS:
440 : static constexpr std::string_view RoutineName("SimDistrictEnergy");
441 :
442 7034957 : auto &loop = state.dataPlnt->PlantLoop(this->plantLoc.loopNum);
443 :
444 : // set inlet and outlet nodes
445 7034957 : int const LoopNum = this->plantLoc.loopNum;
446 7034957 : Real64 const LoopMinTemp = state.dataPlnt->PlantLoop(LoopNum).MinTemp;
447 7034957 : Real64 const LoopMaxTemp = state.dataPlnt->PlantLoop(LoopNum).MaxTemp;
448 7034957 : Real64 const LoopMinMdot = state.dataPlnt->PlantLoop(LoopNum).MinMassFlowRate;
449 7034957 : Real64 const LoopMaxMdot = state.dataPlnt->PlantLoop(LoopNum).MaxMassFlowRate;
450 :
451 : // apply power limit from input
452 7034957 : Real64 CapFraction = this->capFractionSched->getCurrentVal();
453 7034957 : CapFraction = max(0.0, CapFraction); // ensure non negative
454 7034957 : Real64 const CurrentCap = this->NomCap * CapFraction;
455 7034957 : if (std::abs(MyLoad) > CurrentCap) {
456 40 : MyLoad = sign(CurrentCap, MyLoad);
457 : }
458 :
459 7034957 : if (this->EnergyType == DataPlant::PlantEquipmentType::PurchChilledWater) {
460 3260310 : if (MyLoad > 0.0) {
461 10 : MyLoad = 0.0;
462 : }
463 3774647 : } else if (this->EnergyType == DataPlant::PlantEquipmentType::PurchHotWater || this->EnergyType == DataPlant::PlantEquipmentType::PurchSteam) {
464 3774647 : if (MyLoad < 0.0) {
465 0 : MyLoad = 0.0;
466 : }
467 : }
468 :
469 : // determine outlet temp based on inlet temp, cp, and MyLoad
470 7034957 : if ((this->MassFlowRate > 0.0) && runFlag) {
471 1691712 : if (this->EnergyType == DataPlant::PlantEquipmentType::PurchChilledWater ||
472 1375112 : this->EnergyType == DataPlant::PlantEquipmentType::PurchHotWater) {
473 1686248 : Real64 const Cp = state.dataPlnt->PlantLoop(LoopNum).glycol->getSpecificHeat(state, this->InletTemp, RoutineName);
474 1686248 : this->OutletTemp = (MyLoad + this->MassFlowRate * Cp * this->InletTemp) / (this->MassFlowRate * Cp);
475 : // apply loop limits on temperature result to keep in check
476 1686248 : if (this->OutletTemp < LoopMinTemp) {
477 0 : this->OutletTemp = max(this->OutletTemp, LoopMinTemp);
478 0 : MyLoad = this->MassFlowRate * Cp * (this->OutletTemp - this->InletTemp);
479 : }
480 1686248 : if (this->OutletTemp > LoopMaxTemp) {
481 0 : this->OutletTemp = min(this->OutletTemp, LoopMaxTemp);
482 0 : MyLoad = this->MassFlowRate * Cp * (this->OutletTemp - this->InletTemp);
483 : }
484 1691712 : } else if (this->EnergyType == DataPlant::PlantEquipmentType::PurchSteam) { // determine mass flow rate based on inlet temp, saturate temp at
485 : // atmospheric pressure, Cp of inlet condensate, and MyLoad
486 5464 : Real64 SatTempAtmPress = loop.steam->getSatTemperature(state, DataEnvironment::StdPressureSeaLevel, RoutineName);
487 5464 : Real64 CpCondensate = loop.glycol->getSpecificHeat(state, this->InletTemp, RoutineName);
488 5464 : Real64 deltaTsensible = SatTempAtmPress - this->InletTemp;
489 5464 : Real64 EnthSteamInDry = loop.steam->getSatEnthalpy(state, this->InletTemp, 1.0, RoutineName);
490 5464 : Real64 EnthSteamOutWet = loop.steam->getSatEnthalpy(state, this->InletTemp, 0.0, RoutineName);
491 5464 : Real64 LatentHeatSteam = EnthSteamInDry - EnthSteamOutWet;
492 5464 : this->MassFlowRate = MyLoad / (LatentHeatSteam + (CpCondensate * deltaTsensible));
493 5464 : PlantUtilities::SetComponentFlowRate(state, this->MassFlowRate, this->InletNodeNum, this->OutletNodeNum, this->plantLoc);
494 : // Like the assumption in Boiler:Steam, assume that it can meet the steam loop setpoint
495 5464 : this->OutletTemp = state.dataLoopNodes->Node(loop.TempSetPointNodeNum).TempSetPoint;
496 5464 : this->OutletSteamQuality = 0.0;
497 : // apply loop limits on mass flow rate result to keep in check
498 5464 : if (this->MassFlowRate < LoopMinMdot) {
499 0 : this->MassFlowRate = max(this->MassFlowRate, LoopMinMdot);
500 0 : PlantUtilities::SetComponentFlowRate(state, this->MassFlowRate, this->InletNodeNum, this->OutletNodeNum, this->plantLoc);
501 0 : MyLoad = this->MassFlowRate * LatentHeatSteam;
502 : }
503 5464 : if (this->MassFlowRate > LoopMaxMdot) {
504 0 : this->MassFlowRate = min(this->MassFlowRate, LoopMaxMdot);
505 0 : PlantUtilities::SetComponentFlowRate(state, this->MassFlowRate, this->InletNodeNum, this->OutletNodeNum, this->plantLoc);
506 0 : MyLoad = this->MassFlowRate * LatentHeatSteam;
507 : }
508 : // Like the assumption in Boiler:Steam, assume that saturated steam is leaving the district heating steam plant
509 5464 : state.dataLoopNodes->Node(this->OutletNodeNum).Quality = 1.0;
510 : }
511 1691712 : } else {
512 5343245 : this->OutletTemp = this->InletTemp;
513 5343245 : MyLoad = 0.0;
514 : }
515 7034957 : int const OutletNode = this->OutletNodeNum;
516 7034957 : state.dataLoopNodes->Node(OutletNode).Temp = this->OutletTemp;
517 7034957 : this->EnergyRate = std::abs(MyLoad);
518 7034957 : this->EnergyTransfer = this->EnergyRate * state.dataHVACGlobal->TimeStepSysSec;
519 7034957 : }
520 :
521 278 : void OutsideEnergySourceSpecs::oneTimeInit_new(EnergyPlusData &state)
522 : {
523 : // Locate the unit on the plant loops for later usage
524 278 : bool errFlag = false;
525 278 : PlantUtilities::ScanPlantLoopsForObject(state, this->Name, this->EnergyType, this->plantLoc, errFlag, _, _, _, _, _);
526 278 : if (errFlag) {
527 0 : ShowFatalError(state, "InitSimVars: Program terminated due to previous condition(s).");
528 : }
529 :
530 278 : auto &loop = state.dataPlnt->PlantLoop(this->plantLoc.loopNum);
531 : // set limits on outlet node temps to plant loop limits
532 278 : DataPlant::CompData::getPlantComponent(state, this->plantLoc).MinOutletTemp = loop.MinTemp;
533 278 : DataPlant::CompData::getPlantComponent(state, this->plantLoc).MaxOutletTemp = loop.MaxTemp;
534 : // Register design flow rate for inlet node (helps to autosize comp setpoint op scheme flows
535 278 : PlantUtilities::RegisterPlantCompDesignFlow(state, this->InletNodeNum, loop.MaxVolFlowRate);
536 :
537 278 : std::string reportVarPrefix = "District Heating Water ";
538 278 : OutputProcessor::EndUseCat heatingOrCooling = OutputProcessor::EndUseCat::Heating;
539 278 : Constant::eResource meterTypeKey = Constant::eResource::DistrictHeatingWater;
540 :
541 278 : if (this->EnergyType == DataPlant::PlantEquipmentType::PurchChilledWater) {
542 133 : reportVarPrefix = "District Cooling Water ";
543 133 : heatingOrCooling = OutputProcessor::EndUseCat::Cooling;
544 133 : meterTypeKey = Constant::eResource::DistrictCooling;
545 145 : } else if (this->EnergyType == DataPlant::PlantEquipmentType::PurchSteam) {
546 1 : reportVarPrefix = "District Heating Steam ";
547 1 : heatingOrCooling = OutputProcessor::EndUseCat::Heating;
548 1 : meterTypeKey = Constant::eResource::DistrictHeatingSteam;
549 : }
550 834 : SetupOutputVariable(state,
551 556 : format("{}Energy", reportVarPrefix),
552 : Constant::Units::J,
553 278 : this->EnergyTransfer,
554 : OutputProcessor::TimeStepType::System,
555 : OutputProcessor::StoreType::Sum,
556 278 : this->Name,
557 : meterTypeKey,
558 : OutputProcessor::Group::Plant,
559 : heatingOrCooling);
560 834 : SetupOutputVariable(state,
561 556 : format("{}Rate", reportVarPrefix),
562 : Constant::Units::W,
563 278 : this->EnergyRate,
564 : OutputProcessor::TimeStepType::System,
565 : OutputProcessor::StoreType::Average,
566 278 : this->Name);
567 834 : SetupOutputVariable(state,
568 556 : format("{}Inlet Temperature", reportVarPrefix),
569 : Constant::Units::C,
570 278 : this->InletTemp,
571 : OutputProcessor::TimeStepType::System,
572 : OutputProcessor::StoreType::Average,
573 278 : this->Name);
574 834 : SetupOutputVariable(state,
575 556 : format("{}Outlet Temperature", reportVarPrefix),
576 : Constant::Units::C,
577 278 : this->OutletTemp,
578 : OutputProcessor::TimeStepType::System,
579 : OutputProcessor::StoreType::Average,
580 278 : this->Name);
581 834 : SetupOutputVariable(state,
582 556 : format("{}Mass Flow Rate", reportVarPrefix),
583 : Constant::Units::kg_s,
584 278 : this->MassFlowRate,
585 : OutputProcessor::TimeStepType::System,
586 : OutputProcessor::StoreType::Average,
587 278 : this->Name);
588 278 : }
589 :
590 0 : void OutsideEnergySourceSpecs::oneTimeInit([[maybe_unused]] EnergyPlusData &state)
591 : {
592 0 : }
593 :
594 : } // namespace EnergyPlus::OutsideEnergySources
|