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