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 <cassert>
50 : #include <cmath>
51 :
52 : // ObjexxFCL Headers
53 : #include <ObjexxFCL/Fmath.hh>
54 :
55 : // EnergyPlus Headers
56 : #include <EnergyPlus/BranchNodeConnections.hh>
57 : #include <EnergyPlus/CurveManager.hh>
58 : #include <EnergyPlus/Data/EnergyPlusData.hh>
59 : #include <EnergyPlus/DataBranchAirLoopPlant.hh>
60 : #include <EnergyPlus/DataHVACGlobals.hh>
61 : #include <EnergyPlus/DataIPShortCuts.hh>
62 : #include <EnergyPlus/DataLoopNode.hh>
63 : #include <EnergyPlus/FluidProperties.hh>
64 : #include <EnergyPlus/General.hh>
65 : #include <EnergyPlus/IceThermalStorage.hh>
66 : #include <EnergyPlus/InputProcessing/InputProcessor.hh>
67 : #include <EnergyPlus/NodeInputManager.hh>
68 : #include <EnergyPlus/OutputProcessor.hh>
69 : #include <EnergyPlus/Plant/DataPlant.hh>
70 : #include <EnergyPlus/Plant/PlantLocation.hh>
71 : #include <EnergyPlus/PlantUtilities.hh>
72 : #include <EnergyPlus/Psychrometrics.hh>
73 : #include <EnergyPlus/ScheduleManager.hh>
74 : #include <EnergyPlus/UtilityRoutines.hh>
75 :
76 : namespace EnergyPlus {
77 :
78 : namespace IceThermalStorage {
79 :
80 : // MODULE INFORMATION:
81 : // AUTHOR Pyeongchan Ihm
82 : // DATE WRITTEN April 2002
83 : // MODIFIED Modified Refined model, added Simple model, by Guo Zhou, Oct 2002
84 : // Remove chiller, make just a storage tank, Michael J. Witte, Sep 2005
85 : // Added detailed ice storage model, Rick Strand, Feb 2006
86 : // B. Griffith, Sept 2010, plant upgrades, fluid properties
87 : // Enhancements to detailed ice storage model, Rick Strand, Aug 2012
88 : // RE-ENGINEERED na
89 :
90 : // PURPOSE OF THIS MODULE:
91 : // This module simulates the performance of Ice Thermal Storage
92 :
93 : // METHODOLOGY EMPLOYED:
94 : // Once the PlantLoopManager determines that the Ice Thermal Storage
95 : // is available to meet a loop cooling demand, it calls SimIceStorage
96 : // which in turn calls the appropriate Ice Thermal Storage model.
97 :
98 : // REFERENCES: Dion J. King, ASHRAE Transactions v104, pt1, 1998.
99 :
100 : std::string const cIceStorageSimple("ThermalStorage:Ice:Simple");
101 : std::string const cIceStorageDetailed("ThermalStorage:Ice:Detailed");
102 :
103 : // ITS parameter
104 : constexpr Real64 FreezTemp(0.0); // Water freezing Temperature, 0[C]
105 : constexpr Real64 FreezTempIP(32.0); // Water freezing Temperature, 32[F]
106 : constexpr Real64 TimeInterval(3600.0); // Time Interval (1 hr) [s]
107 :
108 : // Conversion parameter
109 : constexpr Real64 EpsLimitForX(0.0); // 0.02 ! See Dion's code as eps1
110 : constexpr Real64 EpsLimitForDisCharge(0.0); // 0.20 ! See Dion's code as eps2
111 : constexpr Real64 EpsLimitForCharge(0.0); // 0.20 ! See Dion's code as eps3
112 :
113 : // Parameter used by the Detailed Ice Storage Model
114 : constexpr Real64 DeltaTofMin(0.5); // Minimum allowed outlet side temperature difference [C]
115 : // This is (Tout - Tfreezing)
116 : constexpr Real64 DeltaTifMin(1.0); // Minimum allowed inlet side temperature difference [C]
117 : // This is (Tin - Tfreezing)
118 :
119 0 : PlantComponent *SimpleIceStorageData::factory(EnergyPlusData &state, std::string const &objectName)
120 : {
121 : // Process the input data for boilers if it hasn't been done already
122 0 : if (state.dataIceThermalStorage->getITSInput) {
123 0 : GetIceStorageInput(state);
124 0 : state.dataIceThermalStorage->getITSInput = false;
125 : }
126 :
127 : // Now look for this particular pipe in the list
128 0 : for (auto &ITS : state.dataIceThermalStorage->SimpleIceStorage) {
129 0 : if (ITS.Name == objectName) {
130 0 : return &ITS;
131 : }
132 : }
133 : // If we didn't find it, fatal
134 0 : ShowFatalError(state,
135 : format("LocalSimpleIceStorageFactory: Error getting inputs for simple ice storage named: {}", objectName)); // LCOV_EXCL_LINE
136 : // Shut up the compiler
137 : return nullptr; // LCOV_EXCL_LINE
138 : }
139 :
140 0 : PlantComponent *DetailedIceStorageData::factory(EnergyPlusData &state, std::string const &objectName)
141 : {
142 : // Process the input data for boilers if it hasn't been done already
143 0 : if (state.dataIceThermalStorage->getITSInput) {
144 0 : GetIceStorageInput(state);
145 0 : state.dataIceThermalStorage->getITSInput = false;
146 : }
147 :
148 : // Now look for this particular pipe in the list
149 0 : for (auto &ITS : state.dataIceThermalStorage->DetailedIceStorage) {
150 0 : if (ITS.Name == objectName) {
151 0 : return &ITS;
152 : }
153 : }
154 : // If we didn't find it, fatal
155 0 : ShowFatalError(
156 : state, format("LocalDetailedIceStorageFactory: Error getting inputs for detailed ice storage named: {}", objectName)); // LCOV_EXCL_LINE
157 : // Shut up the compiler
158 : return nullptr; // LCOV_EXCL_LINE
159 : }
160 :
161 0 : void SimpleIceStorageData::simulate(EnergyPlusData &state,
162 : const PlantLocation &calledFromLocation,
163 : [[maybe_unused]] bool FirstHVACIteration,
164 : [[maybe_unused]] Real64 &CurLoad,
165 : bool RunFlag)
166 : {
167 : static constexpr std::string_view RoutineName("SimpleIceStorageData::simulate");
168 :
169 : // this was happening in PlantLoopEquip before
170 0 : auto &thisComp(state.dataPlnt->PlantLoop(calledFromLocation.loopNum)
171 0 : .LoopSide(calledFromLocation.loopSideNum)
172 0 : .Branch(calledFromLocation.branchNum)
173 0 : .Comp(calledFromLocation.compNum));
174 :
175 : // If component setpoint based control is active for this equipment
176 : // then reset CurLoad to original EquipDemand.
177 : // Allow negative CurLoad. For cold storage this means the storage should
178 : // charge, for hot storage, this means the storage should discharge.
179 0 : if (thisComp.CurOpSchemeType == DataPlant::OpScheme::CompSetPtBased) {
180 0 : Real64 localCurLoad = thisComp.EquipDemand;
181 0 : if (localCurLoad != 0) RunFlag = true;
182 : }
183 :
184 0 : if (state.dataGlobal->BeginEnvrnFlag && this->MyEnvrnFlag) {
185 0 : this->ResetXForITSFlag = true;
186 0 : this->MyEnvrnFlag = false;
187 : }
188 :
189 0 : if (!state.dataGlobal->BeginEnvrnFlag) {
190 0 : this->MyEnvrnFlag = true;
191 : }
192 :
193 0 : this->oneTimeInit(state);
194 :
195 : //------------------------------------------------------------------------
196 : // FIRST PROCESS (MyLoad = 0.0 as IN)
197 : // At this moment as first calling of ITS, ITS provide ONLY MaxCap/OptCap/MinCap.
198 : //------------------------------------------------------------------------
199 : // First process is in subroutine CalcIceStorageCapacity(MaxCap,MinCap,OptCap) shown bellow.
200 :
201 : //------------------------------------------------------------------------
202 : // SECOND PROCESS (MyLoad is provided by E+ based on MaxCap/OptCap/MinCap)
203 : //------------------------------------------------------------------------
204 : // Below routines are starting when second calling.
205 : // After previous return, MyLoad is calculated based on MaxCap, OptCap, and MinCap.
206 : // Then PlandSupplySideManager provides MyLoad to simulate Ice Thermal Storage.
207 : // The process will be decided based on sign(+,-,0) of input U.
208 :
209 : // MJW 19 Sep 2005 - New approach - calculate MyLoad locally from inlet node temp
210 : // and outlet node setpoint until MyLoad that is passed in behaves well
211 :
212 0 : Real64 TempSetPt(0.0);
213 0 : Real64 TempIn = state.dataLoopNodes->Node(this->PltInletNodeNum).Temp;
214 0 : switch (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).LoopDemandCalcScheme) {
215 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
216 0 : TempSetPt = state.dataLoopNodes->Node(this->PltOutletNodeNum).TempSetPoint;
217 0 : } break;
218 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
219 0 : TempSetPt = state.dataLoopNodes->Node(this->PltOutletNodeNum).TempSetPointHi;
220 0 : } break;
221 0 : default: {
222 0 : assert(false);
223 : } break;
224 : }
225 0 : Real64 DemandMdot = this->DesignMassFlowRate;
226 :
227 0 : Real64 Cp = state.dataPlnt->PlantLoop(this->plantLoc.loopNum).glycol->getSpecificHeat(state, TempIn, RoutineName);
228 :
229 0 : Real64 MyLoad2 = (DemandMdot * Cp * (TempIn - TempSetPt));
230 0 : MyLoad = MyLoad2;
231 :
232 : // Set fraction of ice remaining in storage
233 0 : this->XCurIceFrac = this->IceFracRemain;
234 :
235 : //***** Dormant Process for ITS *****************************************
236 : //************************************************************************
237 : // IF( U .EQ. 0.0 ) THEN
238 0 : if ((MyLoad2 == 0.0) || (DemandMdot == 0.0)) {
239 0 : this->CalcIceStorageDormant(state);
240 :
241 : //***** Charging Process for ITS *****************************************
242 : //************************************************************************
243 : // ELSE IF( U .GT. 0.0 ) THEN
244 0 : } else if (MyLoad2 < 0.0) {
245 :
246 : Real64 MaxCap;
247 : Real64 MinCap;
248 : Real64 OptCap;
249 0 : this->CalcIceStorageCapacity(state, MaxCap, MinCap, OptCap);
250 0 : this->CalcIceStorageCharge(state);
251 :
252 : //***** Discharging Process for ITS *****************************************
253 : //************************************************************************
254 : // ELSE IF( U .LT. 0.0 ) THEN
255 0 : } else if (MyLoad2 > 0.0) {
256 :
257 : Real64 MaxCap;
258 : Real64 MinCap;
259 : Real64 OptCap;
260 0 : this->CalcIceStorageCapacity(state, MaxCap, MinCap, OptCap);
261 0 : this->CalcIceStorageDischarge(state, MyLoad, RunFlag, MaxCap);
262 : } // Based on input of U value, deciding Dormant/Charge/Discharge process
263 :
264 : // Update Node properties: mdot and Temperature
265 0 : this->UpdateNode(state, MyLoad2, RunFlag);
266 :
267 : // Update report variables.
268 0 : this->RecordOutput(MyLoad2, RunFlag);
269 0 : }
270 :
271 0 : void DetailedIceStorageData::simulate(EnergyPlusData &state,
272 : [[maybe_unused]] const PlantLocation &calledFromLocation,
273 : [[maybe_unused]] bool FirstHVACIteration,
274 : [[maybe_unused]] Real64 &CurLoad,
275 : [[maybe_unused]] bool RunFlag)
276 : {
277 :
278 0 : if (state.dataGlobal->BeginEnvrnFlag && this->MyEnvrnFlag) {
279 0 : this->ResetXForITSFlag = true;
280 0 : this->MyEnvrnFlag = false;
281 : }
282 :
283 0 : if (!state.dataGlobal->BeginEnvrnFlag) {
284 0 : this->MyEnvrnFlag = true;
285 : }
286 :
287 0 : this->oneTimeInit(state); // Initialize detailed ice storage
288 :
289 0 : this->SimDetailedIceStorage(state); // Simulate detailed ice storage
290 :
291 0 : this->UpdateDetailedIceStorage(state); // Update detailed ice storage
292 :
293 0 : this->ReportDetailedIceStorage(state); // Report detailed ice storage
294 0 : }
295 :
296 0 : void DetailedIceStorageData::SimDetailedIceStorage(EnergyPlusData &state)
297 : {
298 :
299 : // SUBROUTINE INFORMATION:
300 : // AUTHOR Rick Strand
301 : // DATE WRITTEN February 2006
302 : // MODIFIED na
303 : // RE-ENGINEERED na
304 :
305 : // PURPOSE OF THIS SUBROUTINE:
306 : // This subroutine is the main simulation subroutine for the detailed
307 : // ice storage model.
308 :
309 : // METHODOLOGY EMPLOYED:
310 : // Based on whether the unit is dormant, in charging mode, or in discharging
311 : // mode, the code either passes the flow through the bypass, through the tank,
312 : // or both. This depends on the temperature relative to the setpoint temperature
313 : // and other features of the model. The model itself is a LMTD model that uses
314 : // performance curve fits that are quadratic in fraction charged/discharged and
315 : // linear in LMTD for the calculation of Q. The equations are actually non-
316 : // dimensionalized.
317 :
318 : // REFERENCES:
319 : // Ice Storage Component Model Proposal (Revised).doc by Rick Strand (Dec 2005/Jan 2006)
320 :
321 0 : int constexpr MaxIterNum(100); // Maximum number of internal iterations for ice storage solution
322 0 : Real64 constexpr SmallestLoad(0.1); // Smallest load to actually run the ice storage unit [Watts]
323 0 : Real64 constexpr TankDischargeToler(0.001); // Below this fraction, there is nothing left to discharge
324 0 : Real64 constexpr TankChargeToler(0.999); // Above this fraction, we don't have anything left to charge
325 0 : Real64 constexpr TemperatureToler(0.1); // Temperature difference between iterations that indicates convergence [C]
326 0 : Real64 constexpr SIEquiv100GPMinMassFlowRate(6.31); // Used to non-dimensionalize flow rate for use in CubicLinear charging equation
327 : // Flow rate divided by nominal 100GPM used to non-dimensionalize volume flow rate
328 : // Assumes approximate density of 1000 kg/m3 to get an estimate for mass flow rate
329 : static constexpr std::string_view RoutineName("DetailedIceStorageData::SimDetailedIceStorage");
330 :
331 0 : int NodeNumIn = this->PlantInNodeNum; // Plant loop inlet node number for component
332 0 : int NodeNumOut = this->PlantOutNodeNum; // Plant loop outlet node number for component
333 0 : Real64 TempIn = state.dataLoopNodes->Node(NodeNumIn).Temp; // Inlet temperature to component (from plant loop) [C]
334 0 : Real64 TempSetPt(0.0); // Setpoint temperature defined by loop controls [C]
335 0 : switch (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).LoopDemandCalcScheme) {
336 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
337 0 : TempSetPt = state.dataLoopNodes->Node(NodeNumOut).TempSetPoint;
338 0 : } break;
339 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
340 0 : TempSetPt = state.dataLoopNodes->Node(NodeNumOut).TempSetPointHi;
341 0 : } break;
342 0 : default: {
343 0 : assert(false);
344 : } break;
345 : }
346 :
347 0 : int IterNum = 0;
348 :
349 : // Set derived type variables
350 0 : this->InletTemp = TempIn;
351 0 : this->MassFlowRate = state.dataLoopNodes->Node(NodeNumIn).MassFlowRate;
352 :
353 : // if two-way common pipe and no mass flow and tank is not full, then use design flow rate
354 0 : if ((state.dataPlnt->PlantLoop(this->plantLoc.loopNum).CommonPipeType == DataPlant::CommonPipeType::TwoWay) &&
355 0 : (std::abs(this->MassFlowRate) < DataBranchAirLoopPlant::MassFlowTolerance) && (this->IceFracRemaining < TankChargeToler)) {
356 0 : this->MassFlowRate = this->DesignMassFlowRate;
357 : }
358 :
359 : // Calculate the current load on the ice storage unit
360 0 : Real64 Cp = state.dataPlnt->PlantLoop(this->plantLoc.loopNum).glycol->getSpecificHeat(state, TempIn, RoutineName);
361 :
362 : // Estimated load on the ice storage unit [W]
363 0 : Real64 LocalLoad = this->MassFlowRate * Cp * (TempIn - TempSetPt);
364 :
365 : // Determine what the status is regarding the ice storage unit and the loop level flow
366 0 : if ((std::abs(LocalLoad) <= SmallestLoad) || (this->availSched->getCurrentVal() <= 0)) {
367 : // No real load on the ice storage device or ice storage OFF--bypass all of the flow and leave the tank alone
368 0 : this->CompLoad = 0.0;
369 0 : this->OutletTemp = TempIn;
370 0 : this->TankOutletTemp = TempIn;
371 0 : Real64 mdot = 0.0;
372 0 : PlantUtilities::SetComponentFlowRate(state, mdot, this->PlantInNodeNum, this->PlantOutNodeNum, this->plantLoc);
373 :
374 0 : this->BypassMassFlowRate = mdot;
375 0 : this->TankMassFlowRate = 0.0;
376 0 : this->MassFlowRate = mdot;
377 :
378 0 : } else if (LocalLoad < 0.0) {
379 : // The load is less than zero so we should be charging
380 : // Before we do anything, we should check to make sure that we will actually be charging the unit
381 :
382 0 : if ((TempIn > (this->FreezingTemp - DeltaTifMin)) || (this->IceFracRemaining >= TankChargeToler)) {
383 : // If the inlet temperature is not below the freezing temperature of the
384 : // device, then we cannot actually do any charging. Bypass all of the flow.
385 : // Also, if the tank is already sufficiently charged, we don't need to
386 : // do any further charging. So, bypass all of the flow.
387 0 : this->CompLoad = 0.0;
388 0 : this->OutletTemp = TempIn;
389 0 : this->TankOutletTemp = TempIn;
390 0 : Real64 mdot = 0.0;
391 0 : PlantUtilities::SetComponentFlowRate(state, mdot, this->PlantInNodeNum, this->PlantOutNodeNum, this->plantLoc);
392 :
393 0 : this->BypassMassFlowRate = mdot;
394 0 : this->TankMassFlowRate = 0.0;
395 0 : this->MassFlowRate = mdot;
396 :
397 0 : } else {
398 : // make flow request so tank will get flow
399 0 : Real64 mdot = this->DesignMassFlowRate;
400 0 : PlantUtilities::SetComponentFlowRate(state, mdot, this->PlantInNodeNum, this->PlantOutNodeNum, this->plantLoc);
401 :
402 : // We are in charging mode, the temperatures are low enough to charge
403 : // the tank, and we have some charging left to do.
404 : // Make first guess at Qstar based on the current ice fraction remaining
405 : // and LMTDstar that is based on the freezing or TempSetPt temperature.
406 0 : if (TempSetPt > (this->FreezingTemp - DeltaTofMin)) {
407 : // Outlet temperature cannot be above the freezing temperature so set
408 : // the outlet temperature to the freezing temperature and calculate
409 : // LMTDstar based on that assumption.
410 0 : TempSetPt = this->FreezingTemp - DeltaTofMin;
411 : }
412 :
413 : // Tank outlet temperature from the last iteration [C]
414 0 : Real64 ToutOld = TempSetPt;
415 : // Non-dimensional log mean temperature difference of ice storage unit [non-dimensional]
416 0 : Real64 LMTDstar = CalcDetIceStorLMTDstar(TempIn, ToutOld, this->FreezingTemp);
417 0 : Real64 MassFlowstar = this->MassFlowRate / SIEquiv100GPMinMassFlowRate;
418 :
419 : // Find initial guess at average fraction charged during time step
420 : // Fraction of tank to be charged in the current time step
421 0 : Real64 ChargeFrac = LocalLoad * state.dataHVACGlobal->TimeStepSys / this->NomCapacity;
422 0 : if ((this->IceFracRemaining + ChargeFrac) > 1.0) {
423 0 : ChargeFrac = 1.0 - this->IceFracRemaining;
424 : }
425 :
426 : Real64 AvgFracCharged; // Average fraction charged for the current time step
427 0 : if (this->ThawProcessIndex == DetIce::InsideMelt) {
428 0 : AvgFracCharged = this->IceFracOnCoil + (ChargeFrac / 2.0);
429 : } else { // (DetailedIceStorage(IceNum)%ThawProcessIndex == DetIce::OutsideMelt)
430 0 : AvgFracCharged = this->IceFracRemaining + (ChargeFrac / 2.0);
431 : }
432 :
433 : // Current load on the ice storage unit [non-dimensional]
434 0 : Real64 Qstar = std::abs(CalcQstar(state, this->ChargeCurveNum, this->ChargeCurveTypeNum, AvgFracCharged, LMTDstar, MassFlowstar));
435 :
436 : // Actual load on the ice storage unit [W]
437 0 : Real64 ActualLoad = Qstar * this->NomCapacity / this->CurveFitTimeStep;
438 :
439 : // Updated outlet temperature from the tank [C]
440 0 : Real64 ToutNew = TempIn + (ActualLoad / (this->MassFlowRate * Cp));
441 : // Again, the outlet temperature cannot be above the freezing temperature (factoring in the tolerance)
442 0 : if (ToutNew > (this->FreezingTemp - DeltaTofMin)) ToutNew = this->FreezingTemp - DeltaTofMin;
443 :
444 0 : if (ActualLoad > std::abs(LocalLoad)) {
445 : // We have more than enough capacity to meet the load so no need to iterate to find a solution
446 0 : this->OutletTemp = TempSetPt;
447 0 : this->TankOutletTemp = ToutNew;
448 0 : this->CompLoad = this->MassFlowRate * Cp * std::abs(TempIn - TempSetPt);
449 0 : this->TankMassFlowRate = this->CompLoad / Cp / std::abs(TempIn - ToutNew);
450 0 : this->BypassMassFlowRate = this->MassFlowRate - this->TankMassFlowRate;
451 :
452 : } else {
453 :
454 0 : while (IterNum < MaxIterNum) {
455 0 : if (std::abs(ToutOld - ToutNew) > TemperatureToler) {
456 : // Not converged yet so recalculated what is needed and keep iterating
457 : // Calculate new values for LMTDstar and Qstar based on updated outlet temperature
458 0 : ToutOld = ToutNew;
459 0 : LMTDstar = CalcDetIceStorLMTDstar(TempIn, ToutOld, this->FreezingTemp);
460 0 : MassFlowstar = this->MassFlowRate / SIEquiv100GPMinMassFlowRate;
461 : Qstar =
462 0 : std::abs(CalcQstar(state, this->ChargeCurveNum, this->ChargeCurveTypeNum, AvgFracCharged, LMTDstar, MassFlowstar));
463 :
464 : // Now make sure that we don't go above 100% charged and calculate the new average fraction
465 0 : ChargeFrac = Qstar * (state.dataHVACGlobal->TimeStepSys / this->CurveFitTimeStep);
466 0 : if ((this->IceFracRemaining + ChargeFrac) > 1.0) {
467 0 : ChargeFrac = 1.0 - this->IceFracRemaining;
468 0 : Qstar = ChargeFrac;
469 : }
470 0 : if (this->ThawProcessIndex == DetIce::InsideMelt) {
471 0 : AvgFracCharged = this->IceFracOnCoil + (ChargeFrac / 2.0);
472 : } else { // (DetailedIceStorage(IceNum)%ThawProcessIndex == DetIce::OutsideMelt)
473 0 : AvgFracCharged = this->IceFracRemaining + (ChargeFrac / 2.0);
474 : }
475 :
476 : // Finally, update the actual load and calculate the new outlet temperature; increment iteration counter
477 0 : ActualLoad = Qstar * this->NomCapacity / this->CurveFitTimeStep;
478 0 : ToutNew = TempIn + (ActualLoad / (this->MassFlowRate * Cp));
479 : // Again, the outlet temperature cannot be above the freezing temperature (factoring in the tolerance)
480 0 : if (ToutNew < (this->FreezingTemp - DeltaTofMin)) ToutNew = this->FreezingTemp - DeltaTofMin;
481 0 : ++IterNum;
482 :
483 : } else {
484 : // Converged to acceptable tolerance so set output variables and exit DO WHILE loop
485 0 : break;
486 : }
487 :
488 : } // ...loop iterating for the ice storage outlet temperature
489 :
490 : // Keep track of times that the iterations got excessive and report if necessary
491 0 : if (IterNum >= MaxIterNum) {
492 0 : ++this->ChargeIterErrors;
493 0 : if (this->ChargeIterErrors <= 25) {
494 0 : ShowWarningError(state, "Detailed Ice Storage model exceeded its internal charging maximum iteration limit");
495 0 : ShowContinueError(state, format("Detailed Ice Storage System Name = {}", this->Name));
496 0 : ShowContinueErrorTimeStamp(state, "");
497 : } else {
498 0 : ShowRecurringWarningErrorAtEnd(state,
499 0 : "Detailed Ice Storage system [" + this->Name +
500 : "] charging maximum iteration limit exceeded occurrence continues.",
501 0 : this->ChargeErrorCount);
502 : }
503 : }
504 :
505 : // Set the values for the key outlet parameters
506 : // Note that in REAL(r64)ity the tank will probably bypass some flow when it
507 : // gets close to full charge. This is a simplification that assumes
508 : // all flow through the tank during charging and a lower delta T near
509 : // the full charge level. From an energy perspective, this is a reasonable
510 : // approximation.
511 0 : this->OutletTemp = ToutNew;
512 0 : this->TankOutletTemp = ToutNew;
513 0 : this->BypassMassFlowRate = 0.0;
514 0 : this->TankMassFlowRate = this->MassFlowRate;
515 0 : this->CompLoad = this->MassFlowRate * Cp * std::abs(TempIn - ToutNew);
516 : }
517 : }
518 :
519 0 : } else if (LocalLoad > 0.0) {
520 : // The load is greater than zero so we should be discharging
521 : // Before we do anything, we should check to make sure that we will actually be discharging the unit
522 :
523 0 : if ((this->InletTemp < (this->FreezingTemp + DeltaTifMin)) || (this->IceFracRemaining <= TankDischargeToler)) {
524 : // If the inlet temperature is below the freezing temperature of the
525 : // device, then we cannot actually do any discharging. Bypass all of the flow.
526 : // Also, if the tank is already discharged, we can't to do any further
527 : // discharging. So, bypass all of the flow.
528 0 : this->CompLoad = 0.0;
529 0 : this->OutletTemp = this->InletTemp;
530 0 : this->TankOutletTemp = this->InletTemp;
531 0 : Real64 mdot = 0.0;
532 0 : PlantUtilities::SetComponentFlowRate(state, mdot, this->PlantInNodeNum, this->PlantOutNodeNum, this->plantLoc);
533 :
534 0 : this->BypassMassFlowRate = mdot;
535 0 : this->TankMassFlowRate = 0.0;
536 0 : this->MassFlowRate = mdot;
537 :
538 0 : } else {
539 :
540 : // make flow request so tank will get flow
541 0 : Real64 mdot = this->DesignMassFlowRate;
542 0 : PlantUtilities::SetComponentFlowRate(state, mdot, this->PlantInNodeNum, this->PlantOutNodeNum, this->plantLoc);
543 :
544 : // We are in discharging mode, the temperatures are high enough to discharge
545 : // the tank, and we have some discharging left to do.
546 0 : if (TempSetPt < (this->FreezingTemp + DeltaTofMin)) {
547 : // Outlet temperature cannot be below the freezing temperature so set
548 : // the outlet temperature to the freezing temperature and calculate
549 : // LMTDstar based on that assumption.
550 0 : TempSetPt = this->FreezingTemp + DeltaTofMin;
551 : }
552 :
553 : // Tank outlet temperature from the last iteration [C]
554 0 : Real64 ToutOld = TempSetPt;
555 : // Non-dimensional log mean temperature difference of ice storage unit [non-dimensional]
556 0 : Real64 LMTDstar = CalcDetIceStorLMTDstar(TempIn, ToutOld, this->FreezingTemp);
557 0 : Real64 MassFlowstar = this->MassFlowRate / SIEquiv100GPMinMassFlowRate;
558 :
559 : // Find initial guess at average fraction charged during time step
560 0 : Real64 ChargeFrac = LocalLoad * state.dataHVACGlobal->TimeStepSys / this->NomCapacity;
561 0 : if ((this->IceFracRemaining - ChargeFrac) < 0.0) ChargeFrac = this->IceFracRemaining;
562 0 : Real64 AvgFracCharged = this->IceFracRemaining - (ChargeFrac / 2.0);
563 :
564 : // Current load on the ice storage unit [non-dimensional]
565 : Real64 Qstar =
566 0 : std::abs(CalcQstar(state, this->DischargeCurveNum, this->DischargeCurveTypeNum, AvgFracCharged, LMTDstar, MassFlowstar));
567 :
568 : // Actual load on the ice storage unit [W]
569 0 : Real64 ActualLoad = Qstar * this->NomCapacity / this->CurveFitTimeStep;
570 :
571 : // Updated outlet temperature from the tank [C]
572 0 : Real64 ToutNew = TempIn - (ActualLoad / (this->MassFlowRate * Cp));
573 : // Again, the outlet temperature cannot be below the freezing temperature (factoring in the tolerance)
574 0 : if (ToutNew < (this->FreezingTemp + DeltaTofMin)) ToutNew = this->FreezingTemp + DeltaTofMin;
575 :
576 0 : if (ActualLoad > LocalLoad) {
577 : // We have more than enough storage to meet the load so no need to iterate to find a solution
578 0 : this->OutletTemp = TempSetPt;
579 0 : this->TankOutletTemp = ToutNew;
580 0 : this->CompLoad = this->MassFlowRate * Cp * std::abs(TempIn - TempSetPt);
581 0 : this->TankMassFlowRate = this->CompLoad / Cp / std::abs(TempIn - ToutNew);
582 0 : this->BypassMassFlowRate = this->MassFlowRate - this->TankMassFlowRate;
583 :
584 : } else {
585 :
586 0 : while (IterNum < MaxIterNum) {
587 0 : if (std::abs(ToutOld - ToutNew) > TemperatureToler) {
588 : // Not converged yet so recalculated what is needed and keep iterating
589 : // Calculate new values for LMTDstar and Qstar based on updated outlet temperature
590 0 : ToutOld = ToutNew;
591 0 : LMTDstar = CalcDetIceStorLMTDstar(TempIn, ToutOld, this->FreezingTemp);
592 :
593 0 : Qstar = std::abs(
594 : CalcQstar(state, this->DischargeCurveNum, this->DischargeCurveTypeNum, AvgFracCharged, LMTDstar, MassFlowstar));
595 :
596 : // Now make sure that we don't go below 100% discharged and calculate the new average fraction
597 0 : ChargeFrac = Qstar * (state.dataHVACGlobal->TimeStepSys / this->CurveFitTimeStep);
598 0 : if ((this->IceFracRemaining - ChargeFrac) < 0.0) {
599 0 : ChargeFrac = this->IceFracRemaining;
600 0 : Qstar = ChargeFrac;
601 : }
602 0 : AvgFracCharged = this->IceFracRemaining - (ChargeFrac / 2.0);
603 :
604 : // Finally, update the actual load and calculate the new outlet temperature; increment iteration counter
605 0 : ActualLoad = Qstar * this->NomCapacity / this->CurveFitTimeStep;
606 0 : ToutNew = TempIn - (ActualLoad / (this->MassFlowRate * Cp));
607 : // Again, the outlet temperature cannot be below the freezing temperature (factoring in the tolerance)
608 0 : if (ToutNew < (this->FreezingTemp + DeltaTofMin)) ToutNew = this->FreezingTemp + DeltaTofMin;
609 0 : ++IterNum;
610 :
611 : } else {
612 : // Converged to acceptable tolerance so set output variables and exit DO WHILE loop
613 0 : break;
614 : }
615 :
616 : } // ...loop iterating for the ice storage outlet temperature
617 :
618 : // Keep track of times that the iterations got excessive
619 0 : if (IterNum >= MaxIterNum && (!state.dataGlobal->WarmupFlag)) {
620 0 : ++this->DischargeIterErrors;
621 0 : if (this->DischargeIterErrors <= 25) {
622 0 : ShowWarningError(state, "Detailed Ice Storage model exceeded its internal discharging maximum iteration limit");
623 0 : ShowContinueError(state, format("Detailed Ice Storage System Name = {}", this->Name));
624 0 : ShowContinueErrorTimeStamp(state, "");
625 : } else {
626 0 : ShowRecurringWarningErrorAtEnd(state,
627 0 : "Detailed Ice Storage system [" + this->Name +
628 : "] discharging maximum iteration limit exceeded occurrence continues.",
629 0 : this->DischargeErrorCount);
630 : }
631 : }
632 :
633 : // We are now done finding the outlet temperature of the tank. We need
634 : // to compare the outlet temperature to the setpoint temperature again
635 : // to see where we are at and then we can set the values for the key
636 : // outlet parameters. If outlet temperature is greater than or equal
637 : // to the setpoint temperature, then send all flow through the tank.
638 : // Otherwise, we have more capacity than needed so let's bypass some
639 : // flow and meet the setpoint temperature.
640 0 : if (ToutNew >= TempSetPt) {
641 0 : this->OutletTemp = ToutNew;
642 0 : this->TankOutletTemp = ToutNew;
643 0 : this->BypassMassFlowRate = 0.0;
644 0 : this->TankMassFlowRate = this->MassFlowRate;
645 0 : this->CompLoad = this->MassFlowRate * Cp * std::abs(TempIn - ToutNew);
646 : } else {
647 0 : this->OutletTemp = TempSetPt;
648 0 : this->TankOutletTemp = ToutNew;
649 0 : this->CompLoad = this->MassFlowRate * Cp * std::abs(TempIn - TempSetPt);
650 0 : this->TankMassFlowRate = this->CompLoad / (Cp * std::abs(TempIn - ToutNew));
651 0 : this->BypassMassFlowRate = this->MassFlowRate - this->TankMassFlowRate;
652 : }
653 : }
654 : }
655 :
656 : } else { // Shouldn't get here ever (print error if we do)
657 :
658 0 : ShowFatalError(state, "Detailed Ice Storage systemic code error--contact EnergyPlus support");
659 : }
660 0 : }
661 :
662 0 : void GetIceStorageInput(EnergyPlusData &state)
663 : {
664 : // SUBROUTINE INFORMATION:
665 : // AUTHOR:
666 : // DATE WRITTEN:
667 :
668 : // PURPOSE OF THIS SUBROUTINE:!This routine will get the input
669 : // required by the PrimaryPlantLoopManager. As such
670 : // it will interact with the Input Scanner to retrieve
671 : // information from the input file, count the number of
672 : // heating and cooling loops and begin to fill the
673 : // arrays associated with the type PlantLoopProps.
674 :
675 : static constexpr std::string_view routineName = "GetIceStorageInput";
676 :
677 : bool ErrorsFound;
678 :
679 0 : ErrorsFound = false; // Always need to reset this since there are multiple types of ice storage systems
680 :
681 : // LOAD ARRAYS WITH SimpleIceStorage DATA
682 0 : state.dataIceThermalStorage->NumSimpleIceStorage =
683 0 : state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, cIceStorageSimple); // by ZG
684 0 : state.dataIceThermalStorage->NumDetailedIceStorage =
685 0 : state.dataInputProcessing->inputProcessor->getNumObjectsFound(state, cIceStorageDetailed);
686 :
687 : // Allocate SimpleIceStorage based on NumOfIceStorage
688 0 : state.dataIceThermalStorage->SimpleIceStorage.allocate(state.dataIceThermalStorage->NumSimpleIceStorage);
689 :
690 0 : state.dataIPShortCut->cCurrentModuleObject = cIceStorageSimple;
691 0 : for (int iceNum = 1; iceNum <= state.dataIceThermalStorage->NumSimpleIceStorage; ++iceNum) {
692 :
693 : int NumAlphas;
694 : int NumNums;
695 : int IOStat;
696 0 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
697 0 : state.dataIPShortCut->cCurrentModuleObject,
698 : iceNum,
699 0 : state.dataIPShortCut->cAlphaArgs,
700 : NumAlphas,
701 0 : state.dataIPShortCut->rNumericArgs,
702 : NumNums,
703 : IOStat,
704 : _,
705 : _,
706 : _,
707 0 : state.dataIPShortCut->cNumericFieldNames);
708 0 : Util::IsNameEmpty(state, state.dataIPShortCut->cAlphaArgs(1), state.dataIPShortCut->cCurrentModuleObject, ErrorsFound);
709 :
710 0 : ++state.dataIceThermalStorage->TotalNumIceStorage;
711 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).MapNum = state.dataIceThermalStorage->TotalNumIceStorage;
712 :
713 : // ITS name
714 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).Name = state.dataIPShortCut->cAlphaArgs(1);
715 :
716 : // Get Ice Thermal Storage Type
717 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSType = state.dataIPShortCut->cAlphaArgs(2);
718 0 : if (Util::SameString(state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSType, "IceOnCoilInternal")) {
719 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSType_Num = ITSType::IceOnCoilInternal;
720 0 : } else if (Util::SameString(state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSType, "IceOnCoilExternal")) {
721 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSType_Num = ITSType::IceOnCoilExternal;
722 : } else {
723 0 : ShowSevereError(state, format("{}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
724 0 : ShowContinueError(state, format("Invalid {}={}", state.dataIPShortCut->cAlphaFieldNames(2), state.dataIPShortCut->cAlphaArgs(2)));
725 0 : ErrorsFound = true;
726 : }
727 :
728 : // Get and Verify ITS nominal Capacity (user input is in GJ, internal value in in J)
729 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSNomCap = state.dataIPShortCut->rNumericArgs(1) * 1.e+09;
730 0 : if (state.dataIPShortCut->rNumericArgs(1) == 0.0) {
731 0 : ShowSevereError(state, format("{}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
732 0 : ShowContinueError(state,
733 0 : format("Invalid {}={:.2R}", state.dataIPShortCut->cNumericFieldNames(1), state.dataIPShortCut->rNumericArgs(1)));
734 0 : ErrorsFound = true;
735 : }
736 :
737 : // Get Plant Inlet Node Num
738 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).PltInletNodeNum =
739 0 : NodeInputManager::GetOnlySingleNode(state,
740 0 : state.dataIPShortCut->cAlphaArgs(3),
741 : ErrorsFound,
742 : DataLoopNode::ConnectionObjectType::ThermalStorageIceSimple,
743 0 : state.dataIPShortCut->cAlphaArgs(1),
744 : DataLoopNode::NodeFluidType::Water,
745 : DataLoopNode::ConnectionType::Inlet,
746 : NodeInputManager::CompFluidStream::Primary,
747 : DataLoopNode::ObjectIsNotParent);
748 :
749 : // Get Plant Outlet Node Num
750 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).PltOutletNodeNum =
751 0 : NodeInputManager::GetOnlySingleNode(state,
752 0 : state.dataIPShortCut->cAlphaArgs(4),
753 : ErrorsFound,
754 : DataLoopNode::ConnectionObjectType::ThermalStorageIceSimple,
755 0 : state.dataIPShortCut->cAlphaArgs(1),
756 : DataLoopNode::NodeFluidType::Water,
757 : DataLoopNode::ConnectionType::Outlet,
758 : NodeInputManager::CompFluidStream::Primary,
759 : DataLoopNode::ObjectIsNotParent);
760 :
761 : // Test InletNode and OutletNode
762 0 : BranchNodeConnections::TestCompSet(state,
763 0 : state.dataIPShortCut->cCurrentModuleObject,
764 0 : state.dataIPShortCut->cAlphaArgs(1),
765 0 : state.dataIPShortCut->cAlphaArgs(3),
766 0 : state.dataIPShortCut->cAlphaArgs(4),
767 : "Chilled Water Nodes");
768 :
769 : // Initialize Report Variables
770 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).MyLoad = 0.0;
771 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).Urate = 0.0;
772 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).IceFracRemain = 1.0;
773 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSCoolingRate_rep = 0.0;
774 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSCoolingEnergy_rep = 0.0;
775 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSChargingRate = 0.0;
776 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSChargingEnergy = 0.0;
777 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSmdot = 0.0;
778 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSInletTemp = 0.0;
779 0 : state.dataIceThermalStorage->SimpleIceStorage(iceNum).ITSOutletTemp = 0.0;
780 :
781 : } // IceNum
782 :
783 0 : if (ErrorsFound) {
784 0 : ShowFatalError(state, format("Errors found in processing input for {}", state.dataIPShortCut->cCurrentModuleObject));
785 : }
786 :
787 0 : ErrorsFound = false; // Always need to reset this since there are multiple types of ice storage systems
788 :
789 : // Determine the number of detailed ice storage devices are in the input file and allocate appropriately
790 0 : state.dataIPShortCut->cCurrentModuleObject = cIceStorageDetailed;
791 :
792 0 : state.dataIceThermalStorage->DetailedIceStorage.allocate(
793 0 : state.dataIceThermalStorage->NumDetailedIceStorage); // Allocate DetIceStorage based on NumDetIceStorages
794 :
795 0 : for (int iceNum = 1; iceNum <= state.dataIceThermalStorage->NumDetailedIceStorage; ++iceNum) {
796 :
797 : int NumAlphas;
798 : int NumNums;
799 : int IOStat;
800 0 : state.dataInputProcessing->inputProcessor->getObjectItem(state,
801 0 : state.dataIPShortCut->cCurrentModuleObject,
802 : iceNum,
803 0 : state.dataIPShortCut->cAlphaArgs,
804 : NumAlphas,
805 0 : state.dataIPShortCut->rNumericArgs,
806 : NumNums,
807 : IOStat,
808 : _,
809 0 : state.dataIPShortCut->lAlphaFieldBlanks,
810 0 : state.dataIPShortCut->cAlphaFieldNames,
811 0 : state.dataIPShortCut->cNumericFieldNames);
812 :
813 0 : ErrorObjectHeader eoh{routineName, state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)};
814 :
815 0 : Util::IsNameEmpty(state, state.dataIPShortCut->cAlphaArgs(1), state.dataIPShortCut->cCurrentModuleObject, ErrorsFound);
816 :
817 0 : ++state.dataIceThermalStorage->TotalNumIceStorage;
818 :
819 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).MapNum = state.dataIceThermalStorage->TotalNumIceStorage;
820 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).Name = state.dataIPShortCut->cAlphaArgs(1); // Detailed ice storage name
821 :
822 : // Get and verify availability schedule
823 0 : if (state.dataIPShortCut->lAlphaFieldBlanks(2)) {
824 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).availSched = Sched::GetScheduleAlwaysOn(state);
825 0 : } else if ((state.dataIceThermalStorage->DetailedIceStorage(iceNum).availSched =
826 0 : Sched::GetSchedule(state, state.dataIPShortCut->cAlphaArgs(2))) == nullptr) {
827 0 : ShowSevereItemNotFound(state, eoh, state.dataIPShortCut->cAlphaFieldNames(2), state.dataIPShortCut->cAlphaArgs(2));
828 0 : ErrorsFound = true;
829 : }
830 :
831 : // Get and Verify ITS nominal Capacity (user input is in GJ, internal value is in W-hr)
832 : // Convert GJ to J by multiplying by 10^9
833 : // Convert J to W-hr by dividing by number of seconds in an hour (3600)
834 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).NomCapacity =
835 0 : state.dataIPShortCut->rNumericArgs(1) * (1.e+09) / Constant::rSecsInHour;
836 :
837 0 : if (state.dataIPShortCut->rNumericArgs(1) <= 0.0) {
838 0 : ShowSevereError(state,
839 0 : format("Invalid {}={:.2R}", state.dataIPShortCut->cNumericFieldNames(1), state.dataIPShortCut->rNumericArgs(1)));
840 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
841 0 : ErrorsFound = true;
842 : }
843 :
844 : // Get Plant Inlet Node Num
845 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).PlantInNodeNum =
846 0 : NodeInputManager::GetOnlySingleNode(state,
847 0 : state.dataIPShortCut->cAlphaArgs(3),
848 : ErrorsFound,
849 : DataLoopNode::ConnectionObjectType::ThermalStorageIceDetailed,
850 0 : state.dataIPShortCut->cAlphaArgs(1),
851 : DataLoopNode::NodeFluidType::Water,
852 : DataLoopNode::ConnectionType::Inlet,
853 : NodeInputManager::CompFluidStream::Primary,
854 : DataLoopNode::ObjectIsNotParent);
855 :
856 : // Get Plant Outlet Node Num
857 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).PlantOutNodeNum =
858 0 : NodeInputManager::GetOnlySingleNode(state,
859 0 : state.dataIPShortCut->cAlphaArgs(4),
860 : ErrorsFound,
861 : DataLoopNode::ConnectionObjectType::ThermalStorageIceDetailed,
862 0 : state.dataIPShortCut->cAlphaArgs(1),
863 : DataLoopNode::NodeFluidType::Water,
864 : DataLoopNode::ConnectionType::Outlet,
865 : NodeInputManager::CompFluidStream::Primary,
866 : DataLoopNode::ObjectIsNotParent);
867 :
868 : // Test InletNode and OutletNode
869 0 : BranchNodeConnections::TestCompSet(state,
870 0 : state.dataIPShortCut->cCurrentModuleObject,
871 0 : state.dataIPShortCut->cAlphaArgs(1),
872 0 : state.dataIPShortCut->cAlphaArgs(3),
873 0 : state.dataIPShortCut->cAlphaArgs(4),
874 : "Chilled Water Nodes");
875 :
876 : // Obtain the Charging and Discharging Curve types and names
877 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeCurveName = state.dataIPShortCut->cAlphaArgs(6);
878 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeCurveNum =
879 0 : Curve::GetCurveIndex(state, state.dataIPShortCut->cAlphaArgs(6));
880 0 : if (state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeCurveNum <= 0) {
881 0 : ShowSevereError(state, format("Invalid {}={}", state.dataIPShortCut->cAlphaFieldNames(6), state.dataIPShortCut->cAlphaArgs(6)));
882 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
883 0 : ErrorsFound = true;
884 : }
885 :
886 : int dischargeCurveDim =
887 0 : state.dataCurveManager->curves(state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeCurveNum)->numDims;
888 0 : if (dischargeCurveDim != 2) {
889 0 : ShowSevereError(state, format("{}: Discharge curve must have 2 independent variables", state.dataIPShortCut->cCurrentModuleObject));
890 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
891 0 : ShowContinueError(state,
892 0 : format("{} does not have 2 independent variables and thus cannot be used for detailed ice storage",
893 0 : state.dataIPShortCut->cAlphaArgs(6)));
894 0 : ErrorsFound = true;
895 : } else {
896 0 : if (state.dataIPShortCut->cAlphaArgs(5) == "FRACTIONCHARGEDLMTD") {
897 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeCurveTypeNum = CurveVars::FracChargedLMTD;
898 0 : } else if (state.dataIPShortCut->cAlphaArgs(5) == "FRACTIONDISCHARGEDLMTD") {
899 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeCurveTypeNum = CurveVars::FracDischargedLMTD;
900 0 : } else if (state.dataIPShortCut->cAlphaArgs(5) == "LMTDMASSFLOW") {
901 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeCurveTypeNum = CurveVars::LMTDMassFlow;
902 0 : } else if (state.dataIPShortCut->cAlphaArgs(5) == "LMTDFRACTIONCHARGED") {
903 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeCurveTypeNum = CurveVars::LMTDFracCharged;
904 : } else {
905 0 : ShowSevereError(state,
906 0 : format("{}: Discharge curve independent variable options not valid, option={}",
907 0 : state.dataIPShortCut->cCurrentModuleObject,
908 0 : state.dataIPShortCut->cAlphaArgs(5)));
909 0 : ShowContinueError(state,
910 0 : format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
911 0 : ShowContinueError(state,
912 : "The valid options are: FractionChargedLMTD, FractionDischargedLMTD, LMTDMassFlow or LMTDFractionCharged");
913 0 : ErrorsFound = true;
914 : }
915 : }
916 :
917 0 : ErrorsFound |= Curve::CheckCurveDims(state,
918 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeCurveNum, // Curve index
919 : {2}, // Valid dimensions
920 : "GetIceStorageInput: ", // Routine name
921 0 : state.dataIPShortCut->cCurrentModuleObject, // Object Type
922 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).Name, // Object Name
923 0 : state.dataIPShortCut->cAlphaFieldNames(6)); // Field Name
924 :
925 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeCurveName = state.dataIPShortCut->cAlphaArgs(8);
926 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeCurveNum = Curve::GetCurveIndex(state, state.dataIPShortCut->cAlphaArgs(8));
927 0 : if (state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeCurveNum <= 0) {
928 0 : ShowSevereError(state, format("Invalid {}={}", state.dataIPShortCut->cAlphaFieldNames(8), state.dataIPShortCut->cAlphaArgs(8)));
929 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
930 0 : ErrorsFound = true;
931 : }
932 :
933 0 : int chargeCurveDim = state.dataCurveManager->curves(state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeCurveNum)->numDims;
934 0 : if (chargeCurveDim != 2) {
935 0 : ShowSevereError(state, format("{}: Charge curve must have 2 independent variables", state.dataIPShortCut->cCurrentModuleObject));
936 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
937 0 : ShowContinueError(state,
938 0 : format("{} does not have 2 independent variables and thus cannot be used for detailed ice storage",
939 0 : state.dataIPShortCut->cAlphaArgs(8)));
940 0 : ErrorsFound = true;
941 : } else {
942 0 : if (state.dataIPShortCut->cAlphaArgs(7) == "FRACTIONCHARGEDLMTD") {
943 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeCurveTypeNum = CurveVars::FracChargedLMTD;
944 0 : } else if (state.dataIPShortCut->cAlphaArgs(7) == "FRACTIONDISCHARGEDLMTD") {
945 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeCurveTypeNum = CurveVars::FracDischargedLMTD;
946 0 : } else if (state.dataIPShortCut->cAlphaArgs(7) == "LMTDMASSFLOW") {
947 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeCurveTypeNum = CurveVars::LMTDMassFlow;
948 0 : } else if (state.dataIPShortCut->cAlphaArgs(7) == "LMTDFRACTIONCHARGED") {
949 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeCurveTypeNum = CurveVars::LMTDFracCharged;
950 : } else {
951 0 : ShowSevereError(state,
952 0 : format("{}: Charge curve independent variable options not valid, option={}",
953 0 : state.dataIPShortCut->cCurrentModuleObject,
954 0 : state.dataIPShortCut->cAlphaArgs(7)));
955 0 : ShowContinueError(state,
956 0 : format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
957 0 : ShowContinueError(state,
958 : "The valid options are: FractionChargedLMTD, FractionDischargedLMTD, LMTDMassFlow or LMTDFractionCharged");
959 0 : ErrorsFound = true;
960 : }
961 : }
962 :
963 0 : ErrorsFound |= Curve::CheckCurveDims(state,
964 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeCurveNum, // Curve index
965 : {2}, // Valid dimensions
966 : "GetIceStorageInput: ", // Routine name
967 0 : state.dataIPShortCut->cCurrentModuleObject, // Object Type
968 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).Name, // Object Name
969 0 : state.dataIPShortCut->cAlphaFieldNames(8)); // Field Name
970 :
971 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).CurveFitTimeStep = state.dataIPShortCut->rNumericArgs(2);
972 0 : if ((state.dataIceThermalStorage->DetailedIceStorage(iceNum).CurveFitTimeStep <= 0.0) ||
973 0 : (state.dataIceThermalStorage->DetailedIceStorage(iceNum).CurveFitTimeStep > 1.0)) {
974 0 : ShowSevereError(state,
975 0 : format("Invalid {}={:.3R}", state.dataIPShortCut->cNumericFieldNames(2), state.dataIPShortCut->rNumericArgs(2)));
976 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
977 0 : ShowContinueError(
978 0 : state, format("Curve fit time step invalid, less than zero or greater than 1 for {}", state.dataIPShortCut->cAlphaArgs(1)));
979 0 : ErrorsFound = true;
980 : }
981 :
982 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ThawProcessIndicator = state.dataIPShortCut->cAlphaArgs(9);
983 0 : if (Util::SameString(state.dataIceThermalStorage->DetailedIceStorage(iceNum).ThawProcessIndicator, "INSIDEMELT")) {
984 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ThawProcessIndex = DetIce::InsideMelt;
985 0 : } else if ((Util::SameString(state.dataIceThermalStorage->DetailedIceStorage(iceNum).ThawProcessIndicator, "OUTSIDEMELT")) ||
986 0 : (state.dataIceThermalStorage->DetailedIceStorage(iceNum).ThawProcessIndicator.empty())) {
987 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ThawProcessIndex = DetIce::OutsideMelt;
988 : } else {
989 0 : ShowSevereError(state, format("Invalid thaw process indicator of {} was entered", state.dataIPShortCut->cAlphaArgs(9)));
990 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
991 0 : ShowContinueError(state, R"(Value should either be "InsideMelt" or "OutsideMelt")");
992 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ThawProcessIndex =
993 : DetIce::InsideMelt; // Severe error will end simulation, but just in case...
994 0 : ErrorsFound = true;
995 : }
996 :
997 : // Get the other ice storage parameters (electric, heat loss, freezing temperature) and stupidity check each one
998 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeParaElecLoad = state.dataIPShortCut->rNumericArgs(3);
999 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeParaElecLoad = state.dataIPShortCut->rNumericArgs(4);
1000 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).TankLossCoeff = state.dataIPShortCut->rNumericArgs(5);
1001 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).FreezingTemp = state.dataIPShortCut->rNumericArgs(6);
1002 :
1003 0 : if ((state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeParaElecLoad < 0.0) ||
1004 0 : (state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargeParaElecLoad > 1.0)) {
1005 0 : ShowSevereError(state,
1006 0 : format("Invalid {}={:.3R}", state.dataIPShortCut->cNumericFieldNames(3), state.dataIPShortCut->rNumericArgs(3)));
1007 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
1008 0 : ShowContinueError(state, "Value is either less than/equal to zero or greater than 1");
1009 0 : ErrorsFound = true;
1010 : }
1011 :
1012 0 : if ((state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeParaElecLoad < 0.0) ||
1013 0 : (state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargeParaElecLoad > 1.0)) {
1014 0 : ShowSevereError(state,
1015 0 : format("Invalid {}={:.3R}", state.dataIPShortCut->cNumericFieldNames(4), state.dataIPShortCut->rNumericArgs(4)));
1016 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
1017 0 : ShowContinueError(state, "Value is either less than/equal to zero or greater than 1");
1018 0 : ErrorsFound = true;
1019 : }
1020 :
1021 0 : if ((state.dataIceThermalStorage->DetailedIceStorage(iceNum).TankLossCoeff < 0.0) ||
1022 0 : (state.dataIceThermalStorage->DetailedIceStorage(iceNum).TankLossCoeff > 0.1)) {
1023 0 : ShowSevereError(state,
1024 0 : format("Invalid {}={:.3R}", state.dataIPShortCut->cNumericFieldNames(5), state.dataIPShortCut->rNumericArgs(5)));
1025 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
1026 0 : ShowContinueError(state, "Value is either less than/equal to zero or greater than 0.1 (10%)");
1027 0 : ErrorsFound = true;
1028 : }
1029 :
1030 0 : if ((state.dataIceThermalStorage->DetailedIceStorage(iceNum).FreezingTemp < -10.0) ||
1031 0 : (state.dataIceThermalStorage->DetailedIceStorage(iceNum).FreezingTemp > 10.0)) {
1032 0 : ShowWarningError(
1033 : state,
1034 0 : format("Potentially invalid {}={:.3R}", state.dataIPShortCut->cNumericFieldNames(6), state.dataIPShortCut->rNumericArgs(6)));
1035 0 : ShowContinueError(state, format("Entered in {}={}", state.dataIPShortCut->cCurrentModuleObject, state.dataIPShortCut->cAlphaArgs(1)));
1036 0 : ShowContinueError(state, "Value is either less than -10.0C or greater than 10.0C");
1037 0 : ShowContinueError(state, "This value will be allowed but the user should verify that this temperature is correct");
1038 : }
1039 :
1040 : // Initialize Report Variables
1041 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).CompLoad = 0.0;
1042 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).IceFracChange = 0.0;
1043 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).IceFracRemaining = 1.0;
1044 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).IceFracOnCoil = 1.0;
1045 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargingRate = 0.0;
1046 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).DischargingEnergy = 0.0;
1047 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargingRate = 0.0;
1048 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ChargingEnergy = 0.0;
1049 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).MassFlowRate = 0.0;
1050 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).BypassMassFlowRate = 0.0;
1051 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).TankMassFlowRate = 0.0;
1052 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).InletTemp = 0.0;
1053 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).OutletTemp = 0.0;
1054 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).TankOutletTemp = 0.0;
1055 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ParasiticElecRate = 0.0;
1056 0 : state.dataIceThermalStorage->DetailedIceStorage(iceNum).ParasiticElecEnergy = 0.0;
1057 :
1058 : } // ...over detailed ice storage units
1059 :
1060 0 : if ((state.dataIceThermalStorage->NumSimpleIceStorage + state.dataIceThermalStorage->NumDetailedIceStorage) <= 0) {
1061 0 : ShowSevereError(state, "No Ice Storage Equipment found in GetIceStorage");
1062 0 : ErrorsFound = true;
1063 : }
1064 :
1065 0 : if (ErrorsFound) {
1066 0 : ShowFatalError(state, format("Errors found in processing input for {}", state.dataIPShortCut->cCurrentModuleObject));
1067 : }
1068 0 : }
1069 :
1070 0 : void SimpleIceStorageData::setupOutputVars(EnergyPlusData &state)
1071 : {
1072 0 : SetupOutputVariable(state,
1073 : "Ice Thermal Storage Requested Load",
1074 : Constant::Units::W,
1075 0 : this->MyLoad,
1076 : OutputProcessor::TimeStepType::System,
1077 : OutputProcessor::StoreType::Average,
1078 0 : this->Name);
1079 :
1080 0 : SetupOutputVariable(state,
1081 : "Ice Thermal Storage End Fraction",
1082 : Constant::Units::None,
1083 0 : this->IceFracRemain,
1084 : OutputProcessor::TimeStepType::Zone,
1085 : OutputProcessor::StoreType::Average,
1086 0 : this->Name);
1087 :
1088 0 : SetupOutputVariable(state,
1089 : "Ice Thermal Storage Mass Flow Rate",
1090 : Constant::Units::kg_s,
1091 0 : this->ITSmdot,
1092 : OutputProcessor::TimeStepType::System,
1093 : OutputProcessor::StoreType::Average,
1094 0 : this->Name);
1095 :
1096 0 : SetupOutputVariable(state,
1097 : "Ice Thermal Storage Inlet Temperature",
1098 : Constant::Units::C,
1099 0 : this->ITSInletTemp,
1100 : OutputProcessor::TimeStepType::System,
1101 : OutputProcessor::StoreType::Average,
1102 0 : this->Name);
1103 :
1104 0 : SetupOutputVariable(state,
1105 : "Ice Thermal Storage Outlet Temperature",
1106 : Constant::Units::C,
1107 0 : this->ITSOutletTemp,
1108 : OutputProcessor::TimeStepType::System,
1109 : OutputProcessor::StoreType::Average,
1110 0 : this->Name);
1111 :
1112 0 : SetupOutputVariable(state,
1113 : "Ice Thermal Storage Cooling Discharge Rate",
1114 : Constant::Units::W,
1115 0 : this->ITSCoolingRate_rep,
1116 : OutputProcessor::TimeStepType::System,
1117 : OutputProcessor::StoreType::Average,
1118 0 : this->Name);
1119 :
1120 0 : SetupOutputVariable(state,
1121 : "Ice Thermal Storage Cooling Discharge Energy",
1122 : Constant::Units::J,
1123 0 : this->ITSCoolingEnergy_rep,
1124 : OutputProcessor::TimeStepType::System,
1125 : OutputProcessor::StoreType::Sum,
1126 0 : this->Name);
1127 :
1128 0 : SetupOutputVariable(state,
1129 : "Ice Thermal Storage Cooling Charge Rate",
1130 : Constant::Units::W,
1131 0 : this->ITSChargingRate,
1132 : OutputProcessor::TimeStepType::System,
1133 : OutputProcessor::StoreType::Average,
1134 0 : this->Name);
1135 :
1136 0 : SetupOutputVariable(state,
1137 : "Ice Thermal Storage Cooling Charge Energy",
1138 : Constant::Units::J,
1139 0 : this->ITSChargingEnergy,
1140 : OutputProcessor::TimeStepType::System,
1141 : OutputProcessor::StoreType::Sum,
1142 0 : this->Name);
1143 0 : }
1144 :
1145 0 : void DetailedIceStorageData::setupOutputVars(EnergyPlusData &state)
1146 : {
1147 0 : SetupOutputVariable(state,
1148 : "Ice Thermal Storage Cooling Rate",
1149 : Constant::Units::W,
1150 0 : this->CompLoad,
1151 : OutputProcessor::TimeStepType::System,
1152 : OutputProcessor::StoreType::Average,
1153 0 : this->Name);
1154 :
1155 0 : SetupOutputVariable(state,
1156 : "Ice Thermal Storage Change Fraction",
1157 : Constant::Units::None,
1158 0 : this->IceFracChange,
1159 : OutputProcessor::TimeStepType::System,
1160 : OutputProcessor::StoreType::Average,
1161 0 : this->Name);
1162 :
1163 0 : SetupOutputVariable(state,
1164 : "Ice Thermal Storage End Fraction",
1165 : Constant::Units::None,
1166 0 : this->IceFracRemaining,
1167 : OutputProcessor::TimeStepType::System,
1168 : OutputProcessor::StoreType::Average,
1169 0 : this->Name);
1170 :
1171 0 : SetupOutputVariable(state,
1172 : "Ice Thermal Storage On Coil Fraction",
1173 : Constant::Units::None,
1174 0 : this->IceFracOnCoil,
1175 : OutputProcessor::TimeStepType::System,
1176 : OutputProcessor::StoreType::Average,
1177 0 : this->Name);
1178 :
1179 0 : SetupOutputVariable(state,
1180 : "Ice Thermal Storage Mass Flow Rate",
1181 : Constant::Units::kg_s,
1182 0 : this->MassFlowRate,
1183 : OutputProcessor::TimeStepType::System,
1184 : OutputProcessor::StoreType::Average,
1185 0 : this->Name);
1186 :
1187 0 : SetupOutputVariable(state,
1188 : "Ice Thermal Storage Bypass Mass Flow Rate",
1189 : Constant::Units::kg_s,
1190 0 : this->BypassMassFlowRate,
1191 : OutputProcessor::TimeStepType::System,
1192 : OutputProcessor::StoreType::Average,
1193 0 : this->Name);
1194 :
1195 0 : SetupOutputVariable(state,
1196 : "Ice Thermal Storage Tank Mass Flow Rate",
1197 : Constant::Units::kg_s,
1198 0 : this->TankMassFlowRate,
1199 : OutputProcessor::TimeStepType::System,
1200 : OutputProcessor::StoreType::Average,
1201 0 : this->Name);
1202 :
1203 0 : SetupOutputVariable(state,
1204 : "Ice Thermal Storage Fluid Inlet Temperature",
1205 : Constant::Units::C,
1206 0 : this->InletTemp,
1207 : OutputProcessor::TimeStepType::System,
1208 : OutputProcessor::StoreType::Average,
1209 0 : this->Name);
1210 :
1211 0 : SetupOutputVariable(state,
1212 : "Ice Thermal Storage Blended Outlet Temperature",
1213 : Constant::Units::C,
1214 0 : this->OutletTemp,
1215 : OutputProcessor::TimeStepType::System,
1216 : OutputProcessor::StoreType::Average,
1217 0 : this->Name);
1218 :
1219 0 : SetupOutputVariable(state,
1220 : "Ice Thermal Storage Tank Outlet Temperature",
1221 : Constant::Units::C,
1222 0 : this->TankOutletTemp,
1223 : OutputProcessor::TimeStepType::System,
1224 : OutputProcessor::StoreType::Average,
1225 0 : this->Name);
1226 :
1227 0 : SetupOutputVariable(state,
1228 : "Ice Thermal Storage Cooling Discharge Rate",
1229 : Constant::Units::W,
1230 0 : this->DischargingRate,
1231 : OutputProcessor::TimeStepType::System,
1232 : OutputProcessor::StoreType::Average,
1233 0 : this->Name);
1234 :
1235 0 : SetupOutputVariable(state,
1236 : "Ice Thermal Storage Cooling Discharge Energy",
1237 : Constant::Units::J,
1238 0 : this->DischargingEnergy,
1239 : OutputProcessor::TimeStepType::System,
1240 : OutputProcessor::StoreType::Sum,
1241 0 : this->Name);
1242 :
1243 0 : SetupOutputVariable(state,
1244 : "Ice Thermal Storage Cooling Charge Rate",
1245 : Constant::Units::W,
1246 0 : this->ChargingRate,
1247 : OutputProcessor::TimeStepType::System,
1248 : OutputProcessor::StoreType::Average,
1249 0 : this->Name);
1250 :
1251 0 : SetupOutputVariable(state,
1252 : "Ice Thermal Storage Cooling Charge Energy",
1253 : Constant::Units::J,
1254 0 : this->ChargingEnergy,
1255 : OutputProcessor::TimeStepType::System,
1256 : OutputProcessor::StoreType::Sum,
1257 0 : this->Name);
1258 :
1259 0 : SetupOutputVariable(state,
1260 : "Ice Thermal Storage Ancillary Electricity Rate",
1261 : Constant::Units::W,
1262 0 : this->ParasiticElecRate,
1263 : OutputProcessor::TimeStepType::System,
1264 : OutputProcessor::StoreType::Average,
1265 0 : this->Name);
1266 :
1267 0 : SetupOutputVariable(state,
1268 : "Ice Thermal Storage Ancillary Electricity Energy",
1269 : Constant::Units::J,
1270 0 : this->ParasiticElecEnergy,
1271 : OutputProcessor::TimeStepType::System,
1272 : OutputProcessor::StoreType::Sum,
1273 0 : this->Name,
1274 : Constant::eResource::Electricity,
1275 : OutputProcessor::Group::HVAC,
1276 : OutputProcessor::EndUseCat::Invalid);
1277 0 : }
1278 :
1279 0 : void DetailedIceStorageData::oneTimeInit(EnergyPlusData &state)
1280 : {
1281 :
1282 : // SUBROUTINE INFORMATION:
1283 : // AUTHOR Rick Strand
1284 : // DATE WRITTEN February 2006
1285 : // MODIFIED na
1286 : // RE-ENGINEERED na
1287 :
1288 : // PURPOSE OF THIS SUBROUTINE:
1289 : // This subroutine initializes variables for the detailed ice storage model.
1290 :
1291 : // METHODOLOGY EMPLOYED:
1292 : // Initializes parameters based on current status flag values.
1293 :
1294 0 : if (this->MyPlantScanFlag) {
1295 0 : bool errFlag = false;
1296 0 : PlantUtilities::ScanPlantLoopsForObject(state, this->Name, DataPlant::PlantEquipmentType::TS_IceDetailed, this->plantLoc, errFlag);
1297 :
1298 0 : if (errFlag) {
1299 0 : ShowFatalError(state, "DetailedIceStorageData: oneTimeInit: Program terminated due to previous condition(s).");
1300 : }
1301 :
1302 0 : this->setupOutputVars(state);
1303 0 : this->MyPlantScanFlag = false;
1304 : }
1305 :
1306 0 : if (state.dataGlobal->BeginEnvrnFlag && this->MyEnvrnFlag2) { // Beginning of environment initializations
1307 : // Make sure all state variables are reset at the beginning of every environment to avoid problems.
1308 : // The storage unit is assumed to be fully charged at the start of any environment.
1309 : // The IceNum variable is a module level variable that is already set before this subroutine is called.
1310 0 : this->IceFracChange = 0.0;
1311 0 : this->IceFracRemaining = 1.0;
1312 0 : this->IceFracOnCoil = 1.0;
1313 0 : this->InletTemp = 0.0;
1314 0 : this->OutletTemp = 0.0;
1315 0 : this->TankOutletTemp = 0.0;
1316 0 : this->DischargeIterErrors = 0;
1317 0 : this->ChargeIterErrors = 0;
1318 0 : this->DesignMassFlowRate = state.dataPlnt->PlantLoop(this->plantLoc.loopNum).MaxMassFlowRate;
1319 : // no design flow rates for model, assume min is zero and max is plant loop's max
1320 0 : PlantUtilities::InitComponentNodes(state, 0.0, this->DesignMassFlowRate, this->PlantInNodeNum, this->PlantOutNodeNum);
1321 :
1322 0 : if ((state.dataPlnt->PlantLoop(this->plantLoc.loopNum).CommonPipeType == DataPlant::CommonPipeType::TwoWay) &&
1323 0 : (this->plantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply)) {
1324 : // up flow priority of other components on the same branch as the Ice tank
1325 0 : for (int CompNum = 1; CompNum <= state.dataPlnt->PlantLoop(this->plantLoc.loopNum)
1326 0 : .LoopSide(DataPlant::LoopSideLocation::Supply)
1327 0 : .Branch(this->plantLoc.branchNum)
1328 0 : .TotalComponents;
1329 : ++CompNum) {
1330 0 : state.dataPlnt->PlantLoop(this->plantLoc.loopNum)
1331 0 : .LoopSide(DataPlant::LoopSideLocation::Supply)
1332 0 : .Branch(this->plantLoc.branchNum)
1333 0 : .Comp(CompNum)
1334 0 : .FlowPriority = DataPlant::LoopFlowStatus::NeedyAndTurnsLoopOn;
1335 : }
1336 : }
1337 :
1338 0 : this->MyEnvrnFlag2 = false;
1339 : }
1340 0 : if (!state.dataGlobal->BeginEnvrnFlag) this->MyEnvrnFlag2 = true;
1341 :
1342 : // Initializations that are done every iteration
1343 : // Make sure all of the reporting variables are always reset at the start of any iteration
1344 0 : this->CompLoad = 0.0;
1345 0 : this->IceFracChange = 0.0;
1346 0 : this->DischargingRate = 0.0;
1347 0 : this->DischargingEnergy = 0.0;
1348 0 : this->ChargingRate = 0.0;
1349 0 : this->ChargingEnergy = 0.0;
1350 0 : this->MassFlowRate = 0.0;
1351 0 : this->BypassMassFlowRate = 0.0;
1352 0 : this->TankMassFlowRate = 0.0;
1353 0 : this->ParasiticElecRate = 0.0;
1354 0 : this->ParasiticElecEnergy = 0.0;
1355 0 : }
1356 :
1357 0 : void SimpleIceStorageData::oneTimeInit(EnergyPlusData &state)
1358 : {
1359 :
1360 0 : if (this->MyPlantScanFlag) {
1361 : // Locate the storage on the plant loops for later usage
1362 0 : bool errFlag = false;
1363 0 : PlantUtilities::ScanPlantLoopsForObject(
1364 0 : state, this->Name, DataPlant::PlantEquipmentType::TS_IceSimple, this->plantLoc, errFlag, _, _, _, _, _);
1365 0 : if (errFlag) {
1366 0 : ShowFatalError(state, "SimpleIceStorageData:oneTimeInit: Program terminated due to previous condition(s).");
1367 : }
1368 :
1369 0 : this->setupOutputVars(state);
1370 0 : this->MyPlantScanFlag = false;
1371 : }
1372 :
1373 0 : if (state.dataGlobal->BeginEnvrnFlag && this->MyEnvrnFlag2) {
1374 0 : this->DesignMassFlowRate = state.dataPlnt->PlantLoop(this->plantLoc.loopNum).MaxMassFlowRate;
1375 : // no design flow rates for model, assume min is zero and max is plant loop's max
1376 0 : PlantUtilities::InitComponentNodes(state, 0.0, this->DesignMassFlowRate, this->PltInletNodeNum, this->PltOutletNodeNum);
1377 0 : if ((state.dataPlnt->PlantLoop(this->plantLoc.loopNum).CommonPipeType == DataPlant::CommonPipeType::TwoWay) &&
1378 0 : (this->plantLoc.loopSideNum == DataPlant::LoopSideLocation::Supply)) {
1379 : // up flow priority of other components on the same branch as the Ice tank
1380 0 : for (int compNum = 1; compNum <= state.dataPlnt->PlantLoop(this->plantLoc.loopNum)
1381 0 : .LoopSide(DataPlant::LoopSideLocation::Supply)
1382 0 : .Branch(this->plantLoc.branchNum)
1383 0 : .TotalComponents;
1384 : ++compNum) {
1385 0 : state.dataPlnt->PlantLoop(this->plantLoc.loopNum)
1386 0 : .LoopSide(DataPlant::LoopSideLocation::Supply)
1387 0 : .Branch(this->plantLoc.branchNum)
1388 0 : .Comp(compNum)
1389 0 : .FlowPriority = DataPlant::LoopFlowStatus::NeedyAndTurnsLoopOn;
1390 : }
1391 : }
1392 0 : this->MyLoad = 0.0;
1393 0 : this->Urate = 0.0;
1394 0 : this->IceFracRemain = 1.0;
1395 0 : this->ITSCoolingRate = 0.0;
1396 0 : this->ITSCoolingEnergy_rep = 0.0;
1397 0 : this->ITSChargingRate = 0.0;
1398 0 : this->ITSChargingEnergy = 0.0;
1399 0 : this->ITSmdot = 0.0;
1400 0 : this->ITSInletTemp = 0.0;
1401 0 : this->ITSOutletTemp = 0.0;
1402 :
1403 0 : this->MyEnvrnFlag2 = false;
1404 : }
1405 :
1406 0 : if (!state.dataGlobal->BeginEnvrnFlag) this->MyEnvrnFlag2 = true;
1407 0 : }
1408 :
1409 : //******************************************************************************
1410 :
1411 0 : void SimpleIceStorageData::CalcIceStorageCapacity(EnergyPlusData &state, Real64 &MaxCap, Real64 &MinCap, Real64 &OptCap)
1412 : {
1413 : //------------------------------------------------------------------------
1414 : // FIRST PROCESS (MyLoad = 0.0 as IN)
1415 : // At this moment as first calling of ITS, ITS provide ONLY MaxCap/OptCap/MinCap.
1416 : //------------------------------------------------------------------------
1417 :
1418 : // Initialize Capacity
1419 0 : MaxCap = 0.0;
1420 0 : MinCap = 0.0;
1421 0 : OptCap = 0.0;
1422 :
1423 : // XCurIceFrac is reset to 1.0 when first hour of day.
1424 : // Starting full is assumed, because most ice systems are fully charged overnight
1425 0 : if (this->ResetXForITSFlag) {
1426 0 : this->XCurIceFrac = 1.0;
1427 0 : this->IceFracRemain = 1.0;
1428 0 : this->Urate = 0.0;
1429 0 : this->ResetXForITSFlag = false;
1430 : }
1431 :
1432 : // Calculate UAIceDisch[W/C] and UAIceCh[W/F] based on ONLY XCurIceFrac
1433 0 : this->CalcUAIce(this->XCurIceFrac, this->UAIceCh, this->UAIceDisCh, this->HLoss);
1434 :
1435 : // Calculate QiceMin by UAIceDisCh*deltaTlm
1436 : // with UAIceDisCh(function of XCurIceFrac), ITSInletTemp and ITSOutletTemp(=Node(OutletNodeNum)%TempSetPoint by E+[C])
1437 : // QiceMin is REAL(r64) ITS capacity.
1438 : Real64 QiceMin;
1439 0 : this->CalcQiceDischageMax(state, QiceMin);
1440 :
1441 : // At the first call of ITS model, MyLoad is 0. After that proper MyLoad will be provided by E+.
1442 : // Therefore, Umin is decided between input U and ITS REAL(r64) capacity.
1443 0 : Real64 Umin = min(max((-(1.0 - EpsLimitForDisCharge) * QiceMin * TimeInterval / this->ITSNomCap), (-this->XCurIceFrac + EpsLimitForX)), 0.0);
1444 :
1445 : // Calculate CoolingRate with Uact to provide E+.
1446 0 : Real64 Uact = Umin;
1447 0 : Real64 ITSCoolingRateMax = std::abs(Uact * this->ITSNomCap / TimeInterval);
1448 0 : Real64 ITSCoolingRateOpt = ITSCoolingRateMax;
1449 0 : Real64 ITSCoolingRateMin = 0.0;
1450 :
1451 : // Define MaxCap, OptCap, and MinCap
1452 0 : MaxCap = ITSCoolingRateMax;
1453 0 : OptCap = ITSCoolingRateOpt;
1454 0 : MinCap = ITSCoolingRateMin;
1455 0 : }
1456 :
1457 : //******************************************************************************
1458 :
1459 0 : void SimpleIceStorageData::CalcIceStorageDormant(EnergyPlusData &state)
1460 : {
1461 : // Provide output results for ITS.
1462 0 : this->ITSMassFlowRate = 0.0; //[kg/s]
1463 :
1464 0 : PlantUtilities::SetComponentFlowRate(state, this->ITSMassFlowRate, this->PltInletNodeNum, this->PltOutletNodeNum, this->plantLoc);
1465 :
1466 0 : this->ITSInletTemp = state.dataLoopNodes->Node(this->PltInletNodeNum).Temp; //[C]
1467 0 : this->ITSOutletTemp = this->ITSInletTemp; //[C]
1468 0 : switch (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).LoopDemandCalcScheme) {
1469 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
1470 0 : this->ITSOutletSetPointTemp = state.dataLoopNodes->Node(this->PltOutletNodeNum).TempSetPoint;
1471 0 : } break;
1472 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
1473 0 : this->ITSOutletSetPointTemp = state.dataLoopNodes->Node(this->PltOutletNodeNum).TempSetPointHi;
1474 0 : } break;
1475 0 : default:
1476 0 : break;
1477 : }
1478 0 : this->ITSCoolingRate = 0.0; //[W]
1479 0 : this->ITSCoolingEnergy = 0.0; //[J]
1480 :
1481 0 : this->Urate = 0.0; //[n/a]
1482 0 : }
1483 :
1484 : //******************************************************************************
1485 :
1486 0 : void SimpleIceStorageData::CalcIceStorageCharge(EnergyPlusData &state)
1487 : {
1488 : //--------------------------------------------------------
1489 : // Initialize
1490 : //--------------------------------------------------------
1491 : // Below values for ITS are reported forCharging process.
1492 0 : this->ITSMassFlowRate = this->DesignMassFlowRate; //[kg/s]
1493 :
1494 0 : PlantUtilities::SetComponentFlowRate(state, this->ITSMassFlowRate, this->PltInletNodeNum, this->PltOutletNodeNum, this->plantLoc);
1495 :
1496 0 : this->ITSInletTemp = state.dataLoopNodes->Node(this->PltInletNodeNum).Temp; //[C]
1497 0 : this->ITSOutletTemp = this->ITSInletTemp; //[C]
1498 0 : switch (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).LoopDemandCalcScheme) {
1499 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
1500 0 : this->ITSOutletSetPointTemp = state.dataLoopNodes->Node(this->PltOutletNodeNum).TempSetPoint;
1501 0 : } break;
1502 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
1503 0 : this->ITSOutletSetPointTemp = state.dataLoopNodes->Node(this->PltOutletNodeNum).TempSetPointHi;
1504 0 : } break;
1505 0 : default:
1506 0 : break;
1507 : }
1508 0 : this->ITSCoolingRate = 0.0; //[W]
1509 0 : this->ITSCoolingEnergy = 0.0; //[J]
1510 :
1511 : // Initialize processed U values
1512 0 : this->Urate = 0.0;
1513 :
1514 : // Calculate QiceMax which is REAL(r64) ITS capacity.
1515 : // There are three possible to calculate QiceMax
1516 : // with ChillerCapacity(Chiller+ITS), ITS capacity(ITS), and QchillerMax(Chiller).
1517 : //--------------------------------------------------------
1518 : // Calculate QiceMax with QiceMaxByChiller, QiceMaxByITS, QchillerMax
1519 : //--------------------------------------------------------
1520 : // Calculate Qice charge max by Chiller with Twb and UAIceCh
1521 : Real64 QiceMaxByChiller;
1522 0 : this->CalcQiceChargeMaxByChiller(state, QiceMaxByChiller); //[W]
1523 :
1524 : // Chiller is remote now, so chiller out is inlet node temp
1525 0 : Real64 chillerOutletTemp = state.dataLoopNodes->Node(this->PltInletNodeNum).Temp;
1526 : // Calculate Qice charge max by ITS with ChillerOutletTemp
1527 : Real64 QiceMaxByITS;
1528 0 : this->CalcQiceChargeMaxByITS(chillerOutletTemp, QiceMaxByITS); //[W]
1529 :
1530 : // Select minimum as QiceMax
1531 : // Because It is uncertain that QiceMax by chiller is same as QiceMax by ITS.
1532 0 : Real64 QiceMax = min(QiceMaxByChiller, QiceMaxByITS);
1533 :
1534 : //--------------------------------------------------------
1535 : // Calculate Umin,Umax,Uact
1536 : //--------------------------------------------------------
1537 : // Set Umin
1538 : // Calculate Umax based on real ITS Max Capacity and remained XCurIceFrac.
1539 : // Umax should be equal or larger than 0.02 for realistic purpose by Dion.
1540 0 : Real64 Umax = max(min(((1.0 - EpsLimitForCharge) * QiceMax * TimeInterval / this->ITSNomCap), (1.0 - this->XCurIceFrac - EpsLimitForX)), 0.0);
1541 :
1542 : // Cannot charge more than the fraction that is left uncharged
1543 0 : Umax = min(Umax, (1.0 - this->IceFracRemain) / state.dataHVACGlobal->TimeStepSys);
1544 : // First, check input U value.
1545 : // Based on Umax and Umin, if necessary to run E+, calculate proper Uact.
1546 : Real64 Uact;
1547 0 : if (Umax == 0.0) { //(No Capacity of ITS), ITS is OFF.
1548 0 : Uact = 0.0;
1549 :
1550 : } else { // Umax non-zero
1551 0 : Uact = Umax;
1552 : } // Check Uact for Discharging Process
1553 :
1554 : //--------------------------------------------------------
1555 : // Calculate possible ITSChargingRate with Uact, Then error check
1556 : //--------------------------------------------------------
1557 : // Calculate possible ITSChargingRate with Uact
1558 0 : Real64 Qice = Uact * this->ITSNomCap / TimeInterval; //[W]
1559 : // If Qice is equal or less than 0.0, no need to calculate anymore.
1560 0 : if (Qice <= 0.0) {
1561 0 : this->Urate = 0.0; //[ratio]
1562 : }
1563 :
1564 : // Calculate leaving water temperature
1565 0 : if ((Qice <= 0.0) || (this->XCurIceFrac >= 1.0)) {
1566 0 : this->ITSOutletTemp = this->ITSInletTemp;
1567 0 : Qice = 0.0;
1568 0 : Uact = 0.0;
1569 : } else {
1570 0 : Real64 DeltaTemp = Qice / Psychrometrics::CPCW(this->ITSInletTemp) / this->ITSMassFlowRate;
1571 0 : this->ITSOutletTemp = this->ITSInletTemp + DeltaTemp;
1572 : // Limit leaving temp to be no greater than setpoint or freezing temp minus 1C
1573 0 : this->ITSOutletTemp = min(this->ITSOutletTemp, this->ITSOutletSetPointTemp, (FreezTemp - 1));
1574 : // Limit leaving temp to be no less than inlet temp
1575 0 : this->ITSOutletTemp = max(this->ITSOutletTemp, this->ITSInletTemp);
1576 0 : DeltaTemp = this->ITSOutletTemp - this->ITSInletTemp;
1577 0 : Qice = DeltaTemp * Psychrometrics::CPCW(this->ITSInletTemp) * this->ITSMassFlowRate;
1578 0 : Uact = Qice / (this->ITSNomCap / TimeInterval);
1579 : } // End of leaving temp checks
1580 :
1581 0 : this->Urate = Uact;
1582 0 : this->ITSCoolingRate = -Qice;
1583 0 : this->ITSCoolingEnergy = this->ITSCoolingRate * state.dataHVACGlobal->TimeStepSysSec;
1584 0 : }
1585 :
1586 : //******************************************************************************
1587 :
1588 0 : void SimpleIceStorageData::CalcQiceChargeMaxByChiller(EnergyPlusData &state, Real64 &QiceMaxByChiller)
1589 : {
1590 : // METHODOLOGY EMPLOYED:
1591 : // Calculation inside is IP unit, then return QiceMaxByChiller as SI [W] unit.
1592 :
1593 : // Chiller is remote now, so chiller out is inlet node temp
1594 0 : Real64 TchillerOut = state.dataLoopNodes->Node(this->PltInletNodeNum).Temp;
1595 0 : QiceMaxByChiller = this->UAIceCh * (FreezTemp - TchillerOut); //[W] = [W/degC]*[degC]
1596 :
1597 : // If it happened, it is occurred at the Discharging or Dormant process.
1598 0 : if (QiceMaxByChiller <= 0.0) {
1599 0 : QiceMaxByChiller = 0.0;
1600 : }
1601 0 : }
1602 :
1603 0 : void SimpleIceStorageData::CalcQiceChargeMaxByITS(Real64 const chillerOutletTemp, // [degC]
1604 : Real64 &QiceMaxByITS // [W]
1605 : )
1606 : {
1607 : // Qice is maximized when ChillerInletTemp and ChillerOutletTemp(input data) is almost same due to LMTD method.
1608 : // Qice is minimized(=0) when ChillerInletTemp is almost same as FreezTemp(=0).
1609 :
1610 : // Initialize
1611 0 : Real64 Tfr = FreezTempIP;
1612 0 : Real64 ChOutletTemp = TempSItoIP(chillerOutletTemp); //[degF] = ConvertSItoIP[degC]
1613 : // Chiller outlet temp must be below freeze temp, or else no charge
1614 0 : if (ChOutletTemp >= Tfr) {
1615 0 : QiceMaxByITS = 0.0;
1616 : } else {
1617 : // Make ChillerInletTemp as almost same as ChillerOutletTemp(input data)
1618 0 : Real64 ChillerInletTemp = ChOutletTemp + 0.01;
1619 : // ChillerInletTemp cannot be greater than or equal to freeze temp
1620 0 : if (ChillerInletTemp >= Tfr) {
1621 0 : ChillerInletTemp = ChOutletTemp + (Tfr - ChOutletTemp) / 2;
1622 : }
1623 :
1624 0 : Real64 LogTerm = (Tfr - ChOutletTemp) / (Tfr - ChillerInletTemp);
1625 : // Need to protect this from LogTerm <= 0 - not sure what it should do then
1626 0 : if (LogTerm <= 0.0) {
1627 0 : ChillerInletTemp = ChOutletTemp;
1628 0 : QiceMaxByITS = 0.0;
1629 : }
1630 0 : QiceMaxByITS = this->UAIceCh * (TempIPtoSI(ChillerInletTemp) - TempIPtoSI(ChOutletTemp)) / std::log(LogTerm);
1631 : }
1632 0 : }
1633 :
1634 0 : void SimpleIceStorageData::CalcIceStorageDischarge(EnergyPlusData &state,
1635 : Real64 const myLoad, // operating load
1636 : bool const RunFlag, // TRUE when ice storage operating
1637 : Real64 const MaxCap // Max possible discharge rate (positive value)
1638 : )
1639 : {
1640 : static constexpr std::string_view RoutineName("SimpleIceStorageData::CalcIceStorageDischarge");
1641 :
1642 : // Initialize processed Rate and Energy
1643 0 : this->ITSMassFlowRate = 0.0;
1644 0 : this->ITSCoolingRate = 0.0;
1645 0 : this->ITSCoolingEnergy = 0.0;
1646 :
1647 0 : switch (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).LoopDemandCalcScheme) {
1648 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
1649 0 : this->ITSOutletSetPointTemp = state.dataLoopNodes->Node(this->PltOutletNodeNum).TempSetPoint;
1650 0 : } break;
1651 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
1652 0 : this->ITSOutletSetPointTemp = state.dataLoopNodes->Node(this->PltOutletNodeNum).TempSetPointHi;
1653 0 : } break;
1654 0 : default:
1655 0 : break;
1656 : }
1657 :
1658 : // Initialize processed U values
1659 0 : this->Urate = 0.0;
1660 :
1661 : // If no component demand or ITS OFF, then RETURN.
1662 0 : if (myLoad == 0 || !RunFlag) {
1663 0 : this->ITSMassFlowRate = 0.0;
1664 0 : this->ITSInletTemp = state.dataLoopNodes->Node(this->PltInletNodeNum).Temp;
1665 0 : this->ITSOutletTemp = this->ITSInletTemp;
1666 0 : this->ITSCoolingRate = 0.0;
1667 0 : this->ITSCoolingEnergy = 0.0;
1668 0 : return;
1669 : }
1670 :
1671 : // If FlowLock(provided by PlantSupplyManager) is False(=0), that is, MyLoad is not changed.
1672 : // then based on MyLoad, new ITSMassFlowRate will be calculated.
1673 :
1674 : //----------------------------
1675 0 : int loopNum = this->plantLoc.loopNum;
1676 :
1677 : Real64 CpFluid =
1678 0 : state.dataPlnt->PlantLoop(loopNum).glycol->getSpecificHeat(state, state.dataLoopNodes->Node(this->PltInletNodeNum).Temp, RoutineName);
1679 :
1680 : // Calculate Umyload based on MyLoad from E+
1681 0 : Real64 Umyload = -myLoad * TimeInterval / this->ITSNomCap;
1682 : // Calculate Umax and Umin
1683 : // Cannot discharge more than the fraction that is left
1684 0 : Real64 Umax = -this->IceFracRemain / state.dataHVACGlobal->TimeStepSys;
1685 : // Calculate Umin based on returned MyLoad from E+.
1686 0 : Real64 Umin = min(Umyload, 0.0);
1687 : // Based on Umax and Umin, if necessary to run E+, calculate proper Uact
1688 : // U is negative here.
1689 0 : Real64 Uact = max(Umin, Umax);
1690 :
1691 : // Set ITSInletTemp provided by E+
1692 0 : this->ITSInletTemp = state.dataLoopNodes->Node(this->PltInletNodeNum).Temp;
1693 : // The first thing is to set the ITSMassFlowRate
1694 0 : this->ITSMassFlowRate = this->DesignMassFlowRate; //[kg/s]
1695 :
1696 0 : PlantUtilities::SetComponentFlowRate(state, this->ITSMassFlowRate, this->PltInletNodeNum, this->PltOutletNodeNum, this->plantLoc);
1697 :
1698 : // Qice is calculate input U which is within boundary between Umin and Umax.
1699 0 : Real64 Qice = Uact * this->ITSNomCap / TimeInterval;
1700 : // Qice cannot exceed MaxCap calculated by CalcIceStorageCapacity
1701 : // Note Qice is negative here, MaxCap is positive
1702 0 : Qice = max(Qice, -MaxCap);
1703 :
1704 : // Calculate leaving water temperature
1705 0 : if ((Qice >= 0.0) || (this->XCurIceFrac <= 0.0) || (this->ITSMassFlowRate < DataBranchAirLoopPlant::MassFlowTolerance)) {
1706 0 : this->ITSOutletTemp = this->ITSInletTemp;
1707 0 : Qice = 0.0;
1708 0 : Uact = 0.0;
1709 : } else {
1710 0 : Real64 DeltaTemp = Qice / CpFluid / this->ITSMassFlowRate;
1711 0 : this->ITSOutletTemp = this->ITSInletTemp + DeltaTemp;
1712 : // Limit leaving temp to be no less than setpoint or freezing temp plus 1C
1713 0 : this->ITSOutletTemp = max(this->ITSOutletTemp, this->ITSOutletSetPointTemp, (FreezTemp + 1));
1714 : // Limit leaving temp to be no greater than inlet temp
1715 0 : this->ITSOutletTemp = min(this->ITSOutletTemp, this->ITSInletTemp);
1716 0 : DeltaTemp = this->ITSOutletTemp - this->ITSInletTemp;
1717 0 : Qice = DeltaTemp * CpFluid * this->ITSMassFlowRate;
1718 0 : Uact = Qice / (this->ITSNomCap / TimeInterval);
1719 : } // End of leaving temp checks
1720 :
1721 : // Calculate reported U value
1722 0 : this->Urate = Uact;
1723 : // Calculate ITSCoolingEnergy [J]
1724 0 : this->ITSCoolingRate = -Qice;
1725 0 : this->ITSCoolingEnergy = this->ITSCoolingRate * state.dataHVACGlobal->TimeStepSysSec;
1726 : }
1727 :
1728 0 : void SimpleIceStorageData::CalcQiceDischageMax(EnergyPlusData &state, Real64 &QiceMin)
1729 : {
1730 :
1731 : // Qice is minimized when ITSInletTemp and ITSOutletTemp is almost same due to LMTD method.
1732 : // Qice is maximized(=0) when ITSOutletTemp is almost same as FreezTemp(=0).
1733 :
1734 0 : Real64 ITSInletTemp_loc = state.dataLoopNodes->Node(this->PltInletNodeNum).Temp;
1735 0 : Real64 ITSOutletTemp_loc = 0.0;
1736 0 : switch (state.dataPlnt->PlantLoop(this->plantLoc.loopNum).LoopDemandCalcScheme) {
1737 0 : case DataPlant::LoopDemandCalcScheme::SingleSetPoint: {
1738 0 : ITSOutletTemp_loc = state.dataLoopNodes->Node(this->PltOutletNodeNum).TempSetPoint;
1739 0 : } break;
1740 0 : case DataPlant::LoopDemandCalcScheme::DualSetPointDeadBand: {
1741 0 : ITSOutletTemp_loc = state.dataLoopNodes->Node(this->PltOutletNodeNum).TempSetPointHi;
1742 0 : } break;
1743 0 : default: {
1744 0 : assert(false);
1745 : } break;
1746 : }
1747 :
1748 0 : Real64 LogTerm = (ITSInletTemp_loc - FreezTemp) / (ITSOutletTemp_loc - FreezTemp);
1749 :
1750 0 : if (LogTerm <= 1) {
1751 0 : QiceMin = 0.0;
1752 : } else {
1753 0 : QiceMin = this->UAIceDisCh * (ITSInletTemp_loc - ITSOutletTemp_loc) / std::log(LogTerm);
1754 : }
1755 0 : }
1756 :
1757 0 : void SimpleIceStorageData::CalcUAIce(Real64 const XCurIceFrac_loc, Real64 &UAIceCh_loc, Real64 &UAIceDisCh_loc, Real64 &HLoss_loc)
1758 : {
1759 : // SUBROUTINE INFORMATION:
1760 : // AUTHOR
1761 : // DATE WRITTEN
1762 : // MODIFIED
1763 : // RE-ENGINEERED
1764 :
1765 : // PURPOSE OF THIS SUBROUTINE:
1766 :
1767 : // METHODOLOGY EMPLOYED:
1768 : // This routine is function of XCurIceFrac, and UA value is based on 1 hour.
1769 :
1770 0 : switch (this->ITSType_Num) {
1771 0 : case ITSType::IceOnCoilInternal: {
1772 0 : Real64 y = XCurIceFrac_loc;
1773 0 : UAIceCh_loc = (1.3879 - 7.6333 * y + 26.3423 * pow_2(y) - 47.6084 * pow_3(y) + 41.8498 * pow_4(y) - 14.2948 * pow_5(y)) *
1774 0 : this->ITSNomCap / TimeInterval / 10.0; // [W/C]
1775 0 : y = 1.0 - XCurIceFrac_loc;
1776 0 : UAIceDisCh_loc = (1.3879 - 7.6333 * y + 26.3423 * pow_2(y) - 47.6084 * pow_3(y) + 41.8498 * pow_4(y) - 14.2948 * pow_5(y)) *
1777 0 : this->ITSNomCap / TimeInterval / 10.0; // [W/C]
1778 0 : HLoss_loc = 0.0;
1779 0 : } break;
1780 0 : case ITSType::IceOnCoilExternal: {
1781 0 : Real64 y = XCurIceFrac_loc;
1782 0 : UAIceCh_loc = (1.3879 - 7.6333 * y + 26.3423 * pow_2(y) - 47.6084 * pow_3(y) + 41.8498 * pow_4(y) - 14.2948 * pow_5(y)) *
1783 0 : this->ITSNomCap / TimeInterval / 10.0; // [W/C]
1784 0 : y = 1.0 - XCurIceFrac_loc;
1785 0 : UAIceDisCh_loc = (1.1756 - 5.3689 * y + 17.3602 * pow_2(y) - 30.1077 * pow_3(y) + 25.6387 * pow_4(y) - 8.5102 * pow_5(y)) *
1786 0 : this->ITSNomCap / TimeInterval / 10.0; // [W/C]
1787 0 : HLoss_loc = 0.0;
1788 0 : } break;
1789 0 : default:
1790 0 : break;
1791 : }
1792 0 : }
1793 :
1794 0 : Real64 CalcDetIceStorLMTDstar(Real64 const Tin, // ice storage unit inlet temperature
1795 : Real64 const Tout, // ice storage unit outlet (setpoint) temperature
1796 : Real64 const Tfr // freezing temperature
1797 : )
1798 : {
1799 :
1800 : // SUBROUTINE INFORMATION:
1801 : // AUTHOR Rick Strand
1802 : // DATE WRITTEN February 2006
1803 : // MODIFIED na
1804 : // RE-ENGINEERED na
1805 :
1806 : // PURPOSE OF THIS SUBROUTINE:
1807 : // This subroutine calculates the log mean temperature difference for
1808 : // the detailed ice storage unit. The temperature difference is non-
1809 : // dimensionalized using a nominal temperature difference of 10C.
1810 : // This value must be used when obtaining the curve fit coefficients.
1811 :
1812 : // METHODOLOGY EMPLOYED:
1813 : // Straight-forward calculation where:
1814 : // LMTD* = LMTD/Tnom
1815 : // LMTD = (Tin-Tout)/ln((Tin-Tfr)/(Tout-Tfr))
1816 :
1817 : Real64 CalcDetIceStorLMTDstar;
1818 0 : Real64 constexpr Tnom(10.0); // Nominal temperature difference across the ice storage unit [C]
1819 :
1820 : // First set the temperature differences and avoid problems with the LOG
1821 : // term by setting some reasonable minimums
1822 0 : Real64 DeltaTio = std::abs(Tin - Tout); // Inlet to outlet temperature difference
1823 0 : Real64 DeltaTif = std::abs(Tin - Tfr); // Inlet to freezing temperature difference
1824 0 : Real64 DeltaTof = std::abs(Tout - Tfr); // Outlet to freezing temperature difference
1825 :
1826 0 : if (DeltaTif < DeltaTifMin) DeltaTif = DeltaTifMin;
1827 0 : if (DeltaTof < DeltaTofMin) DeltaTof = DeltaTofMin;
1828 :
1829 0 : CalcDetIceStorLMTDstar = (DeltaTio / std::log(DeltaTif / DeltaTof)) / Tnom;
1830 :
1831 0 : return CalcDetIceStorLMTDstar;
1832 : }
1833 :
1834 4 : Real64 CalcQstar(EnergyPlusData &state,
1835 : int const CurveIndex, // curve index
1836 : enum CurveVars CurveIndVarType, // independent variable type for ice storage
1837 : Real64 const FracCharged, // fraction charged for ice storage unit
1838 : Real64 const LMTDstar, // normalized log mean temperature difference across the ice storage unit
1839 : Real64 const MassFlowstar // normalized mass flow rate through the ice storage unit
1840 : )
1841 : {
1842 :
1843 : Real64 CalcQstar;
1844 :
1845 4 : if (CurveIndVarType == CurveVars::FracChargedLMTD) {
1846 1 : CalcQstar = std::abs(Curve::CurveValue(state, CurveIndex, FracCharged, LMTDstar));
1847 3 : } else if (CurveIndVarType == CurveVars::FracDischargedLMTD) {
1848 1 : CalcQstar = std::abs(Curve::CurveValue(state, CurveIndex, (1.0 - FracCharged), LMTDstar));
1849 2 : } else if (CurveIndVarType == CurveVars::LMTDMassFlow) {
1850 1 : CalcQstar = std::abs(Curve::CurveValue(state, CurveIndex, LMTDstar, MassFlowstar));
1851 1 : } else if (CurveIndVarType == CurveVars::LMTDFracCharged) {
1852 1 : CalcQstar = std::abs(Curve::CurveValue(state, CurveIndex, LMTDstar, FracCharged));
1853 : } else { // should never get here as this is checked on input
1854 0 : CalcQstar = 0.0;
1855 : }
1856 :
1857 4 : return CalcQstar;
1858 : }
1859 :
1860 0 : Real64 TempSItoIP(Real64 const Temp)
1861 : {
1862 0 : return (Temp * 9.0 / 5.0) + 32.0;
1863 : }
1864 :
1865 208 : Real64 TempIPtoSI(Real64 const Temp)
1866 : {
1867 208 : return (Temp - 32.0) * 5.0 / 9.0;
1868 : }
1869 :
1870 0 : void SimpleIceStorageData::UpdateNode(EnergyPlusData &state, Real64 const myLoad, bool const RunFlag)
1871 : {
1872 : // SUBROUTINE INFORMATION:
1873 : // AUTHOR: Dan Fisher
1874 : // DATE WRITTEN: October 1998
1875 :
1876 : // Update Node Inlet & Outlet MassFlowRat
1877 0 : PlantUtilities::SafeCopyPlantNode(state, this->PltInletNodeNum, this->PltOutletNodeNum);
1878 0 : if (myLoad == 0 || !RunFlag) {
1879 : // Update Outlet Conditions so that same as Inlet, so component can be bypassed if necessary
1880 0 : state.dataLoopNodes->Node(this->PltOutletNodeNum).Temp = state.dataLoopNodes->Node(this->PltInletNodeNum).Temp;
1881 : } else {
1882 0 : state.dataLoopNodes->Node(this->PltOutletNodeNum).Temp = this->ITSOutletTemp;
1883 : }
1884 0 : }
1885 :
1886 0 : void SimpleIceStorageData::RecordOutput(Real64 const myLoad, bool const RunFlag)
1887 : {
1888 0 : if (myLoad == 0 || !RunFlag) {
1889 0 : this->MyLoad = myLoad;
1890 0 : this->ITSCoolingRate_rep = 0.0;
1891 0 : this->ITSCoolingEnergy_rep = 0.0;
1892 0 : this->ITSChargingRate = 0.0;
1893 0 : this->ITSChargingEnergy = 0.0;
1894 0 : this->ITSmdot = 0.0;
1895 :
1896 : } else {
1897 0 : this->MyLoad = myLoad;
1898 0 : if (this->ITSCoolingRate > 0.0) {
1899 0 : this->ITSCoolingRate_rep = this->ITSCoolingRate;
1900 0 : this->ITSCoolingEnergy_rep = this->ITSCoolingEnergy;
1901 0 : this->ITSChargingRate = 0.0;
1902 0 : this->ITSChargingEnergy = 0.0;
1903 : } else {
1904 0 : this->ITSCoolingRate_rep = 0.0;
1905 0 : this->ITSCoolingEnergy_rep = 0.0;
1906 0 : this->ITSChargingRate = -this->ITSCoolingRate;
1907 0 : this->ITSChargingEnergy = -this->ITSCoolingEnergy;
1908 : }
1909 0 : this->ITSmdot = this->ITSMassFlowRate;
1910 : }
1911 0 : }
1912 :
1913 287262 : void UpdateIceFractions(EnergyPlusData const &state)
1914 : {
1915 :
1916 : // SUBROUTINE INFORMATION:
1917 : // AUTHOR Mike Witte
1918 : // DATE WRITTEN September 2005
1919 : // MODIFIED Rick Strand (Feb 2006, for detailed ice storage model)
1920 : // RE-ENGINEERED na
1921 :
1922 : // PURPOSE OF THIS SUBROUTINE:
1923 : // Update all ice fractions at end of system time step.
1924 :
1925 : // METHODOLOGY EMPLOYED:
1926 : // This is called from HVACManager once we have actually stepped forward
1927 : // a system time step.
1928 :
1929 287262 : for (auto &thisITS : state.dataIceThermalStorage->SimpleIceStorage) {
1930 0 : thisITS.IceFracRemain += thisITS.Urate * state.dataHVACGlobal->TimeStepSys;
1931 0 : if (thisITS.IceFracRemain <= 0.001) thisITS.IceFracRemain = 0.0;
1932 0 : if (thisITS.IceFracRemain > 1.0) thisITS.IceFracRemain = 1.0;
1933 : }
1934 :
1935 287262 : for (auto &thisITS : state.dataIceThermalStorage->DetailedIceStorage) {
1936 0 : thisITS.IceFracRemaining += thisITS.IceFracChange - (thisITS.TankLossCoeff * state.dataHVACGlobal->TimeStepSys);
1937 0 : if (thisITS.IceFracRemaining < 0.001) thisITS.IceFracRemaining = 0.0;
1938 0 : if (thisITS.IceFracRemaining > 1.000) thisITS.IceFracRemaining = 1.0;
1939 : // Reset the ice on the coil to zero for inside melt whenever discharging takes place.
1940 : // This assumes that any remaining ice floats away from the coil and resettles perfectly.
1941 : // While this is not exactly what happens and it is possible theoretically to have multiple
1942 : // freeze thaw cycles that are not complete, this is the best we can do.
1943 0 : if (thisITS.ThawProcessIndex == DetIce::InsideMelt) {
1944 0 : if (thisITS.IceFracChange < 0.0) {
1945 0 : thisITS.IceFracOnCoil = 0.0;
1946 : } else {
1947 : // Assume loss term does not impact ice on the coil but what is remaining
1948 0 : thisITS.IceFracOnCoil += thisITS.IceFracChange;
1949 : // If the ice remaining has run out because of tank losses, reset ice fraction on coil so that it keeps track of losses
1950 0 : if (thisITS.IceFracOnCoil > thisITS.IceFracRemaining) thisITS.IceFracOnCoil = thisITS.IceFracRemaining;
1951 : }
1952 : } else { // Outside melt system so IceFracOnCoil is always the same as IceFracRemaining (needs to be done for reporting only)
1953 0 : thisITS.IceFracOnCoil = thisITS.IceFracRemaining;
1954 : }
1955 : }
1956 287262 : }
1957 :
1958 0 : void DetailedIceStorageData::UpdateDetailedIceStorage(EnergyPlusData &state)
1959 : {
1960 :
1961 : // SUBROUTINE INFORMATION:
1962 : // AUTHOR Rick Strand
1963 : // DATE WRITTEN February 2006
1964 : // MODIFIED na
1965 : // RE-ENGINEERED na
1966 :
1967 : // PURPOSE OF THIS SUBROUTINE:
1968 : // This subroutine takes the necessary information from the local data
1969 : // structure and moves it back to the loop node data structure.
1970 :
1971 : // METHODOLOGY EMPLOYED:
1972 : // Not much mystery here--just move the data to the appropriate place
1973 : // for the detailed ice storage system in question.
1974 :
1975 : // Set the temperature and flow rate for the component outlet node
1976 0 : int InNodeNum = this->PlantInNodeNum;
1977 0 : int OutNodeNum = this->PlantOutNodeNum;
1978 :
1979 0 : PlantUtilities::SafeCopyPlantNode(state, InNodeNum, OutNodeNum);
1980 :
1981 0 : state.dataLoopNodes->Node(OutNodeNum).Temp = this->OutletTemp;
1982 0 : }
1983 :
1984 0 : void DetailedIceStorageData::ReportDetailedIceStorage(EnergyPlusData &state)
1985 : {
1986 :
1987 : // SUBROUTINE INFORMATION:
1988 : // AUTHOR Rick Strand
1989 : // DATE WRITTEN February 2006
1990 : // MODIFIED na
1991 : // RE-ENGINEERED na
1992 :
1993 : // PURPOSE OF THIS SUBROUTINE:
1994 : // This subroutine reports all of the output necessary for the model.
1995 :
1996 : // METHODOLOGY EMPLOYED:
1997 : // Just take what has already been calculated or calculate the appropriate
1998 : // output value based on simulation data.
1999 :
2000 0 : Real64 constexpr LowLoadLimit(0.1); // Load below which device can be assumed off [W]
2001 :
2002 0 : if (this->CompLoad < LowLoadLimit) { // No load condition
2003 :
2004 0 : this->IceFracChange = 0.0;
2005 0 : this->DischargingRate = 0.0;
2006 0 : this->DischargingEnergy = 0.0;
2007 0 : this->ChargingRate = 0.0;
2008 0 : this->ChargingEnergy = 0.0;
2009 0 : this->ParasiticElecRate = 0.0;
2010 0 : this->ParasiticElecEnergy = 0.0;
2011 :
2012 : } else { // There is a load, determine whether we are charging or discharging based on inlet and outlet temperature
2013 :
2014 0 : if (this->InletTemp < this->OutletTemp) { // Charging Mode
2015 :
2016 0 : this->ChargingRate = this->CompLoad;
2017 0 : this->ChargingEnergy = this->CompLoad * (state.dataHVACGlobal->TimeStepSysSec);
2018 0 : this->IceFracChange = this->CompLoad * state.dataHVACGlobal->TimeStepSys / this->NomCapacity;
2019 0 : this->DischargingRate = 0.0;
2020 0 : this->DischargingEnergy = 0.0;
2021 0 : this->ParasiticElecRate = this->ChargeParaElecLoad * this->CompLoad;
2022 0 : this->ParasiticElecEnergy = this->ChargeParaElecLoad * this->ChargingEnergy;
2023 :
2024 : } else { // (DetailedIceStorage(IceNum)%InletTemp < DetailedIceStorage(IceNum)%OutletTemp) Discharging Mode
2025 :
2026 0 : this->DischargingRate = this->CompLoad;
2027 0 : this->DischargingEnergy = this->CompLoad * (state.dataHVACGlobal->TimeStepSysSec);
2028 0 : this->IceFracChange = -this->CompLoad * state.dataHVACGlobal->TimeStepSys / this->NomCapacity;
2029 0 : this->ChargingRate = 0.0;
2030 0 : this->ChargingEnergy = 0.0;
2031 0 : this->ParasiticElecRate = this->DischargeParaElecLoad * this->CompLoad;
2032 0 : this->ParasiticElecEnergy = this->DischargeParaElecLoad * this->ChargingEnergy;
2033 : }
2034 : }
2035 0 : }
2036 :
2037 : } // namespace IceThermalStorage
2038 :
2039 : } // namespace EnergyPlus
|