Line data Source code
1 : // EnergyPlus, Copyright (c) 1996-2024, The Board of Trustees of the University of Illinois,
2 : // The Regents of the University of California, through Lawrence Berkeley National Laboratory
3 : // (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge
4 : // National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other
5 : // contributors. All rights reserved.
6 : //
7 : // NOTICE: This Software was developed under funding from the U.S. Department of Energy and the
8 : // U.S. Government consequently retains certain rights. As such, the U.S. Government has been
9 : // granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable,
10 : // worldwide license in the Software to reproduce, distribute copies to the public, prepare
11 : // derivative works, and perform publicly and display publicly, and to permit others to do so.
12 : //
13 : // Redistribution and use in source and binary forms, with or without modification, are permitted
14 : // provided that the following conditions are met:
15 : //
16 : // (1) Redistributions of source code must retain the above copyright notice, this list of
17 : // conditions and the following disclaimer.
18 : //
19 : // (2) Redistributions in binary form must reproduce the above copyright notice, this list of
20 : // conditions and the following disclaimer in the documentation and/or other materials
21 : // provided with the distribution.
22 : //
23 : // (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory,
24 : // the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be
25 : // used to endorse or promote products derived from this software without specific prior
26 : // written permission.
27 : //
28 : // (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form
29 : // without changes from the version obtained under this License, or (ii) Licensee makes a
30 : // reference solely to the software portion of its product, Licensee must refer to the
31 : // software as "EnergyPlus version X" software, where "X" is the version number Licensee
32 : // obtained under this License and may not use a different name for the software. Except as
33 : // specifically required in this Section (4), Licensee shall not use in a company name, a
34 : // product name, in advertising, publicity, or other promotional activities any name, trade
35 : // name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly
36 : // similar designation, without the U.S. Department of Energy's prior written consent.
37 : //
38 : // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
39 : // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
40 : // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
41 : // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 : // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 : // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
44 : // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
45 : // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46 : // POSSIBILITY OF SUCH DAMAGE.
47 :
48 : // C++ Headers
49 : #include <cmath>
50 :
51 : // ObjexxFCL Headers
52 : #include <ObjexxFCL/Array.functions.hh>
53 : #include <ObjexxFCL/Fmath.hh>
54 :
55 : // EnergyPlus Headers
56 : #include <EnergyPlus/CurveManager.hh>
57 : #include <EnergyPlus/Data/EnergyPlusData.hh>
58 : #include <EnergyPlus/DataGenerators.hh>
59 : #include <EnergyPlus/DataGlobalConstants.hh>
60 : #include <EnergyPlus/DataHVACGlobals.hh>
61 : #include <EnergyPlus/DataLoopNode.hh>
62 : #include <EnergyPlus/GeneratorDynamicsManager.hh>
63 : #include <EnergyPlus/MicroCHPElectricGenerator.hh>
64 : #include <EnergyPlus/PlantUtilities.hh>
65 : #include <EnergyPlus/ScheduleManager.hh>
66 :
67 : namespace EnergyPlus {
68 :
69 : namespace GeneratorDynamicsManager {
70 :
71 : //_______________________________________________
72 : // Utility modules used by other generators.
73 : //
74 : // GeneratorDynamicsManager
75 : // reused among some generators to on/off state, transient limits, control implications etc.
76 :
77 : // Module containing the routines dealing with the management of dynamic constraints on Generator response
78 :
79 : // MODULE INFORMATION:
80 : // AUTHOR B. Griffith
81 : // DATE WRITTEN July 2006
82 :
83 : // PURPOSE OF THIS MODULE:
84 : // collect routines for managing generator states
85 : // reused by different generator models
86 : // determine response that generator is capable of providing
87 : // given load request data
88 : // models requiring calculations across timesteps
89 :
90 10 : void SetupGeneratorControlStateManager(EnergyPlusData &state, int const GenNum) // index of generator to setup
91 : {
92 : // SUBROUTINE INFORMATION:
93 : // AUTHOR B. Griffith
94 : // DATE WRITTEN July 2006
95 :
96 : // PURPOSE OF THIS SUBROUTINE:
97 : // sets up data structures
98 :
99 : // METHODOLOGY EMPLOYED:
100 : // like a get input routine but feeds from
101 : // parent objects, could have its own input object someday
102 :
103 : // get the number of generators that might use this module
104 10 : int const NumGensWDynamics = state.dataCHPElectGen->NumMicroCHPs; // TODO + NumFuelCellCGenerators
105 :
106 10 : if (!allocated(state.dataGenerator->GeneratorDynamics)) {
107 2 : state.dataGenerator->GeneratorDynamics.allocate(NumGensWDynamics);
108 : }
109 :
110 : // first populate with Micro CHP data
111 10 : auto &thisGen = state.dataGenerator->GeneratorDynamics(GenNum);
112 10 : auto &thisMicroCHP = state.dataCHPElectGen->MicroCHP(GenNum);
113 10 : thisGen.Name = thisMicroCHP.Name;
114 10 : thisGen.PelMin = thisMicroCHP.A42Model.MinElecPower;
115 10 : thisGen.PelMax = thisMicroCHP.A42Model.MaxElecPower;
116 10 : thisGen.UpTranLimit = thisMicroCHP.A42Model.DeltaPelMax;
117 10 : thisGen.DownTranLimit = thisMicroCHP.A42Model.DeltaPelMax;
118 10 : thisGen.UpTranLimitFuel = thisMicroCHP.A42Model.DeltaFuelMdotMax;
119 10 : thisGen.DownTranLimitFuel = thisMicroCHP.A42Model.DeltaFuelMdotMax;
120 10 : thisGen.WarmUpByTimeDelay = thisMicroCHP.A42Model.WarmUpByTimeDelay;
121 10 : thisGen.WarmUpByEngineTemp = thisMicroCHP.A42Model.WarmUpByEngineTemp;
122 10 : thisGen.MandatoryFullCoolDown = thisMicroCHP.A42Model.MandatoryFullCoolDown;
123 10 : thisGen.WarmRestartOkay = thisMicroCHP.A42Model.WarmRestartOkay;
124 10 : thisGen.WarmUpDelay = thisMicroCHP.A42Model.WarmUpDelay;
125 10 : thisGen.CoolDownDelay = thisMicroCHP.A42Model.CoolDownDelay / Constant::SecInHour; // seconds to hours
126 10 : thisGen.PcoolDown = thisMicroCHP.A42Model.PcoolDown;
127 10 : thisGen.Pstandby = thisMicroCHP.A42Model.Pstandby;
128 10 : thisGen.MCeng = thisMicroCHP.A42Model.MCeng;
129 10 : thisGen.MCcw = thisMicroCHP.A42Model.MCcw;
130 10 : thisGen.kf = thisMicroCHP.A42Model.kf;
131 10 : thisGen.TnomEngOp = thisMicroCHP.A42Model.TnomEngOp;
132 10 : thisGen.kp = thisMicroCHP.A42Model.kp;
133 10 : thisGen.AvailabilitySchedID = thisMicroCHP.AvailabilitySchedID;
134 10 : thisGen.StartUpTimeDelay = thisMicroCHP.A42Model.WarmUpDelay / Constant::SecInHour; // seconds to hours
135 :
136 10 : thisGen.ElectEffNom = thisMicroCHP.A42Model.ElecEff;
137 10 : thisGen.ThermEffNom = thisMicroCHP.A42Model.ThermEff;
138 10 : thisGen.QdotHXMax = thisMicroCHP.A42Model.ThermEff * thisMicroCHP.A42Model.MaxElecPower / thisMicroCHP.A42Model.ElecEff;
139 10 : thisGen.QdotHXMin = thisMicroCHP.A42Model.ThermEff * thisMicroCHP.A42Model.MinElecPower / thisMicroCHP.A42Model.ElecEff;
140 10 : thisGen.QdotHXOpt = thisGen.QdotHXMax;
141 10 : thisMicroCHP.DynamicsControlID = GenNum;
142 10 : }
143 :
144 15328 : void ManageGeneratorControlState(EnergyPlusData &state,
145 : int const GeneratorNum, // Generator number
146 : bool const RunFlagElectCenter, // TRUE when Generator operating per electric load center request
147 : bool const RunFlagPlant, // TRUE when generator operating per Plant request (always false)
148 : Real64 const ElecLoadRequest, // Generator Electrical power demand
149 : Real64 const ThermalLoadRequest, // cogenerator Thermal power demand
150 : Real64 &ElecLoadProvided, // power allowed
151 : DataGenerators::OperatingMode &OperatingMode, // operating mode
152 : Real64 &PLRforSubtimestepStartUp, // part load ratio for switch to normal from start up
153 : Real64 &PLRforSubtimestepShutDown // part load ratio for switch from cool down to other
154 : )
155 : {
156 :
157 : // SUBROUTINE INFORMATION:
158 : // AUTHOR B Griffith
159 : // DATE WRITTEN February-March 2007 (replaced July 2006 attempt)
160 : // MODIFIED Dec 2009, check and constrain with flow available from plant
161 :
162 : // PURPOSE OF THIS SUBROUTINE:
163 : // provide a service to other generators to make decisions, mostly temporal, or cross-timestep issues
164 : // used to model internal controlling issues within an individual generator model
165 : // This subroutine determines the current operating mode and returns the allowed power and
166 : // and part load ratio for certain sub-time step switching e.g. in and out of normal mode or cool down mode
167 :
168 : // METHODOLOGY EMPLOYED:
169 : // model controls-related issues, rules based algorithm
170 : // Control decision results include:
171 : // -- electrical load allowed/resulting/provided
172 : // -- new operating mode
173 : // -- part load this timestep for shift to normal mode occuring midway in timestep
174 : // -- part load this timestep for shift out of cool down mode
175 :
176 : // Input data used to make control decisions include:
177 : // -- Electrical load request
178 : // -- Thermal Load request
179 : // -- RunFlagElectricCenter
180 : // -- RunFlagPlant
181 : // -- previous timestep operating mode
182 : // -- previous timestep Power generated
183 : // -- availability schedule (off if not available)
184 : // -- Generator control parameter constants including
185 : // ** Start Up Time Delay (in hours)
186 : // ** Cool-down time delay (in hours)
187 : // -- Expected Plant flow rate
188 : // -- minimum cooling water flow rate
189 :
190 : // Algorithm summary
191 : // 1. examine calling run flags and refine electric load request to account for
192 : // thermal load requests (not yet ready for prime time)
193 : // 2. Determine states of various control inputs that change during simulation
194 : // 3. enter case statement based on previous operating mode.
195 : // -- decide on current operating mode
196 : // -- calculate part loads
197 :
198 : // 4. based on current operating mode determine allowed/provided electrical load
199 : // a. set allowed elec load by mode
200 : // b. set allowed elec load by constraints on rate of change
201 : // c. set allowed elec load by min and max
202 :
203 : // 5. Calculated part load ratios for special cases.
204 : // REFERENCES:
205 : // controls specifications in Annex 42 model specs.
206 : // Using/Aliasing
207 15328 : Real64 const SysTimeElapsed = state.dataHVACGlobal->SysTimeElapsed;
208 15328 : Real64 const TimeStepSys = state.dataHVACGlobal->TimeStepSys;
209 15328 : Real64 const TimeStepSysSec = state.dataHVACGlobal->TimeStepSysSec;
210 :
211 : // SUBROUTINE LOCAL VARIABLE DECLARATIONS:
212 : bool RunFlag; // true if generator supposed to run
213 15328 : DataGenerators::OperatingMode newOpMode(DataGenerators::OperatingMode::Invalid);
214 :
215 : // inits
216 15328 : PLRforSubtimestepStartUp = 1.0;
217 15328 : PLRforSubtimestepShutDown = 0.0;
218 15328 : bool PLRStartUp = false; // true if subtimestep issue involving startup
219 15328 : bool PLRShutDown = false;
220 15328 : state.dataGenerator->InternalFlowControl = false;
221 :
222 : // get index for this generator in dynamics control structure
223 15328 : int DynaCntrlNum = state.dataCHPElectGen->MicroCHP(GeneratorNum).DynamicsControlID;
224 : // OutletCWnode = MicroCHPElectricGenerator::MicroCHP(GeneratorNum)%PlantOutletNodeID
225 15328 : state.dataGenerator->InletCWnode = state.dataCHPElectGen->MicroCHP(GeneratorNum).PlantInletNodeID;
226 15328 : state.dataGenerator->TcwIn = state.dataLoopNodes->Node(state.dataCHPElectGen->MicroCHP(GeneratorNum).PlantInletNodeID).Temp;
227 15328 : if (state.dataCHPElectGen->MicroCHP(GeneratorNum).A42Model.InternalFlowControl) {
228 9194 : state.dataGenerator->InternalFlowControl = true;
229 : }
230 15328 : state.dataGenerator->LimitMinMdotcw = state.dataCHPElectGen->MicroCHP(GeneratorNum).A42Model.MinWaterMdot;
231 :
232 15328 : auto &thisGen = state.dataGenerator->GeneratorDynamics(DynaCntrlNum);
233 15328 : Real64 PelInput = ElecLoadRequest; // holds initial value of IN var
234 15328 : Real64 ElectLoadForThermalRequest = 0.0;
235 15328 : if ((ThermalLoadRequest > 0.0) && RunFlagPlant) { // deal with possible thermal load following
236 : // Modify electric load request based on thermal load following signal using nominal efficiencies
237 0 : ElectLoadForThermalRequest = thisGen.ThermEffNom * ThermalLoadRequest / thisGen.ElectEffNom;
238 0 : PelInput = max(PelInput, ElectLoadForThermalRequest);
239 : }
240 :
241 15328 : if ((RunFlagElectCenter) || (RunFlagPlant)) {
242 5144 : RunFlag = true;
243 : } else {
244 10184 : RunFlag = false;
245 : }
246 :
247 : // check availability schedule
248 15328 : Real64 SchedVal = ScheduleManager::GetCurrentScheduleValue(state, thisGen.AvailabilitySchedID);
249 15328 : Real64 Pel = PelInput;
250 :
251 : // get data to check if sufficient flow available from Plant
252 15328 : if (state.dataGenerator->InternalFlowControl && (SchedVal > 0.0)) {
253 9194 : state.dataGenerator->TrialMdotcw = FuncDetermineCWMdotForInternalFlowControl(state, GeneratorNum, Pel, state.dataGenerator->TcwIn);
254 : } else {
255 6134 : state.dataGenerator->TrialMdotcw = state.dataLoopNodes->Node(state.dataGenerator->InletCWnode).MassFlowRate;
256 : }
257 :
258 : // determine current operating mode.
259 15328 : switch (thisGen.LastOpMode) {
260 10546 : case DataGenerators::OperatingMode::Off:
261 : case DataGenerators::OperatingMode::Standby: {
262 : // possible future states {Off, Standby, WarmUp,Normal }
263 10546 : if (SchedVal == 0.0) {
264 5588 : newOpMode = DataGenerators::OperatingMode::Off;
265 :
266 4958 : } else if (((SchedVal != 0.0) && (!RunFlag)) || (state.dataGenerator->TrialMdotcw < state.dataGenerator->LimitMinMdotcw)) {
267 3420 : newOpMode = DataGenerators::OperatingMode::Standby;
268 1538 : } else if ((SchedVal != 0.0) && (RunFlag)) {
269 :
270 1538 : if (thisGen.WarmUpByTimeDelay) {
271 :
272 1538 : if (thisGen.StartUpTimeDelay == 0.0) {
273 42 : newOpMode = DataGenerators::OperatingMode::Normal;
274 :
275 : // is startUp time delay longer than timestep?
276 1496 : } else if (thisGen.StartUpTimeDelay >= TimeStepSys) {
277 0 : newOpMode = DataGenerators::OperatingMode::WarmUp;
278 : // generator just started so set start time
279 0 : thisGen.FractionalDayofLastStartUp =
280 0 : double(state.dataGlobal->DayOfSim) +
281 0 : (int(state.dataGlobal->CurrentTime) +
282 0 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime) - TimeStepSys))) /
283 : Constant::HoursInDay;
284 :
285 : } else { // warm up period is less than a single system time step
286 1496 : newOpMode = DataGenerators::OperatingMode::Normal;
287 1496 : PLRStartUp = true;
288 1496 : PLRforSubtimestepStartUp = (TimeStepSys - thisGen.StartUpTimeDelay) / TimeStepSys;
289 : }
290 : }
291 1538 : if (thisGen.WarmUpByEngineTemp) {
292 0 : if (state.dataCHPElectGen->MicroCHP(GeneratorNum).A42Model.Teng >= thisGen.TnomEngOp) {
293 0 : auto const &thisMicroCHP = state.dataCHPElectGen->MicroCHP(GeneratorNum);
294 0 : newOpMode = DataGenerators::OperatingMode::Normal;
295 : // assume linear interpolation for PLR
296 0 : PLRStartUp = true;
297 0 : if ((thisMicroCHP.A42Model.Teng - thisMicroCHP.A42Model.TengLast) > 0.0) {
298 : // protect divide by zero or neg
299 0 : PLRforSubtimestepStartUp =
300 0 : (thisMicroCHP.A42Model.Teng - thisGen.TnomEngOp) / (thisMicroCHP.A42Model.Teng - thisMicroCHP.A42Model.TengLast);
301 : } else {
302 0 : PLRforSubtimestepStartUp = 1.0;
303 : }
304 : } else {
305 0 : newOpMode = DataGenerators::OperatingMode::WarmUp;
306 : }
307 : }
308 : }
309 :
310 10546 : } break;
311 0 : case DataGenerators::OperatingMode::WarmUp: {
312 : // possible Future states {OFF, WarmUp, Normal, CoolDown }
313 : // check availability manager
314 0 : if (SchedVal == 0.0) {
315 : // to off unless cool down time period is needed
316 0 : if (thisGen.CoolDownDelay == 0.0) {
317 0 : newOpMode = DataGenerators::OperatingMode::Off;
318 : } else {
319 0 : if (thisGen.CoolDownDelay > TimeStepSys) {
320 0 : newOpMode = DataGenerators::OperatingMode::CoolDown;
321 : // need to reset time of last shut down here
322 0 : thisGen.FractionalDayofLastShutDown =
323 0 : double(state.dataGlobal->DayOfSim) +
324 0 : (int(state.dataGlobal->CurrentTime) +
325 0 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime)))) /
326 : Constant::HoursInDay;
327 : } else {
328 0 : newOpMode = DataGenerators::OperatingMode::Off;
329 : }
330 : }
331 0 : } else if (((SchedVal != 0.0) && (!RunFlag)) || (state.dataGenerator->TrialMdotcw < state.dataGenerator->LimitMinMdotcw)) {
332 : // to standby unless cool down time period is needed
333 0 : if (thisGen.CoolDownDelay == 0.0) {
334 0 : newOpMode = DataGenerators::OperatingMode::Standby;
335 : } else {
336 0 : if (thisGen.CoolDownDelay > TimeStepSys) {
337 0 : newOpMode = DataGenerators::OperatingMode::CoolDown;
338 : // need to reset time of last shut down here
339 0 : thisGen.FractionalDayofLastShutDown =
340 0 : double(state.dataGlobal->DayOfSim) +
341 0 : (int(state.dataGlobal->CurrentTime) +
342 0 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime)))) /
343 : Constant::HoursInDay;
344 :
345 : } else {
346 0 : newOpMode = DataGenerators::OperatingMode::Standby;
347 : // assuming no PLR situation unless engine made to normal operation.
348 : }
349 : }
350 0 : } else if ((SchedVal != 0.0) && (RunFlag)) {
351 : // either warm up or normal
352 : // check if warm up completed, depends on type of warm up control time delay or reach nominal temperature
353 0 : if (thisGen.WarmUpByTimeDelay) {
354 : // compare current time to when warm up is over
355 : // calculate time for end of warmup period
356 0 : Real64 CurrentFractionalDay = double(state.dataGlobal->DayOfSim) +
357 0 : (int(state.dataGlobal->CurrentTime) +
358 0 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime)))) /
359 0 : Constant::HoursInDay;
360 0 : Real64 EndingFractionalDay = thisGen.FractionalDayofLastStartUp + thisGen.StartUpTimeDelay / Constant::HoursInDay;
361 0 : if ((std::abs(CurrentFractionalDay - EndingFractionalDay) < 0.000001) || (CurrentFractionalDay > EndingFractionalDay)) {
362 0 : newOpMode = DataGenerators::OperatingMode::Normal;
363 0 : PLRStartUp = true;
364 0 : Real64 LastSystemTimeStepFractionalDay = CurrentFractionalDay - (TimeStepSys / Constant::HoursInDay);
365 0 : PLRforSubtimestepStartUp =
366 0 : ((CurrentFractionalDay - EndingFractionalDay) / (CurrentFractionalDay - LastSystemTimeStepFractionalDay));
367 : } else {
368 0 : newOpMode = DataGenerators::OperatingMode::WarmUp;
369 : }
370 :
371 0 : } else if (thisGen.WarmUpByEngineTemp) {
372 : // only change to normal if this is result from completed timestep, not just an interation
373 0 : if (state.dataCHPElectGen->MicroCHP(GeneratorNum).A42Model.TengLast >= thisGen.TnomEngOp) {
374 0 : auto const &thisMicroCHP = state.dataCHPElectGen->MicroCHP(GeneratorNum);
375 0 : newOpMode = DataGenerators::OperatingMode::Normal;
376 : // assume linear interpolation for PLR
377 0 : PLRStartUp = true;
378 0 : if ((thisMicroCHP.A42Model.Teng - thisMicroCHP.A42Model.TengLast) > 0.0) {
379 : // protect divide by zero or neg
380 0 : PLRforSubtimestepStartUp =
381 0 : (thisMicroCHP.A42Model.Teng - thisGen.TnomEngOp) / (thisMicroCHP.A42Model.Teng - thisMicroCHP.A42Model.TengLast);
382 : } else {
383 0 : PLRforSubtimestepStartUp = 1.0;
384 : }
385 : } else {
386 0 : newOpMode = DataGenerators::OperatingMode::WarmUp;
387 : }
388 : } else {
389 : // shouldn't come here
390 : // Write(*,*) 'problem with warm up type of control logical flags'
391 : }
392 : }
393 0 : } break;
394 4782 : case DataGenerators::OperatingMode::Normal: {
395 : // possible Future states {CoolDown, standby, off}
396 4782 : if (((SchedVal == 0.0) || (!RunFlag)) || (state.dataGenerator->TrialMdotcw < state.dataGenerator->LimitMinMdotcw)) {
397 : // is cool down time delay longer than timestep?
398 1176 : if (thisGen.CoolDownDelay == 0.0) {
399 42 : if (SchedVal != 0.0) {
400 0 : newOpMode = DataGenerators::OperatingMode::Standby;
401 : } else {
402 42 : newOpMode = DataGenerators::OperatingMode::Off;
403 : }
404 1134 : } else if (thisGen.CoolDownDelay >= TimeStepSys) {
405 0 : newOpMode = DataGenerators::OperatingMode::CoolDown;
406 : // also, generator just shut down so record shut down time
407 0 : thisGen.FractionalDayofLastShutDown = double(state.dataGlobal->DayOfSim) +
408 0 : (int(state.dataGlobal->CurrentTime) +
409 0 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime)))) /
410 : Constant::HoursInDay;
411 : } else { // cool down period is less than a single system time step
412 1134 : if (SchedVal != 0.0) {
413 1134 : newOpMode = DataGenerators::OperatingMode::Standby;
414 : } else {
415 0 : newOpMode = DataGenerators::OperatingMode::Off;
416 : }
417 1134 : PLRShutDown = true;
418 1134 : PLRforSubtimestepShutDown = (thisGen.CoolDownDelay) / TimeStepSys;
419 :
420 : // also, generator just shut down so record shut down time
421 1134 : thisGen.FractionalDayofLastShutDown = double(state.dataGlobal->DayOfSim) +
422 1134 : (int(state.dataGlobal->CurrentTime) +
423 1134 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime)))) /
424 : Constant::HoursInDay;
425 : }
426 : } else {
427 :
428 3606 : newOpMode = DataGenerators::OperatingMode::Normal;
429 : }
430 4782 : } break;
431 0 : case DataGenerators::OperatingMode::CoolDown: {
432 : // possible Future States {Standby, OFF, WarmUp, Normal}
433 :
434 0 : if (SchedVal == 0.0) { // no longer available.
435 : // probably goes to off but could be stuck in cool down for awhile
436 0 : if (thisGen.CoolDownDelay > 0.0) {
437 : // calculate time for end of cool down period
438 0 : Real64 CurrentFractionalDay = double(state.dataGlobal->DayOfSim) +
439 0 : (int(state.dataGlobal->CurrentTime) +
440 0 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime)))) /
441 0 : Constant::HoursInDay;
442 0 : Real64 EndingFractionalDay =
443 0 : thisGen.FractionalDayofLastShutDown + thisGen.CoolDownDelay / Constant::HoursInDay - (TimeStepSys / Constant::HoursInDay);
444 0 : if ((std::abs(CurrentFractionalDay - EndingFractionalDay) < 0.000001) ||
445 : (CurrentFractionalDay > EndingFractionalDay)) { // CurrentFractionalDay == EndingFractionalDay
446 0 : newOpMode = DataGenerators::OperatingMode::Off;
447 0 : PLRShutDown = true;
448 0 : Real64 LastSystemTimeStepFractionalDay = CurrentFractionalDay - (TimeStepSys / Constant::HoursInDay);
449 0 : PLRforSubtimestepShutDown = (EndingFractionalDay - LastSystemTimeStepFractionalDay) * Constant::HoursInDay / TimeStepSys;
450 : } else { // CurrentFractionalDay > EndingFractionalDay
451 0 : newOpMode = DataGenerators::OperatingMode::CoolDown;
452 : }
453 : } else {
454 0 : newOpMode = DataGenerators::OperatingMode::Off;
455 : }
456 0 : } else if (((SchedVal != 0.0) && (!RunFlag)) || (state.dataGenerator->TrialMdotcw < state.dataGenerator->LimitMinMdotcw)) {
457 : // probably goes to standby but could be stuck in cool down for awhile
458 0 : if (thisGen.CoolDownDelay > 0.0) {
459 : // calculate time for end of cool down period
460 0 : Real64 CurrentFractionalDay = double(state.dataGlobal->DayOfSim) +
461 0 : (int(state.dataGlobal->CurrentTime) +
462 0 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime)))) /
463 0 : Constant::HoursInDay;
464 0 : Real64 EndingFractionalDay =
465 0 : thisGen.FractionalDayofLastShutDown + thisGen.CoolDownDelay / Constant::HoursInDay - (TimeStepSys / Constant::HoursInDay);
466 0 : if ((std::abs(CurrentFractionalDay - EndingFractionalDay) < 0.000001) ||
467 : (CurrentFractionalDay > EndingFractionalDay)) { // CurrentFractionalDay == EndingFractionalDay
468 0 : newOpMode = DataGenerators::OperatingMode::Standby;
469 0 : PLRShutDown = true;
470 0 : Real64 LastSystemTimeStepFractionalDay = CurrentFractionalDay - (TimeStepSys / Constant::HoursInDay);
471 0 : PLRforSubtimestepShutDown = (EndingFractionalDay - LastSystemTimeStepFractionalDay) * Constant::HoursInDay / TimeStepSys;
472 : } else { // CurrentFractionalDay < EndingFractionalDay
473 0 : newOpMode = DataGenerators::OperatingMode::CoolDown;
474 : }
475 : } else {
476 0 : newOpMode = DataGenerators::OperatingMode::Standby;
477 : }
478 0 : } else if ((SchedVal != 0.0) && (RunFlag)) {
479 : // was in cool down mode but is now being asked to restart
480 : // probably goes to warm up but could be stuck in cool down or jump to normal
481 0 : if (thisGen.MandatoryFullCoolDown) {
482 : // is cool down done or not?
483 0 : if (thisGen.CoolDownDelay > 0.0) {
484 : // calculate time for end of cool down period
485 0 : Real64 CurrentFractionalDay = double(state.dataGlobal->DayOfSim) +
486 0 : (int(state.dataGlobal->CurrentTime) +
487 0 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime)))) /
488 0 : Constant::HoursInDay;
489 0 : Real64 EndingFractionalDay =
490 0 : thisGen.FractionalDayofLastShutDown + thisGen.CoolDownDelay / Constant::HoursInDay - (TimeStepSys / Constant::HoursInDay);
491 0 : if ((std::abs(CurrentFractionalDay - EndingFractionalDay) < 0.000001) ||
492 : (CurrentFractionalDay < EndingFractionalDay)) { // CurrentFractionalDay == EndingFractionalDay
493 :
494 0 : newOpMode = DataGenerators::OperatingMode::CoolDown;
495 : } else { // CurrentFractionalDay > EndingFractionalDay
496 : // could go to warm up or normal now
497 0 : PLRShutDown = true;
498 0 : Real64 LastSystemTimeStepFractionalDay = CurrentFractionalDay - (TimeStepSys / Constant::HoursInDay);
499 0 : PLRforSubtimestepShutDown = (EndingFractionalDay - LastSystemTimeStepFractionalDay) * Constant::HoursInDay / TimeStepSys;
500 0 : if (thisGen.StartUpTimeDelay == 0.0) {
501 0 : newOpMode = DataGenerators::OperatingMode::Normal;
502 : // possible PLR on start up.
503 0 : PLRStartUp = true;
504 0 : PLRforSubtimestepStartUp =
505 0 : ((CurrentFractionalDay - EndingFractionalDay) / (CurrentFractionalDay - LastSystemTimeStepFractionalDay));
506 :
507 0 : } else if (thisGen.StartUpTimeDelay > 0.0) {
508 : // is remaining time enough?
509 0 : if ((CurrentFractionalDay - EndingFractionalDay) > thisGen.StartUpTimeDelay) {
510 0 : newOpMode = DataGenerators::OperatingMode::Normal;
511 : // possible PLR on start up.
512 0 : PLRStartUp = true;
513 0 : PLRforSubtimestepStartUp =
514 0 : ((CurrentFractionalDay - EndingFractionalDay) / (CurrentFractionalDay - LastSystemTimeStepFractionalDay));
515 : } else {
516 0 : newOpMode = DataGenerators::OperatingMode::WarmUp;
517 : // generator just started so set start time
518 0 : thisGen.FractionalDayofLastStartUp =
519 0 : double(state.dataGlobal->DayOfSim) +
520 0 : (int(state.dataGlobal->CurrentTime) +
521 0 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime) - TimeStepSys))) /
522 : Constant::HoursInDay;
523 : }
524 : }
525 : }
526 : } else {
527 :
528 0 : newOpMode = DataGenerators::OperatingMode::Standby;
529 : }
530 : } else { // not mandetory cool donw
531 : // likely to go into warm up but if no warm up then back to normal
532 0 : if (thisGen.WarmUpByTimeDelay) {
533 0 : if (thisGen.StartUpTimeDelay == 0.0) {
534 0 : newOpMode = DataGenerators::OperatingMode::Normal;
535 :
536 0 : } else if (thisGen.StartUpTimeDelay > 0.0) {
537 0 : Real64 CurrentFractionalDay = double(state.dataGlobal->DayOfSim) +
538 0 : (int(state.dataGlobal->CurrentTime) +
539 0 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime)))) /
540 0 : Constant::HoursInDay;
541 0 : Real64 EndingFractionalDay = thisGen.FractionalDayofLastShutDown + thisGen.CoolDownDelay / Constant::HoursInDay;
542 0 : if ((std::abs(CurrentFractionalDay - EndingFractionalDay) < 0.000001) ||
543 : (CurrentFractionalDay > EndingFractionalDay)) { // CurrentFractionalDay == EndingFractionalDay
544 0 : newOpMode = DataGenerators::OperatingMode::Normal;
545 : // possible PLR on start up.
546 0 : PLRStartUp = true;
547 0 : Real64 LastSystemTimeStepFractionalDay = CurrentFractionalDay - (TimeStepSys / Constant::HoursInDay);
548 0 : PLRforSubtimestepStartUp =
549 0 : ((CurrentFractionalDay - EndingFractionalDay) / (CurrentFractionalDay - LastSystemTimeStepFractionalDay));
550 : } else {
551 0 : newOpMode = DataGenerators::OperatingMode::WarmUp;
552 : // set start up time
553 : // generator just started so set start time
554 0 : thisGen.FractionalDayofLastStartUp =
555 0 : double(state.dataGlobal->DayOfSim) +
556 0 : (int(state.dataGlobal->CurrentTime) +
557 0 : (SysTimeElapsed + (state.dataGlobal->CurrentTime - int(state.dataGlobal->CurrentTime) - TimeStepSys))) /
558 : Constant::HoursInDay;
559 : }
560 : }
561 : }
562 : }
563 : }
564 0 : } break;
565 0 : default:
566 0 : break;
567 : } // previous case
568 :
569 15328 : if (PLRforSubtimestepStartUp < 0.0) PLRforSubtimestepStartUp = 0.0;
570 15328 : if (PLRforSubtimestepStartUp > 1.0) PLRforSubtimestepStartUp = 1.0;
571 :
572 15328 : if (PLRforSubtimestepShutDown < 0.0) PLRforSubtimestepShutDown = 0.0;
573 15328 : if (PLRforSubtimestepShutDown > 1.0) PLRforSubtimestepShutDown = 1.0;
574 :
575 15328 : if (newOpMode == DataGenerators::OperatingMode::WarmUp) {
576 0 : Pel = PelInput * PLRforSubtimestepStartUp;
577 : }
578 :
579 15328 : if (newOpMode == DataGenerators::OperatingMode::Normal) {
580 : // correct if switched to normal at sub timestep
581 5144 : Pel *= PLRforSubtimestepStartUp;
582 : // unit may have constraints from transient limits or operating ranges.
583 5144 : if (Pel > thisGen.PelLastTimeStep) { // powering up
584 3028 : Real64 MaxPel = thisGen.PelLastTimeStep + thisGen.UpTranLimit * TimeStepSysSec;
585 3028 : if (MaxPel < Pel) {
586 0 : Pel = MaxPel;
587 : }
588 2116 : } else if (Pel < thisGen.PelLastTimeStep) { // powering down
589 609 : Real64 MinPel = thisGen.PelLastTimeStep - thisGen.DownTranLimit * TimeStepSysSec;
590 609 : if (Pel < MinPel) {
591 0 : Pel = MinPel;
592 : }
593 : }
594 : }
595 :
596 15328 : if (newOpMode == DataGenerators::OperatingMode::CoolDown) {
597 0 : Pel = 0.0; // assumes no power generated during shut down
598 : }
599 :
600 15328 : if (newOpMode == DataGenerators::OperatingMode::Off) {
601 5630 : Pel = 0.0; // assumes no power generated during OFF mode
602 : }
603 :
604 15328 : if (newOpMode == DataGenerators::OperatingMode::Standby) {
605 4554 : Pel = 0.0; // assumes no power generated during standby mode
606 : }
607 :
608 : // Control step 3: adjust for max and min limits on Pel
609 :
610 15328 : if (Pel < thisGen.PelMin) {
611 0 : Pel = thisGen.PelMin;
612 : }
613 15328 : if (Pel > thisGen.PelMax) {
614 0 : Pel = thisGen.PelMax;
615 : }
616 :
617 15328 : auto &thisMicroCHP = state.dataCHPElectGen->MicroCHP(GeneratorNum);
618 : // now do record keeping for amount of time spent in various operating modes
619 : // first clear out values
620 15328 : thisMicroCHP.A42Model.OffModeTime = 0.0;
621 15328 : thisMicroCHP.A42Model.StandyByModeTime = 0.0;
622 15328 : thisMicroCHP.A42Model.WarmUpModeTime = 0.0;
623 15328 : thisMicroCHP.A42Model.NormalModeTime = 0.0;
624 15328 : thisMicroCHP.A42Model.CoolDownModeTime = 0.0;
625 15328 : switch (newOpMode) {
626 5630 : case DataGenerators::OperatingMode::Off: {
627 5630 : if (PLRforSubtimestepShutDown == 0.0) {
628 5630 : thisMicroCHP.A42Model.OffModeTime = TimeStepSysSec;
629 0 : } else if ((PLRforSubtimestepShutDown > 0.0) && (PLRforSubtimestepShutDown < 1.0)) {
630 0 : thisMicroCHP.A42Model.CoolDownModeTime = TimeStepSysSec * (PLRforSubtimestepShutDown);
631 0 : thisMicroCHP.A42Model.OffModeTime = TimeStepSysSec * (1.0 - PLRforSubtimestepShutDown);
632 : } else {
633 0 : thisMicroCHP.A42Model.OffModeTime = TimeStepSysSec;
634 : }
635 5630 : } break;
636 4554 : case DataGenerators::OperatingMode::Standby: {
637 4554 : if (PLRforSubtimestepShutDown == 0.0) {
638 3420 : thisMicroCHP.A42Model.StandyByModeTime = TimeStepSysSec;
639 1134 : } else if ((PLRforSubtimestepShutDown > 0.0) && (PLRforSubtimestepShutDown < 1.0)) {
640 1134 : thisMicroCHP.A42Model.CoolDownModeTime = TimeStepSysSec * (PLRforSubtimestepShutDown);
641 1134 : thisMicroCHP.A42Model.StandyByModeTime = TimeStepSysSec * (1.0 - PLRforSubtimestepShutDown);
642 : } else {
643 0 : thisMicroCHP.A42Model.StandyByModeTime = TimeStepSysSec;
644 : }
645 4554 : } break;
646 0 : case DataGenerators::OperatingMode::WarmUp: {
647 0 : if (PLRforSubtimestepShutDown == 0.0) {
648 0 : thisMicroCHP.A42Model.WarmUpModeTime = TimeStepSysSec;
649 0 : } else if ((PLRforSubtimestepShutDown > 0.0) && (PLRforSubtimestepShutDown < 1.0)) {
650 0 : thisMicroCHP.A42Model.CoolDownModeTime = TimeStepSysSec * (PLRforSubtimestepShutDown);
651 0 : thisMicroCHP.A42Model.WarmUpModeTime = TimeStepSysSec * (1.0 - PLRforSubtimestepShutDown);
652 : } else {
653 0 : thisMicroCHP.A42Model.WarmUpModeTime = TimeStepSysSec;
654 : }
655 0 : } break;
656 5144 : case DataGenerators::OperatingMode::Normal: {
657 5144 : if (PLRforSubtimestepStartUp == 0.0) {
658 0 : thisMicroCHP.A42Model.WarmUpModeTime = TimeStepSysSec;
659 :
660 5144 : } else if ((PLRforSubtimestepStartUp > 0.0) && (PLRforSubtimestepStartUp < 1.0)) {
661 1496 : thisMicroCHP.A42Model.WarmUpModeTime = TimeStepSysSec * (1.0 - PLRforSubtimestepStartUp);
662 1496 : thisMicroCHP.A42Model.NormalModeTime = TimeStepSysSec * (PLRforSubtimestepStartUp);
663 : } else {
664 3648 : if (PLRforSubtimestepShutDown == 0.0) {
665 3648 : thisMicroCHP.A42Model.NormalModeTime = TimeStepSysSec;
666 0 : } else if ((PLRforSubtimestepShutDown > 0.0) && (PLRforSubtimestepShutDown < 1.0)) {
667 0 : thisMicroCHP.A42Model.CoolDownModeTime = TimeStepSysSec * (PLRforSubtimestepShutDown);
668 0 : thisMicroCHP.A42Model.NormalModeTime = TimeStepSysSec * (1.0 - PLRforSubtimestepShutDown);
669 : } else {
670 0 : thisMicroCHP.A42Model.NormalModeTime = TimeStepSysSec;
671 : }
672 : }
673 5144 : } break;
674 0 : case DataGenerators::OperatingMode::CoolDown: {
675 0 : thisMicroCHP.A42Model.CoolDownModeTime = TimeStepSysSec;
676 0 : } break;
677 0 : default:
678 0 : break;
679 : }
680 :
681 15328 : ElecLoadProvided = Pel;
682 :
683 15328 : thisGen.CurrentOpMode = newOpMode;
684 15328 : OperatingMode = newOpMode;
685 15328 : }
686 :
687 5144 : void ManageGeneratorFuelFlow(EnergyPlusData &state,
688 : int const GeneratorNum, // Generator number
689 : Real64 const FuelFlowRequest, // Generator demand mdot kg/ s
690 : Real64 &FuelFlowProvided, // allowed after constraints kg/s
691 : bool &ConstrainedIncreasingMdot, // true if request was altered because of fuel rate of change up
692 : bool &ConstrainedDecreasingMdot // true if request was altered because of fuel rate of change down
693 : )
694 : {
695 :
696 : // SUBROUTINE INFORMATION:
697 : // AUTHOR B. Griffith
698 : // DATE WRITTEN July 2006
699 :
700 : // PURPOSE OF THIS SUBROUTINE:
701 : // check if change in fuel flow rate is okay
702 :
703 : // Using/Aliasing
704 5144 : Real64 const TimeStepSysSec = state.dataHVACGlobal->TimeStepSysSec;
705 :
706 5144 : ConstrainedIncreasingMdot = false;
707 5144 : ConstrainedDecreasingMdot = false;
708 5144 : Real64 MdotFuel = FuelFlowRequest;
709 :
710 : // get index from GeneratorNum
711 5144 : int DynaCntrlNum = state.dataCHPElectGen->MicroCHP(GeneratorNum).DynamicsControlID;
712 5144 : auto const &thisGen = state.dataGenerator->GeneratorDynamics(DynaCntrlNum);
713 :
714 5144 : if (FuelFlowRequest > thisGen.FuelMdotLastTimestep) { // fuel flow is up
715 1736 : Real64 MaxMdot = thisGen.FuelMdotLastTimestep + thisGen.UpTranLimitFuel * TimeStepSysSec;
716 1736 : if (MaxMdot < FuelFlowRequest) {
717 0 : MdotFuel = MaxMdot;
718 0 : ConstrainedIncreasingMdot = true;
719 : }
720 :
721 3408 : } else if (FuelFlowRequest < thisGen.FuelMdotLastTimestep) { // fuel flow is down
722 613 : Real64 MinMdot = thisGen.FuelMdotLastTimestep - thisGen.DownTranLimitFuel * TimeStepSysSec;
723 613 : if (FuelFlowRequest < MinMdot) {
724 0 : MdotFuel = MinMdot;
725 0 : ConstrainedDecreasingMdot = true;
726 : }
727 : } else {
728 : // do nothing
729 : }
730 :
731 5144 : FuelFlowProvided = MdotFuel;
732 5144 : }
733 :
734 13834 : Real64 FuncDetermineCWMdotForInternalFlowControl(EnergyPlusData &state,
735 : int const GeneratorNum, // ID of generator
736 : Real64 const Pnetss, // power net steady state
737 : Real64 const TcwIn // temperature of cooling water at inlet
738 : )
739 : {
740 :
741 : // FUNCTION INFORMATION:
742 : // AUTHOR B. Griffith
743 : // DATE WRITTEN Dec 2009
744 : // RE-ENGINEERED B. Griffith, Sept 2010, plant upgrade
745 :
746 : // PURPOSE OF THIS FUNCTION:
747 : // common place to figure flow rates with internal flow control
748 :
749 : // METHODOLOGY EMPLOYED:
750 : // apply contraints imposed by plant according to flow lock, first HVAC iteration etc.
751 :
752 : // Return value
753 : Real64 FuncDetermineCWMdotForInternalFlowControl;
754 13834 : auto const &thisMicroCHP = state.dataCHPElectGen->MicroCHP(GeneratorNum);
755 13834 : int const InletNode = thisMicroCHP.PlantInletNodeID;
756 13834 : int const OutletNode = thisMicroCHP.PlantOutletNodeID;
757 :
758 : // first evaluate curve
759 13834 : Real64 MdotCW = Curve::CurveValue(state, thisMicroCHP.A42Model.WaterFlowCurveID, Pnetss, TcwIn);
760 :
761 : // now apply constraints
762 13834 : MdotCW = max(0.0, MdotCW);
763 :
764 : // make sure plant can provide, utility call may change flow
765 13834 : if (thisMicroCHP.CWPlantLoc.loopNum > 0) { // protect early calls
766 13834 : PlantUtilities::SetComponentFlowRate(state, MdotCW, InletNode, OutletNode, thisMicroCHP.CWPlantLoc);
767 : }
768 :
769 13834 : FuncDetermineCWMdotForInternalFlowControl = MdotCW;
770 13834 : return FuncDetermineCWMdotForInternalFlowControl;
771 : }
772 :
773 : } // namespace GeneratorDynamicsManager
774 :
775 : } // namespace EnergyPlus
|